Bug 1202657 - Add markers for workers' message passing and serialization/deserialization, r=smaug, jsantell, tromey, ejpbruel
authorVictor Porof <vporof@mozilla.com>
Wed, 21 Oct 2015 23:10:05 +0200
changeset 290524 f95c614295a0971529da8c0c93d28718e7c4c5bd
parent 290523 9e60fa0fc1e313841abb7480ceed394bb8be95be
child 290525 ae58f36aff6666ecb2120448a8dd309fd1d16141
push id8654
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:48:40 +0000
treeherdermozilla-aurora@bc4551debe17 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, jsantell, tromey, ejpbruel
bugs1202657
milestone44.0a1
Bug 1202657 - Add markers for workers' message passing and serialization/deserialization, r=smaug, jsantell, tromey, ejpbruel
browser/locales/en-US/chrome/browser/devtools/markers.properties
devtools/client/performance/modules/logic/marker-utils.js
devtools/client/performance/modules/markers.js
devtools/client/performance/test/browser.ini
devtools/client/performance/test/browser_timeline-waterfall-workers.js
devtools/client/performance/test/doc_worker.html
devtools/client/performance/test/head.js
devtools/client/performance/test/js_simpleWorker.js
devtools/client/themes/performance.css
devtools/client/themes/variables.css
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/timeline/AbstractTimelineMarker.cpp
docshell/base/timeline/AbstractTimelineMarker.h
docshell/base/timeline/AutoGlobalTimelineMarker.cpp
docshell/base/timeline/AutoGlobalTimelineMarker.h
docshell/base/timeline/AutoTimelineMarker.cpp
docshell/base/timeline/AutoTimelineMarker.h
docshell/base/timeline/LayerTimelineMarker.h
docshell/base/timeline/MarkersStorage.cpp
docshell/base/timeline/MarkersStorage.h
docshell/base/timeline/OTMTMarkerReceiver.h
docshell/base/timeline/ObservedDocShell.cpp
docshell/base/timeline/ObservedDocShell.h
docshell/base/timeline/TimelineConsumers.cpp
docshell/base/timeline/TimelineConsumers.h
docshell/base/timeline/TimelineMarker.cpp
docshell/base/timeline/TimelineMarker.h
docshell/base/timeline/WorkerTimelineMarker.h
docshell/base/timeline/moz.build
docshell/test/browser/browser_timelineMarkers-frame-02.js
dom/base/Console.cpp
dom/events/EventListenerManager.cpp
dom/ipc/TabChild.cpp
dom/webidl/ProfileTimelineMarker.webidl
dom/workers/WorkerPrivate.cpp
layout/base/FrameLayerBuilder.cpp
layout/base/RestyleTracker.cpp
layout/base/nsPresShell.cpp
layout/base/nsRefreshDriver.cpp
view/nsView.cpp
--- a/browser/locales/en-US/chrome/browser/devtools/markers.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/markers.properties
@@ -25,16 +25,17 @@ marker.label.parseXML=Parse XML
 marker.label.domevent=DOM Event
 marker.label.consoleTime=Console
 marker.label.garbageCollection2=Garbage Collection
 marker.label.garbageCollection.incremental=Incremental GC
 marker.label.garbageCollection.nonIncremental=Non-incremental GC
 marker.label.cycleCollection=Cycle Collection
 marker.label.cycleCollection.forgetSkippable=CC Graph Reduction
 marker.label.timestamp=Timestamp
+marker.label.worker=Worker
 marker.label.unknown=Unknown
 
 # LOCALIZATION NOTE (marker.label.javascript.*):
 # These strings are displayed as JavaScript markers that have special
 # reasons that can be translated.
 marker.label.javascript.scriptElement=Script Tag
 marker.label.javascript.promiseCallback=Promise Callback
 marker.label.javascript.promiseInit=Promise Init
@@ -70,16 +71,21 @@ marker.field.DOMEventPhase=Phase:
 # Non-incremental cause for a Garbage Collection marker
 marker.field.nonIncrementalCause=Non-incremental Cause:
 # For "Recalculate Style" markers
 marker.field.restyleHint=Restyle Hint:
 # General "reason" for a marker (JavaScript, Garbage Collection)
 marker.field.causeName=Cause:
 # General "type" for a marker (Cycle Collection, Garbage Collection)
 marker.field.type=Type:
+# The type of operation performed by a Worker.
+marker.worker.serializeDataOffMainThread=Serialize data in Worker
+marker.worker.serializeDataOnMainThread=Serialize data on the main thread
+marker.worker.deserializeDataOffMainThread=Deserialize data in Worker
+marker.worker.deserializeDataOnMainThread=Deserialize data on the main thread
 
 # Strings used in the waterfall sidebar as values.
 marker.value.unknownFrame=<unknown location>
 marker.value.DOMEventTargetPhase=Target
 marker.value.DOMEventCapturingPhase=Capture
 marker.value.DOMEventBubblingPhase=Bubbling
 
 # LOCALIZATION NOTE (marker.gcreason.label.*):
--- a/devtools/client/performance/modules/logic/marker-utils.js
+++ b/devtools/client/performance/modules/logic/marker-utils.js
@@ -434,16 +434,23 @@ const Formatters = {
     }
   },
 
   CycleCollectionFields: function (marker) {
     return {
       [L10N.getStr("marker.field.type")]: marker.name.replace(/nsCycleCollector::/g, "")
     };
   },
+
+  WorkerFields: function(marker) {
+    return {
+      [L10N.getStr("marker.field.type")]:
+        L10N.getStr(`marker.worker.${marker.workerOperation}`)
+    };
+  }
 };
 
 /**
  * Takes a marker and returns the definition for that marker type,
  * falling back to the UNKNOWN definition if undefined.
  *
  * @param {Marker} marker
  * @return {object}
--- a/devtools/client/performance/modules/markers.js
+++ b/devtools/client/performance/modules/markers.js
@@ -120,16 +120,22 @@ const TIMELINE_BLUEPRINT = {
     fields: Formatters.CycleCollectionFields,
   },
   "nsCycleCollector::ForgetSkippable": {
     group: 1,
     colorName: "graphs-red",
     label: L10N.getStr("marker.label.cycleCollection.forgetSkippable"),
     fields: Formatters.CycleCollectionFields,
   },
+  "Worker": {
+    group: 1,
+    colorName: "graphs-orange",
+    label: L10N.getStr("marker.label.worker"),
+    fields: Formatters.WorkerFields
+  },
 
   /* Group 2 - User Controlled */
   "ConsoleTime": {
     group: 2,
     colorName: "graphs-blue",
     label: sublabelForProperty(L10N.getStr("marker.label.consoleTime"), "causeName"),
     fields: [{
       property: "causeName",
--- a/devtools/client/performance/test/browser.ini
+++ b/devtools/client/performance/test/browser.ini
@@ -1,16 +1,18 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   doc_allocs.html
   doc_innerHTML.html
   doc_markers.html
   doc_simple-test.html
+  doc_worker.html
+  js_simpleWorker.js
   head.js
 
 [browser_aaa-run-first-leaktest.js]
 [browser_perf-categories-js-calltree.js]
 [browser_perf-clear-01.js]
 [browser_perf-clear-02.js]
 [browser_perf-columns-js-calltree.js]
 [browser_perf-columns-memory-calltree.js]
@@ -140,8 +142,9 @@ skip-if = os == 'linux' || debug # bug 1
 skip-if = true # Bug 1176370
 [browser_timeline-filters-02.js]
 [browser_timeline-waterfall-background.js]
 [browser_timeline-waterfall-generic.js]
 [browser_timeline-waterfall-rerender.js]
 skip-if = true # Bug 1170105
 [browser_timeline-waterfall-sidebar.js]
 skip-if = true # Bug 1161817
+[browser_timeline-waterfall-workers.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/test/browser_timeline-waterfall-workers.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the sidebar is properly updated with worker markers.
+ */
+
+function* spawnTest() {
+  let { panel } = yield initPerformance(WORKER_URL);
+  let { PerformanceController } = panel.panelWin;
+
+  loadFrameScripts();
+
+  yield startRecording(panel);
+  ok(true, "Recording has started.");
+
+  evalInDebuggee("performWork()");
+
+  yield waitUntil(() => {
+    // Wait until we get the worker markers.
+    let markers = PerformanceController.getCurrentRecording().getMarkers();
+    if (!markers.some(m => m.name == "Worker") ||
+        !markers.some(m => m.workerOperation == "serializeDataOffMainThread") ||
+        !markers.some(m => m.workerOperation == "serializeDataOnMainThread") ||
+        !markers.some(m => m.workerOperation == "deserializeDataOffMainThread") ||
+        !markers.some(m => m.workerOperation == "deserializeDataOnMainThread")) {
+      return false;
+    }
+
+    testWorkerMarker(markers.find(m => m.name == "Worker"));
+    return true;
+  });
+
+  yield stopRecording(panel);
+  ok(true, "Recording has ended.");
+
+  yield teardown(panel);
+  finish();
+}
+
+function testWorkerMarker(marker) {
+  ok(true, "Found a worker marker.");
+
+  ok("start" in marker,
+    "The start time is specified in the worker marker.");
+  ok("end" in marker,
+    "The end time is specified in the worker marker.");
+  ok("workerOperation" in marker,
+    "The worker operation is specified in the worker marker.");
+}
+
+/**
+ * Takes a string `script` and evaluates it directly in the content
+ * in potentially a different process.
+ */
+function evalInDebuggee (script) {
+  let { generateUUID } = Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator);
+  let deferred = Promise.defer();
+
+  if (!mm) {
+    throw new Error("`loadFrameScripts()` must be called when using MessageManager.");
+  }
+
+  let id = generateUUID().toString();
+  mm.sendAsyncMessage("devtools:test:eval", { script: script, id: id });
+  mm.addMessageListener("devtools:test:eval:response", handler);
+
+  function handler ({ data }) {
+    if (id !== data.id) {
+      return;
+    }
+
+    mm.removeMessageListener("devtools:test:eval:response", handler);
+    deferred.resolve(data.value);
+  }
+
+  return deferred.promise;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/test/doc_worker.html
@@ -0,0 +1,26 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Performance test page</title>
+  </head>
+
+  <body>
+    <script type="text/javascript">
+      function performWork() {
+        var worker = new Worker("js_simpleWorker.js");
+
+        worker.addEventListener("message", function(e) {
+          console.log(e.data);
+          console.timeStamp("Done");
+        }, false);
+
+        worker.postMessage("Hello World");
+      }
+    </script>
+  </body>
+
+</html>
--- a/devtools/client/performance/test/head.js
+++ b/devtools/client/performance/test/head.js
@@ -24,16 +24,17 @@ var {
 
 var mm = null;
 
 const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js"
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/performance/test/";
 const SIMPLE_URL = EXAMPLE_URL + "doc_simple-test.html";
 const MARKERS_URL = EXAMPLE_URL + "doc_markers.html";
 const ALLOCS_URL = EXAMPLE_URL + "doc_allocs.html";
+const WORKER_URL = EXAMPLE_URL + "doc_worker.html";
 
 const MEMORY_SAMPLE_PROB_PREF = "devtools.performance.memory.sample-probability";
 const MEMORY_MAX_LOG_LEN_PREF = "devtools.performance.memory.max-log-length";
 const PROFILER_BUFFER_SIZE_PREF = "devtools.performance.profiler.buffer-size";
 const PROFILER_SAMPLE_RATE_PREF = "devtools.performance.profiler.sample-frequency-khz";
 
 const FRAMERATE_PREF = "devtools.performance.ui.enable-framerate";
 const MEMORY_PREF = "devtools.performance.ui.enable-memory";
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/test/js_simpleWorker.js
@@ -0,0 +1,4 @@
+self.addEventListener('message', function(e) {
+  self.postMessage(e.data);
+  self.close()
+}, false);
--- a/devtools/client/themes/performance.css
+++ b/devtools/client/themes/performance.css
@@ -564,16 +564,20 @@ menuitem.marker-color-graphs-bluegrey:be
 menuitem.marker-color-graphs-purple:before,
 .marker-color-graphs-purple {
   background-color: var(--theme-graphs-purple);
 }
 menuitem.marker-color-graphs-yellow:before,
 .marker-color-graphs-yellow {
   background-color: var(--theme-graphs-yellow);
 }
+menuitem.marker-color-graphs-orange:before,
+.marker-color-graphs-orange {
+  background-color: var(--theme-graphs-orange);
+}
 menuitem.marker-color-graphs-red:before,
 .marker-color-graphs-red {
   background-color: var(--theme-graphs-red);
 }
 menuitem.marker-color-graphs-grey:before,
 .marker-color-graphs-grey{
   background-color: var(--theme-graphs-grey);
 }
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -43,16 +43,17 @@
   --theme-highlight-pink: #b82ee5;
 
   /* Colors used in Graphs, like performance tools. Similar colors to Chrome's timeline. */
   --theme-graphs-green: #85d175;
   --theme-graphs-blue: #83b7f6;
   --theme-graphs-bluegrey: #0072ab;
   --theme-graphs-purple: #b693eb;
   --theme-graphs-yellow: #efc052;
+  --theme-graphs-orange: #d97e00;
   --theme-graphs-red: #e57180;
   --theme-graphs-grey: #cccccc;
 }
 
 :root.theme-dark {
   --theme-body-background: #14171a;
   --theme-sidebar-background: #181d20;
   --theme-contrast-background: #b28025;
@@ -81,11 +82,12 @@
   --theme-highlight-pink: #df80ff;
 
   /* Colors used in Graphs, like performance tools. Mostly similar to some "highlight-*" colors. */
   --theme-graphs-green: #70bf53;
   --theme-graphs-blue: #46afe3;
   --theme-graphs-bluegrey: #5e88b0;
   --theme-graphs-purple: #df80ff;
   --theme-graphs-yellow: #d99b28;
+  --theme-graphs-orange: #d96629;
   --theme-graphs-red: #eb5368;
   --theme-graphs-grey: #757873;
 }
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -835,17 +835,17 @@ nsDocShell::nsDocShell()
                   getpid(),
                   AssertedCast<unsigned long long>(mHistoryID));
   }
 #endif
 }
 
 nsDocShell::~nsDocShell()
 {
-  MOZ_ASSERT(!IsObserved());
+  MOZ_ASSERT(!mObserved);
 
   Destroy();
 
   nsCOMPtr<nsISHistoryInternal> shPrivate(do_QueryInterface(mSessionHistory));
   if (shPrivate) {
     shPrivate->SetRootDocShell(nullptr);
   }
 
@@ -2834,45 +2834,56 @@ nsDocShell::HistoryTransactionRemoved(in
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetRecordProfileTimelineMarkers(bool aValue)
 {
   bool currentValue = nsIDocShell::GetRecordProfileTimelineMarkers();
-  if (currentValue != aValue) {
-    if (aValue) {
-      TimelineConsumers::AddConsumer(this);
-      UseEntryScriptProfiling();
-    } else {
-      TimelineConsumers::RemoveConsumer(this);
-      UnuseEntryScriptProfiling();
-    }
+  if (currentValue == aValue) {
+    return NS_OK;
+  }
+
+  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+  if (!timelines) {
+    return NS_OK;
+  }
+
+  if (aValue) {
+    MOZ_ASSERT(!timelines->HasConsumer(this));
+    timelines->AddConsumer(this);
+    MOZ_ASSERT(timelines->HasConsumer(this));
+    UseEntryScriptProfiling();
+  } else {
+    MOZ_ASSERT(timelines->HasConsumer(this));
+    timelines->RemoveConsumer(this);
+    MOZ_ASSERT(!timelines->HasConsumer(this));
+    UnuseEntryScriptProfiling();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue)
 {
-  *aValue = IsObserved();
+  *aValue = !!mObserved;
   return NS_OK;
 }
 
 nsresult
 nsDocShell::PopProfileTimelineMarkers(
     JSContext* aCx,
     JS::MutableHandle<JS::Value> aOut)
 {
   nsTArray<dom::ProfileTimelineMarker> store;
   SequenceRooter<dom::ProfileTimelineMarker> rooter(aCx, &store);
 
-  if (IsObserved()) {
+  if (mObserved) {
     mObserved->PopMarkers(aCx, store);
   }
 
   if (!ToJSValue(aCx, store, aOut)) {
     JS_ClearPendingException(aCx);
     return NS_ERROR_UNEXPECTED;
   }
 
@@ -10645,25 +10656,25 @@ nsDocShell::DoURILoad(nsIURI* aURI,
     // Currently only http and ftp channels support this.
     props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"),
                                   aReferrerURI);
   }
 
   nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel));
   nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(channel));
 
-  
+
   /* Get the cache Key from SH */
   nsCOMPtr<nsISupports> cacheKey;
   if (mLSHE) {
     mLSHE->GetCacheKey(getter_AddRefs(cacheKey));
   } else if (mOSHE) {  // for reload cases
     mOSHE->GetCacheKey(getter_AddRefs(cacheKey));
   }
-  
+
   if (uploadChannel) {
     // figure out if we need to set the post data stream on the channel...
     // right now, this is only done for http channels.....
     if (aPostData) {
       // XXX it's a bit of a hack to rewind the postdata stream here but
       // it has to be done in case the post data is being reused multiple
       // times.
       nsCOMPtr<nsISeekableStream> postDataSeekable =
@@ -10704,17 +10715,17 @@ nsDocShell::DoURILoad(nsIURI* aURI,
           mLoadType == LOAD_RELOAD_NORMAL ||
           mLoadType == LOAD_RELOAD_CHARSET_CHANGE) {
         if (cacheChannel && cacheKey) {
           cacheChannel->SetCacheKey(cacheKey);
         }
       }
     }
   }
-  
+
   if (httpChannel) {
     if (aHeadersData) {
       rv = AddHeadersToChannel(aHeadersData, httpChannel);
     }
     // Set the referrer explicitly
     if (aReferrerURI && aSendReferrer) {
       // Referrer is currenly only set for link clicks here.
       httpChannel->SetReferrerWithPolicy(aReferrerURI, aReferrerPolicy);
@@ -13887,36 +13898,40 @@ nsDocShell::GetOpener()
 }
 
 void
 nsDocShell::NotifyJSRunToCompletionStart(const char* aReason,
                                          const char16_t* aFunctionName,
                                          const char16_t* aFilename,
                                          const uint32_t aLineNumber)
 {
-  bool timelineOn = nsIDocShell::GetRecordProfileTimelineMarkers();
-
   // If first start, mark interval start.
-  if (timelineOn && mJSRunToCompletionDepth == 0) {
-    UniquePtr<TimelineMarker> marker = MakeUnique<JavascriptTimelineMarker>(
-      aReason, aFunctionName, aFilename, aLineNumber, MarkerTracingType::START);
-    TimelineConsumers::AddMarkerForDocShell(this, Move(marker));
-  }
+  if (mJSRunToCompletionDepth == 0) {
+    RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+    if (timelines && timelines->HasConsumer(this)) {
+      timelines->AddMarkerForDocShell(this, Move(
+        MakeUnique<JavascriptTimelineMarker>(
+          aReason, aFunctionName, aFilename, aLineNumber, MarkerTracingType::START)));
+    }
+  }
+
   mJSRunToCompletionDepth++;
 }
 
 void
 nsDocShell::NotifyJSRunToCompletionStop()
 {
-  bool timelineOn = nsIDocShell::GetRecordProfileTimelineMarkers();
+  mJSRunToCompletionDepth--;
 
   // If last stop, mark interval end.
-  mJSRunToCompletionDepth--;
-  if (timelineOn && mJSRunToCompletionDepth == 0) {
-    TimelineConsumers::AddMarkerForDocShell(this, "Javascript", MarkerTracingType::END);
+  if (mJSRunToCompletionDepth == 0) {
+    RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+    if (timelines && timelines->HasConsumer(this)) {
+      timelines->AddMarkerForDocShell(this, "Javascript", MarkerTracingType::END);
+    }
   }
 }
 
 void
 nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
                                             const nsString& aKeyword)
 {
   if (aProvider.IsEmpty()) {
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -270,33 +270,30 @@ public:
   }
   bool InFrameSwap();
 
   mozilla::OriginAttributes GetOriginAttributes();
 
 private:
   // An observed docshell wrapper is created when recording markers is enabled.
   mozilla::UniquePtr<mozilla::ObservedDocShell> mObserved;
-  bool IsObserved() const { return !!mObserved; }
 
   // It is necessary to allow adding a timeline marker wherever a docshell
   // instance is available. This operation happens frequently and needs to
   // be very fast, so instead of using a Map or having to search for some
   // docshell-specific markers storage, a pointer to an `ObservedDocShell` is
   // is stored on docshells directly.
   friend void mozilla::TimelineConsumers::AddConsumer(nsDocShell*);
   friend void mozilla::TimelineConsumers::RemoveConsumer(nsDocShell*);
   friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
     nsDocShell*, const char*, MarkerTracingType);
   friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
     nsDocShell*, const char*, const TimeStamp&, MarkerTracingType);
   friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
     nsDocShell*, UniquePtr<AbstractTimelineMarker>&&);
-  friend void mozilla::TimelineConsumers::AddOTMTMarkerForDocShell(
-    nsDocShell*, UniquePtr<AbstractTimelineMarker>&);
 
 public:
   // Tell the favicon service that aNewURI has the same favicon as aOldURI.
   static void CopyFavicon(nsIURI* aOldURI,
                           nsIURI* aNewURI,
                           bool aInPrivateBrowsing);
 
   static nsDocShell* Cast(nsIDocShell* aDocShell)
--- a/docshell/base/timeline/AbstractTimelineMarker.cpp
+++ b/docshell/base/timeline/AbstractTimelineMarker.cpp
@@ -30,16 +30,24 @@ AbstractTimelineMarker::AbstractTimeline
 
 UniquePtr<AbstractTimelineMarker>
 AbstractTimelineMarker::Clone()
 {
   MOZ_ASSERT(false, "Clone method not yet implemented on this marker type.");
   return nullptr;
 }
 
+bool
+AbstractTimelineMarker::Equals(const AbstractTimelineMarker& aOther)
+{
+  // Check whether two markers should be considered the same, for the purpose
+  // of pairing start and end markers. Normally this definition suffices.
+  return strcmp(mName, aOther.mName) == 0;
+}
+
 AbstractTimelineMarker::~AbstractTimelineMarker()
 {
   MOZ_COUNT_DTOR(AbstractTimelineMarker);
 }
 
 void
 AbstractTimelineMarker::SetCurrentTime()
 {
@@ -49,9 +57,15 @@ AbstractTimelineMarker::SetCurrentTime()
 
 void
 AbstractTimelineMarker::SetCustomTime(const TimeStamp& aTime)
 {
   bool isInconsistent = false;
   mTime = (aTime - TimeStamp::ProcessCreation(isInconsistent)).ToMilliseconds();
 }
 
+void
+AbstractTimelineMarker::SetCustomTime(DOMHighResTimeStamp aTime)
+{
+  mTime = aTime;
+}
+
 } // namespace mozilla
--- a/docshell/base/timeline/AbstractTimelineMarker.h
+++ b/docshell/base/timeline/AbstractTimelineMarker.h
@@ -7,55 +7,58 @@
 #ifndef mozilla_AbstractTimelineMarker_h_
 #define mozilla_AbstractTimelineMarker_h_
 
 #include "TimelineMarkerEnums.h" // for MarkerTracingType
 #include "nsDOMNavigationTiming.h" // for DOMHighResTimeStamp
 #include "mozilla/UniquePtr.h"
 
 struct JSContext;
+class JSObject;
 
 namespace mozilla {
 class TimeStamp;
 
 namespace dom {
 struct ProfileTimelineMarker;
 }
 
 class AbstractTimelineMarker
 {
 private:
   AbstractTimelineMarker() = delete;
   AbstractTimelineMarker(const AbstractTimelineMarker& aOther) = delete;
   void operator=(const AbstractTimelineMarker& aOther) = delete;
 
 public:
-  AbstractTimelineMarker(const char* aName,
-                         MarkerTracingType aTracingType);
+  explicit AbstractTimelineMarker(const char* aName,
+                                  MarkerTracingType aTracingType);
 
-  AbstractTimelineMarker(const char* aName,
-                         const TimeStamp& aTime,
-                         MarkerTracingType aTracingType);
+  explicit AbstractTimelineMarker(const char* aName,
+                                  const TimeStamp& aTime,
+                                  MarkerTracingType aTracingType);
 
   virtual ~AbstractTimelineMarker();
 
   virtual UniquePtr<AbstractTimelineMarker> Clone();
+  virtual bool Equals(const AbstractTimelineMarker& aOther);
 
-  virtual bool Equals(const AbstractTimelineMarker& aOther) = 0;
   virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) = 0;
   virtual JSObject* GetStack() = 0;
 
   const char* GetName() const { return mName; }
   DOMHighResTimeStamp GetTime() const { return mTime; }
   MarkerTracingType GetTracingType() const { return mTracingType; }
 
 private:
   const char* mName;
   DOMHighResTimeStamp mTime;
   MarkerTracingType mTracingType;
 
+protected:
   void SetCurrentTime();
   void SetCustomTime(const TimeStamp& aTime);
+  void SetCustomTime(DOMHighResTimeStamp aTime);
 };
 
 } // namespace mozilla
 
 #endif /* mozilla_AbstractTimelineMarker_h_ */
--- a/docshell/base/timeline/AutoGlobalTimelineMarker.cpp
+++ b/docshell/base/timeline/AutoGlobalTimelineMarker.cpp
@@ -1,37 +1,41 @@
 /* -*- 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 "mozilla/AutoGlobalTimelineMarker.h"
+#include "AutoGlobalTimelineMarker.h"
 
 #include "TimelineConsumers.h"
 #include "MainThreadUtils.h"
 
 namespace mozilla {
 
 AutoGlobalTimelineMarker::AutoGlobalTimelineMarker(const char* aName
                                                    MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : mName(aName)
 {
   MOZ_GUARD_OBJECT_NOTIFIER_INIT;
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (TimelineConsumers::IsEmpty()) {
+  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+  if (!timelines || timelines->IsEmpty()) {
     return;
   }
 
-  TimelineConsumers::AddMarkerForAllObservedDocShells(mName, MarkerTracingType::START);
+  timelines->AddMarkerForAllObservedDocShells(mName, MarkerTracingType::START);
 }
 
 AutoGlobalTimelineMarker::~AutoGlobalTimelineMarker()
 {
-  if (TimelineConsumers::IsEmpty()) {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+  if (!timelines || timelines->IsEmpty()) {
     return;
   }
 
-  TimelineConsumers::AddMarkerForAllObservedDocShells(mName, MarkerTracingType::END);
+  timelines->AddMarkerForAllObservedDocShells(mName, MarkerTracingType::END);
 }
 
 } // namespace mozilla
--- a/docshell/base/timeline/AutoGlobalTimelineMarker.h
+++ b/docshell/base/timeline/AutoGlobalTimelineMarker.h
@@ -3,19 +3,16 @@
 /* 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_AutoGlobalTimelineMarker_h_
 #define mozilla_AutoGlobalTimelineMarker_h_
 
 #include "mozilla/GuardObjects.h"
-#include "mozilla/RefPtr.h"
-
-class nsDocShell;
 
 namespace mozilla {
 
 // # AutoGlobalTimelineMarker
 //
 // Similar to `AutoTimelineMarker`, but adds its traced marker to all docshells,
 // not a single particular one. This is useful for operations that aren't
 // associated with any one particular doc shell, or when it isn't clear which
--- a/docshell/base/timeline/AutoTimelineMarker.cpp
+++ b/docshell/base/timeline/AutoTimelineMarker.cpp
@@ -1,40 +1,51 @@
 /* -*- 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 "mozilla/AutoTimelineMarker.h"
+#include "AutoTimelineMarker.h"
 
 #include "TimelineConsumers.h"
 #include "MainThreadUtils.h"
-#include "nsDocShell.h"
 
 namespace mozilla {
 
 AutoTimelineMarker::AutoTimelineMarker(nsIDocShell* aDocShell, const char* aName
                                        MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : mName(aName)
   , mDocShell(nullptr)
 {
   MOZ_GUARD_OBJECT_NOTIFIER_INIT;
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!aDocShell || TimelineConsumers::IsEmpty()) {
+  if (!aDocShell) {
     return;
   }
 
-  mDocShell = static_cast<nsDocShell*>(aDocShell);
-  TimelineConsumers::AddMarkerForDocShell(mDocShell, mName, MarkerTracingType::START);
+  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+  if (!timelines || !timelines->HasConsumer(aDocShell)) {
+    return;
+  }
+
+  mDocShell = aDocShell;
+  timelines->AddMarkerForDocShell(mDocShell, mName, MarkerTracingType::START);
 }
 
 AutoTimelineMarker::~AutoTimelineMarker()
 {
-  if (!mDocShell || TimelineConsumers::IsEmpty()) {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mDocShell) {
     return;
   }
 
-  TimelineConsumers::AddMarkerForDocShell(mDocShell, mName, MarkerTracingType::END);
+  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+  if (!timelines || !timelines->HasConsumer(mDocShell)) {
+    return;
+  }
+
+  timelines->AddMarkerForDocShell(mDocShell, mName, MarkerTracingType::END);
 }
 
 } // namespace mozilla
--- a/docshell/base/timeline/AutoTimelineMarker.h
+++ b/docshell/base/timeline/AutoTimelineMarker.h
@@ -6,17 +6,16 @@
 
 #ifndef mozilla_AutoTimelineMarker_h_
 #define mozilla_AutoTimelineMarker_h_
 
 #include "mozilla/GuardObjects.h"
 #include "mozilla/RefPtr.h"
 
 class nsIDocShell;
-class nsDocShell;
 
 namespace mozilla {
 
 // # AutoTimelineMarker
 //
 // An RAII class to trace some task in the platform by adding a start and end
 // timeline marker pair. These markers are then rendered in the devtools'
 // performance tool's waterfall graph.
@@ -31,17 +30,17 @@ namespace mozilla {
 class MOZ_RAII AutoTimelineMarker
 {
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
 
   // The name of the marker we are adding.
   const char* mName;
 
   // The docshell that is associated with this marker.
-  RefPtr<nsDocShell> mDocShell;
+  RefPtr<nsIDocShell> mDocShell;
 
 public:
   explicit AutoTimelineMarker(nsIDocShell* aDocShell, const char* aName
                               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
   ~AutoTimelineMarker();
 
   AutoTimelineMarker(const AutoTimelineMarker& aOther) = delete;
   void operator=(const AutoTimelineMarker& aOther) = delete;
--- a/docshell/base/timeline/LayerTimelineMarker.h
+++ b/docshell/base/timeline/LayerTimelineMarker.h
@@ -4,30 +4,28 @@
  * 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_LayerTimelineMarker_h_
 #define mozilla_LayerTimelineMarker_h_
 
 #include "TimelineMarker.h"
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "nsRegion.h"
 
 namespace mozilla {
 
 class LayerTimelineMarker : public TimelineMarker
 {
 public:
   explicit LayerTimelineMarker(const nsIntRegion& aRegion)
     : TimelineMarker("Layer", MarkerTracingType::HELPER_EVENT)
     , mRegion(aRegion)
   {}
 
-  ~LayerTimelineMarker()
-  {}
-
   void AddLayerRectangles(dom::Sequence<dom::ProfileTimelineLayerRect>& aRectangles)
   {
     nsIntRegionRectIterator it(mRegion);
     while (const nsIntRect* iterRect = it.Next()) {
       dom::ProfileTimelineLayerRect rect;
       rect.mX = iterRect->X();
       rect.mY = iterRect->Y();
       rect.mWidth = iterRect->Width();
new file mode 100644
--- /dev/null
+++ b/docshell/base/timeline/MarkersStorage.cpp
@@ -0,0 +1,29 @@
+/* -*- 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 "MarkersStorage.h"
+#include "MainThreadUtils.h"
+
+namespace mozilla {
+
+MarkersStorage::MarkersStorage(const char* aMutexName)
+  : mLock(aMutexName)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+}
+
+MarkersStorage::~MarkersStorage()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+}
+
+Mutex&
+MarkersStorage::GetLock()
+{
+  return mLock;
+}
+
+} // namespace mozilla
rename from docshell/base/timeline/OTMTMarkerReceiver.h
rename to docshell/base/timeline/MarkersStorage.h
--- a/docshell/base/timeline/OTMTMarkerReceiver.h
+++ b/docshell/base/timeline/MarkersStorage.h
@@ -1,40 +1,48 @@
 /* -*- 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_OTMTMarkerObserver_h_
-#define mozilla_OTMTMarkerObserver_h_
+#ifndef mozilla_MarkersStorage_h_
+#define mozilla_MarkersStorage_h_
 
+#include "TimelineMarkerEnums.h" // for MarkerReleaseRequest
 #include "mozilla/Mutex.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/LinkedList.h"
+#include "nsTArray.h"
 
 namespace mozilla {
 class AbstractTimelineMarker;
 
-class OTMTMarkerReceiver
+namespace dom {
+struct ProfileTimelineMarker;
+}
+
+class MarkersStorage : public LinkedListElement<MarkersStorage>
 {
 private:
-  OTMTMarkerReceiver() = delete;
-  OTMTMarkerReceiver(const OTMTMarkerReceiver& aOther) = delete;
-  void operator=(const OTMTMarkerReceiver& aOther) = delete;
+  MarkersStorage() = delete;
+  MarkersStorage(const MarkersStorage& aOther) = delete;
+  void operator=(const MarkersStorage& aOther) = delete;
 
 public:
-  explicit OTMTMarkerReceiver(const char* aMutexName)
-    : mLock(aMutexName)
-  {
-  }
+  explicit MarkersStorage(const char* aMutexName);
+  virtual ~MarkersStorage();
 
-  virtual ~OTMTMarkerReceiver() {}
-  virtual void AddOTMTMarkerClone(UniquePtr<AbstractTimelineMarker>& aMarker) = 0;
+  virtual void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) = 0;
+  virtual void AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) = 0;
+  virtual void ClearMarkers() = 0;
+  virtual void PopMarkers(JSContext* aCx, nsTArray<dom::ProfileTimelineMarker>& aStore) = 0;
 
 protected:
-  Mutex& GetLock() { return mLock; };
+  Mutex& GetLock();
 
 private:
   Mutex mLock;
 };
 
 } // namespace mozilla
 
-#endif /* mozilla_OTMTMarkerObserver_h_ */
+#endif /* mozilla_MarkersStorage_h_ */
--- a/docshell/base/timeline/ObservedDocShell.cpp
+++ b/docshell/base/timeline/ObservedDocShell.cpp
@@ -8,58 +8,71 @@
 
 #include "AbstractTimelineMarker.h"
 #include "LayerTimelineMarker.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Move.h"
 
 namespace mozilla {
 
-ObservedDocShell::ObservedDocShell(nsDocShell* aDocShell)
-  : OTMTMarkerReceiver("ObservedDocShellMutex")
+ObservedDocShell::ObservedDocShell(nsIDocShell* aDocShell)
+  : MarkersStorage("ObservedDocShellMutex")
   , mDocShell(aDocShell)
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 void
 ObservedDocShell::AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker)
 {
+  // Only allow main thread markers to go into this list. No need to lock
+  // here since `mTimelineMarkers` will only be accessed or modified on the
+  // main thread only.
   MOZ_ASSERT(NS_IsMainThread());
   mTimelineMarkers.AppendElement(Move(aMarker));
 }
 
 void
-ObservedDocShell::AddOTMTMarkerClone(UniquePtr<AbstractTimelineMarker>& aMarker)
+ObservedDocShell::AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker)
 {
+  // Only allow off the main thread markers to go into this list. Since most
+  // of our markers come from the main thread, be a little more efficient and
+  // avoid dealing with multithreading scenarios until all the markers are
+  // actually cleared or popped in `ClearMarkers` or `PopMarkers`.
   MOZ_ASSERT(!NS_IsMainThread());
-  MutexAutoLock lock(GetLock());
-  UniquePtr<AbstractTimelineMarker> cloned = aMarker->Clone();
-  mTimelineMarkers.AppendElement(Move(cloned));
+  MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`.
+  mOffTheMainThreadTimelineMarkers.AppendElement(Move(aMarker));
 }
 
 void
 ObservedDocShell::ClearMarkers()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`.
   mTimelineMarkers.Clear();
+  mOffTheMainThreadTimelineMarkers.Clear();
 }
 
 void
 ObservedDocShell::PopMarkers(JSContext* aCx,
                              nsTArray<dom::ProfileTimelineMarker>& aStore)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`.
+
+  // First, move all of our markers into a single array. We'll chose
+  // the `mTimelineMarkers` store because that's where we expect most of
+  // our markers to be.
+  mTimelineMarkers.AppendElements(Move(mOffTheMainThreadTimelineMarkers));
 
   // If we see an unpaired START, we keep it around for the next call
   // to ObservedDocShell::PopMarkers. We store the kept START objects here.
   nsTArray<UniquePtr<AbstractTimelineMarker>> keptStartMarkers;
 
   for (uint32_t i = 0; i < mTimelineMarkers.Length(); ++i) {
-    UniquePtr<AbstractTimelineMarker>& startPayload = mTimelineMarkers[i];
+    UniquePtr<AbstractTimelineMarker>& startPayload = mTimelineMarkers.ElementAt(i);
 
     // If this is a TIMESTAMP marker, there's no corresponding END,
     // as it's a single unit of time, not a duration.
     if (startPayload->GetTracingType() == MarkerTracingType::TIMESTAMP) {
       dom::ProfileTimelineMarker* marker = aStore.AppendElement();
       marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
       marker->mStart = startPayload->GetTime();
       marker->mEnd = startPayload->GetTime();
@@ -88,22 +101,23 @@ ObservedDocShell::PopMarkers(JSContext* 
       // for the matching end. It doesn't hurt to apply this logic to
       // all event types.
       uint32_t markerDepth = 0;
 
       // The assumption is that the devtools timeline flushes markers frequently
       // enough for the amount of markers to always be small enough that the
       // nested for loop isn't going to be a performance problem.
       for (uint32_t j = i + 1; j < mTimelineMarkers.Length(); ++j) {
-        UniquePtr<AbstractTimelineMarker>& endPayload = mTimelineMarkers[j];
+        UniquePtr<AbstractTimelineMarker>& endPayload = mTimelineMarkers.ElementAt(j);
         bool endIsLayerType = strcmp(endPayload->GetName(), "Layer") == 0;
 
         // Look for "Layer" markers to stream out "Paint" markers.
         if (startIsPaintType && endIsLayerType) {
-          LayerTimelineMarker* layerPayload = static_cast<LayerTimelineMarker*>(endPayload.get());
+          AbstractTimelineMarker* raw = endPayload.get();
+          LayerTimelineMarker* layerPayload = static_cast<LayerTimelineMarker*>(raw);
           layerPayload->AddLayerRectangles(layerRectangles);
           hasSeenLayerType = true;
         }
         if (!startPayload->Equals(*endPayload)) {
           continue;
         }
         if (endPayload->GetTracingType() == MarkerTracingType::START) {
           ++markerDepth;
@@ -128,17 +142,17 @@ ObservedDocShell::PopMarkers(JSContext* 
           }
           hasSeenEnd = true;
           break;
         }
       }
 
       // If we did not see the corresponding END, keep the START.
       if (!hasSeenEnd) {
-        keptStartMarkers.AppendElement(Move(mTimelineMarkers[i]));
+        keptStartMarkers.AppendElement(Move(mTimelineMarkers.ElementAt(i)));
         mTimelineMarkers.RemoveElementAt(i);
         --i;
       }
     }
   }
 
   mTimelineMarkers.SwapElements(keptStartMarkers);
 }
--- a/docshell/base/timeline/ObservedDocShell.h
+++ b/docshell/base/timeline/ObservedDocShell.h
@@ -2,46 +2,50 @@
 /* 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_ObservedDocShell_h_
 #define mozilla_ObservedDocShell_h_
 
-#include "OTMTMarkerReceiver.h"
-#include "nsTArray.h"
+#include "MarkersStorage.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
 
-class nsDocShell;
+class nsIDocShell;
 
 namespace mozilla {
 class AbstractTimelineMarker;
 
 namespace dom {
 struct ProfileTimelineMarker;
 }
 
 // # ObservedDocShell
 //
 // A wrapper around a docshell for which docshell-specific markers are
 // allowed to exist. See TimelineConsumers for register/unregister logic.
-class ObservedDocShell : public LinkedListElement<ObservedDocShell>,
-                         public OTMTMarkerReceiver
+class ObservedDocShell : public MarkersStorage
 {
 private:
-  RefPtr<nsDocShell> mDocShell;
+  RefPtr<nsIDocShell> mDocShell;
+
+  // Main thread only.
   nsTArray<UniquePtr<AbstractTimelineMarker>> mTimelineMarkers;
 
+  // Off the main thread only.
+  nsTArray<UniquePtr<AbstractTimelineMarker>> mOffTheMainThreadTimelineMarkers;
+
 public:
-  explicit ObservedDocShell(nsDocShell* aDocShell);
-  nsDocShell* operator*() const { return mDocShell.get(); }
+  explicit ObservedDocShell(nsIDocShell* aDocShell);
+  nsIDocShell* operator*() const { return mDocShell.get(); }
 
-  void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker);
-  void AddOTMTMarkerClone(UniquePtr<AbstractTimelineMarker>& aMarker) override;
-
-  void ClearMarkers();
-  void PopMarkers(JSContext* aCx, nsTArray<dom::ProfileTimelineMarker>& aStore);
+  void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) override;
+  void AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) override;
+  void ClearMarkers() override;
+  void PopMarkers(JSContext* aCx, nsTArray<dom::ProfileTimelineMarker>& aStore) override;
 };
 
 } // namespace mozilla
 
 #endif /* mozilla_ObservedDocShell_h_ */
--- a/docshell/base/timeline/TimelineConsumers.cpp
+++ b/docshell/base/timeline/TimelineConsumers.cpp
@@ -1,134 +1,218 @@
 /* -*- 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 "mozilla/TimelineConsumers.h"
+#include "TimelineConsumers.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "nsAppRunner.h" // for XRE_IsContentProcess, XRE_IsParentProcess
+#include "nsDocShell.h"
 
 namespace mozilla {
 
-unsigned long TimelineConsumers::sActiveConsumers = 0;
-LinkedList<ObservedDocShell>* TimelineConsumers::sObservedDocShells = nullptr;
-Mutex* TimelineConsumers::sLock = nullptr;
+NS_IMPL_ISUPPORTS(TimelineConsumers, nsIObserver);
+
+StaticMutex TimelineConsumers::sMutex;
+
+// Manually manage this singleton's lifetime and destroy it before shutdown.
+// This avoids the leakchecker detecting false-positive memory leaks when
+// using automatic memory management (i.e. statically instantiating this
+// singleton inside the `Get` method), which would automatically destroy it on
+// application shutdown, but too late for the leakchecker. Sigh...
+StaticRefPtr<TimelineConsumers> TimelineConsumers::sInstance;
+
+// This flag makes sure the singleton never gets instantiated while a shutdown
+// is in progress. This can actually happen, and `ClearOnShutdown` doesn't work
+// in these cases.
+bool TimelineConsumers::sInShutdown = false;
+
+already_AddRefed<TimelineConsumers>
+TimelineConsumers::Get()
+{
+  // Using this class is not supported yet for other processes other than
+  // parent or content. To avoid accidental checks to methods like `IsEmpty`,
+  // which would probably always be true in those cases, assert here.
+  // Remember, there will be different singletons available to each process.
+  MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsParentProcess());
 
-LinkedList<ObservedDocShell>&
-TimelineConsumers::GetOrCreateObservedDocShellsList()
-{
-  if (!sObservedDocShells) {
-    sObservedDocShells = new LinkedList<ObservedDocShell>();
+  // If we are shutting down, don't bother doing anything. Note: we can only
+  // know whether or not we're in shutdown if we're instantiated.
+  if (sInShutdown) {
+    return nullptr;
   }
-  return *sObservedDocShells;
+
+  // Note: We don't simply check `sInstance` for null-ness here, since otherwise
+  // this can resurrect the TimelineConsumers pretty late during shutdown.
+  // We won't know if we're in shutdown or not though, because the singleton
+  // could have been destroyed or just never instantiated, so in the previous
+  // conditional `sInShutdown` would be false.
+  static bool firstTime = true;
+  if (firstTime) {
+    firstTime = false;
+
+    StaticMutexAutoLock lock(sMutex);
+    sInstance = new TimelineConsumers();
+
+    // Make sure the initialization actually suceeds, otherwise don't allow
+    // access by destroying the instance immediately.
+    if (sInstance->Init()) {
+      ClearOnShutdown(&sInstance);
+    } else {
+      NS_WARNING("TimelineConsumers could not be initialized.");
+      sInstance->RemoveObservers();
+      sInstance = nullptr;
+    }
+  }
+
+  RefPtr<TimelineConsumers> copy = sInstance.get();
+  return copy.forget();
 }
 
-Mutex&
-TimelineConsumers::GetLock()
+bool
+TimelineConsumers::Init()
+{
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (!obs) {
+    return false;
+  }
+  if (NS_WARN_IF(NS_FAILED(
+    obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)))) {
+    return false;
+  }
+  return true;
+}
+
+bool
+TimelineConsumers::RemoveObservers()
 {
-  if (!sLock) {
-    sLock = new Mutex("TimelineConsumersMutex");
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (!obs) {
+    return false;
+  }
+  if (NS_WARN_IF(NS_FAILED(
+    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)))) {
+    return false;
   }
-  return *sLock;
+  return true;
+}
+
+nsresult
+TimelineConsumers::Observe(nsISupports* aSubject,
+                           const char* aTopic,
+                           const char16_t* aData)
+{
+  if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    sInShutdown = true;
+    RemoveObservers();
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(false, "TimelineConsumers got unexpected topic!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+TimelineConsumers::TimelineConsumers()
+  : mActiveConsumers(0)
+{
 }
 
 void
 TimelineConsumers::AddConsumer(nsDocShell* aDocShell)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  StaticMutexAutoLock lock(sMutex); // for `mActiveConsumers` and `mMarkersStores`.
+
   UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved;
-
   MOZ_ASSERT(!observed);
-  sActiveConsumers++;
-  observed.reset(new ObservedDocShell(aDocShell));
-  GetOrCreateObservedDocShellsList().insertFront(observed.get());
+
+  mActiveConsumers++;
+
+  ObservedDocShell* obsDocShell = new ObservedDocShell(aDocShell);
+  MarkersStorage* storage = static_cast<MarkersStorage*>(obsDocShell);
+
+  observed.reset(obsDocShell);
+  mMarkersStores.insertFront(storage);
 }
 
 void
 TimelineConsumers::RemoveConsumer(nsDocShell* aDocShell)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  StaticMutexAutoLock lock(sMutex); // for `mActiveConsumers` and `mMarkersStores`.
+
   UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved;
-
   MOZ_ASSERT(observed);
-  sActiveConsumers--;
+
+  mActiveConsumers--;
+
+  // Clear all markers from the `mTimelineMarkers` store.
   observed.get()->ClearMarkers();
+  // Remove self from the `mMarkersStores` store.
   observed.get()->remove();
+  // Prepare for becoming a consumer later.
   observed.reset(nullptr);
 }
 
 bool
+TimelineConsumers::HasConsumer(nsIDocShell* aDocShell)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!aDocShell) {
+    return false;
+  }
+  bool isTimelineRecording = false;
+  aDocShell->GetRecordProfileTimelineMarkers(&isTimelineRecording);
+  return isTimelineRecording;
+}
+
+bool
 TimelineConsumers::IsEmpty()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  return sActiveConsumers == 0;
-}
-
-bool
-TimelineConsumers::GetKnownDocShells(Vector<RefPtr<nsDocShell>>& aStore)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  const LinkedList<ObservedDocShell>& docShells = GetOrCreateObservedDocShellsList();
-
-  for (const ObservedDocShell* rds = docShells.getFirst();
-       rds != nullptr;
-       rds = rds->getNext()) {
-    if (!aStore.append(**rds)) {
-      return false;
-    }
-  }
-
-  return true;
+  StaticMutexAutoLock lock(sMutex); // for `mActiveConsumers`.
+  return mActiveConsumers == 0;
 }
 
 void
 TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell,
                                         const char* aName,
                                         MarkerTracingType aTracingType)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (aDocShell->IsObserved()) {
+  if (HasConsumer(aDocShell)) {
     aDocShell->mObserved->AddMarker(Move(MakeUnique<TimelineMarker>(aName, aTracingType)));
   }
 }
 
 void
 TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell,
                                         const char* aName,
                                         const TimeStamp& aTime,
                                         MarkerTracingType aTracingType)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (aDocShell->IsObserved()) {
+  if (HasConsumer(aDocShell)) {
     aDocShell->mObserved->AddMarker(Move(MakeUnique<TimelineMarker>(aName, aTime, aTracingType)));
   }
 }
 
 void
 TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell,
                                         UniquePtr<AbstractTimelineMarker>&& aMarker)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (aDocShell->IsObserved()) {
+  if (HasConsumer(aDocShell)) {
     aDocShell->mObserved->AddMarker(Move(aMarker));
   }
 }
 
 void
-TimelineConsumers::AddOTMTMarkerForDocShell(nsDocShell* aDocShell,
-                                            UniquePtr<AbstractTimelineMarker>& aMarker)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  GetLock().AssertCurrentThreadOwns();
-  if (aDocShell->IsObserved()) {
-    aDocShell->mObserved->AddOTMTMarkerClone(aMarker);
-  }
-}
-
-void
 TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell,
                                         const char* aName,
                                         MarkerTracingType aTracingType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTracingType);
 }
 
@@ -146,114 +230,67 @@ void
 TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell,
                                         UniquePtr<AbstractTimelineMarker>&& aMarker)
 {
   MOZ_ASSERT(NS_IsMainThread());
   AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), Move(aMarker));
 }
 
 void
-TimelineConsumers::AddOTMTMarkerForDocShell(nsIDocShell* aDocShell,
-                                            UniquePtr<AbstractTimelineMarker>& aMarker)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  GetLock().AssertCurrentThreadOwns();
-  AddOTMTMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aMarker);
-}
-
-void
-TimelineConsumers::AddMarkerForDocShellsList(Vector<RefPtr<nsDocShell>>& aDocShells,
-                                             const char* aName,
-                                             MarkerTracingType aTracingType)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  for (Vector<RefPtr<nsDocShell>>::Range range = aDocShells.all();
-       !range.empty();
-       range.popFront()) {
-    AddMarkerForDocShell(range.front(), aName, aTracingType);
-  }
-}
-
-void
-TimelineConsumers::AddMarkerForDocShellsList(Vector<RefPtr<nsDocShell>>& aDocShells,
-                                             const char* aName,
-                                             const TimeStamp& aTime,
-                                             MarkerTracingType aTracingType)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  for (Vector<RefPtr<nsDocShell>>::Range range = aDocShells.all();
-       !range.empty();
-       range.popFront()) {
-    AddMarkerForDocShell(range.front(), aName, aTime, aTracingType);
-  }
-}
-
-void
-TimelineConsumers::AddMarkerForDocShellsList(Vector<RefPtr<nsDocShell>>& aDocShells,
-                                             UniquePtr<AbstractTimelineMarker>& aMarker)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  for (Vector<RefPtr<nsDocShell>>::Range range = aDocShells.all();
-       !range.empty();
-       range.popFront()) {
-    UniquePtr<AbstractTimelineMarker> cloned = aMarker->Clone();
-    AddMarkerForDocShell(range.front(), Move(cloned));
-  }
-}
-
-void
-TimelineConsumers::AddOTMTMarkerForDocShellsList(Vector<RefPtr<nsDocShell>>& aDocShells,
-                                                 UniquePtr<AbstractTimelineMarker>& aMarker)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  GetLock().AssertCurrentThreadOwns();
-  for (Vector<RefPtr<nsDocShell>>::Range range = aDocShells.all();
-       !range.empty();
-       range.popFront()) {
-    AddOTMTMarkerForDocShell(range.front(), aMarker);
-  }
-}
-
-void
 TimelineConsumers::AddMarkerForAllObservedDocShells(const char* aName,
                                                     MarkerTracingType aTracingType)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  Vector<RefPtr<nsDocShell>> docShells;
-  if (GetKnownDocShells(docShells)) {
-    AddMarkerForDocShellsList(docShells, aName, aTracingType);
+  bool isMainThread = NS_IsMainThread();
+  StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`.
+
+  for (MarkersStorage* storage = mMarkersStores.getFirst();
+       storage != nullptr;
+       storage = storage->getNext()) {
+    UniquePtr<AbstractTimelineMarker> marker =
+      MakeUnique<TimelineMarker>(aName, aTracingType);
+    if (isMainThread) {
+      storage->AddMarker(Move(marker));
+    } else {
+      storage->AddOTMTMarker(Move(marker));
+    }
   }
 }
 
 void
 TimelineConsumers::AddMarkerForAllObservedDocShells(const char* aName,
                                                     const TimeStamp& aTime,
                                                     MarkerTracingType aTracingType)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  Vector<RefPtr<nsDocShell>> docShells;
-  if (GetKnownDocShells(docShells)) {
-    AddMarkerForDocShellsList(docShells, aName, aTime, aTracingType);
+  bool isMainThread = NS_IsMainThread();
+  StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`.
+
+  for (MarkersStorage* storage = mMarkersStores.getFirst();
+       storage != nullptr;
+       storage = storage->getNext()) {
+    UniquePtr<AbstractTimelineMarker> marker =
+      MakeUnique<TimelineMarker>(aName, aTime, aTracingType);
+    if (isMainThread) {
+      storage->AddMarker(Move(marker));
+    } else {
+      storage->AddOTMTMarker(Move(marker));
+    }
   }
 }
 
 void
 TimelineConsumers::AddMarkerForAllObservedDocShells(UniquePtr<AbstractTimelineMarker>& aMarker)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  Vector<RefPtr<nsDocShell>> docShells;
-  if (GetKnownDocShells(docShells)) {
-    AddMarkerForDocShellsList(docShells, aMarker);
-  }
-}
+  bool isMainThread = NS_IsMainThread();
+  StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`.
 
-void
-TimelineConsumers::AddOTMTMarkerForAllObservedDocShells(UniquePtr<AbstractTimelineMarker>& aMarker)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  GetLock().AssertCurrentThreadOwns();
-  Vector<RefPtr<nsDocShell>> docShells;
-  if (GetKnownDocShells(docShells)) {
-    AddOTMTMarkerForDocShellsList(docShells, aMarker);
+  for (MarkersStorage* storage = mMarkersStores.getFirst();
+       storage != nullptr;
+       storage = storage->getNext()) {
+    UniquePtr<AbstractTimelineMarker> clone = aMarker->Clone();
+    if (isMainThread) {
+      storage->AddMarker(Move(clone));
+    } else {
+      storage->AddOTMTMarker(Move(clone));
+    }
   }
 }
 
 } // namespace mozilla
--- a/docshell/base/timeline/TimelineConsumers.h
+++ b/docshell/base/timeline/TimelineConsumers.h
@@ -1,112 +1,120 @@
-/* -*- 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_TimelineConsumers_h_
 #define mozilla_TimelineConsumers_h_
 
+#include "nsIObserver.h"
+#include "mozilla/StaticPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/LinkedList.h"
-#include "mozilla/Vector.h"
-#include "mozilla/TimeStamp.h"
-#include "mozilla/Mutex.h"
-
-#include "TimelineMarkerEnums.h"
+#include "mozilla/StaticMutex.h"
+#include "TimelineMarkerEnums.h" // for MarkerTracingType
 
 class nsDocShell;
 class nsIDocShell;
 
 namespace mozilla {
-class ObservedDocShell;
+class TimeStamp;
+class MarkersStorage;
 class AbstractTimelineMarker;
 
-class TimelineConsumers
+class TimelineConsumers : public nsIObserver
 {
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
 private:
-  // Counter for how many timelines are currently interested in markers.
-  static unsigned long sActiveConsumers;
-  static LinkedList<ObservedDocShell>* sObservedDocShells;
-  static LinkedList<ObservedDocShell>& GetOrCreateObservedDocShellsList();
+  TimelineConsumers();
+  TimelineConsumers(const TimelineConsumers& aOther) = delete;
+  void operator=(const TimelineConsumers& aOther) = delete;
+  virtual ~TimelineConsumers() = default;
 
-  // Lock used when adding off-the-main-thread markers.
-  static Mutex* sLock;
+  bool Init();
+  bool RemoveObservers();
 
 public:
-  static Mutex& GetLock();
+  static already_AddRefed<TimelineConsumers> Get();
 
-  static void AddConsumer(nsDocShell* aDocShell);
-  static void RemoveConsumer(nsDocShell* aDocShell);
-  static bool IsEmpty();
-  static bool GetKnownDocShells(Vector<RefPtr<nsDocShell>>& aStore);
+  // Methods for registering interested consumers (i.e. "devtools toolboxes").
+  // Each consumer should be directly focused on a particular docshell, but
+  // timeline markers don't necessarily have to be tied to that docshell.
+  // See the public `AddMarker*` methods below.
+  // Main thread only.
+  void AddConsumer(nsDocShell* aDocShell);
+  void RemoveConsumer(nsDocShell* aDocShell);
+
+  bool HasConsumer(nsIDocShell* aDocShell);
+
+  // Checks if there's any existing interested consumer.
+  // May be called from any thread.
+  bool IsEmpty();
 
   // Methods for adding markers relevant for particular docshells, or generic
   // (meaning that they either can't be tied to a particular docshell, or one
   // wasn't accessible in the part of the codebase where they're instantiated).
-
   // These will only add markers if at least one docshell is currently being
   // observed by a timeline. Markers tied to a particular docshell won't be
   // created unless that docshell is specifically being currently observed.
   // See nsIDocShell::recordProfileTimelineMarkers
 
-  // These methods create a custom marker from a name and some metadata,
+  // These methods create a basic TimelineMarker from a name and some metadata,
   // relevant for a specific docshell.
-  static void AddMarkerForDocShell(nsDocShell* aDocShell,
-                                   const char* aName,
-                                   MarkerTracingType aTracingType);
-  static void AddMarkerForDocShell(nsIDocShell* aDocShell,
-                                   const char* aName,
-                                   MarkerTracingType aTracingType);
+  // Main thread only.
+  void AddMarkerForDocShell(nsDocShell* aDocShell,
+                            const char* aName,
+                            MarkerTracingType aTracingType);
+  void AddMarkerForDocShell(nsIDocShell* aDocShell,
+                            const char* aName,
+                            MarkerTracingType aTracingType);
 
-  static void AddMarkerForDocShell(nsDocShell* aDocShell,
-                                   const char* aName,
-                                   const TimeStamp& aTime,
-                                   MarkerTracingType aTracingType);
-  static void AddMarkerForDocShell(nsIDocShell* aDocShell,
-                                   const char* aName,
-                                   const TimeStamp& aTime,
-                                   MarkerTracingType aTracingType);
+  void AddMarkerForDocShell(nsDocShell* aDocShell,
+                            const char* aName,
+                            const TimeStamp& aTime,
+                            MarkerTracingType aTracingType);
+  void AddMarkerForDocShell(nsIDocShell* aDocShell,
+                            const char* aName,
+                            const TimeStamp& aTime,
+                            MarkerTracingType aTracingType);
 
   // These methods register and receive ownership of an already created marker,
   // relevant for a specific docshell.
-  static void AddMarkerForDocShell(nsDocShell* aDocShell,
-                                   UniquePtr<AbstractTimelineMarker>&& aMarker);
-  static void AddMarkerForDocShell(nsIDocShell* aDocShell,
-                                   UniquePtr<AbstractTimelineMarker>&& aMarker);
+  // Main thread only.
+  void AddMarkerForDocShell(nsDocShell* aDocShell,
+                            UniquePtr<AbstractTimelineMarker>&& aMarker);
+  void AddMarkerForDocShell(nsIDocShell* aDocShell,
+                            UniquePtr<AbstractTimelineMarker>&& aMarker);
 
-  // These methods create or clone markers relevant for a list of docshells.
-  static void AddMarkerForDocShellsList(Vector<RefPtr<nsDocShell>>& aDocShells,
-                                        const char* aName,
+  // These methods create a basic marker from a name and some metadata,
+  // which doesn't have to be relevant to a specific docshell.
+  // May be called from any thread.
+  void AddMarkerForAllObservedDocShells(const char* aName,
                                         MarkerTracingType aTracingType);
-  static void AddMarkerForDocShellsList(Vector<RefPtr<nsDocShell>>& aDocShells,
-                                        const char* aName,
+  void AddMarkerForAllObservedDocShells(const char* aName,
                                         const TimeStamp& aTime,
                                         MarkerTracingType aTracingType);
-  static void AddMarkerForDocShellsList(Vector<RefPtr<nsDocShell>>& aDocShells,
-                                        UniquePtr<AbstractTimelineMarker>& aMarker);
+
+  // This method clones and registers an already instantiated marker,
+  // which doesn't have to be relevant to a specific docshell.
+  // May be called from any thread.
+  void AddMarkerForAllObservedDocShells(UniquePtr<AbstractTimelineMarker>& aMarker);
 
-  // These methods create or clone markers, none of which have to be tied to
-  // a particular docshell.
-  static void AddMarkerForAllObservedDocShells(const char* aName,
-                                               MarkerTracingType aTracingType);
-  static void AddMarkerForAllObservedDocShells(const char* aName,
-                                               const TimeStamp& aTime,
-                                               MarkerTracingType aTracingType);
-  static void AddMarkerForAllObservedDocShells(UniquePtr<AbstractTimelineMarker>& aMarker);
+private:
+  static StaticRefPtr<TimelineConsumers> sInstance;
+  static bool sInShutdown;
 
-  // Thread-safe versions of the above methods. Need to lock first using
-  // the mutex returned by `TimelineConsumers::GetLock()`.
-  static void AddOTMTMarkerForDocShell(nsDocShell* aDocShell,
-                                       UniquePtr<AbstractTimelineMarker>& aMarker);
-  static void AddOTMTMarkerForDocShell(nsIDocShell* aDocShell,
-                                       UniquePtr<AbstractTimelineMarker>& aMarker);
-  static void AddOTMTMarkerForDocShellsList(Vector<RefPtr<nsDocShell>>& aDocShells,
-                                            UniquePtr<AbstractTimelineMarker>& aMarker);
-  static void AddOTMTMarkerForAllObservedDocShells(UniquePtr<AbstractTimelineMarker>& aMarker);
+  // Counter for how many timelines are currently interested in markers,
+  // and a list of the MarkersStorage interfaces representing them.
+  unsigned long mActiveConsumers;
+  LinkedList<MarkersStorage> mMarkersStores;
 
+  // Protects this class's data structures.
+  static StaticMutex sMutex;
 };
 
 } // namespace mozilla
 
 #endif /* mozilla_TimelineConsumers_h_ */
--- a/docshell/base/timeline/TimelineMarker.cpp
+++ b/docshell/base/timeline/TimelineMarker.cpp
@@ -20,24 +20,16 @@ TimelineMarker::TimelineMarker(const cha
                                const TimeStamp& aTime,
                                MarkerTracingType aTracingType,
                                MarkerStackRequest aStackRequest)
   : AbstractTimelineMarker(aName, aTime, aTracingType)
 {
   CaptureStackIfNecessary(aTracingType, aStackRequest);
 }
 
-bool
-TimelineMarker::Equals(const AbstractTimelineMarker& aOther)
-{
-  // Check whether two markers should be considered the same, for the purpose
-  // of pairing start and end markers. Normally this definition suffices.
-  return strcmp(GetName(), aOther.GetName()) == 0;
-}
-
 void
 TimelineMarker::AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker)
 {
   // Nothing to do here for plain markers.
 }
 
 JSObject*
 TimelineMarker::GetStack()
--- a/docshell/base/timeline/TimelineMarker.h
+++ b/docshell/base/timeline/TimelineMarker.h
@@ -3,35 +3,35 @@
 /* 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_TimelineMarker_h_
 #define mozilla_TimelineMarker_h_
 
 #include "AbstractTimelineMarker.h"
+#include "js/RootingAPI.h"
 
 namespace mozilla {
 
 // Objects of this type can be added to the timeline if there is an interested
 // consumer. The class can also be subclassed to let a given marker creator
 // provide custom details.
 class TimelineMarker : public AbstractTimelineMarker
 {
 public:
-  TimelineMarker(const char* aName,
-                 MarkerTracingType aTracingType,
-                 MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+  explicit TimelineMarker(const char* aName,
+                          MarkerTracingType aTracingType,
+                          MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
 
-  TimelineMarker(const char* aName,
-                 const TimeStamp& aTime,
-                 MarkerTracingType aTracingType,
-                 MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+  explicit TimelineMarker(const char* aName,
+                          const TimeStamp& aTime,
+                          MarkerTracingType aTracingType,
+                          MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
 
-  virtual bool Equals(const AbstractTimelineMarker& aOther) override;
   virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override;
   virtual JSObject* GetStack() override;
 
 protected:
   void CaptureStack();
 
 private:
   // While normally it is not a good idea to make a persistent root,
new file mode 100644
--- /dev/null
+++ b/docshell/base/timeline/WorkerTimelineMarker.h
@@ -0,0 +1,44 @@
+/* -*- 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_WorkerTimelineMarker_h_
+#define mozilla_WorkerTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class WorkerTimelineMarker : public TimelineMarker
+{
+public:
+  explicit WorkerTimelineMarker(ProfileTimelineWorkerOperationType aOperationType,
+                                MarkerTracingType aTracingType)
+    : TimelineMarker("Worker", aTracingType, MarkerStackRequest::NO_STACK)
+    , mOperationType(aOperationType)
+  {}
+
+  virtual UniquePtr<AbstractTimelineMarker> Clone() override
+  {
+    WorkerTimelineMarker* clone = new WorkerTimelineMarker(mOperationType, GetTracingType());
+    clone->SetCustomTime(GetTime());
+    return UniquePtr<AbstractTimelineMarker>(clone);
+  }
+
+  virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override
+  {
+    if (GetTracingType() == MarkerTracingType::START) {
+      aMarker.mWorkerOperation.Construct(mOperationType);
+    }
+  }
+
+private:
+  ProfileTimelineWorkerOperationType mOperationType;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_WorkerTimelineMarker_h_ */
--- a/docshell/base/timeline/moz.build
+++ b/docshell/base/timeline/moz.build
@@ -7,29 +7,31 @@
 EXPORTS.mozilla += [
     'AbstractTimelineMarker.h',
     'AutoGlobalTimelineMarker.h',
     'AutoTimelineMarker.h',
     'ConsoleTimelineMarker.h',
     'EventTimelineMarker.h',
     'JavascriptTimelineMarker.h',
     'LayerTimelineMarker.h',
+    'MarkersStorage.h',
     'ObservedDocShell.h',
-    'OTMTMarkerReceiver.h',
     'RestyleTimelineMarker.h',
     'TimelineConsumers.h',
     'TimelineMarker.h',
     'TimelineMarkerEnums.h',
     'TimestampTimelineMarker.h',
+    'WorkerTimelineMarker.h',
 ]
 
 UNIFIED_SOURCES += [
     'AbstractTimelineMarker.cpp',
     'AutoGlobalTimelineMarker.cpp',
     'AutoTimelineMarker.cpp',
+    'MarkersStorage.cpp',
     'ObservedDocShell.cpp',
     'TimelineConsumers.cpp',
     'TimelineMarker.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
--- a/docshell/test/browser/browser_timelineMarkers-frame-02.js
+++ b/docshell/test/browser/browser_timelineMarkers-frame-02.js
@@ -100,16 +100,17 @@ var TESTS = [{
     is(markers[0].name, "TimeStamp", "Got Timestamp marker");
     is(markers[0].causeName, "rock", "Got Timestamp label value");
     content.console.timeStamp("paper");
     content.console.timeStamp("scissors");
     content.console.timeStamp();
     content.console.timeStamp(undefined);
   },
   check: function (markers) {
+    markers = markers.filter(e => e.name != "Worker");
     is(markers.length, 4, "Got 4 markers");
     is(markers[0].name, "TimeStamp", "Got Timestamp marker");
     is(markers[0].causeName, "paper", "Got Timestamp label value");
     is(markers[1].name, "TimeStamp", "Got Timestamp marker");
     is(markers[1].causeName, "scissors", "Got Timestamp label value");
     is(markers[2].name, "TimeStamp", "Got empty Timestamp marker when no argument given");
     is(markers[2].causeName, void 0, "Got empty Timestamp label value");
     is(markers[3].name, "TimeStamp", "Got empty Timestamp marker when argument is undefined");
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -1060,52 +1060,51 @@ Console::Method(JSContext* aCx, MethodNa
 
       RefPtr<nsPerformance> performance = win->GetPerformance();
       if (!performance) {
         return;
       }
 
       callData->mMonotonicTimer = performance->Now();
 
-      // 'time' and 'timeEnd' are displayed in the devtools timeline if active.
-      bool isTimelineRecording = false;
       nsDocShell* docShell = static_cast<nsDocShell*>(mWindow->GetDocShell());
-      if (docShell) {
-        docShell->GetRecordProfileTimelineMarkers(&isTimelineRecording);
-      }
+      RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+      bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
 
-      // 'timeStamp' recordings do not need an argument; use empty string
-      // if no arguments passed in
+      // The 'timeStamp' recordings do not need an argument; use empty string
+      // if no arguments passed in.
       if (isTimelineRecording && aMethodName == MethodTimeStamp) {
-        JS::Rooted<JS::Value> value(aCx, aData.Length() == 0 ?
-                                    JS_GetEmptyStringValue(aCx) : aData[0]);
+        JS::Rooted<JS::Value> value(aCx, aData.Length() == 0
+          ? JS_GetEmptyStringValue(aCx)
+          : aData[0]);
         JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
+
         nsAutoJSString key;
         if (jsString) {
           key.init(aCx, jsString);
         }
 
-        UniquePtr<TimelineMarker> marker = MakeUnique<TimestampTimelineMarker>(key);
-        TimelineConsumers::AddMarkerForDocShell(docShell, Move(marker));
+        timelines->AddMarkerForDocShell(docShell, Move(
+          MakeUnique<TimestampTimelineMarker>(key)));
       }
-      // For `console.time(foo)` and `console.timeEnd(foo)`
+      // For `console.time(foo)` and `console.timeEnd(foo)`.
       else if (isTimelineRecording && aData.Length() == 1) {
         JS::Rooted<JS::Value> value(aCx, aData[0]);
         JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
+
         if (jsString) {
           nsAutoJSString key;
           if (key.init(aCx, jsString)) {
-            UniquePtr<TimelineMarker> marker = MakeUnique<ConsoleTimelineMarker>(
-              key, aMethodName == MethodTime ? MarkerTracingType::START
-                                             : MarkerTracingType::END);
-            TimelineConsumers::AddMarkerForDocShell(docShell, Move(marker));
+            timelines->AddMarkerForDocShell(docShell, Move(
+              MakeUnique<ConsoleTimelineMarker>(
+                key, aMethodName == MethodTime ? MarkerTracingType::START
+                                               : MarkerTracingType::END)));
           }
         }
       }
-
     } else {
       WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
       MOZ_ASSERT(workerPrivate);
 
       TimeDuration duration =
         mozilla::TimeStamp::Now() - workerPrivate->CreationTimeStamp();
 
       callData->mMonotonicTimer = duration.ToMilliseconds();
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1109,45 +1109,45 @@ EventListenerManager::HandleEventInterna
             aEvent->currentTarget = aCurrentTarget->GetTargetForDOMEvent();
             if (!aEvent->currentTarget) {
               break;
             }
           }
 
           // Maybe add a marker to the docshell's timeline, but only
           // bother with all the logic if some docshell is recording.
-          nsCOMPtr<nsIDocShell> docShell;
-          bool isTimelineRecording = false;
+          nsDocShell* docShell;
+          RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+          bool needsEndEventMarker = false;
+
           if (mIsMainThreadELM &&
-              !TimelineConsumers::IsEmpty() &&
               listener->mListenerType != Listener::eNativeListener) {
-            docShell = GetDocShellForTarget();
-            if (docShell) {
-              docShell->GetRecordProfileTimelineMarkers(&isTimelineRecording);
-            }
-            if (isTimelineRecording) {
-              nsDocShell* ds = static_cast<nsDocShell*>(docShell.get());
-              nsAutoString typeStr;
-              (*aDOMEvent)->GetType(typeStr);
-              uint16_t phase;
-              (*aDOMEvent)->GetEventPhase(&phase);
-              UniquePtr<TimelineMarker> marker = MakeUnique<EventTimelineMarker>(
-                typeStr, phase, MarkerTracingType::START);
-              TimelineConsumers::AddMarkerForDocShell(ds, Move(marker));
+            nsCOMPtr<nsIDocShell> docShellComPtr = GetDocShellForTarget();
+            if (docShellComPtr) {
+              docShell = static_cast<nsDocShell*>(docShellComPtr.get());
+              if (timelines && timelines->HasConsumer(docShell)) {
+                needsEndEventMarker = true;
+                nsAutoString typeStr;
+                (*aDOMEvent)->GetType(typeStr);
+                uint16_t phase;
+                (*aDOMEvent)->GetEventPhase(&phase);
+                timelines->AddMarkerForDocShell(docShell, Move(
+                  MakeUnique<EventTimelineMarker>(
+                    typeStr, phase, MarkerTracingType::START)));
+              }
             }
           }
 
-          if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent,
-                                           aCurrentTarget))) {
+          if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent, aCurrentTarget))) {
             aEvent->mFlags.mExceptionHasBeenRisen = true;
           }
 
-          if (isTimelineRecording) {
-            nsDocShell* ds = static_cast<nsDocShell*>(docShell.get());
-            TimelineConsumers::AddMarkerForDocShell(ds, "DOMEvent", MarkerTracingType::END);
+          if (needsEndEventMarker) {
+            timelines->AddMarkerForDocShell(
+              docShell, "DOMEvent", MarkerTracingType::END);
           }
         }
       }
     }
   }
 
   aEvent->currentTarget = nullptr;
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2970,25 +2970,30 @@ TabChild::DidComposite(uint64_t aTransac
 
   manager->DidComposite(aTransactionId, aCompositeStart, aCompositeEnd);
 }
 
 void
 TabChild::DidRequestComposite(const TimeStamp& aCompositeReqStart,
                               const TimeStamp& aCompositeReqEnd)
 {
-  nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
-  if (!docShell) {
+  nsCOMPtr<nsIDocShell> docShellComPtr = do_GetInterface(WebNavigation());
+  if (!docShellComPtr) {
     return;
   }
 
-  TimelineConsumers::AddMarkerForDocShell(docShell.get(),
-    "CompositeForwardTransaction", aCompositeReqStart, MarkerTracingType::START);
-  TimelineConsumers::AddMarkerForDocShell(docShell.get(),
-    "CompositeForwardTransaction", aCompositeReqEnd, MarkerTracingType::END);
+  nsDocShell* docShell = static_cast<nsDocShell*>(docShellComPtr.get());
+  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+
+  if (timelines && timelines->HasConsumer(docShell)) {
+    timelines->AddMarkerForDocShell(docShell,
+      "CompositeForwardTransaction", aCompositeReqStart, MarkerTracingType::START);
+    timelines->AddMarkerForDocShell(docShell,
+      "CompositeForwardTransaction", aCompositeReqEnd, MarkerTracingType::END);
+  }
 }
 
 void
 TabChild::ClearCachedResources()
 {
   MOZ_ASSERT(mPuppetWidget);
   MOZ_ASSERT(mPuppetWidget->GetLayerManager());
   MOZ_ASSERT(mPuppetWidget->GetLayerManager()->GetBackendType() ==
--- a/dom/webidl/ProfileTimelineMarker.webidl
+++ b/dom/webidl/ProfileTimelineMarker.webidl
@@ -20,25 +20,40 @@ dictionary ProfileTimelineStackFrame {
 
 dictionary ProfileTimelineLayerRect {
   long x = 0;
   long y = 0;
   long width = 0;
   long height = 0;
 };
 
+enum ProfileTimelineWorkerOperationType {
+  "serializeDataOffMainThread",
+  "serializeDataOnMainThread",
+  "deserializeDataOffMainThread",
+  "deserializeDataOnMainThread",
+};
+
 dictionary ProfileTimelineMarker {
   DOMString name = "";
   DOMHighResTimeStamp start = 0;
   DOMHighResTimeStamp end = 0;
   object? stack = null;
+
   /* For ConsoleTime, Timestamp and Javascript markers.  */
   DOMString causeName;
+
   /* For ConsoleTime markers.  */
   object? endStack = null;
+
   /* For DOMEvent markers.  */
   DOMString type;
   unsigned short eventPhase;
+
   /* For Paint markers.  */
   sequence<ProfileTimelineLayerRect> rectangles;
+
   /* For Style markers. */
   DOMString restyleHint;
+
+  /* For Worker markers. */
+  ProfileTimelineWorkerOperationType workerOperation;
 };
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -60,16 +60,18 @@
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/StructuredCloneHolder.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/WorkerBinding.h"
 #include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
 #include "mozilla/dom/WorkerGlobalScopeBinding.h"
 #include "mozilla/dom/indexedDB/IDBFactory.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/TimelineConsumers.h"
+#include "mozilla/WorkerTimelineMarker.h"
 #include "nsAlgorithm.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollector.h"
 #include "nsError.h"
 #include "nsDOMJSUtils.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsJSEnvironment.h"
 #include "nsJSUtils.h"
@@ -633,17 +635,40 @@ public:
   {
     nsCOMPtr<nsPIDOMWindow> parent;
     if (aIsMainThread) {
       parent = do_QueryInterface(aTarget->GetParentObject());
     }
 
     JS::Rooted<JS::Value> messageData(aCx);
     ErrorResult rv;
+
+    UniquePtr<AbstractTimelineMarker> start;
+    UniquePtr<AbstractTimelineMarker> end;
+    RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+    bool isTimelineRecording = timelines && !timelines->IsEmpty();
+
+    if (isTimelineRecording) {
+      start = MakeUnique<WorkerTimelineMarker>(aIsMainThread
+        ? ProfileTimelineWorkerOperationType::DeserializeDataOnMainThread
+        : ProfileTimelineWorkerOperationType::DeserializeDataOffMainThread,
+        MarkerTracingType::START);
+    }
+
     Read(parent, aCx, &messageData, rv);
+
+    if (isTimelineRecording) {
+      end = MakeUnique<WorkerTimelineMarker>(aIsMainThread
+        ? ProfileTimelineWorkerOperationType::DeserializeDataOnMainThread
+        : ProfileTimelineWorkerOperationType::DeserializeDataOffMainThread,
+        MarkerTracingType::END);
+      timelines->AddMarkerForAllObservedDocShells(start);
+      timelines->AddMarkerForAllObservedDocShells(end);
+    }
+
     if (NS_WARN_IF(rv.Failed())) {
       xpc::Throw(aCx, rv.StealNSResult());
       return false;
     }
 
     RefPtr<MessageEvent> event = new MessageEvent(aTarget, nullptr, nullptr);
     rv = event->InitMessageEvent(NS_LITERAL_STRING("message"),
                                  false /* non-bubbling */,
@@ -2791,17 +2816,39 @@ WorkerPrivateParent<Derived>::PostMessag
     }
     transferable.setObject(*array);
   }
 
   RefPtr<MessageEventRunnable> runnable =
     new MessageEventRunnable(ParentAsWorkerPrivate(),
                              WorkerRunnable::WorkerThreadModifyBusyCount);
 
+  UniquePtr<AbstractTimelineMarker> start;
+  UniquePtr<AbstractTimelineMarker> end;
+  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+  bool isTimelineRecording = timelines && !timelines->IsEmpty();
+
+  if (isTimelineRecording) {
+    start = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread()
+      ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+      : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+      MarkerTracingType::START);
+  }
+
   runnable->Write(aCx, aMessage, transferable, aRv);
+
+  if (isTimelineRecording) {
+    end = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread()
+      ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+      : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+      MarkerTracingType::END);
+    timelines->AddMarkerForAllObservedDocShells(start);
+    timelines->AddMarkerForAllObservedDocShells(end);
+  }
+
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   runnable->SetMessageSource(Move(aClientInfo));
 
   if (!runnable->Dispatch(aCx)) {
     aRv.Throw(NS_ERROR_FAILURE);
@@ -5380,17 +5427,39 @@ WorkerPrivate::PostMessageToParentIntern
     }
     transferable.setObject(*array);
   }
 
   RefPtr<MessageEventRunnable> runnable =
     new MessageEventRunnable(this,
                              WorkerRunnable::ParentThreadUnchangedBusyCount);
 
+  UniquePtr<AbstractTimelineMarker> start;
+  UniquePtr<AbstractTimelineMarker> end;
+  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+  bool isTimelineRecording = timelines && !timelines->IsEmpty();
+
+  if (isTimelineRecording) {
+    start = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread()
+      ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+      : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+      MarkerTracingType::START);
+  }
+
   runnable->Write(aCx, aMessage, transferable, aRv);
+
+  if (isTimelineRecording) {
+    end = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread()
+      ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+      : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+      MarkerTracingType::END);
+    timelines->AddMarkerForAllObservedDocShells(start);
+    timelines->AddMarkerForAllObservedDocShells(end);
+  }
+
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   if (!runnable->Dispatch(aCx)) {
     aRv = NS_ERROR_FAILURE;
   }
 }
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -5914,21 +5914,21 @@ FrameLayerBuilder::DrawPaintedLayer(Pain
         gfxUtils::ClipToRegion(aContext, aRegionToDraw);
       }
     }
     FlashPaint(aContext);
   }
 
   if (presContext && presContext->GetDocShell() && isActiveLayerManager) {
     nsDocShell* docShell = static_cast<nsDocShell*>(presContext->GetDocShell());
-    bool isRecording;
-    docShell->GetRecordProfileTimelineMarkers(&isRecording);
-    if (isRecording) {
-      UniquePtr<TimelineMarker> marker = MakeUnique<LayerTimelineMarker>(aRegionToDraw);
-      TimelineConsumers::AddMarkerForDocShell(docShell, Move(marker));
+    RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+
+    if (timelines && timelines->HasConsumer(docShell)) {
+      timelines->AddMarkerForDocShell(docShell, Move(
+        MakeUnique<LayerTimelineMarker>(aRegionToDraw)));
     }
   }
 
   if (!aRegionToInvalidate.IsEmpty()) {
     aLayer->AddInvalidRect(aRegionToInvalidate.GetBounds());
   }
 }
 
--- a/layout/base/RestyleTracker.cpp
+++ b/layout/base/RestyleTracker.cpp
@@ -213,22 +213,19 @@ RestyleTracker::DoProcessRestyles()
       uri->GetSpec(docURL);
     } else {
       docURL = "N/A";
     }
   }
   PROFILER_LABEL_PRINTF("RestyleTracker", "ProcessRestyles",
                         js::ProfileEntry::Category::CSS, "(%s)", docURL.get());
 
-  bool isTimelineRecording = false;
-  nsDocShell* docShell =
-    static_cast<nsDocShell*>(mRestyleManager->PresContext()->GetDocShell());
-  if (docShell) {
-    docShell->GetRecordProfileTimelineMarkers(&isTimelineRecording);
-  }
+  nsDocShell* docShell = static_cast<nsDocShell*>(mRestyleManager->PresContext()->GetDocShell());
+  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+  bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
 
   // Create a AnimationsWithDestroyedFrame during restyling process to
   // stop animations on elements that have no frame at the end of the
   // restyling process.
   RestyleManager::AnimationsWithDestroyedFrame
     animationsWithDestroyedFrame(mRestyleManager);
 
   // Create a ReframingStyleContexts struct on the stack and put it in our
@@ -336,35 +333,35 @@ RestyleTracker::DoProcessRestyles()
 
         nsAutoPtr<RestyleData> data;
         if (!GetRestyleData(element, data)) {
           LOG_RESTYLE("skipping, already restyled");
           continue;
         }
 
         if (isTimelineRecording) {
-          UniquePtr<TimelineMarker> marker = MakeUnique<RestyleTimelineMarker>(
-            data->mRestyleHint, MarkerTracingType::START);
-          TimelineConsumers::AddMarkerForDocShell(docShell, Move(marker));
+          timelines->AddMarkerForDocShell(docShell, Move(
+            MakeUnique<RestyleTimelineMarker>(
+              data->mRestyleHint, MarkerTracingType::START)));
         }
 
 #if defined(MOZ_ENABLE_PROFILER_SPS) && !defined(MOZILLA_XPCOMRT_API)
         Maybe<GeckoProfilerTracingRAII> profilerRAII;
         if (profiler_feature_active("restyle")) {
           profilerRAII.emplace("Paint", "Styles", Move(data->mBacktrace));
         }
 #endif
         ProcessOneRestyle(element, data->mRestyleHint, data->mChangeHint,
                           data->mRestyleHintData);
         AddRestyleRootsIfAwaitingRestyle(data->mDescendants);
 
         if (isTimelineRecording) {
-          UniquePtr<TimelineMarker> marker = MakeUnique<RestyleTimelineMarker>(
-            data->mRestyleHint, MarkerTracingType::END);
-          TimelineConsumers::AddMarkerForDocShell(docShell, Move(marker));
+          timelines->AddMarkerForDocShell(docShell, Move(
+            MakeUnique<RestyleTimelineMarker>(
+              data->mRestyleHint, MarkerTracingType::END)));
         }
       }
 
       if (mHaveLaterSiblingRestyles) {
         // Keep processing restyles for now
         continue;
       }
 
@@ -397,30 +394,30 @@ RestyleTracker::DoProcessRestyles()
 
 #if defined(MOZ_ENABLE_PROFILER_SPS) && !defined(MOZILLA_XPCOMRT_API)
           Maybe<GeckoProfilerTracingRAII> profilerRAII;
           if (profiler_feature_active("restyle")) {
             profilerRAII.emplace("Paint", "Styles", Move(currentRestyle->mBacktrace));
           }
 #endif
           if (isTimelineRecording) {
-            UniquePtr<TimelineMarker> marker = MakeUnique<RestyleTimelineMarker>(
-              currentRestyle->mRestyleHint, MarkerTracingType::START);
-            TimelineConsumers::AddMarkerForDocShell(docShell, Move(marker));
+            timelines->AddMarkerForDocShell(docShell, Move(
+              MakeUnique<RestyleTimelineMarker>(
+                currentRestyle->mRestyleHint, MarkerTracingType::START)));
           }
 
           ProcessOneRestyle(currentRestyle->mElement,
                             currentRestyle->mRestyleHint,
                             currentRestyle->mChangeHint,
                             currentRestyle->mRestyleHintData);
 
           if (isTimelineRecording) {
-            UniquePtr<TimelineMarker> marker = MakeUnique<RestyleTimelineMarker>(
-              currentRestyle->mRestyleHint, MarkerTracingType::END);
-            TimelineConsumers::AddMarkerForDocShell(docShell, Move(marker));
+            timelines->AddMarkerForDocShell(docShell, Move(
+              MakeUnique<RestyleTimelineMarker>(
+                currentRestyle->mRestyleHint, MarkerTracingType::END)));
           }
         }
       }
     }
   }
 
   // mPendingRestyles is now empty.
   mHaveSelectors = false;
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -8965,18 +8965,21 @@ PresShell::DoReflow(nsIFrame* target, bo
   nsIURI *uri = mDocument->GetDocumentURI();
   if (uri)
     uri->GetSpec(docURL);
 
   PROFILER_LABEL_PRINTF("PresShell", "DoReflow",
     js::ProfileEntry::Category::GRAPHICS, "(%s)", docURL.get());
 
   nsDocShell* docShell = static_cast<nsDocShell*>(GetPresContext()->GetDocShell());
-  if (docShell) {
-    TimelineConsumers::AddMarkerForDocShell(docShell, "Reflow", MarkerTracingType::START);
+  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+  bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
+
+  if (isTimelineRecording) {
+    timelines->AddMarkerForDocShell(docShell, "Reflow", MarkerTracingType::START);
   }
 
   if (mReflowContinueTimer) {
     mReflowContinueTimer->Cancel();
     mReflowContinueTimer = nullptr;
   }
 
   nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
@@ -9143,19 +9146,20 @@ PresShell::DoReflow(nsIFrame* target, bo
     if (tp->current.numChars > 100) {
       TimeDuration reflowTime = TimeStamp::Now() - timeStart;
       LogTextPerfStats(tp, this, tp->current,
                        reflowTime.ToMilliseconds(), eLog_reflow, nullptr);
     }
     tp->Accumulate();
   }
 
-  if (docShell) {
-    TimelineConsumers::AddMarkerForDocShell(docShell, "Reflow", MarkerTracingType::END);
-  }
+  if (isTimelineRecording) {
+    timelines->AddMarkerForDocShell(docShell, "Reflow", MarkerTracingType::END);
+  }
+
   return !interrupted;
 }
 
 #ifdef DEBUG
 void
 PresShell::DoVerifyReflow()
 {
   if (GetVerifyReflowEnable()) {
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1261,23 +1261,30 @@ HasPendingAnimations(nsIPresShell* aShel
 
 /**
  * Return a list of all the child docShells in a given root docShell that are
  * visible and are recording markers for the profilingTimeline
  */
 static void GetProfileTimelineSubDocShells(nsDocShell* aRootDocShell,
                                            nsTArray<nsDocShell*>& aShells)
 {
-  if (!aRootDocShell || TimelineConsumers::IsEmpty()) {
+  if (!aRootDocShell) {
+    return;
+  }
+
+  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+  if (!timelines || timelines->IsEmpty()) {
     return;
   }
 
   nsCOMPtr<nsISimpleEnumerator> enumerator;
-  nsresult rv = aRootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeAll,
-    nsIDocShell::ENUMERATE_BACKWARDS, getter_AddRefs(enumerator));
+  nsresult rv = aRootDocShell->GetDocShellEnumerator(
+    nsIDocShellTreeItem::typeAll,
+    nsIDocShell::ENUMERATE_BACKWARDS,
+    getter_AddRefs(enumerator));
 
   if (NS_FAILED(rv)) {
     return;
   }
 
   nsCOMPtr<nsIDocShell> curItem;
   bool hasMore = false;
   while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
@@ -1677,39 +1684,48 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
   }
 
   for (nsIPresShell* shell : mPresShellsToInvalidateIfHidden) {
     shell->InvalidatePresShellIfHidden();
   }
   mPresShellsToInvalidateIfHidden.Clear();
 
   if (mViewManagerFlushIsPending) {
+    RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+
     nsTArray<nsDocShell*> profilingDocShells;
     GetProfileTimelineSubDocShells(GetDocShell(mPresContext), profilingDocShells);
     for (nsDocShell* docShell : profilingDocShells) {
       // For the sake of the profile timeline's simplicity, this is flagged as
       // paint even if it includes creating display lists
-      TimelineConsumers::AddMarkerForDocShell(docShell, "Paint",  MarkerTracingType::START);
+      MOZ_ASSERT(timelines);
+      MOZ_ASSERT(timelines->HasConsumer(docShell));
+      timelines->AddMarkerForDocShell(docShell, "Paint",  MarkerTracingType::START);
     }
+
 #ifdef MOZ_DUMP_PAINTING
     if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
       printf_stderr("Starting ProcessPendingUpdates\n");
     }
 #endif
 
     mViewManagerFlushIsPending = false;
     RefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager();
     vm->ProcessPendingUpdates();
+
 #ifdef MOZ_DUMP_PAINTING
     if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
       printf_stderr("Ending ProcessPendingUpdates\n");
     }
 #endif
+
     for (nsDocShell* docShell : profilingDocShells) {
-      TimelineConsumers::AddMarkerForDocShell(docShell, "Paint",  MarkerTracingType::END);
+      MOZ_ASSERT(timelines);
+      MOZ_ASSERT(timelines->HasConsumer(docShell));
+      timelines->AddMarkerForDocShell(docShell, "Paint",  MarkerTracingType::END);
     }
 
     if (nsContentUtils::XPConnect()) {
       nsContentUtils::XPConnect()->NotifyDidPaint();
       nsJSContext::NotifyDidPaint();
     }
   }
 
--- a/view/nsView.cpp
+++ b/view/nsView.cpp
@@ -57,19 +57,19 @@ nsView::~nsView()
       // just unhook it. Someone else will want to destroy this.
       RemoveChild(child);
     }
   }
 
   if (mViewManager)
   {
     DropMouseGrabbing();
-  
+
     nsView *rootView = mViewManager->GetRootView();
-    
+
     if (rootView)
     {
       // Root views can have parents!
       if (mParent)
       {
         mViewManager->RemoveChild(this);
       }
 
@@ -78,17 +78,17 @@ nsView::~nsView()
         // Inform the view manager that the root view has gone away...
         mViewManager->SetRootView(nullptr);
       }
     }
     else if (mParent)
     {
       mParent->RemoveChild(this);
     }
-    
+
     mViewManager = nullptr;
   }
   else if (mParent)
   {
     mParent->RemoveChild(this);
   }
 
   if (mPreviousWindow) {
@@ -121,17 +121,17 @@ NS_IMETHODIMP DestroyWidgetRunnable::Run
 
 void nsView::DestroyWidget()
 {
   if (mWindow)
   {
     // If we are not attached to a base window, we're going to tear down our
     // widget here. However, if we're attached to somebody elses widget, we
     // want to leave the widget alone: don't reset the client data or call
-    // Destroy. Just clear our event view ptr and free our reference to it. 
+    // Destroy. Just clear our event view ptr and free our reference to it.
     if (mWidgetIsTopLevel) {
       mWindow->SetAttachedWidgetListener(nullptr);
     }
     else {
       mWindow->SetWidgetListener(nullptr);
 
       nsCOMPtr<nsIRunnable> widgetDestroyer =
         new DestroyWidgetRunnable(mWindow);
@@ -584,17 +584,17 @@ nsresult nsView::CreateWidget(nsWidgetIn
   }
 
   // XXX: using aForceUseIWidgetParent=true to preserve previous
   // semantics.  It's not clear that it's actually needed.
   mWindow = parentWidget->CreateChild(trect, aWidgetInitData, true);
   if (!mWindow) {
     return NS_ERROR_FAILURE;
   }
- 
+
   InitializeWindow(aEnableDragDrop, aResetVisibility);
 
   return NS_OK;
 }
 
 nsresult nsView::CreateWidgetForParent(nsIWidget* aParentWidget,
                                         nsWidgetInitData *aWidgetInitData,
                                         bool aEnableDragDrop,
@@ -667,17 +667,17 @@ nsView::InitializeWindow(bool aEnableDra
 {
   MOZ_ASSERT(mWindow, "Must have a window to initialize");
 
   mWindow->SetWidgetListener(this);
 
   if (aEnableDragDrop) {
     mWindow->EnableDragDrop(true);
   }
-      
+
   // propagate the z-index to the widget.
   UpdateNativeWidgetZIndexes(this, FindNonAutoZIndex(this));
 
   //make sure visibility state is accurate
 
   if (aResetVisibility) {
     SetVisibility(GetVisibility());
   }
@@ -720,17 +720,17 @@ nsresult nsView::AttachToTopLevelWidget(
   mWidgetIsTopLevel = true;
 
   // Refresh the view bounds
   CalcWidgetBounds(mWindow->WindowType());
 
   return NS_OK;
 }
 
-// Detach this view from an attached widget. 
+// Detach this view from an attached widget.
 nsresult nsView::DetachFromTopLevelWidget()
 {
   NS_PRECONDITION(mWidgetIsTopLevel, "Not attached currently!");
   NS_PRECONDITION(mWindow, "null mWindow for DetachFromTopLevelWidget!");
 
   mWindow->SetAttachedWidgetListener(nullptr);
   nsIWidgetListener* listener = mWindow->GetPreviouslyAttachedWidgetListener();
 
@@ -742,26 +742,26 @@ nsresult nsView::DetachFromTopLevelWidge
   // If the new view's frame is paint suppressed then the window
   // will want to use us instead until that's done
   mWindow->SetPreviouslyAttachedWidgetListener(this);
 
   mPreviousWindow = mWindow;
   mWindow = nullptr;
 
   mWidgetIsTopLevel = false;
-  
+
   return NS_OK;
 }
 
 void nsView::SetZIndex(bool aAuto, int32_t aZIndex)
 {
   bool oldIsAuto = GetZIndexIsAuto();
   mVFlags = (mVFlags & ~NS_VIEW_FLAG_AUTO_ZINDEX) | (aAuto ? NS_VIEW_FLAG_AUTO_ZINDEX : 0);
   mZIndex = aZIndex;
-  
+
   if (HasWidget() || !oldIsAuto || !aAuto) {
     UpdateNativeWidgetZIndexes(this, FindNonAutoZIndex(this));
   }
 }
 
 void nsView::AssertNoWindow()
 {
   // XXX: it would be nice to make this a strong assert
@@ -1089,21 +1089,24 @@ nsView::DidCompositeWindow(const TimeSta
 
     // If the two timestamps are identical, this was likely a fake composite
     // event which wouldn't be terribly useful to display.
     if (aCompositeStart == aCompositeEnd) {
       return;
     }
 
     nsIDocShell* docShell = context->GetDocShell();
+    RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
 
-    TimelineConsumers::AddMarkerForDocShell(docShell,
-      "Composite", aCompositeStart, MarkerTracingType::START);
-    TimelineConsumers::AddMarkerForDocShell(docShell,
-      "Composite", aCompositeEnd, MarkerTracingType::END);
+    if (timelines && timelines->HasConsumer(docShell)) {
+      timelines->AddMarkerForDocShell(docShell,
+        "Composite", aCompositeStart, MarkerTracingType::START);
+      timelines->AddMarkerForDocShell(docShell,
+        "Composite", aCompositeEnd, MarkerTracingType::END);
+    }
   }
 }
 
 void
 nsView::RequestRepaint()
 {
   nsIPresShell* presShell = mViewManager->GetPresShell();
   if (presShell) {