Bug 1718481 - Use stencil in nsXULPrototypeCache. r?tcampbell! draft
authorTooru Fujisawa <arai_a@mac.com>
Tue, 21 Sep 2021 02:13:21 +0900
changeset 3978292 99836c770e44c47ca6d9549b87dc98967b207b32
parent 3978291 38b6aa5794b045f416990ba675ca93cd81a345a8
child 3978293 f8024a60f2dafc80cf53c25685c3e197ff8d6658
push id731707
push userarai_a@mac.com
push dateMon, 20 Sep 2021 17:20:34 +0000
treeherdertry@f8024a60f2da [default view] [failures only]
reviewerstcampbell
bugs1718481
milestone94.0a1
Bug 1718481 - Use stencil in nsXULPrototypeCache. r?tcampbell! Differential Revision: https://phabricator.services.mozilla.com/D121254
dom/base/Document.cpp
dom/base/Document.h
dom/base/nsCCUncollectableMarker.cpp
dom/base/nsCCUncollectableMarker.h
dom/base/nsIScriptContext.h
dom/prototype/PrototypeDocumentContentSink.cpp
dom/prototype/PrototypeDocumentContentSink.h
dom/xul/nsXULContentSink.cpp
dom/xul/nsXULElement.cpp
dom/xul/nsXULElement.h
dom/xul/nsXULPrototypeCache.cpp
dom/xul/nsXULPrototypeCache.h
dom/xul/nsXULPrototypeDocument.cpp
dom/xul/nsXULPrototypeDocument.h
js/xpconnect/idl/nsIXPConnect.idl
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/nsXPConnect.cpp
js/xpconnect/src/xpcprivate.h
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -16210,22 +16210,16 @@ void Document::ResetUserInteractionTimer
 }
 
 bool Document::IsExtensionPage() const {
   return Preferences::GetBool("media.autoplay.allow-extension-background-pages",
                               true) &&
          BasePrincipal::Cast(NodePrincipal())->AddonPolicy();
 }
 
-void Document::TraceProtos(JSTracer* aTrc) {
-  if (mPrototypeDocument) {
-    mPrototypeDocument->TraceProtos(aTrc);
-  }
-}
-
 /**
  * Retrieves the classification of the Flash plugins in the document based on
  * the classification lists. For more information, see
  * toolkit/components/url-classifier/flash-block-lists.rst
  */
 FlashClassification Document::DocumentFlashClassification() {
   // Disable flash blocking when fission is enabled(See Bug 1584931).
   const auto fnIsFlashBlockingEnabled = [] {
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -5274,18 +5274,16 @@ class Document : public nsINode,
   // Needs to be public because the bindings code pokes at it.
   JS::ExpandoAndGeneration mExpandoAndGeneration;
 
   bool HasPendingInitialTranslation();
 
   nsRefPtrHashtable<nsRefPtrHashKey<Element>, nsXULPrototypeElement>
       mL10nProtoElements;
 
-  void TraceProtos(JSTracer* aTrc);
-
   float GetSavedResolutionBeforeMVM() { return mSavedResolutionBeforeMVM; }
   void SetSavedResolutionBeforeMVM(float aResolution) {
     mSavedResolutionBeforeMVM = aResolution;
   }
 
   void LoadEventFired();
 };
 
--- a/dom/base/nsCCUncollectableMarker.cpp
+++ b/dom/base/nsCCUncollectableMarker.cpp
@@ -424,30 +424,17 @@ nsresult nsCCUncollectableMarker::Observ
     default: {
       break;
     }
   }
 
   return NS_OK;
 }
 
-void mozilla::dom::TraceBlackJS(JSTracer* aTrc, bool aIsShutdownGC) {
-#ifdef MOZ_XUL
-  // Mark the scripts held in the XULPrototypeCache. This is required to keep
-  // the JS script in the cache live across GC.
-  nsXULPrototypeCache* cache = nsXULPrototypeCache::MaybeGetInstance();
-  if (cache) {
-    if (aIsShutdownGC) {
-      cache->FlushScripts();
-    } else {
-      cache->MarkInGC(aTrc);
-    }
-  }
-#endif
-
+void mozilla::dom::TraceBlackJS(JSTracer* aTrc) {
   if (!nsCCUncollectableMarker::sGeneration) {
     return;
   }
 
   if (ContentProcessMessageManager::WasCreated() &&
       nsFrameMessageManager::GetChildProcessManager()) {
     auto* pg = ContentProcessMessageManager::Get();
     if (pg) {
@@ -499,19 +486,12 @@ void mozilla::dom::TraceBlackJS(JSTracer
                 if (elm) {
                   elm->TraceListeners(aTrc);
                 }
                 // As of now there isn't an easy way to trace message listeners.
               }
             }
           }
         }
-
-#ifdef MOZ_XUL
-        Document* doc = window->GetExtantDoc();
-        if (doc) {
-          doc->TraceProtos(aTrc);
-        }
-#endif
       }
     }
   }
 }
--- a/dom/base/nsCCUncollectableMarker.h
+++ b/dom/base/nsCCUncollectableMarker.h
@@ -36,13 +36,13 @@ class nsCCUncollectableMarker final : pu
 
  private:
   nsCCUncollectableMarker() = default;
   ~nsCCUncollectableMarker() = default;
 };
 
 namespace mozilla {
 namespace dom {
-void TraceBlackJS(JSTracer* aTrc, bool aIsShutdownGC);
+void TraceBlackJS(JSTracer* aTrc);
 }  // namespace dom
 }  // namespace mozilla
 
 #endif
--- a/dom/base/nsIScriptContext.h
+++ b/dom/base/nsIScriptContext.h
@@ -7,16 +7,18 @@
 #ifndef nsIScriptContext_h__
 #define nsIScriptContext_h__
 
 #include "nscore.h"
 #include "nsString.h"
 #include "nsISupports.h"
 #include "nsCOMPtr.h"
 #include "jspubtd.h"
+#include "js/experimental/JSStencil.h"
+#include "mozilla/RefPtr.h"
 
 class nsIScriptGlobalObject;
 
 // Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_ISCRIPTCONTEXT_IID                        \
   {                                                  \
     0x54cbe9cf, 0x7282, 0x421a, {                    \
       0x91, 0x6f, 0xd0, 0x70, 0x73, 0xde, 0xb8, 0xc0 \
@@ -72,19 +74,20 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptC
     }                                                \
   }
 
 class nsIOffThreadScriptReceiver : public nsISupports {
  public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IOFFTHREADSCRIPTRECEIVER_IID)
 
   /**
-   * Notify this object that a previous CompileScript call specifying this as
+   * Notify this object that a previous Compile call specifying this as
    * aOffThreadReceiver has completed. The script being passed in must be
    * rooted before any call which could trigger GC.
    */
-  NS_IMETHOD OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) = 0;
+  NS_IMETHOD OnScriptCompileComplete(JS::Stencil* aStencil,
+                                     nsresult aStatus) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIOffThreadScriptReceiver,
                               NS_IOFFTHREADSCRIPTRECEIVER_IID)
 
 #endif  // nsIScriptContext_h__
--- a/dom/prototype/PrototypeDocumentContentSink.cpp
+++ b/dom/prototype/PrototypeDocumentContentSink.cpp
@@ -45,21 +45,23 @@
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLTemplateElement.h"
 #include "mozilla/dom/ProcessingInstruction.h"
 #include "mozilla/dom/XMLStylesheetProcessingInstruction.h"
 #include "mozilla/dom/ScriptLoader.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/ProfilerLabels.h"
+#include "mozilla/RefPtr.h"
 
 #include "nsXULPrototypeCache.h"
 #include "nsXULElement.h"
 #include "mozilla/CycleCollectedJSContext.h"
 #include "js/CompilationAndEvaluation.h"
+#include "js/experimental/JSStencil.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 LazyLogModule PrototypeDocumentContentSink::gLog("PrototypeDocument");
 
 nsresult NS_NewPrototypeDocumentContentSink(nsIContentSink** aResult,
                                             Document* aDoc, nsIURI* aURI,
@@ -561,17 +563,17 @@ nsresult PrototypeDocumentContentSink::R
             // "block" our prototype walk if the script isn't
             // cached, or the cached copy of the script is
             // stale and must be reloaded.
             bool blocked;
             rv = LoadScript(scriptproto, &blocked);
             // If the script cannot be loaded, just keep going!
 
             if (NS_SUCCEEDED(rv) && blocked) return NS_OK;
-          } else if (scriptproto->HasScriptObject()) {
+          } else if (scriptproto->HasStencil()) {
             // An inline script
             rv = ExecuteScript(scriptproto);
             if (NS_FAILED(rv)) return rv;
           }
         } break;
 
         case nsXULPrototypeNode::eType_Text: {
           nsNodeInfoManager* nim = nodeToPushTo->NodeInfo()->NodeInfoManager();
@@ -720,49 +722,49 @@ PrototypeDocumentContentSink::StyleSheet
 
 nsresult PrototypeDocumentContentSink::LoadScript(
     nsXULPrototypeScript* aScriptProto, bool* aBlock) {
   // Load a transcluded script
   nsresult rv;
 
   bool isChromeDoc = IsChromeURI(mDocumentURI);
 
-  if (isChromeDoc && aScriptProto->HasScriptObject()) {
+  if (isChromeDoc && aScriptProto->HasStencil()) {
     rv = ExecuteScript(aScriptProto);
 
     // Ignore return value from execution, and don't block
     *aBlock = false;
     return NS_OK;
   }
 
   // Try the XUL script cache, in case two XUL documents source the same
   // .js file (e.g., strres.js from navigator.xul and utilityOverlay.xul).
   // XXXbe the cache relies on aScriptProto's GC root!
   bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
 
   if (isChromeDoc && useXULCache) {
-    JSScript* newScriptObject =
-        nsXULPrototypeCache::GetInstance()->GetScript(aScriptProto->mSrcURI);
-    if (newScriptObject) {
+    RefPtr<JS::Stencil> newStencil =
+        nsXULPrototypeCache::GetInstance()->GetStencil(aScriptProto->mSrcURI);
+    if (newStencil) {
       // The script language for a proto must remain constant - we
       // can't just change it for this unexpected language.
-      aScriptProto->Set(newScriptObject);
+      aScriptProto->Set(newStencil);
     }
 
-    if (aScriptProto->HasScriptObject()) {
+    if (aScriptProto->HasStencil()) {
       rv = ExecuteScript(aScriptProto);
 
       // Ignore return value from execution, and don't block
       *aBlock = false;
       return NS_OK;
     }
   }
 
-  // Release script objects from FastLoad since we decided against using them
-  aScriptProto->UnlinkJSObjects();
+  // Release stencil from FastLoad since we decided against using them
+  aScriptProto->Set(nullptr);
 
   // Set the current script prototype so that OnStreamComplete can report
   // the right file if there are errors in the script.
   NS_ASSERTION(!mCurrentScriptProto,
                "still loading a script when starting another load?");
   mCurrentScriptProto = aScriptProto;
 
   if (isChromeDoc && aScriptProto->mSrcLoading) {
@@ -836,17 +838,17 @@ PrototypeDocumentContentSink::OnStreamCo
   }
 
   if (NS_SUCCEEDED(aStatus)) {
     // If the including document is a FastLoad document, and we're
     // compiling an out-of-line script (one with src=...), then we must
     // be writing a new FastLoad file.  If we were reading this script
     // from the FastLoad file, XULContentSinkImpl::OpenScript (over in
     // nsXULContentSink.cpp) would have already deserialized a non-null
-    // script->mScriptObject, causing control flow at the top of LoadScript
+    // script->mStencil, causing control flow at the top of LoadScript
     // not to reach here.
     nsCOMPtr<nsIURI> uri = mCurrentScriptProto->mSrcURI;
 
     // XXX should also check nsIHttpChannel::requestSucceeded
 
     MOZ_ASSERT(!mOffThreadCompiling && (mOffThreadCompileStringLength == 0 &&
                                         !mOffThreadCompileStringBuf),
                "PrototypeDocument can't load multiple scripts at once");
@@ -862,40 +864,41 @@ PrototypeDocumentContentSink::OnStreamCo
       size_t unitsLength = 0;
 
       std::swap(units, mOffThreadCompileStringBuf);
       std::swap(unitsLength, mOffThreadCompileStringLength);
 
       rv = mCurrentScriptProto->Compile(units, unitsLength,
                                         JS::SourceOwnership::TakeOwnership, uri,
                                         1, mDocument, this);
-      if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->HasScriptObject()) {
+      if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->HasStencil()) {
         mOffThreadCompiling = true;
         mDocument->BlockOnload();
         return NS_OK;
       }
     }
   }
 
-  return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv);
+  return OnScriptCompileComplete(mCurrentScriptProto->GetStencil(), rv);
 }
 
 NS_IMETHODIMP
-PrototypeDocumentContentSink::OnScriptCompileComplete(JSScript* aScript,
+PrototypeDocumentContentSink::OnScriptCompileComplete(JS::Stencil* aStencil,
                                                       nsresult aStatus) {
   // The mCurrentScriptProto may have been cleared out by another
   // PrototypeDocumentContentSink.
   if (!mCurrentScriptProto) {
     return NS_OK;
   }
 
   // When compiling off thread the script will not have been attached to the
   // script proto yet.
-  if (aScript && !mCurrentScriptProto->HasScriptObject())
-    mCurrentScriptProto->Set(aScript);
+  if (aStencil && !mCurrentScriptProto->HasStencil()) {
+    mCurrentScriptProto->Set(aStencil);
+  }
 
   // Allow load events to be fired once off thread compilation finishes.
   if (mOffThreadCompiling) {
     mOffThreadCompiling = false;
     mDocument->UnblockOnload(false);
   }
 
   // After compilation finishes the script's characters are no longer needed.
@@ -938,21 +941,19 @@ PrototypeDocumentContentSink::OnScriptCo
     // GC root protecting the script object.  Otherwise, the script
     // cache entry will dangle once the uncached prototype document
     // is released when its owning document is unloaded.
     //
     // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for
     // the true crime story.)
     bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
 
-    if (useXULCache && IsChromeURI(mDocumentURI) &&
-        scriptProto->HasScriptObject()) {
-      JS::Rooted<JSScript*> script(RootingCx(), scriptProto->GetScriptObject());
-      nsXULPrototypeCache::GetInstance()->PutScript(scriptProto->mSrcURI,
-                                                    script);
+    if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->HasStencil()) {
+      nsXULPrototypeCache::GetInstance()->PutStencil(scriptProto->mSrcURI,
+                                                     scriptProto->GetStencil());
     }
     // ignore any evaluation errors
   }
 
   rv = ResumeWalk();
 
   // Load a pointer to the prototype-script's list of documents who
   // raced to load the same script
@@ -965,28 +966,28 @@ PrototypeDocumentContentSink::OnScriptCo
     NS_ASSERTION(doc->mCurrentScriptProto == scriptProto,
                  "waiting for wrong script to load?");
     doc->mCurrentScriptProto = nullptr;
 
     // Unlink doc from scriptProto's list before executing and resuming
     *docp = doc->mNextSrcLoadWaiter;
     doc->mNextSrcLoadWaiter = nullptr;
 
-    if (aStatus == NS_BINDING_ABORTED && !scriptProto->HasScriptObject()) {
+    if (aStatus == NS_BINDING_ABORTED && !scriptProto->HasStencil()) {
       // If the previous doc load was aborted, we want to try loading
       // again for the next doc. Otherwise, one abort would lead to all
       // subsequent waiting docs to abort as well.
       bool block = false;
       doc->LoadScript(scriptProto, &block);
       NS_RELEASE(doc);
       return rv;
     }
 
     // Execute only if we loaded and compiled successfully, then resume
-    if (NS_SUCCEEDED(aStatus) && scriptProto->HasScriptObject()) {
+    if (NS_SUCCEEDED(aStatus) && scriptProto->HasStencil()) {
       doc->ExecuteScript(scriptProto);
     }
     doc->ResumeWalk();
     NS_RELEASE(doc);
   }
 
   return rv;
 }
@@ -1005,32 +1006,32 @@ nsresult PrototypeDocumentContentSink::E
 
   nsresult rv;
   rv = scriptGlobalObject->EnsureScriptEnvironment();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Execute the precompiled script with the given version
   nsAutoMicroTask mt;
 
-  // We're about to run script via JS::CloneAndExecuteScript, so we need an
+  // We're about to run script via JS_ExecuteScript, so we need an
   // AutoEntryScript. This is Gecko specific and not in any spec.
   AutoEntryScript aes(scriptGlobalObject, "precompiled XUL <script> element");
   JSContext* cx = aes.cx();
 
-  JS::Rooted<JSScript*> scriptObject(cx, aScript->GetScriptObject());
-  NS_ENSURE_TRUE(scriptObject, NS_ERROR_UNEXPECTED);
+  JS::Rooted<JSScript*> scriptObject(cx);
+  rv = aScript->InstantiateScript(cx, &scriptObject);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
   NS_ENSURE_TRUE(xpc::Scriptability::Get(global).Allowed(), NS_OK);
 
-  // The script is in the compilation scope. Clone it into the target scope
-  // and execute it. On failure, ~AutoScriptEntry will handle exceptions, so
+  // On failure, ~AutoScriptEntry will handle exceptions, so
   // there is no need to manually check the return value.
   JS::RootedValue rval(cx);
-  JS::CloneAndExecuteScript(cx, scriptObject, &rval);
+  Unused << JS_ExecuteScript(cx, scriptObject, &rval);
 
   return NS_OK;
 }
 
 nsresult PrototypeDocumentContentSink::CreateElementFromPrototype(
     nsXULPrototypeElement* aPrototype, Element** aResult, nsIContent* aParent) {
   // Create a content model element from a prototype element.
   MOZ_ASSERT(aPrototype, "null ptr");
--- a/dom/prototype/PrototypeDocumentContentSink.h
+++ b/dom/prototype/PrototypeDocumentContentSink.h
@@ -16,16 +16,18 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsIDTD.h"
 #include "mozilla/dom/FromParser.h"
 #include "nsXULPrototypeDocument.h"
 #include "nsIStreamLoader.h"
 #include "nsIScriptContext.h"
 #include "nsICSSLoaderObserver.h"
 #include "mozilla/Logging.h"
+#include "js/experimental/JSStencil.h"
+#include "mozilla/RefPtr.h"
 
 class nsIURI;
 class nsIChannel;
 class nsIContent;
 class nsIParser;
 class nsTextNode;
 class nsINode;
 class nsXULPrototypeElement;
@@ -81,17 +83,17 @@ class PrototypeDocumentContentSink final
   virtual bool IsScriptExecuting() override;
   virtual void ContinueInterruptedParsingAsync() override;
 
   // nsICSSLoaderObserver
   NS_IMETHOD StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
                               nsresult aStatus) override;
 
   // nsIOffThreadScriptReceiver
-  NS_IMETHOD OnScriptCompileComplete(JSScript* aScript,
+  NS_IMETHOD OnScriptCompileComplete(JS::Stencil* aStencil,
                                      nsresult aStatus) override;
 
   nsresult OnPrototypeLoadDone(nsXULPrototypeDocument* aPrototype);
 
  protected:
   virtual ~PrototypeDocumentContentSink();
 
   static LazyLogModule gLog;
--- a/dom/xul/nsXULContentSink.cpp
+++ b/dom/xul/nsXULContentSink.cpp
@@ -422,17 +422,17 @@ XULContentSinkImpl::HandleEndElement(con
       }
     } break;
 
     case nsXULPrototypeNode::eType_Script: {
       nsXULPrototypeScript* script =
           static_cast<nsXULPrototypeScript*>(node.get());
 
       // If given a src= attribute, we must ignore script tag content.
-      if (!script->mSrcURI && !script->HasScriptObject()) {
+      if (!script->mSrcURI && !script->HasStencil()) {
         nsCOMPtr<Document> doc = do_QueryReferent(mDocument);
 
         script->mOutOfLine = false;
         if (doc) {
           script->Compile(mText, mTextLength, JS::SourceOwnership::Borrowed,
                           mDocumentURL, script->mLineNo, doc);
         }
       }
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -14,34 +14,39 @@
 #include "XULFrameElement.h"
 #include "XULMenuElement.h"
 #include "XULPopupElement.h"
 #include "XULTextElement.h"
 #include "XULTooltipElement.h"
 #include "XULTreeElement.h"
 #include "js/CompilationAndEvaluation.h"
 #include "js/CompileOptions.h"
+#include "js/experimental/JSStencil.h"
 #include "js/OffThreadScriptCompilation.h"
 #include "js/SourceText.h"
+#include "js/Transcoding.h"
 #include "js/Utility.h"
 #include "jsapi.h"
+#include "mozilla/Assertions.h"
 #include "mozilla/ArrayIterator.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/DeclarationBlock.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/FlushType.h"
 #include "mozilla/GlobalKeyListener.h"
 #include "mozilla/HoldDropJSObjects.h"
 #include "mozilla/MacroForEach.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/OwningNonNull.h"
 #include "mozilla/PresShell.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ScopeExit.h"
 #include "mozilla/StaticAnalysisFunctions.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/URLExtraData.h"
 #include "mozilla/dom/BindContext.h"
 #include "mozilla/dom/BorrowedAttrInfo.h"
 #include "mozilla/dom/CSSRuleBinding.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/DocumentInlines.h"
@@ -1202,18 +1207,16 @@ bool nsXULElement::IsInteractiveHTMLCont
          Element::IsInteractiveHTMLContent();
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeNode)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeNode)
   if (tmp->mType == nsXULPrototypeNode::eType_Element) {
     static_cast<nsXULPrototypeElement*>(tmp)->Unlink();
-  } else if (tmp->mType == nsXULPrototypeNode::eType_Script) {
-    static_cast<nsXULPrototypeScript*>(tmp)->UnlinkJSObjects();
   }
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeNode)
   if (tmp->mType == nsXULPrototypeNode::eType_Element) {
     nsXULPrototypeElement* elem = static_cast<nsXULPrototypeElement*>(tmp);
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mNodeInfo");
     cb.NoteNativeChild(elem->mNodeInfo,
                        NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
@@ -1226,20 +1229,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
         cb.NoteNativeChild(name.NodeInfo(),
                            NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
       }
     }
     ImplCycleCollectionTraverse(cb, elem->mChildren, "mChildren");
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXULPrototypeNode)
-  if (tmp->mType == nsXULPrototypeNode::eType_Script) {
-    nsXULPrototypeScript* script = static_cast<nsXULPrototypeScript*>(tmp);
-    script->Trace(aCallbacks, aClosure);
-  }
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXULPrototypeNode, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXULPrototypeNode, Release)
 
 //----------------------------------------------------------------------
 //
 // nsXULPrototypeAttribute
@@ -1338,17 +1337,17 @@ nsresult nsXULPrototypeElement::Serializ
           }
         } else {
           tmp = aStream->WriteCompoundObject(script->mSrcURI,
                                              NS_GET_IID(nsIURI), true);
           if (NS_FAILED(tmp)) {
             rv = tmp;
           }
 
-          if (script->HasScriptObject()) {
+          if (script->HasStencil()) {
             // This may return NS_OK without muxing script->mSrcURI's
             // data into the cache file, in the case where that
             // muxed document is already there (written by a prior
             // session, or by an earlier cache episode during this
             // session).
             tmp = script->SerializeOutOfLine(aStream, aProtoDoc);
             if (NS_FAILED(tmp)) {
               rv = tmp;
@@ -1560,67 +1559,157 @@ nsresult nsXULPrototypeElement::SetAttrA
   return NS_OK;
 }
 
 void nsXULPrototypeElement::Unlink() {
   mAttributes.Clear();
   mChildren.Clear();
 }
 
-void nsXULPrototypeElement::TraceAllScripts(JSTracer* aTrc) {
-  for (uint32_t i = 0; i < mChildren.Length(); ++i) {
-    nsXULPrototypeNode* child = mChildren[i];
-    if (child->mType == nsXULPrototypeNode::eType_Element) {
-      static_cast<nsXULPrototypeElement*>(child)->TraceAllScripts(aTrc);
-    } else if (child->mType == nsXULPrototypeNode::eType_Script) {
-      static_cast<nsXULPrototypeScript*>(child)->TraceScriptObject(aTrc);
-    }
-  }
-}
-
 //----------------------------------------------------------------------
 //
 // nsXULPrototypeScript
 //
 
 nsXULPrototypeScript::nsXULPrototypeScript(uint32_t aLineNo)
     : nsXULPrototypeNode(eType_Script),
       mLineNo(aLineNo),
       mSrcLoading(false),
       mOutOfLine(true),
       mSrcLoadWaiters(nullptr),
-      mScriptObject(nullptr) {}
+      mStencil(nullptr) {}
+
+static nsresult WriteStencil(nsIObjectOutputStream* aStream, JSContext* aCx,
+                             const JS::ReadOnlyCompileOptions& aOptions,
+                             JS::Stencil* aStencil) {
+  uint8_t flags = 0;  // We don't have flags anymore.
+  nsresult rv = aStream->Write8(flags);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  JS::TranscodeBuffer buffer;
+  JS::TranscodeResult code;
+  code = JS::EncodeStencil(aCx, aOptions, aStencil, buffer);
+
+  if (code != JS::TranscodeResult::Ok) {
+    if (code == JS::TranscodeResult::Throw) {
+      JS_ClearPendingException(aCx);
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    MOZ_ASSERT(IsTranscodeFailureResult(code));
+    return NS_ERROR_FAILURE;
+  }
+
+  size_t size = buffer.length();
+  if (size > UINT32_MAX) {
+    return NS_ERROR_FAILURE;
+  }
+  rv = aStream->Write32(size);
+  if (NS_SUCCEEDED(rv)) {
+    // Ideally we could just pass "buffer" here.  See bug 1566574.
+    rv = aStream->WriteBytes(Span(buffer.begin(), size));
+  }
+
+  return rv;
+}
+
+static nsresult ReadStencil(nsIObjectInputStream* aStream, JSContext* aCx,
+                            const JS::ReadOnlyCompileOptions& aOptions,
+                            JS::Stencil** aStencilOut) {
+  uint8_t flags;
+  nsresult rv = aStream->Read8(&flags);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
-nsXULPrototypeScript::~nsXULPrototypeScript() { UnlinkJSObjects(); }
+  // We don't serialize mutedError-ness of scripts, which is fine as long as
+  // we only serialize system and XUL-y things. We can detect this by checking
+  // where the caller wants us to deserialize.
+  //
+  // CompilationScope() could theoretically GC, so get that out of the way
+  // before comparing to the cx global.
+  JSObject* loaderGlobal = xpc::CompilationScope();
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsSystemCaller(aCx) ||
+                     JS::CurrentGlobalOrNull(aCx) == loaderGlobal);
+
+  uint32_t size;
+  rv = aStream->Read32(&size);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  char* data;
+  rv = aStream->ReadBytes(size, &data);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // The decoded stencil shouldn't borrow from the XDR buffer.
+  MOZ_ASSERT(!aOptions.borrowBuffer);
+  auto cleanupData = MakeScopeExit([&]() { free(data); });
+
+  JS::TranscodeRange range(reinterpret_cast<uint8_t*>(data), size);
+
+  {
+    JS::TranscodeResult code;
+    RefPtr<JS::Stencil> stencil;
+    code = JS::DecodeStencil(aCx, aOptions, range, getter_AddRefs(stencil));
+    if (code != JS::TranscodeResult::Ok) {
+      if (code == JS::TranscodeResult::Throw) {
+        JS_ClearPendingException(aCx);
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+
+      MOZ_ASSERT(IsTranscodeFailureResult(code));
+      return NS_ERROR_FAILURE;
+    }
+
+    stencil.forget(aStencilOut);
+  }
+
+  return rv;
+}
+
+void nsXULPrototypeScript::FillCompileOptions(JS::CompileOptions& options) {
+  // If the script was inline, tell the JS parser to save source for
+  // Function.prototype.toSource(). If it's out of line, we retrieve the
+  // source from the files on demand.
+  options.setSourceIsLazy(mOutOfLine);
+}
 
 nsresult nsXULPrototypeScript::Serialize(
     nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
     const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
   NS_ENSURE_TRUE(aProtoDoc, NS_ERROR_UNEXPECTED);
 
   AutoJSAPI jsapi;
   if (!jsapi.Init(xpc::CompilationScope())) {
     return NS_ERROR_UNEXPECTED;
   }
 
-  NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mScriptObject,
+  NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mStencil,
                "script source still loading when serializing?!");
-  if (!mScriptObject) return NS_ERROR_FAILURE;
+  if (!mStencil) return NS_ERROR_FAILURE;
 
   // Write basic prototype data
   nsresult rv;
   rv = aStream->Write32(mLineNo);
   if (NS_FAILED(rv)) return rv;
   rv = aStream->Write32(0);  // See bug 1418294.
   if (NS_FAILED(rv)) return rv;
 
   JSContext* cx = jsapi.cx();
-  JS::Rooted<JSScript*> script(cx, mScriptObject);
   MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx));
-  return nsContentUtils::XPConnect()->WriteScript(aStream, cx, script);
+
+  JS::CompileOptions options(cx);
+  FillCompileOptions(options);
+
+  return WriteStencil(aStream, cx, options, mStencil);
 }
 
 nsresult nsXULPrototypeScript::SerializeOutOfLine(
     nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc) {
   if (!mSrcURI->SchemeIs("chrome"))
     // Don't cache scripts that don't come from chrome uris.
     return NS_ERROR_NOT_IMPLEMENTED;
 
@@ -1651,29 +1740,22 @@ nsresult nsXULPrototypeScript::Serialize
   if (NS_FAILED(tmp)) {
     rv = tmp;
   }
 
   if (NS_FAILED(rv)) cache->AbortCaching();
   return rv;
 }
 
-void nsXULPrototypeScript::FillCompileOptions(JS::CompileOptions& options) {
-  // If the script was inline, tell the JS parser to save source for
-  // Function.prototype.toSource(). If it's out of line, we retrieve the
-  // source from the files on demand.
-  options.setSourceIsLazy(mOutOfLine);
-}
-
 nsresult nsXULPrototypeScript::Deserialize(
     nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
     nsIURI* aDocumentURI,
     const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
   nsresult rv;
-  NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mScriptObject,
+  NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mStencil,
                "prototype script not well-initialized when deserializing?!");
 
   // Read basic prototype data
   rv = aStream->Read32(&mLineNo);
   if (NS_FAILED(rv)) return rv;
   uint32_t dummy;
   rv = aStream->Read32(&dummy);  // See bug 1418294.
   if (NS_FAILED(rv)) return rv;
@@ -1681,22 +1763,23 @@ nsresult nsXULPrototypeScript::Deseriali
   AutoJSAPI jsapi;
   if (!jsapi.Init(xpc::CompilationScope())) {
     return NS_ERROR_UNEXPECTED;
   }
   JSContext* cx = jsapi.cx();
 
   JS::CompileOptions options(cx);
   FillCompileOptions(options);
+  // The decoded stencil must live longer than the temporary XDR buffer.
+  options.borrowBuffer = false;
 
-  JS::Rooted<JSScript*> newScriptObject(cx);
-  rv = nsContentUtils::XPConnect()->ReadScript(aStream, cx, options,
-                                               newScriptObject.address());
+  RefPtr<JS::Stencil> newStencil;
+  rv = ReadStencil(aStream, cx, options, getter_AddRefs(newStencil));
   NS_ENSURE_SUCCESS(rv, rv);
-  Set(newScriptObject);
+  Set(newStencil);
   return NS_OK;
 }
 
 nsresult nsXULPrototypeScript::DeserializeOutOfLine(
     nsIObjectInputStream* aInput, nsXULPrototypeDocument* aProtoDoc) {
   // Keep track of failure via rv, so we can
   // AbortCaching if things look bad.
   nsresult rv = NS_OK;
@@ -1713,41 +1796,42 @@ nsresult nsXULPrototypeScript::Deseriali
       // serialization case.
       //
       // We need do this only for <script src='strres.js'> and the
       // like, i.e., out-of-line scripts that are included by several
       // different XUL documents stored in the cache file.
       useXULCache = cache->IsEnabled();
 
       if (useXULCache) {
-        JSScript* newScriptObject = cache->GetScript(mSrcURI);
-        if (newScriptObject) Set(newScriptObject);
+        RefPtr<JS::Stencil> newStencil = cache->GetStencil(mSrcURI);
+        if (newStencil) {
+          Set(newStencil);
+        }
       }
     }
 
-    if (!mScriptObject) {
+    if (!mStencil) {
       if (mSrcURI) {
         rv = cache->GetInputStream(mSrcURI, getter_AddRefs(objectInput));
       }
       // If !mSrcURI, we have an inline script. We shouldn't have
       // to do anything else in that case, I think.
 
       // We do reflect errors into rv, but our caller may want to
-      // ignore our return value, because mScriptObject will be null
+      // ignore our return value, because mStencil will be null
       // after any error, and that suffices to cause the script to
       // be reloaded (from the src= URI, if any) and recompiled.
       // We're better off slow-loading than bailing out due to a
       // error.
       if (NS_SUCCEEDED(rv))
         rv = Deserialize(objectInput, aProtoDoc, nullptr, nullptr);
 
       if (NS_SUCCEEDED(rv)) {
         if (useXULCache && mSrcURI && mSrcURI->SchemeIs("chrome")) {
-          JS::Rooted<JSScript*> script(RootingCx(), GetScriptObject());
-          cache->PutScript(mSrcURI, script);
+          cache->PutStencil(mSrcURI, GetStencil());
         }
         cache->FinishInputStream(mSrcURI);
       } else {
         // If mSrcURI is not in the cache,
         // rv will be NS_ERROR_NOT_AVAILABLE and we'll try to
         // update the cache file to hold a serialization of
         // this script, once it has finished loading.
         if (rv != NS_ERROR_NOT_AVAILABLE) cache->AbortCaching();
@@ -1798,41 +1882,41 @@ class NotifyOffThreadScriptCompletedRunn
 StaticAutoPtr<nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>>
     NotifyOffThreadScriptCompletedRunnable::sReceivers;
 bool NotifyOffThreadScriptCompletedRunnable::sSetupClearOnShutdown = false;
 
 NS_IMETHODIMP
 NotifyOffThreadScriptCompletedRunnable::Run() {
   MOZ_ASSERT(NS_IsMainThread());
 
-  JS::Rooted<JSScript*> script(RootingCx());
+  RefPtr<JS::Stencil> stencil;
   {
     AutoJSAPI jsapi;
     if (!jsapi.Init(xpc::CompilationScope())) {
       // Now what?  I guess we just leak... this should probably never
       // happen.
       return NS_ERROR_UNEXPECTED;
     }
     JSContext* cx = jsapi.cx();
-    script = JS::FinishOffThreadScript(cx, mToken);
+    stencil = JS::FinishOffThreadCompileToStencil(cx, mToken);
   }
 
   if (!sReceivers) {
     // We've already shut down.
     return NS_OK;
   }
 
   auto index = sReceivers->IndexOf(mReceiver);
   MOZ_RELEASE_ASSERT(index != sReceivers->NoIndex);
   nsCOMPtr<nsIOffThreadScriptReceiver> receiver =
       std::move((*sReceivers)[index]);
   sReceivers->RemoveElementAt(index);
 
-  return receiver->OnScriptCompileComplete(script,
-                                           script ? NS_OK : NS_ERROR_FAILURE);
+  return receiver->OnScriptCompileComplete(stencil,
+                                           stencil ? NS_OK : NS_ERROR_FAILURE);
 }
 
 static void OffThreadScriptReceiverCallback(JS::OffThreadToken* aToken,
                                             void* aCallbackData) {
   // Be careful not to adjust the refcount on the receiver, as this callback
   // may be invoked off the main thread.
   nsIOffThreadScriptReceiver* aReceiver =
       static_cast<nsIOffThreadScriptReceiver*>(aCallbackData);
@@ -1873,48 +1957,53 @@ nsresult nsXULPrototypeScript::Compile(
   JS::CompileOptions options(cx);
   FillCompileOptions(options);
   options.setIntroductionType(mOutOfLine ? "srcScript" : "inlineScript")
       .setFileAndLine(urlspec.get(), mOutOfLine ? 1 : aLineNo);
 
   JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
 
   if (aOffThreadReceiver && JS::CanCompileOffThread(cx, options, aTextLength)) {
-    if (!JS::CompileOffThread(cx, options, srcBuf,
-                              OffThreadScriptReceiverCallback,
-                              static_cast<void*>(aOffThreadReceiver))) {
+    if (!JS::CompileToStencilOffThread(
+            cx, options, srcBuf, OffThreadScriptReceiverCallback,
+            static_cast<void*>(aOffThreadReceiver))) {
+      JS_ClearPendingException(cx);
       return NS_ERROR_OUT_OF_MEMORY;
     }
     NotifyOffThreadScriptCompletedRunnable::NoteReceiver(aOffThreadReceiver);
   } else {
-    JS::Rooted<JSScript*> script(cx, JS::Compile(cx, options, srcBuf));
-    if (!script) {
+    RefPtr<JS::Stencil> stencil =
+        JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+    if (!stencil) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
-    Set(script);
+    Set(stencil);
   }
   return NS_OK;
 }
 
-void nsXULPrototypeScript::UnlinkJSObjects() {
-  if (mScriptObject) {
-    mozilla::DropJSObjects(this);
+nsresult nsXULPrototypeScript::InstantiateScript(
+    JSContext* aCx, JS::MutableHandleScript aScript) {
+  MOZ_ASSERT(mStencil);
+
+  JS::CompileOptions options(aCx);
+  FillCompileOptions(options);
+  // We don't need setIntroductionType and setFileAndLine here, unlike
+  // nsXULPrototypeScript::Compile.
+  // mStencil already contains the information.
+  aScript.set(JS::InstantiateGlobalStencil(aCx, options, mStencil));
+  if (!aScript) {
+    JS_ClearPendingException(aCx);
+    return NS_ERROR_OUT_OF_MEMORY;
   }
+
+  return NS_OK;
 }
 
-void nsXULPrototypeScript::Set(JSScript* aObject) {
-  MOZ_ASSERT(!mScriptObject, "Leaking script object.");
-  if (!aObject) {
-    mScriptObject = nullptr;
-    return;
-  }
-
-  mScriptObject = aObject;
-  mozilla::HoldJSObjects(this);
-}
+void nsXULPrototypeScript::Set(JS::Stencil* aStencil) { mStencil = aStencil; }
 
 //----------------------------------------------------------------------
 //
 // nsXULPrototypeText
 //
 
 nsresult nsXULPrototypeText::Serialize(
     nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
--- a/dom/xul/nsXULElement.h
+++ b/dom/xul/nsXULElement.h
@@ -10,19 +10,21 @@
 */
 
 #ifndef nsXULElement_h__
 #define nsXULElement_h__
 
 #include <stdint.h>
 #include <stdio.h>
 #include "ErrorList.h"
+#include "js/experimental/JSStencil.h"
 #include "js/RootingAPI.h"
 #include "js/SourceText.h"
 #include "js/TracingAPI.h"
+#include "js/TypeDecls.h"
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/dom/DOMString.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/FragmentOrElement.h"
@@ -43,17 +45,16 @@
 #include "nsLiteralString.h"
 #include "nsString.h"
 #include "nsStyledElement.h"
 #include "nsTArray.h"
 #include "nsTLiteralString.h"
 #include "nscore.h"
 
 class JSObject;
-class JSScript;
 class nsAttrValueOrString;
 class nsIControllers;
 class nsIObjectInputStream;
 class nsIObjectOutputStream;
 class nsIOffThreadScriptReceiver;
 class nsIPrincipal;
 class nsIURI;
 class nsXULPrototypeDocument;
@@ -190,36 +191,33 @@ class nsXULPrototypeElement : public nsX
       nsIURI* aDocumentURI,
       const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) override;
 
   nsresult SetAttrAt(uint32_t aPos, const nsAString& aValue,
                      nsIURI* aDocumentURI);
 
   void Unlink();
 
-  // Trace all scripts held by this element and its children.
-  void TraceAllScripts(JSTracer* aTrc);
-
   nsPrototypeArray mChildren;
 
   RefPtr<mozilla::dom::NodeInfo> mNodeInfo;
 
   uint32_t mHasIdAttribute : 1;
   uint32_t mHasClassAttribute : 1;
   uint32_t mHasStyleAttribute : 1;
   nsTArray<nsXULPrototypeAttribute> mAttributes;  // [OWNER]
   RefPtr<nsAtom> mIsAtom;
 };
 
 class nsXULPrototypeScript : public nsXULPrototypeNode {
  public:
   explicit nsXULPrototypeScript(uint32_t aLineNo);
 
  private:
-  virtual ~nsXULPrototypeScript();
+  virtual ~nsXULPrototypeScript() = default;
 
   void FillCompileOptions(JS::CompileOptions& options);
 
  public:
   virtual nsresult Serialize(
       nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
       const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) override;
   nsresult SerializeOutOfLine(nsIObjectOutputStream* aStream,
@@ -231,45 +229,32 @@ class nsXULPrototypeScript : public nsXU
   nsresult DeserializeOutOfLine(nsIObjectInputStream* aInput,
                                 nsXULPrototypeDocument* aProtoDoc);
 
   nsresult Compile(const char16_t* aText, size_t aTextLength,
                    JS::SourceOwnership aOwnership, nsIURI* aURI,
                    uint32_t aLineNo, mozilla::dom::Document* aDocument,
                    nsIOffThreadScriptReceiver* aOffThreadReceiver = nullptr);
 
-  void UnlinkJSObjects();
-
-  void Set(JSScript* aObject);
+  void Set(JS::Stencil* aStencil);
 
-  bool HasScriptObject() {
-    // Conversion to bool doesn't trigger mScriptObject's read barrier.
-    return mScriptObject;
-  }
+  bool HasStencil() { return mStencil; }
 
-  JSScript* GetScriptObject() { return mScriptObject; }
-
-  void TraceScriptObject(JSTracer* aTrc) {
-    JS::TraceEdge(aTrc, &mScriptObject, "active window XUL prototype script");
-  }
+  JS::Stencil* GetStencil() { return mStencil.get(); }
 
-  void Trace(const TraceCallbacks& aCallbacks, void* aClosure) {
-    if (mScriptObject) {
-      aCallbacks.Trace(&mScriptObject, "mScriptObject", aClosure);
-    }
-  }
+  nsresult InstantiateScript(JSContext* aCx, JS::MutableHandleScript aScript);
 
   nsCOMPtr<nsIURI> mSrcURI;
   uint32_t mLineNo;
   bool mSrcLoading;
   bool mOutOfLine;
   mozilla::dom::PrototypeDocumentContentSink*
       mSrcLoadWaiters;  // [OWNER] but not COMPtr
  private:
-  JS::Heap<JSScript*> mScriptObject;
+  RefPtr<JS::Stencil> mStencil;
 };
 
 class nsXULPrototypeText : public nsXULPrototypeNode {
  public:
   nsXULPrototypeText() : nsXULPrototypeNode(eType_Text) {}
 
  private:
   virtual ~nsXULPrototypeText() = default;
--- a/dom/xul/nsXULPrototypeCache.cpp
+++ b/dom/xul/nsXULPrototypeCache.cpp
@@ -15,23 +15,25 @@
 #include "nsIMemoryReporter.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIObserverService.h"
 #include "nsIStorageStream.h"
 
 #include "nsAppDirectoryServiceDefs.h"
 
+#include "js/experimental/JSStencil.h"
 #include "js/TracingAPI.h"
 
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/scache/StartupCache.h"
 #include "mozilla/scache/StartupCacheUtils.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/RefPtr.h"
 #include "mozilla/intl/LocaleService.h"
 
 using namespace mozilla;
 using namespace mozilla::scache;
 using mozilla::intl::LocaleService;
 
 #define XUL_CACHE_DISABLED_DEFAULT false
 
@@ -68,18 +70,16 @@ static void DisableXULCacheChangedCallba
 }
 
 //----------------------------------------------------------------------
 
 nsXULPrototypeCache* nsXULPrototypeCache::sInstance = nullptr;
 
 nsXULPrototypeCache::nsXULPrototypeCache() = default;
 
-nsXULPrototypeCache::~nsXULPrototypeCache() { FlushScripts(); }
-
 NS_IMPL_ISUPPORTS(nsXULPrototypeCache, nsIObserver)
 
 /* static */
 nsXULPrototypeCache* nsXULPrototypeCache::GetInstance() {
   if (!sInstance) {
     NS_ADDREF(sInstance = new nsXULPrototypeCache());
 
     UpdategDisableXULCache();
@@ -160,48 +160,45 @@ nsresult nsXULPrototypeCache::PutPrototy
   NS_GetURIWithoutRef(aDocument->GetURI(), getter_AddRefs(uri));
 
   // Put() releases any old value
   mPrototypeTable.InsertOrUpdate(uri, RefPtr{aDocument});
 
   return NS_OK;
 }
 
-JSScript* nsXULPrototypeCache::GetScript(nsIURI* aURI) {
-  if (auto* entry = mScriptTable.GetEntry(aURI)) {
-    return entry->mScript.get();
+JS::Stencil* nsXULPrototypeCache::GetStencil(nsIURI* aURI) {
+  if (auto* entry = mStencilTable.GetEntry(aURI)) {
+    return entry->mStencil;
   }
   return nullptr;
 }
 
-nsresult nsXULPrototypeCache::PutScript(nsIURI* aURI,
-                                        JS::Handle<JSScript*> aScriptObject) {
-  MOZ_ASSERT(aScriptObject, "Need a non-NULL script");
+nsresult nsXULPrototypeCache::PutStencil(nsIURI* aURI, JS::Stencil* aStencil) {
+  MOZ_ASSERT(aStencil, "Need a non-NULL stencil");
 
 #ifdef DEBUG_BUG_392650
-  if (mScriptTable.Get(aURI)) {
+  if (mStencilTable.Get(aURI)) {
     nsAutoCString scriptName;
     aURI->GetSpec(scriptName);
     nsAutoCString message("Loaded script ");
     message += scriptName;
     message += " twice (bug 392650)";
     NS_WARNING(message.get());
   }
 #endif
 
-  mScriptTable.PutEntry(aURI)->mScript.set(aScriptObject);
+  mStencilTable.PutEntry(aURI)->mStencil = aStencil;
 
   return NS_OK;
 }
 
-void nsXULPrototypeCache::FlushScripts() { mScriptTable.Clear(); }
-
 void nsXULPrototypeCache::Flush() {
   mPrototypeTable.Clear();
-  mScriptTable.Clear();
+  mStencilTable.Clear();
 }
 
 bool nsXULPrototypeCache::IsEnabled() { return !gDisableXULCache; }
 
 void nsXULPrototypeCache::AbortCaching() {
 #ifdef DEBUG_brendan
   NS_BREAK();
 #endif
@@ -456,22 +453,16 @@ nsresult nsXULPrototypeCache::BeginCachi
 }
 
 void nsXULPrototypeCache::MarkInCCGeneration(uint32_t aGeneration) {
   for (const auto& prototype : mPrototypeTable.Values()) {
     prototype->MarkInCCGeneration(aGeneration);
   }
 }
 
-void nsXULPrototypeCache::MarkInGC(JSTracer* aTrc) {
-  for (auto& entry : mScriptTable) {
-    JS::TraceEdge(aTrc, &entry.mScript, "nsXULPrototypeCache script");
-  }
-}
-
 MOZ_DEFINE_MALLOC_SIZE_OF(CacheMallocSizeOf)
 
 static void ReportSize(const nsCString& aPath, size_t aAmount,
                        const nsCString& aDescription,
                        nsIHandleReportCallback* aHandleReport,
                        nsISupports* aData) {
   nsAutoCString path("explicit/xul-prototype-cache/");
   path += aPath;
@@ -491,18 +482,18 @@ void nsXULPrototypeCache::CollectMemoryR
   size_t other = mallocSizeOf(sInstance);
 
 #define REPORT_SIZE(_path, _amount, _desc) \
   ReportSize(_path, _amount, nsLiteralCString(_desc), aHandleReport, aData)
 
   other += sInstance->mPrototypeTable.ShallowSizeOfExcludingThis(mallocSizeOf);
   // TODO Report content in mPrototypeTable?
 
-  other += sInstance->mScriptTable.ShallowSizeOfExcludingThis(mallocSizeOf);
-  // TODO Report content inside mScriptTable?
+  other += sInstance->mStencilTable.ShallowSizeOfExcludingThis(mallocSizeOf);
+  // TODO Report content inside mStencilTable?
 
   other +=
       sInstance->mStartupCacheURITable.ShallowSizeOfExcludingThis(mallocSizeOf);
 
   other +=
       sInstance->mOutputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf);
   other +=
       sInstance->mInputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf);
--- a/dom/xul/nsXULPrototypeCache.h
+++ b/dom/xul/nsXULPrototypeCache.h
@@ -11,16 +11,18 @@
 #include "nsIObserver.h"
 #include "nsInterfaceHashtable.h"
 #include "nsRefPtrHashtable.h"
 #include "nsURIHashKey.h"
 #include "nsXULPrototypeDocument.h"
 #include "nsIStorageStream.h"
 
 #include "mozilla/scache/StartupCache.h"
+#include "js/experimental/JSStencil.h"
+#include "mozilla/RefPtr.h"
 
 class nsIHandleReportCallback;
 namespace mozilla {
 class StyleSheet;
 }  // namespace mozilla
 
 /**
  * The XUL prototype cache can be used to store and retrieve shared data for
@@ -51,18 +53,18 @@ class nsXULPrototypeCache : public nsIOb
   void Flush();
 
   // The following methods are used to put and retrive various items into and
   // from the cache.
 
   nsXULPrototypeDocument* GetPrototype(nsIURI* aURI);
   nsresult PutPrototype(nsXULPrototypeDocument* aDocument);
 
-  JSScript* GetScript(nsIURI* aURI);
-  nsresult PutScript(nsIURI* aURI, JS::Handle<JSScript*> aScriptObject);
+  JS::Stencil* GetStencil(nsIURI* aURI);
+  nsresult PutStencil(nsIURI* aURI, JS::Stencil* aStencil);
 
   /**
    * Write the XUL prototype document to a cache file. The proto must be
    * fully loaded.
    */
   nsresult WritePrototype(nsXULPrototypeDocument* aPrototypeDocument);
 
   /**
@@ -76,47 +78,41 @@ class nsXULPrototypeCache : public nsIOb
   nsresult HasData(nsIURI* aURI, bool* exists);
 
   static nsXULPrototypeCache* GetInstance();
   static nsXULPrototypeCache* MaybeGetInstance() { return sInstance; }
 
   static void ReleaseGlobals() { NS_IF_RELEASE(sInstance); }
 
   void MarkInCCGeneration(uint32_t aGeneration);
-  void MarkInGC(JSTracer* aTrc);
-  void FlushScripts();
 
   static void CollectMemoryReports(nsIHandleReportCallback* aHandleReport,
                                    nsISupports* aData);
 
  protected:
   friend nsresult NS_NewXULPrototypeCache(nsISupports* aOuter, REFNSIID aIID,
                                           void** aResult);
 
   nsXULPrototypeCache();
-  virtual ~nsXULPrototypeCache();
+  virtual ~nsXULPrototypeCache() = default;
 
   static nsXULPrototypeCache* sInstance;
 
   nsRefPtrHashtable<nsURIHashKey, nsXULPrototypeDocument>
       mPrototypeTable;  // owns the prototypes
 
-  class ScriptHashKey : public nsURIHashKey {
+  class StencilHashKey : public nsURIHashKey {
    public:
-    explicit ScriptHashKey(const nsIURI* aKey) : nsURIHashKey(aKey) {}
-    ScriptHashKey(ScriptHashKey&&) = default;
+    explicit StencilHashKey(const nsIURI* aKey) : nsURIHashKey(aKey) {}
+    StencilHashKey(StencilHashKey&&) = default;
 
-    // Mark ALLOW_MEMMOVE as false, as hash tables containing JS:Heap<T>
-    // values must be copied rather than memmoved.
-    enum { ALLOW_MEMMOVE = false };
-
-    JS::Heap<JSScript*> mScript;
+    RefPtr<JS::Stencil> mStencil;
   };
 
-  nsTHashtable<ScriptHashKey> mScriptTable;
+  nsTHashtable<StencilHashKey> mStencilTable;
 
   // URIs already written to the startup cache, to prevent double-caching.
   nsTHashtable<nsURIHashKey> mStartupCacheURITable;
 
   nsInterfaceHashtable<nsURIHashKey, nsIStorageStream> mOutputStreamTable;
   nsInterfaceHashtable<nsURIHashKey, nsIObjectInputStream> mInputStreamTable;
 
   // Bootstrap caching service
--- a/dom/xul/nsXULPrototypeDocument.cpp
+++ b/dom/xul/nsXULPrototypeDocument.cpp
@@ -37,21 +37,17 @@ using mozilla::dom::DestroyProtoAndIface
 uint32_t nsXULPrototypeDocument::gRefCnt;
 
 //----------------------------------------------------------------------
 //
 // ctors, dtors, n' stuff
 //
 
 nsXULPrototypeDocument::nsXULPrototypeDocument()
-    : mRoot(nullptr),
-      mLoaded(false),
-      mCCGeneration(0),
-      mGCNumber(0),
-      mWasL10nCached(false) {
+    : mRoot(nullptr), mLoaded(false), mCCGeneration(0), mWasL10nCached(false) {
   ++gRefCnt;
 }
 
 nsresult nsXULPrototypeDocument::Init() {
   mNodeInfoManager = new nsNodeInfoManager();
   return mNodeInfoManager->Init(nullptr);
 }
 
@@ -422,31 +418,16 @@ nsresult nsXULPrototypeDocument::NotifyL
     --i;
     mPrototypeWaiters[i]();
   }
   mPrototypeWaiters.Clear();
 
   return NS_OK;
 }
 
-void nsXULPrototypeDocument::TraceProtos(JSTracer* aTrc) {
-  // Only trace the protos once per GC if we are marking.
-  if (aTrc->isMarkingTracer()) {
-    uint32_t currentGCNumber = aTrc->gcNumberForMarking();
-    if (mGCNumber == currentGCNumber) {
-      return;
-    }
-    mGCNumber = currentGCNumber;
-  }
-
-  if (mRoot) {
-    mRoot->TraceAllScripts(aTrc);
-  }
-}
-
 void nsXULPrototypeDocument::SetIsL10nCached(bool aIsCached) {
   mWasL10nCached = aIsCached;
 }
 
 void nsXULPrototypeDocument::RebuildPrototypeFromElement(
     nsXULPrototypeElement* aPrototype, Element* aElement, bool aDeep) {
   aPrototype->mHasIdAttribute = aElement->HasID();
   aPrototype->mHasClassAttribute = aElement->MayHaveClass();
--- a/dom/xul/nsXULPrototypeDocument.h
+++ b/dom/xul/nsXULPrototypeDocument.h
@@ -89,18 +89,16 @@ class nsXULPrototypeDocument final : pub
   nsresult NotifyLoadDone();
 
   nsNodeInfoManager* GetNodeInfoManager();
 
   void MarkInCCGeneration(uint32_t aCCGeneration);
 
   NS_DECL_CYCLE_COLLECTION_CLASS(nsXULPrototypeDocument)
 
-  void TraceProtos(JSTracer* aTrc);
-
   bool WasL10nCached() { return mWasL10nCached; };
 
   void SetIsL10nCached(bool aIsCached);
   void RebuildPrototypeFromElement(nsXULPrototypeElement* aPrototype,
                                    mozilla::dom::Element* aElement, bool aDeep);
   void RebuildL10nPrototype(mozilla::dom::Element* aElement, bool aDeep);
 
  protected:
@@ -109,17 +107,16 @@ class nsXULPrototypeDocument final : pub
   nsTArray<RefPtr<nsXULPrototypePI> > mProcessingInstructions;
 
   bool mLoaded;
   nsTArray<Callback> mPrototypeWaiters;
 
   RefPtr<nsNodeInfoManager> mNodeInfoManager;
 
   uint32_t mCCGeneration;
-  uint32_t mGCNumber;
 
   nsXULPrototypeDocument();
   virtual ~nsXULPrototypeDocument();
   nsresult Init();
 
   friend NS_IMETHODIMP NS_NewXULPrototypeDocument(
       nsXULPrototypeDocument** aResult);
 
--- a/js/xpconnect/idl/nsIXPConnect.idl
+++ b/js/xpconnect/idl/nsIXPConnect.idl
@@ -20,17 +20,16 @@ class nsWrapperCache;
 %}
 
 /***************************************************************************/
 
 // NB: jsval and jsid are declared in nsrootidl.idl
 
 [ptr] native JSContextPtr(JSContext);
 [ptr] native JSObjectPtr(JSObject);
-[ptr] native JSScriptPtr(JSScript);
 [ptr] native nsWrapperCachePtr(nsWrapperCache);
       native JSHandleId(JS::Handle<jsid>);
 [ref] native const_JSReadOnlyCompileOptionsRef(const JS::ReadOnlyCompileOptions);
 
 /***************************************************************************/
 
 // forward declarations...
 interface nsIPrincipal;
@@ -254,19 +253,9 @@ interface nsIXPConnect : nsISupports
      * @return The result of the evaluation as a jsval. If the caller
      *         intends to use the return value from this call the caller
      *         is responsible for rooting the jsval before making a call
      *         to this method.
      */
     [noscript] jsval evalInSandboxObject(in AString source, in string filename,
                                          in JSContextPtr cx,
                                          in JSObjectPtr sandbox);
-
-    [noscript] void writeScript(in nsIObjectOutputStream aStream,
-                                in JSContextPtr aJSContext,
-                                in JSScriptPtr aJSScript);
-
-    [noscript] JSScriptPtr readScript(in nsIObjectInputStream aStream,
-                                      in JSContextPtr aJSContext,
-                                      in const_JSReadOnlyCompileOptionsRef aOptions);
-
-    [infallible] readonly attribute boolean isShuttingDown;
 };
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -690,17 +690,17 @@ bool XPCJSRuntime::UsefulToMergeZones() 
 void XPCJSRuntime::TraceNativeBlackRoots(JSTracer* trc) {
   if (CycleCollectedJSContext* ccx = GetContext()) {
     const auto* cx = static_cast<const XPCJSContext*>(ccx);
     if (AutoMarkingPtr* roots = cx->mAutoRoots) {
       roots->TraceJSAll(trc);
     }
   }
 
-  dom::TraceBlackJS(trc, nsIXPConnect::XPConnect()->GetIsShuttingDown());
+  dom::TraceBlackJS(trc);
 }
 
 void XPCJSRuntime::TraceAdditionalNativeGrayRoots(JSTracer* trc) {
   XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(this, trc);
 
   for (XPCRootSetElem* e = mVariantRoots; e; e = e->GetNextRoot()) {
     static_cast<XPCTraceableVariant*>(e)->TraceJS(trc);
   }
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -65,17 +65,17 @@ nsIScriptSecurityManager* nsXPConnect::g
 nsIPrincipal* nsXPConnect::gSystemPrincipal = nullptr;
 
 const char XPC_EXCEPTION_CONTRACTID[] = "@mozilla.org/js/xpc/Exception;1";
 const char XPC_CONSOLE_CONTRACTID[] = "@mozilla.org/consoleservice;1";
 const char XPC_SCRIPT_ERROR_CONTRACTID[] = "@mozilla.org/scripterror;1";
 
 /***************************************************************************/
 
-nsXPConnect::nsXPConnect() : mShuttingDown(false) {
+nsXPConnect::nsXPConnect() {
 #ifdef MOZ_GECKO_PROFILER
   JS::SetProfilingThreadCallbacks(profiler_register_thread,
                                   profiler_unregister_thread);
 #endif
 }
 
 // static
 void nsXPConnect::InitJSContext() {
@@ -106,17 +106,16 @@ nsXPConnect::~nsXPConnect() {
   // In order to clean up everything properly, we need to GC twice: once now,
   // to clean anything that can go away on its own (like the Junk Scope, which
   // we unrooted above), and once after forcing a bunch of shutdown in
   // XPConnect, to clean the stuff we forcibly disconnected. The forced
   // shutdown code defaults to leaking in a number of situations, so we can't
   // get by with only the second GC. :-(
   mRuntime->GarbageCollect(JS::GCReason::XPCONNECT_SHUTDOWN);
 
-  mShuttingDown = true;
   XPCWrappedNativeScope::SystemIsBeingShutDown();
   mRuntime->SystemIsBeingShutDown();
 
   // The above causes us to clean up a bunch of XPConnect data structures,
   // after which point we need to GC to clean everything up. We need to do
   // this before deleting the XPCJSContext, because doing so destroys the
   // maps that our finalize callback depends on.
   mRuntime->GarbageCollect(JS::GCReason::XPCONNECT_SHUTDOWN);
@@ -914,120 +913,16 @@ void SetLocationForGlobal(JSObject* glob
 
 void SetLocationForGlobal(JSObject* global, nsIURI* locationURI) {
   MOZ_ASSERT(global);
   RealmPrivate::Get(global)->SetLocationURI(locationURI);
 }
 
 }  // namespace xpc
 
-NS_IMETHODIMP
-nsXPConnect::WriteScript(nsIObjectOutputStream* stream, JSContext* cx,
-                         JSScript* scriptArg) {
-  RootedScript script(cx, scriptArg);
-
-  uint8_t flags = 0;  // We don't have flags anymore.
-  nsresult rv = stream->Write8(flags);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  TranscodeBuffer buffer;
-  TranscodeResult code;
-  code = EncodeScript(cx, buffer, script);
-
-  if (code != TranscodeResult::Ok) {
-    if (code == TranscodeResult::Throw) {
-      JS_ClearPendingException(cx);
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    MOZ_ASSERT(IsTranscodeFailureResult(code));
-    return NS_ERROR_FAILURE;
-  }
-
-  size_t size = buffer.length();
-  if (size > UINT32_MAX) {
-    return NS_ERROR_FAILURE;
-  }
-  rv = stream->Write32(size);
-  if (NS_SUCCEEDED(rv)) {
-    // Ideally we could just pass "buffer" here.  See bug 1566574.
-    rv = stream->WriteBytes(Span(buffer.begin(), size));
-  }
-
-  return rv;
-}
-
-NS_IMETHODIMP
-nsXPConnect::ReadScript(nsIObjectInputStream* stream, JSContext* cx,
-                        const JS::ReadOnlyCompileOptions& options,
-                        JSScript** scriptp) {
-  uint8_t flags;
-  nsresult rv = stream->Read8(&flags);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  // We don't serialize mutedError-ness of scripts, which is fine as long as
-  // we only serialize system and XUL-y things. We can detect this by checking
-  // where the caller wants us to deserialize.
-  //
-  // CompilationScope() could theoretically GC, so get that out of the way
-  // before comparing to the cx global.
-  JSObject* loaderGlobal = xpc::CompilationScope();
-  MOZ_RELEASE_ASSERT(nsContentUtils::IsSystemCaller(cx) ||
-                     CurrentGlobalOrNull(cx) == loaderGlobal);
-
-  uint32_t size;
-  rv = stream->Read32(&size);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  char* data;
-  rv = stream->ReadBytes(size, &data);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  TranscodeBuffer buffer;
-  buffer.replaceRawBuffer(reinterpret_cast<uint8_t*>(data), size);
-
-  {
-    TranscodeResult code;
-    Rooted<JSScript*> script(cx);
-    code = DecodeScript(cx, options, buffer, &script);
-    if (code == TranscodeResult::Ok) {
-      *scriptp = script.get();
-    } else {
-      if (code == TranscodeResult::Throw) {
-        JS_ClearPendingException(cx);
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-
-      MOZ_ASSERT(IsTranscodeFailureResult(code));
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  return rv;
-}
-
-NS_IMETHODIMP
-nsXPConnect::GetIsShuttingDown(bool* aIsShuttingDown) {
-  if (!aIsShuttingDown) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  *aIsShuttingDown = mShuttingDown;
-
-  return NS_OK;
-}
-
 // static
 nsIXPConnect* nsIXPConnect::XPConnect() {
   // Do a release-mode assert that we're not doing anything significant in
   // XPConnect off the main thread. If you're an extension developer hitting
   // this, you need to change your code. See bug 716167.
   if (!MOZ_LIKELY(NS_IsMainThread())) {
     MOZ_CRASH();
   }
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -244,17 +244,16 @@ class nsXPConnect final : public nsIXPCo
 
  private:
   // Singleton instance
   static nsXPConnect* gSelf;
   static bool gOnceAliveNowDead;
 
   XPCJSContext* mContext = nullptr;
   XPCJSRuntime* mRuntime = nullptr;
-  bool mShuttingDown;
 
   friend class nsIXPConnect;
 
  public:
   static nsIScriptSecurityManager* gScriptSecurityManager;
   static nsIPrincipal* gSystemPrincipal;
 };