diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp
index c8d76f7..241f6f3 100644
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -680,17 +680,17 @@ ScriptSourceObject*
 BytecodeCompiler::sourceObjectPtr() const
 {
     return sourceObject.get();
 }
 
 ScriptSourceObject*
 frontend::CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options)
 {
-    ScriptSource* ss = cx->new_<ScriptSource>();
+    ScriptSource* ss = cx->new_<ScriptSource>(cx->zone()->runtimeFromAnyThread());
     if (!ss)
         return nullptr;
     ScriptSourceHolder ssHolder(ss);
 
     if (!ss->initFromOptions(cx, options))
         return nullptr;
 
     RootedScriptSource sso(cx, ScriptSourceObject::create(cx, ss));
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index 96d59c9b..85d9f44 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6249,32 +6249,32 @@ JS_IsIdentifier(const char16_t* chars, size_t length)
     return js::frontend::IsIdentifier(chars, length);
 }
 
 namespace JS {
 
 void AutoFilename::reset()
 {
     if (ss_) {
-        ss_->decref();
+        ss_->decref("--AutoFilename");
         ss_ = nullptr;
     }
     if (filename_.is<const char*>())
         filename_.as<const char*>() = nullptr;
     else
         filename_.as<UniqueChars>().reset();
 }
 
 void AutoFilename::setScriptSource(js::ScriptSource* p)
 {
     MOZ_ASSERT(!ss_);
     MOZ_ASSERT(!get());
     ss_ = p;
     if (p) {
-        p->incref();
+        p->incref("++AutoFilename");
         setUnowned(p->filename());
     }
 }
 
 void AutoFilename::setUnowned(const char* filename)
 {
     MOZ_ASSERT(!get());
     filename_.as<const char*>() = filename ? filename : "";
diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp
index 93705ce..43396c7 100644
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -766,18 +766,17 @@ CreateFunctionPrototype(JSContext* cx, JSProtoKey key)
     RootedFunction functionProto(cx, &functionProto_->as<JSFunction>());
 
     const char* rawSource = "() {\n}";
     size_t sourceLen = strlen(rawSource);
     char16_t* source = InflateString(cx, rawSource, &sourceLen);
     if (!source)
         return nullptr;
 
-    ScriptSource* ss =
-        cx->new_<ScriptSource>();
+    ScriptSource* ss = cx->new_<ScriptSource>(cx->runtime());
     if (!ss) {
         js_free(source);
         return nullptr;
     }
     ScriptSourceHolder ssHolder(ss);
     ss->setSource(source, sourceLen);
     CompileOptions options(cx);
     options.setNoScriptRval(true)
diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp
index ee3c20e..01ea170 100644
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -807,17 +807,17 @@ js::XDRScript(XDRState<mode>* xdr, HandleObject enclosingScopeArg, HandleScript
         MOZ_ASSERT((version_ & VersionFlags::MASK) == unsigned(version_));
 
         CompileOptions options(cx);
         options.setVersion(version_)
                .setNoScriptRval(!!(scriptBits & (1 << NoScriptRval)))
                .setSelfHostingMode(!!(scriptBits & (1 << SelfHosted)));
         RootedScriptSource sourceObject(cx);
         if (scriptBits & (1 << OwnSource)) {
-            ScriptSource* ss = cx->new_<ScriptSource>();
+            ScriptSource* ss = cx->new_<ScriptSource>(xdr->cx()->runtime());
             if (!ss)
                 return false;
             ScriptSourceHolder ssHolder(ss);
 
             /*
              * We use this CompileOptions only to initialize the
              * ScriptSourceObject. Most CompileOptions fields aren't used by
              * ScriptSourceObject, and those that are (element; elementAttributeName)
@@ -1693,17 +1693,17 @@ ScriptSourceObject::finalize(FreeOp* fop, JSObject* obj)
 {
     ScriptSourceObject* sso = &obj->as<ScriptSourceObject>();
 
     // If code coverage is enabled, record the filename associated with this
     // source object.
     if (fop->runtime()->lcovOutput.isEnabled())
         sso->compartment()->lcovOutput.collectSourceFile(sso->compartment(), sso);
 
-    sso->source()->decref();
+    sso->source()->decref("--ScriptSourceObject::finalize");
     sso->setReservedSlot(SOURCE_SLOT, PrivateValue(nullptr));
 }
 
 static const ClassOps ScriptSourceObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* getProperty */
     nullptr, /* setProperty */
@@ -1727,17 +1727,17 @@ const Class ScriptSourceObject::class_ = {
 ScriptSourceObject*
 ScriptSourceObject::create(ExclusiveContext* cx, ScriptSource* source)
 {
     RootedObject object(cx, NewObjectWithGivenProto(cx, &class_, nullptr));
     if (!object)
         return nullptr;
     RootedScriptSource sourceObject(cx, &object->as<ScriptSourceObject>());
 
-    source->incref();    // The matching decref is in ScriptSourceObject::finalize.
+    source->incref("++ScriptSourceObject::create");    // The matching decref is in ScriptSourceObject::finalize.
     sourceObject->initReservedSlot(SOURCE_SLOT, PrivateValue(source));
 
     // The remaining slots should eventually be populated by a call to
     // initFromOptions. Poison them until that point.
     sourceObject->initReservedSlot(ELEMENT_SLOT, MagicValue(JS_GENERIC_MAGIC));
     sourceObject->initReservedSlot(ELEMENT_PROPERTY_SLOT, MagicValue(JS_GENERIC_MAGIC));
     sourceObject->initReservedSlot(INTRODUCTION_SCRIPT_SLOT, MagicValue(JS_GENERIC_MAGIC));
 
@@ -2050,17 +2050,17 @@ ScriptSource::updateCompressedSourceSet(JSRuntime* rt)
     MOZ_ASSERT(!inCompressedSourceSet);
 
     CompressedSourceSet::AddPtr p = rt->compressedSourceSet.lookupForAdd(this);
     if (p) {
         // There is another ScriptSource with the same compressed data.
         // Mark that ScriptSource as the parent and use it for all attempts to
         // get the source for this ScriptSource.
         ScriptSource* parent = *p;
-        parent->incref();
+        parent->incref("++ScriptSource::updateCompressedSourceSet");
 
         js_free(compressedData());
         data = SourceType(Parent(parent));
     } else {
         if (rt->compressedSourceSet.add(p, this))
             inCompressedSourceSet = true;
     }
 }
@@ -2183,16 +2183,36 @@ SourceCompressionTask::work()
 
     // Shrink the buffer to the size of the compressed data.
     if (void* newCompressed = js_realloc(compressed, compressedBytes))
         compressed = newCompressed;
 
     return Success;
 }
 
+ScriptSource::ScriptSource(JSRuntime* rt)
+  : refs(0),
+    data(SourceType(Missing())),
+    length_(0),
+    filename_(nullptr),
+    displayURL_(nullptr),
+    sourceMapURL_(nullptr),
+    mutedErrors_(false),
+    introductionOffset_(0),
+    introducerFilename_(nullptr),
+    introductionType_(nullptr),
+    sourceRetrievable_(false),
+    argumentsNotIncluded_(false),
+    hasIntroductionOffset_(false),
+    inCompressedSourceSet(false)
+{
+    auto locked = rt->sourceList.lock();
+    locked->insertBack(this);
+}
+
 ScriptSource::~ScriptSource()
 {
     struct DestroyMatcher
     {
         using ReturnType = void;
 
         ScriptSource& ss;
 
@@ -2207,27 +2227,28 @@ ScriptSource::~ScriptSource()
 
         ReturnType match(Compressed& c) {
             if (ss.inCompressedSourceSet)
                 TlsPerThreadData.get()->runtimeFromMainThread()->compressedSourceSet.remove(&ss);
             js_free(c.raw);
         }
 
         ReturnType match(Parent& p) {
-            p.parent->decref();
+            p.parent->decref("--ScriptSource::~ScriptSource");
         }
 
         ReturnType match(Missing&) {
             // Nothing to do here.
         }
     };
 
     MOZ_ASSERT(refs == 0);
     MOZ_ASSERT_IF(inCompressedSourceSet, data.is<Compressed>());
 
+    printf("FITZGEN: ~ScriptSource %p\n", this);
     DestroyMatcher dm(*this);
     data.match(dm);
 }
 
 void
 ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                      JS::ScriptSourceInfo* info) const
 {
diff --git a/js/src/jsscript.h b/js/src/jsscript.h
index c9a1322..9227742 100644
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -611,18 +611,20 @@ class UncompressedSourceCache
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
   private:
     void holdEntry(AutoHoldEntry& holder, ScriptSource* ss);
     void releaseEntry(AutoHoldEntry& holder);
 };
 
-class ScriptSource
+class ScriptSource : private mozilla::LinkedListElement<ScriptSource>
 {
+    friend class mozilla::LinkedListElement<ScriptSource>;
+    friend class mozilla::LinkedList<ScriptSource>;
     friend struct SourceCompressionTask;
 
     uint32_t refs;
 
     // Note: while ScriptSources may be compressed off thread, they are only
     // modified by the main thread, and all members are always safe to access
     // on the main thread.
 
@@ -711,37 +713,27 @@ class ScriptSource
     bool sourceRetrievable_:1;
     bool argumentsNotIncluded_:1;
     bool hasIntroductionOffset_:1;
 
     // Whether this is in the runtime's set of compressed ScriptSources.
     bool inCompressedSourceSet:1;
 
   public:
-    explicit ScriptSource()
-      : refs(0),
-        data(SourceType(Missing())),
-        length_(0),
-        filename_(nullptr),
-        displayURL_(nullptr),
-        sourceMapURL_(nullptr),
-        mutedErrors_(false),
-        introductionOffset_(0),
-        introducerFilename_(nullptr),
-        introductionType_(nullptr),
-        sourceRetrievable_(false),
-        argumentsNotIncluded_(false),
-        hasIntroductionOffset_(false),
-        inCompressedSourceSet(false)
-    {
-    }
+    mozilla::Vector<const char*> reflog;
+
+    explicit ScriptSource(JSRuntime* rt);
     ~ScriptSource();
-    void incref() { refs++; }
-    void decref() {
+    void incref(const char* who) {
+        refs++;
+        MOZ_RELEASE_ASSERT(reflog.append(who));
+    }
+    void decref(const char* who) {
         MOZ_ASSERT(refs != 0);
+        MOZ_RELEASE_ASSERT(reflog.append(who));
         if (--refs == 0)
             js_delete(this);
     }
     bool initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions& options);
     bool setSourceCopy(ExclusiveContext* cx,
                        JS::SourceBufferHolder& srcBuf,
                        bool argumentsNotIncluded,
                        SourceCompressionTask* tok);
@@ -850,28 +842,28 @@ class ScriptSourceHolder
     ScriptSource* ss;
   public:
     ScriptSourceHolder()
       : ss(nullptr)
     {}
     explicit ScriptSourceHolder(ScriptSource* ss)
       : ss(ss)
     {
-        ss->incref();
+        ss->incref("++ScriptSourceHolder");
     }
     ~ScriptSourceHolder()
     {
         if (ss)
-            ss->decref();
+            ss->decref("--ScriptSourceHolder");
     }
     void reset(ScriptSource* newss) {
         if (ss)
-            ss->decref();
+            ss->decref("--ScriptSourceHolder");
         ss = newss;
-        ss->incref();
+        ss->incref("++ScriptSourceHolder");
     }
     ScriptSource* get() const {
         return ss;
     }
 };
 
 struct CompressedSourceHasher
 {
diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp
index a634085..5d66927 100644
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -196,16 +196,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
     profilingScripts(false),
     suppressProfilerSampling(false),
     hadOutOfMemory(false),
 #ifdef DEBUG
     handlingInitFailure(false),
 #endif
     haveCreatedContext(false),
     allowRelazificationForTesting(false),
+    sourceList(mozilla::LinkedList<ScriptSource>()),
     data(nullptr),
     signalHandlersInstalled_(false),
     canUseSignalHandlers_(false),
     defaultFreeOp_(thisFromCtor()),
     debuggerMutations(0),
     securityCallbacks(&NullSecurityCallbacks),
     DOMcallbacks(nullptr),
     destroyPrincipals(nullptr),
@@ -479,16 +480,27 @@ JSRuntime::~JSRuntime()
     MOZ_ASSERT(oldCount > 0);
 
     js::TlsPerThreadData.set(nullptr);
 
 #ifdef XP_WIN
     if (ownerThreadNative_)
         CloseHandle((HANDLE)ownerThreadNative_);
 #endif
+
+    {
+        auto locked = sourceList.lock();
+        for (ScriptSource* ss : locked.get()) {
+            printf("FITZGEN: FOUND A LEAKED SCRIPT SOURCE %p ==========================\n", ss);
+            for (const char* op : ss->reflog) {
+                printf("FITZGEN: %s\n", op);
+            }
+            printf("==================================================================================\n");
+        }
+    }
 }
 
 void
 JSRuntime::addTelemetry(int id, uint32_t sample, const char* key)
 {
     if (telemetryCallback)
         (*telemetryCallback)(id, sample, key);
 }
diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h
index 451b16e..433ced3 100644
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -34,16 +34,17 @@
 #include "js/Debug.h"
 #include "js/GCVector.h"
 #include "js/HashTable.h"
 #ifdef DEBUG
 # include "js/Proxy.h" // For AutoEnterPolicy
 #endif
 #include "js/UniquePtr.h"
 #include "js/Vector.h"
+#include "threading/ExclusiveData.h"
 #include "vm/CodeCoverage.h"
 #include "vm/CommonPropertyNames.h"
 #include "vm/DateTime.h"
 #include "vm/MallocProvider.h"
 #include "vm/SPSProfiler.h"
 #include "vm/Stack.h"
 #include "vm/Stopwatch.h"
 #include "vm/Symbol.h"
@@ -54,16 +55,17 @@
 #endif
 
 namespace js {
 
 class PerThreadData;
 class ExclusiveContext;
 class AutoKeepAtoms;
 class EnterDebuggeeNoExecute;
+class ScriptSource;
 #ifdef JS_TRACE_LOGGING
 class TraceLoggerThread;
 #endif
 
 /* Thread Local Storage slot for storing the runtime for a thread. */
 extern MOZ_THREAD_LOCAL(PerThreadData*) TlsPerThreadData;
 
 } // namespace js
@@ -1204,16 +1206,19 @@ struct JSRuntime : public JS::shadow::Runtime,
      * Allow relazifying functions in compartments that are active. This is
      * only used by the relazifyFunctions() testing function.
      */
     bool                allowRelazificationForTesting;
 
     /* Linked list of all Debugger objects in the runtime. */
     mozilla::LinkedList<js::Debugger> debuggerList;
 
+    /* Linked list of all ScriptSource objects in the runtime. */
+    js::ExclusiveData<mozilla::LinkedList<js::ScriptSource>> sourceList;
+
     /*
      * Head of circular list of all enabled Debuggers that have
      * onNewGlobalObject handler methods established.
      */
     JSCList             onNewGlobalObjectWatchers;
 
     /* Client opaque pointers */
     void*               data;
