Bug 1425458 - Resource timing entries Workers - part 3 - PerformanceStorageWorker, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 24 Jan 2018 17:17:32 +0100
changeset 400802 619d2fe88e78799e1e5606159e228597c07f8021
parent 400801 cd25cfc3defe1fe948e0d6b408d841bce491a631
child 400803 4186a931f07c6a598db20122fdedf3e35539325d
push id33319
push useraiakab@mozilla.com
push dateFri, 26 Jan 2018 00:22:49 +0000
treeherdermozilla-central@b5b38db26ed7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1425458
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1425458 - Resource timing entries Workers - part 3 - PerformanceStorageWorker, r=smaug
dom/performance/Performance.cpp
dom/performance/Performance.h
dom/performance/PerformanceMainThread.cpp
dom/performance/PerformanceStorage.h
dom/performance/PerformanceStorageWorker.cpp
dom/performance/PerformanceStorageWorker.h
dom/performance/PerformanceTiming.cpp
dom/performance/PerformanceTiming.h
dom/performance/moz.build
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
--- a/dom/performance/Performance.cpp
+++ b/dom/performance/Performance.cpp
@@ -433,16 +433,17 @@ Performance::InsertResourceEntry(Perform
   MOZ_ASSERT(aEntry);
   MOZ_ASSERT(mResourceEntries.Length() < mResourceTimingBufferSize);
 
   // We won't add an entry when 'privacy.resistFingerprint' is true.
   if (nsContentUtils::ShouldResistFingerprinting()) {
     return;
   }
 
+  // Don't add the entry if the buffer is full
   if (mResourceEntries.Length() >= mResourceTimingBufferSize) {
     return;
   }
 
   mResourceEntries.InsertElementSorted(aEntry,
                                        PerformanceEntryComparator());
   if (mResourceEntries.Length() == mResourceTimingBufferSize) {
     // call onresourcetimingbufferfull
--- a/dom/performance/Performance.h
+++ b/dom/performance/Performance.h
@@ -95,58 +95,54 @@ public:
 
   virtual void GetMozMemory(JSContext *aCx,
                             JS::MutableHandle<JSObject*> aObj) = 0;
 
   virtual nsDOMNavigationTiming* GetDOMTiming() const = 0;
 
   virtual nsITimedChannel* GetChannel() const = 0;
 
+  virtual TimeStamp CreationTimeStamp() const = 0;
+
   void MemoryPressure();
 
   size_t SizeOfUserEntries(mozilla::MallocSizeOf aMallocSizeOf) const;
   size_t SizeOfResourceEntries(mozilla::MallocSizeOf aMallocSizeOf) const;
 
+  void InsertResourceEntry(PerformanceEntry* aEntry);
+
 protected:
   Performance();
   explicit Performance(nsPIDOMWindowInner* aWindow);
 
   virtual ~Performance();
 
   virtual void InsertUserEntry(PerformanceEntry* aEntry);
-  void InsertResourceEntry(PerformanceEntry* aEntry);
 
   void ClearUserEntries(const Optional<nsAString>& aEntryName,
                         const nsAString& aEntryType);
 
   DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName,
                                                ErrorResult& aRv);
 
   virtual void DispatchBufferFullEvent() = 0;
 
-  virtual TimeStamp CreationTimeStamp() const = 0;
-
   virtual DOMHighResTimeStamp CreationTime() const = 0;
 
   virtual bool IsPerformanceTimingAttribute(const nsAString& aName)
   {
     return false;
   }
 
   virtual DOMHighResTimeStamp
   GetPerformanceTimingFromString(const nsAString& aTimingName)
   {
     return 0;
   }
 
-  bool IsResourceEntryLimitReached() const
-  {
-    return mResourceEntries.Length() >= mResourceTimingBufferSize;
-  }
-
   void LogEntry(PerformanceEntry* aEntry, const nsACString& aOwner) const;
   void TimingNotification(PerformanceEntry* aEntry, const nsACString& aOwner,
                           uint64_t epoch);
 
   void RunNotificationObserversTask();
   void QueueEntry(PerformanceEntry* aEntry);
 
   DOMHighResTimeStamp RoundTime(double aTime) const;
--- a/dom/performance/PerformanceMainThread.cpp
+++ b/dom/performance/PerformanceMainThread.cpp
@@ -113,74 +113,34 @@ PerformanceMainThread::Navigation()
  * This method is not thread safe and can only be called on the main thread.
  */
 void
 PerformanceMainThread::AddEntry(nsIHttpChannel* channel,
                                 nsITimedChannel* timedChannel)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  // Check if resource timing is prefed off.
-  if (!nsContentUtils::IsResourceTimingEnabled()) {
-    return;
-  }
+  nsAutoString initiatorType;
+  nsAutoString entryName;
 
-  // Don't add the entry if the buffer is full
-  if (IsResourceEntryLimitReached()) {
+  UniquePtr<PerformanceTimingData> performanceTimingData(
+    PerformanceTimingData::Create(timedChannel, channel, 0, initiatorType,
+                                  entryName));
+  if (!performanceTimingData) {
     return;
   }
 
-  if (channel && timedChannel) {
-    nsAutoCString name;
-    nsAutoString initiatorType;
-    nsCOMPtr<nsIURI> originalURI;
-
-    timedChannel->GetInitiatorType(initiatorType);
-
-    // According to the spec, "The name attribute must return the resolved URL
-    // of the requested resource. This attribute must not change even if the
-    // fetch redirected to a different URL."
-    channel->GetOriginalURI(getter_AddRefs(originalURI));
-    originalURI->GetSpec(name);
-    NS_ConvertUTF8toUTF16 entryName(name);
-
-    bool reportTiming = true;
-    timedChannel->GetReportResourceTiming(&reportTiming);
+  // The PerformanceResourceTiming object will use the PerformanceTimingData
+  // object to get all the required timings.
+  RefPtr<PerformanceResourceTiming> performanceEntry =
+    new PerformanceResourceTiming(Move(performanceTimingData), this,
+                                  entryName);
 
-    if (!reportTiming) {
-#ifdef DEBUG_jwatt
-      NS_WARNING(
-        nsPrintfCString("Not reporting CORS resource: %s", name.get()).get());
-#endif
-      return;
-    }
-
-    // The nsITimedChannel argument will be used to gather all the timings.
-    // The nsIHttpChannel argument will be used to check if any cross-origin
-    // redirects occurred.
-    // The last argument is the "zero time" (offset). Since we don't want
-    // any offset for the resource timing, this will be set to "0" - the
-    // resource timing returns a relative timing (no offset).
-    UniquePtr<PerformanceTimingData> performanceTimingData(
-        new PerformanceTimingData(timedChannel, channel, 0));
-
-    // The PerformanceResourceTiming object will use the PerformanceTiming
-    // object to get all the required timings.
-    RefPtr<PerformanceResourceTiming> performanceEntry =
-      new PerformanceResourceTiming(Move(performanceTimingData), this,
-                                    entryName);
-
-    // If the initiator type had no valid value, then set it to the default
-    // ("other") value.
-    if (initiatorType.IsEmpty()) {
-      initiatorType = NS_LITERAL_STRING("other");
-    }
-    performanceEntry->SetInitiatorType(initiatorType);
-    InsertResourceEntry(performanceEntry);
-  }
+  performanceEntry->SetInitiatorType(initiatorType);
+  InsertResourceEntry(performanceEntry);
 }
 
 // To be removed once bug 1124165 lands
 bool
 PerformanceMainThread::IsPerformanceTimingAttribute(const nsAString& aName)
 {
   // Note that toJSON is added to this list due to bug 1047848
   static const char* attributes[] =
--- a/dom/performance/PerformanceStorage.h
+++ b/dom/performance/PerformanceStorage.h
@@ -10,25 +10,26 @@
 #include "nsISupportsImpl.h"
 
 class nsIHttpChannel;
 class nsITimedChannel;
 
 namespace mozilla {
 namespace dom {
 
+class PerformanceTimingData;
+
 class PerformanceStorage
 {
 public:
   NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
 
   virtual void AddEntry(nsIHttpChannel* aChannel,
                         nsITimedChannel* aTimedChannel) = 0;
 
-
 protected:
   virtual ~PerformanceStorage() {}
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PerformanceStorage_h
new file mode 100644
--- /dev/null
+++ b/dom/performance/PerformanceStorageWorker.cpp
@@ -0,0 +1,271 @@
+/* -*- 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 "PerformanceStorageWorker.h"
+#include "WorkerPrivate.h"
+#include "WorkerHolder.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+class PerformanceProxyData
+{
+public:
+  PerformanceProxyData(UniquePtr<PerformanceTimingData>&& aData,
+                       const nsAString& aInitiatorType,
+                       const nsAString& aEntryName)
+    : mData(Move(aData))
+    , mInitiatorType(aInitiatorType)
+    , mEntryName(aEntryName)
+  {}
+
+  UniquePtr<PerformanceTimingData> mData;
+  nsString mInitiatorType;
+  nsString mEntryName;
+};
+
+namespace {
+
+// This runnable calls InitializeOnWorker() on the worker thread. Here a
+// workerHolder is used to monitor when the worker thread is starting the
+// shutdown procedure.
+// Here we use control runnable because this code must be executed also when in
+// a sync event loop.
+class PerformanceStorageInitializer final : public WorkerControlRunnable
+{
+  RefPtr<PerformanceStorageWorker> mStorage;
+
+public:
+  PerformanceStorageInitializer(WorkerPrivate* aWorkerPrivate,
+                                PerformanceStorageWorker* aStorage)
+    : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+    , mStorage(aStorage)
+  {}
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    mStorage->InitializeOnWorker();
+    return true;
+  }
+
+  nsresult
+  Cancel() override
+  {
+    mStorage->ShutdownOnWorker();
+    return WorkerRunnable::Cancel();
+  }
+
+  bool
+  PreDispatch(WorkerPrivate* aWorkerPrivate) override
+  {
+    return true;
+  }
+
+  void
+  PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+  {}
+};
+
+// Here we use control runnable because this code must be executed also when in
+// a sync event loop
+class PerformanceEntryAdder final : public WorkerControlRunnable
+{
+public:
+  PerformanceEntryAdder(WorkerPrivate* aWorkerPrivate,
+                        PerformanceStorageWorker* aStorage,
+                        UniquePtr<PerformanceProxyData>&& aData)
+    : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+    , mStorage(aStorage)
+    , mData(Move(aData))
+  {}
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    mStorage->AddEntryOnWorker(Move(mData));
+    return true;
+  }
+
+  nsresult
+  Cancel() override
+  {
+    mStorage->ShutdownOnWorker();
+    return WorkerRunnable::Cancel();
+  }
+
+  bool
+  PreDispatch(WorkerPrivate* aWorkerPrivate) override
+  {
+    return true;
+  }
+
+  void
+  PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+  {}
+
+private:
+  RefPtr<PerformanceStorageWorker> mStorage;
+  UniquePtr<PerformanceProxyData> mData;
+};
+
+class PerformanceStorageWorkerHolder final : public WorkerHolder
+{
+  RefPtr<PerformanceStorageWorker> mStorage;
+
+public:
+  explicit PerformanceStorageWorkerHolder(PerformanceStorageWorker* aStorage)
+    : WorkerHolder("PerformanceStorageWorkerHolder",
+                   WorkerHolder::AllowIdleShutdownStart)
+    , mStorage(aStorage)
+  {}
+
+  bool
+  Notify(Status aStatus) override
+  {
+    if (mStorage) {
+      RefPtr<PerformanceStorageWorker> storage;
+      storage.swap(mStorage);
+      storage->ShutdownOnWorker();
+    }
+
+    return true;
+  }
+};
+
+} // anonymous
+
+/* static */ already_AddRefed<PerformanceStorageWorker>
+PerformanceStorageWorker::Create(WorkerPrivate* aWorkerPrivate)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RefPtr<PerformanceStorageWorker> storage =
+    new PerformanceStorageWorker(aWorkerPrivate);
+
+  RefPtr<PerformanceStorageInitializer> r =
+    new PerformanceStorageInitializer(aWorkerPrivate, storage);
+  if (NS_WARN_IF(!r->Dispatch())) {
+    return nullptr;
+  }
+
+  return storage.forget();
+}
+
+PerformanceStorageWorker::PerformanceStorageWorker(WorkerPrivate* aWorkerPrivate)
+  : mMutex("PerformanceStorageWorker::mMutex")
+  , mWorkerPrivate(aWorkerPrivate)
+  , mState(eInitializing)
+{
+}
+
+PerformanceStorageWorker::~PerformanceStorageWorker() = default;
+
+void
+PerformanceStorageWorker::AddEntry(nsIHttpChannel* aChannel,
+                                   nsITimedChannel* aTimedChannel)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  MutexAutoLock lock(mMutex);
+
+  if (mState == eTerminated) {
+    return;
+  }
+
+  nsAutoString initiatorType;
+  nsAutoString entryName;
+
+  UniquePtr<PerformanceTimingData> performanceTimingData(
+    PerformanceTimingData::Create(aTimedChannel, aChannel, 0, initiatorType,
+                                  entryName));
+  if (!performanceTimingData) {
+    return;
+  }
+
+  UniquePtr<PerformanceProxyData> data(
+    new PerformanceProxyData(Move(performanceTimingData), initiatorType,
+                             entryName));
+
+  RefPtr<PerformanceEntryAdder> r =
+    new PerformanceEntryAdder(mWorkerPrivate, this, Move(data));
+  Unused << NS_WARN_IF(!r->Dispatch());
+}
+
+void
+PerformanceStorageWorker::InitializeOnWorker()
+{
+  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mState == eInitializing);
+  MOZ_ASSERT(mWorkerPrivate);
+  mWorkerPrivate->AssertIsOnWorkerThread();
+
+  mWorkerHolder.reset(new PerformanceStorageWorkerHolder(this));
+  if (!mWorkerHolder->HoldWorker(mWorkerPrivate, Canceling)) {
+    MutexAutoUnlock lock(mMutex);
+    ShutdownOnWorker();
+    return;
+  }
+
+  // We are ready to accept entries.
+  mState = eReady;
+}
+
+void
+PerformanceStorageWorker::ShutdownOnWorker()
+{
+  MutexAutoLock lock(mMutex);
+
+  if (mState == eTerminated) {
+    return;
+  }
+
+  MOZ_ASSERT(mWorkerPrivate);
+  mWorkerPrivate->AssertIsOnWorkerThread();
+
+  mState = eTerminated;
+  mWorkerHolder = nullptr;
+  mWorkerPrivate = nullptr;
+}
+
+void
+PerformanceStorageWorker::AddEntryOnWorker(UniquePtr<PerformanceProxyData>&& aData)
+{
+  RefPtr<Performance> performance;
+  UniquePtr<PerformanceProxyData> data = Move(aData);
+
+  {
+    MutexAutoLock lock(mMutex);
+
+    if (mState == eTerminated) {
+      return;
+    }
+
+    MOZ_ASSERT(mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+
+    MOZ_ASSERT(mState == eReady);
+
+    WorkerGlobalScope* scope = mWorkerPrivate->GlobalScope();
+    performance = scope->GetPerformance();
+  }
+
+  if (NS_WARN_IF(!performance)) {
+    return;
+  }
+
+  RefPtr<PerformanceResourceTiming> performanceEntry =
+    new PerformanceResourceTiming(Move(data->mData), performance,
+                                 data->mEntryName);
+  performanceEntry->SetInitiatorType(data->mInitiatorType);
+
+  performance->InsertResourceEntry(performanceEntry);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/performance/PerformanceStorageWorker.h
@@ -0,0 +1,64 @@
+/* -*- 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_dom_PerformanceStorageWorker_h
+#define mozilla_dom_PerformanceStorageWorker_h
+
+#include "PerformanceStorage.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace workers {
+class WorkerHolder;
+class WorkerPrivate;
+}
+
+class PerformanceProxyData;
+
+class PerformanceStorageWorker final : public PerformanceStorage
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PerformanceStorageWorker, override)
+
+  static already_AddRefed<PerformanceStorageWorker>
+  Create(workers::WorkerPrivate* aWorkerPrivate);
+
+  void InitializeOnWorker();
+
+  void ShutdownOnWorker();
+
+  void AddEntry(nsIHttpChannel* aChannel,
+                nsITimedChannel* aTimedChannel) override;
+
+  void AddEntryOnWorker(UniquePtr<PerformanceProxyData>&& aData);
+
+private:
+  explicit PerformanceStorageWorker(workers::WorkerPrivate* aWorkerPrivate);
+  ~PerformanceStorageWorker();
+
+  Mutex mMutex;
+
+  // Protected by mutex.
+  // This raw pointer is nullified when the WorkerHolder communicates the
+  // shutting down of the worker thread.
+  workers::WorkerPrivate* mWorkerPrivate;
+
+  // Protected by mutex.
+  enum {
+    eInitializing,
+    eReady,
+    eTerminated,
+  } mState;
+
+  // Touched on worker-thread only.
+  UniquePtr<WorkerHolder> mWorkerHolder;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PerformanceStorageWorker_h
--- a/dom/performance/PerformanceTiming.cpp
+++ b/dom/performance/PerformanceTiming.cpp
@@ -15,16 +15,68 @@
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceTiming, mPerformance)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PerformanceTiming, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PerformanceTiming, Release)
 
+/* static */ PerformanceTimingData*
+PerformanceTimingData::Create(nsITimedChannel* aTimedChannel,
+                              nsIHttpChannel* aChannel,
+                              DOMHighResTimeStamp aZeroTime,
+                              nsAString& aInitiatorType,
+                              nsAString& aEntryName)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Check if resource timing is prefed off.
+  if (!nsContentUtils::IsResourceTimingEnabled()) {
+    return nullptr;
+  }
+
+  if (!aChannel || !aTimedChannel) {
+    return nullptr;
+  }
+
+  bool reportTiming = true;
+  aTimedChannel->GetReportResourceTiming(&reportTiming);
+
+  if (!reportTiming) {
+    return nullptr;
+  }
+
+  aTimedChannel->GetInitiatorType(aInitiatorType);
+
+  // If the initiator type had no valid value, then set it to the default
+  // ("other") value.
+  if (aInitiatorType.IsEmpty()) {
+    aInitiatorType = NS_LITERAL_STRING("other");
+  }
+
+  // According to the spec, "The name attribute must return the resolved URL
+  // of the requested resource. This attribute must not change even if the
+  // fetch redirected to a different URL."
+  nsCOMPtr<nsIURI> originalURI;
+  aChannel->GetOriginalURI(getter_AddRefs(originalURI));
+
+  nsAutoCString name;
+  originalURI->GetSpec(name);
+  aEntryName = NS_ConvertUTF8toUTF16(name);
+
+  // The nsITimedChannel argument will be used to gather all the timings.
+  // The nsIHttpChannel argument will be used to check if any cross-origin
+  // redirects occurred.
+  // The last argument is the "zero time" (offset). Since we don't want
+  // any offset for the resource timing, this will be set to "0" - the
+  // resource timing returns a relative timing (no offset).
+  return new PerformanceTimingData(aTimedChannel, aChannel, 0);
+}
+
 PerformanceTiming::PerformanceTiming(Performance* aPerformance,
                                      nsITimedChannel* aChannel,
                                      nsIHttpChannel* aHttpChannel,
                                      DOMHighResTimeStamp aZeroTime)
   : mPerformance(aPerformance)
 {
   MOZ_ASSERT(aPerformance, "Parent performance object should be provided");
 
--- a/dom/performance/PerformanceTiming.h
+++ b/dom/performance/PerformanceTiming.h
@@ -15,19 +15,31 @@
 #include "Performance.h"
 
 class nsIHttpChannel;
 class nsITimedChannel;
 
 namespace mozilla {
 namespace dom {
 
+class PerformanceTiming;
+
 class PerformanceTimingData final
 {
+  friend class PerformanceTiming;
+
 public:
+  // This can return null.
+  static PerformanceTimingData*
+  Create(nsITimedChannel* aChannel,
+         nsIHttpChannel* aHttpChannel,
+         DOMHighResTimeStamp aZeroTime,
+         nsAString& aInitiatorType,
+         nsAString& aEntryName);
+
   PerformanceTimingData(nsITimedChannel* aChannel,
                         nsIHttpChannel* aHttpChannel,
                         DOMHighResTimeStamp aZeroTime);
 
   void
   SetPropertiesFromHttpChannel(nsIHttpChannel* aHttpChannel);
 
   bool IsInitialized() const
@@ -104,18 +116,17 @@ public:
    * - an absolute wall clock time since the unix epoch
    */
   inline DOMHighResTimeStamp
   TimeStampToDOMHighRes(Performance* aPerformance, TimeStamp aStamp) const
   {
     MOZ_ASSERT(aPerformance);
     MOZ_ASSERT(!aStamp.IsNull());
 
-    TimeDuration duration =
-        aStamp - aPerformance->GetDOMTiming()->GetNavigationStartTimeStamp();
+    TimeDuration duration = aStamp - aPerformance->CreationTimeStamp();
     return duration.ToMilliseconds() + mZeroTime;
   }
 
   // The last channel's AsyncOpen time.  This may occur before the FetchStart
   // in some cases.
   DOMHighResTimeStamp AsyncOpenHighRes(Performance* aPerformance);
 
   // High resolution (used by resource timing)
--- a/dom/performance/moz.build
+++ b/dom/performance/moz.build
@@ -14,31 +14,33 @@ EXPORTS.mozilla.dom += [
     'PerformanceMeasure.h',
     'PerformanceNavigation.h',
     'PerformanceNavigationTiming.h',
     'PerformanceObserver.h',
     'PerformanceObserverEntryList.h',
     'PerformanceResourceTiming.h',
     'PerformanceService.h',
     'PerformanceStorage.h',
+    'PerformanceStorageWorker.h',
     'PerformanceTiming.h',
 ]
 
 UNIFIED_SOURCES += [
     'Performance.cpp',
     'PerformanceEntry.cpp',
     'PerformanceMainThread.cpp',
     'PerformanceMark.cpp',
     'PerformanceMeasure.cpp',
     'PerformanceNavigation.cpp',
     'PerformanceNavigationTiming.cpp',
     'PerformanceObserver.cpp',
     'PerformanceObserverEntryList.cpp',
     'PerformanceResourceTiming.cpp',
     'PerformanceService.cpp',
+    'PerformanceStorageWorker.cpp',
     'PerformanceTiming.cpp',
     'PerformanceWorker.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/workers',
 ]
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -59,16 +59,17 @@
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/IndexedDatabaseManager.h"
 #include "mozilla/dom/MessageEvent.h"
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/MessagePortBinding.h"
 #include "mozilla/dom/nsCSPUtils.h"
 #include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceStorageWorker.h"
 #include "mozilla/dom/PMessagePort.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseDebugging.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/SimpleGlobalObject.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/StructuredCloneHolder.h"
 #include "mozilla/dom/TabChild.h"
@@ -7198,18 +7199,22 @@ WorkerPrivate::DumpCrashInformation(nsAC
     aString.Append(holder->Name());
   }
 }
 
 PerformanceStorage*
 WorkerPrivate::GetPerformanceStorage()
 {
   AssertIsOnMainThread();
-  // TODO
-  return nullptr;
+
+  if (!mPerformanceStorage) {
+    mPerformanceStorage = PerformanceStorageWorker::Create(this);
+  }
+
+  return mPerformanceStorage;
 }
 
 NS_IMPL_ISUPPORTS_INHERITED0(ExternalRunnableWrapper, WorkerRunnable)
 
 template <class Derived>
 NS_IMPL_ADDREF(WorkerPrivateParent<Derived>::EventTarget)
 
 template <class Derived>
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -1063,16 +1063,18 @@ class WorkerPrivate : public WorkerPriva
 
   nsCOMPtr<nsITimer> mGCTimer;
 
   RefPtr<MemoryReporter> mMemoryReporter;
 
   // fired on the main thread if the worker script fails to load
   nsCOMPtr<nsIRunnable> mLoadFailedRunnable;
 
+  RefPtr<PerformanceStorage> mPerformanceStorage;
+
   JS::UniqueChars mDefaultLocale; // nulled during worker JSContext init
   TimeStamp mKillTime;
   uint32_t mErrorHandlerRecursionCount;
   uint32_t mNextTimeoutId;
   Status mStatus;
   UniquePtr<ClientSource> mClientSource;
   bool mFrozen;
   bool mTimerRunning;