Bug 1209263 - Allow embedders to tell SpiderMonkey how to structured clone principals; r=bz
authorNick Fitzgerald <fitzgen@gmail.com>
Fri, 02 Oct 2015 16:44:00 +0200
changeset 298742 f8547896d5823f66f61430cdaf5d7104cb241604
parent 298741 9a0c443ed6f3488bfdd67669e5b3cdb767a45e8e
child 298743 b958e25b1ecf218ca7ddb94357e83878ac9c753b
push id6117
push usermartin.thomson@gmail.com
push dateMon, 05 Oct 2015 18:11:55 +0000
reviewersbz
bugs1209263
milestone44.0a1
Bug 1209263 - Allow embedders to tell SpiderMonkey how to structured clone principals; r=bz
caps/nsJSPrincipals.cpp
caps/nsJSPrincipals.h
dom/base/StructuredCloneHolder.cpp
dom/workers/Principal.cpp
js/public/Principals.h
js/src/jsapi-tests/testCloneScript.cpp
js/src/jsapi-tests/tests.h
js/src/jsapi.cpp
js/src/shell/js.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/SavedFrame.h
--- a/caps/nsJSPrincipals.cpp
+++ b/caps/nsJSPrincipals.cpp
@@ -10,18 +10,23 @@
 #include "nsJSPrincipals.h"
 #include "plstr.h"
 #include "nsXPIDLString.h"
 #include "nsCOMPtr.h"
 #include "nsIServiceManager.h"
 #include "nsMemory.h"
 #include "nsStringBuffer.h"
 
+#include "mozilla/dom/StructuredCloneTags.h"
 // for mozilla::dom::workers::kJSPrincipalsDebugToken
 #include "mozilla/dom/workers/Workers.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::ipc;
 
 NS_IMETHODIMP_(MozExternalRefCountType)
 nsJSPrincipals::AddRef()
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_PRECONDITION(int32_t(refcount) >= 0, "illegal refcnt");
   nsrefcnt count = ++refcount;
   NS_LOG_ADDREF(this, count, "nsJSPrincipals", sizeof(*this));
@@ -80,19 +85,119 @@ nsJSPrincipals::Destroy(JSPrincipals *js
 // Defined here so one can do principals->dump() in the debugger
 JS_EXPORT_API(void)
 JSPrincipals::dump()
 {
     if (debugToken == nsJSPrincipals::DEBUG_TOKEN) {
       nsAutoCString str;
       static_cast<nsJSPrincipals *>(this)->GetScriptLocation(str);
       fprintf(stderr, "nsIPrincipal (%p) = %s\n", static_cast<void*>(this), str.get());
-    } else if (debugToken == mozilla::dom::workers::kJSPrincipalsDebugToken) {
+    } else if (debugToken == dom::workers::kJSPrincipalsDebugToken) {
         fprintf(stderr, "Web Worker principal singleton (%p)\n", this);
     } else {
         fprintf(stderr,
                 "!!! JSPrincipals (%p) is not nsJSPrincipals instance - bad token: "
                 "actual=0x%x expected=0x%x\n",
                 this, unsigned(debugToken), unsigned(nsJSPrincipals::DEBUG_TOKEN));
     }
 }
 
-#endif 
+#endif
+
+/* static */ bool
+nsJSPrincipals::ReadPrincipals(JSContext* aCx, JSStructuredCloneReader* aReader,
+                               JSPrincipals** aOutPrincipals)
+{
+    uint32_t tag;
+    uint32_t unused;
+    if (!JS_ReadUint32Pair(aReader, &tag, &unused)) {
+        return false;
+    }
+
+    if (!(tag == SCTAG_DOM_NULL_PRINCIPAL ||
+          tag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
+          tag == SCTAG_DOM_CONTENT_PRINCIPAL)) {
+        xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+        return false;
+    }
+
+    return ReadKnownPrincipalType(aCx, aReader, tag, aOutPrincipals);
+}
+
+/* static */ bool
+nsJSPrincipals::ReadKnownPrincipalType(JSContext* aCx,
+                                       JSStructuredCloneReader* aReader,
+                                       uint32_t aTag,
+                                       JSPrincipals** aOutPrincipals)
+{
+    MOZ_ASSERT(aTag == SCTAG_DOM_NULL_PRINCIPAL ||
+               aTag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
+               aTag == SCTAG_DOM_CONTENT_PRINCIPAL);
+
+    if (NS_WARN_IF(!NS_IsMainThread())) {
+        xpc::Throw(aCx, NS_ERROR_UNCATCHABLE_EXCEPTION);
+        return false;
+    }
+
+    PrincipalInfo info;
+    if (aTag == SCTAG_DOM_SYSTEM_PRINCIPAL) {
+        info = SystemPrincipalInfo();
+    } else if (aTag == SCTAG_DOM_NULL_PRINCIPAL) {
+        info = NullPrincipalInfo();
+    } else {
+        uint32_t suffixLength, specLength;
+        if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) {
+            return false;
+        }
+
+        nsAutoCString suffix;
+        suffix.SetLength(suffixLength);
+        if (!JS_ReadBytes(aReader, suffix.BeginWriting(), suffixLength)) {
+            return false;
+        }
+
+        nsAutoCString spec;
+        spec.SetLength(specLength);
+        if (!JS_ReadBytes(aReader, spec.BeginWriting(), specLength)) {
+            return false;
+        }
+
+        OriginAttributes attrs;
+        attrs.PopulateFromSuffix(suffix);
+        info = ContentPrincipalInfo(attrs, spec);
+    }
+
+    nsresult rv;
+    nsCOMPtr<nsIPrincipal> prin = PrincipalInfoToPrincipal(info, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+        return false;
+    }
+
+    *aOutPrincipals = get(prin.forget().take());
+    return true;
+}
+
+bool
+nsJSPrincipals::write(JSContext* aCx, JSStructuredCloneWriter* aWriter)
+{
+    PrincipalInfo info;
+    if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(this, &info)))) {
+        xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+        return false;
+    }
+
+    if (info.type() == PrincipalInfo::TNullPrincipalInfo) {
+        return JS_WriteUint32Pair(aWriter, SCTAG_DOM_NULL_PRINCIPAL, 0);
+    }
+    if (info.type() == PrincipalInfo::TSystemPrincipalInfo) {
+        return JS_WriteUint32Pair(aWriter, SCTAG_DOM_SYSTEM_PRINCIPAL, 0);
+    }
+
+    MOZ_ASSERT(info.type() == PrincipalInfo::TContentPrincipalInfo);
+    const ContentPrincipalInfo& cInfo = info;
+    nsAutoCString suffix;
+    cInfo.attrs().CreateSuffix(suffix);
+    return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CONTENT_PRINCIPAL, 0) &&
+           JS_WriteUint32Pair(aWriter, suffix.Length(), cInfo.spec().Length()) &&
+           JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) &&
+           JS_WriteBytes(aWriter, cInfo.spec().get(), cInfo.spec().Length());
+}
--- a/caps/nsJSPrincipals.h
+++ b/caps/nsJSPrincipals.h
@@ -11,16 +11,27 @@
 
 class nsJSPrincipals : public nsIPrincipal, public JSPrincipals
 {
 public:
   /* SpiderMonkey security callbacks. */
   static bool Subsume(JSPrincipals *jsprin, JSPrincipals *other);
   static void Destroy(JSPrincipals *jsprin);
 
+  /* JSReadPrincipalsOp for nsJSPrincipals */
+  static bool ReadPrincipals(JSContext* aCx, JSStructuredCloneReader* aReader,
+                             JSPrincipals** aOutPrincipals);
+
+  static bool ReadKnownPrincipalType(JSContext* aCx,
+                                     JSStructuredCloneReader* aReader,
+                                     uint32_t aTag,
+                                     JSPrincipals** aOutPrincipals);
+
+  bool write(JSContext* aCx, JSStructuredCloneWriter* aWriter) final;
+
   /*
    * Get a weak reference to nsIPrincipal associated with the given JS
    * principal, and vice-versa.
    */
   static nsJSPrincipals* get(JSPrincipals *principals) {
     nsJSPrincipals *self = static_cast<nsJSPrincipals *>(principals);
     MOZ_ASSERT_IF(self, self->debugToken == DEBUG_TOKEN);
     return self;
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -413,59 +413,29 @@ StructuredCloneHolder::ReadFullySerializ
       }
     }
     return result;
   }
 
   if (aTag == SCTAG_DOM_NULL_PRINCIPAL ||
       aTag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
       aTag == SCTAG_DOM_CONTENT_PRINCIPAL) {
-    if (!NS_IsMainThread()) {
+    JSPrincipals* prin;
+    if (!nsJSPrincipals::ReadKnownPrincipalType(aCx, aReader, aTag, &prin)) {
       return nullptr;
     }
-
-    mozilla::ipc::PrincipalInfo info;
-    if (aTag == SCTAG_DOM_SYSTEM_PRINCIPAL) {
-      info = mozilla::ipc::SystemPrincipalInfo();
-    } else if (aTag == SCTAG_DOM_NULL_PRINCIPAL) {
-      info = mozilla::ipc::NullPrincipalInfo();
-    } else {
-      
-      uint32_t suffixLength, specLength;
-      if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) {
-        return nullptr;
-      }
-
-      nsAutoCString suffix;
-      suffix.SetLength(suffixLength);
-      if (!JS_ReadBytes(aReader, suffix.BeginWriting(), suffixLength)) {
-       return nullptr;
-      }
-
-      nsAutoCString spec;
-      spec.SetLength(specLength);
-      if (!JS_ReadBytes(aReader, spec.BeginWriting(), specLength)) {
-        return nullptr;
-      }
-
-      OriginAttributes attrs;
-      attrs.PopulateFromSuffix(suffix);
-      info = mozilla::ipc::ContentPrincipalInfo(attrs, spec);
-    }
-
-    nsresult rv;
-    nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(info, &rv);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
-      return nullptr;
-    }
+    // nsJSPrincipals::ReadKnownPrincipalType addrefs for us, but because of the
+    // casting between JSPrincipals* and nsIPrincipal* we can't use
+    // getter_AddRefs above and have to already_AddRefed here.
+    nsCOMPtr<nsIPrincipal> principal = already_AddRefed<nsIPrincipal>(nsJSPrincipals::get(prin));
 
     JS::RootedValue result(aCx);
-    rv = nsContentUtils::WrapNative(aCx, principal, &NS_GET_IID(nsIPrincipal),
-                                    &result);
+    nsresult rv = nsContentUtils::WrapNative(aCx, principal,
+                                             &NS_GET_IID(nsIPrincipal),
+                                             &result);
     if (NS_FAILED(rv)) {
       xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
       return nullptr;
     }
 
     return result.toObjectOrNull();
   }
 
@@ -555,37 +525,18 @@ StructuredCloneHolder::WriteFullySeriali
     }
   }
 #endif
 
   if (NS_IsMainThread() && xpc::IsReflector(aObj)) {
     nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(aObj);
     nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(base);
     if (principal) {
-      mozilla::ipc::PrincipalInfo info;
-      if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(principal, &info)))) {
-        xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
-        return false;
-      }
-
-      if (info.type() == mozilla::ipc::PrincipalInfo::TNullPrincipalInfo) {
-        return JS_WriteUint32Pair(aWriter, SCTAG_DOM_NULL_PRINCIPAL, 0);
-      }
-      if (info.type() == mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo) {
-        return JS_WriteUint32Pair(aWriter, SCTAG_DOM_SYSTEM_PRINCIPAL, 0);
-      }
-
-      MOZ_ASSERT(info.type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
-      const mozilla::ipc::ContentPrincipalInfo& cInfo = info;
-      nsAutoCString suffix;
-      cInfo.attrs().CreateSuffix(suffix);
-      return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CONTENT_PRINCIPAL, 0) &&
-             JS_WriteUint32Pair(aWriter, suffix.Length(), cInfo.spec().Length()) &&
-             JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) &&
-             JS_WriteBytes(aWriter, cInfo.spec().get(), cInfo.spec().Length());
+      auto nsjsprincipals = nsJSPrincipals::get(principal);
+      return nsjsprincipals->write(aCx, aWriter);
     }
   }
 
 #ifdef MOZ_NFC
   {
     MozNDEFRecord* ndefRecord;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(MozNDEFRecord, aObj, ndefRecord))) {
       MOZ_ASSERT(NS_IsMainThread());
--- a/dom/workers/Principal.cpp
+++ b/dom/workers/Principal.cpp
@@ -6,20 +6,28 @@
 
 #include "Principal.h"
 
 #include "jsapi.h"
 #include "mozilla/Assertions.h"
 
 BEGIN_WORKERS_NAMESPACE
 
+struct WorkerPrincipal final : public JSPrincipals
+{
+  bool write(JSContext* aCx, JSStructuredCloneWriter* aWriter) override {
+    MOZ_CRASH("WorkerPrincipal::write not implemented");
+    return false;
+  }
+};
+
 JSPrincipals*
 GetWorkerPrincipal()
 {
-  static JSPrincipals sPrincipal;
+  static WorkerPrincipal sPrincipal;
 
   /*
    * To make sure the the principals refcount is initialized to one, atomically
    * increment it on every pass though this function. If we discover this wasn't
    * the first time, decrement it again. This avoids the need for
    * synchronization.
    */
   int32_t prevRefcount = sPrincipal.refcount++;
--- a/js/public/Principals.h
+++ b/js/public/Principals.h
@@ -10,16 +10,18 @@
 #define js_Principals_h
 
 #include "mozilla/Atomics.h"
 
 #include <stdint.h>
 
 #include "jspubtd.h"
 
+#include "js/StructuredClone.h"
+
 namespace js {
     struct PerformanceGroup;
 } // namespace js
 
 struct JSPrincipals {
     /* Don't call "destroy"; use reference counting macros below. */
     mozilla::Atomic<int32_t> refcount;
 
@@ -32,16 +34,22 @@ struct JSPrincipals {
 
     void setDebugToken(uint32_t token) {
 # ifdef JS_DEBUG
         debugToken = token;
 # endif
     }
 
     /*
+     * Write the principals with the given |writer|. Return false on failure,
+     * true on success.
+     */
+    virtual bool write(JSContext* cx, JSStructuredCloneWriter* writer) = 0;
+
+    /*
      * This is not defined by the JS engine but should be provided by the
      * embedding.
      */
     JS_PUBLIC_API(void) dump();
 };
 
 extern JS_PUBLIC_API(void)
 JS_HoldPrincipals(JSPrincipals* principals);
@@ -94,9 +102,31 @@ typedef void
 /*
  * Initialize the callback that is called to destroy JSPrincipals instance
  * when its reference counter drops to zero. The initialization can be done
  * only once per JS runtime.
  */
 extern JS_PUBLIC_API(void)
 JS_InitDestroyPrincipalsCallback(JSRuntime* rt, JSDestroyPrincipalsOp destroyPrincipals);
 
+/*
+ * Read a JSPrincipals instance from the given |reader| and initialize the out
+ * paratemer |outPrincipals| to the JSPrincipals instance read.
+ *
+ * Return false on failure, true on success. The |outPrincipals| parameter
+ * should not be modified if false is returned.
+ *
+ * The caller is not responsible for calling JS_HoldPrincipals on the resulting
+ * JSPrincipals instance, the JSReadPrincipalsOp must increment the refcount of
+ * the resulting JSPrincipals on behalf of the caller.
+ */
+using JSReadPrincipalsOp = bool (*)(JSContext* cx, JSStructuredCloneReader* reader,
+                                    JSPrincipals** outPrincipals);
+
+/*
+ * Initialize the callback that is called to read JSPrincipals instances from a
+ * buffer. The initialization can be done only once per JS runtime.
+ */
+extern JS_PUBLIC_API(void)
+JS_InitReadPrincipalsCallback(JSRuntime* rt, JSReadPrincipalsOp read);
+
+
 #endif  /* js_Principals_h */
--- a/js/src/jsapi-tests/testCloneScript.cpp
+++ b/js/src/jsapi-tests/testCloneScript.cpp
@@ -46,29 +46,28 @@ BEGIN_TEST(test_cloneScript)
         JSAutoCompartment b(cx, B);
         CHECK(JS::CloneFunctionObject(cx, obj));
     }
 
     return true;
 }
 END_TEST(test_cloneScript)
 
-static void
-DestroyPrincipals(JSPrincipals* principals)
-{
-    delete principals;
-}
-
-struct Principals : public JSPrincipals
+struct Principals final : public JSPrincipals
 {
   public:
     Principals()
     {
         refcount = 0;
     }
+
+    bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
+        MOZ_ASSERT(false, "not imlemented");
+        return false;
+    }
 };
 
 class AutoDropPrincipals
 {
     JSRuntime* rt;
     JSPrincipals* principals;
 
   public:
@@ -79,16 +78,23 @@ class AutoDropPrincipals
     }
 
     ~AutoDropPrincipals()
     {
         JS_DropPrincipals(rt, principals);
     }
 };
 
+static void
+DestroyPrincipals(JSPrincipals* principals)
+{
+    auto p = static_cast<Principals*>(principals);
+    delete p;
+}
+
 BEGIN_TEST(test_cloneScriptWithPrincipals)
 {
     JS_InitDestroyPrincipalsCallback(rt, DestroyPrincipals);
 
     JSPrincipals* principalsA = new Principals();
     AutoDropPrincipals dropA(rt, principalsA);
     JSPrincipals* principalsB = new Principals();
     AutoDropPrincipals dropB(rt, principalsB);
--- a/js/src/jsapi-tests/tests.h
+++ b/js/src/jsapi-tests/tests.h
@@ -404,16 +404,21 @@ class TempFile {
 class TestJSPrincipals : public JSPrincipals
 {
   public:
     explicit TestJSPrincipals(int rc = 0)
       : JSPrincipals()
     {
         refcount = rc;
     }
+
+    bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
+        MOZ_ASSERT(false, "not implemented");
+        return false;
+    }
 };
 
 #ifdef JS_GC_ZEAL
 /*
  * Temporarily disable the GC zeal setting. This is only useful in tests that
  * need very explicit GC behavior and should not be used elsewhere.
  */
 class AutoLeaveZeal
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3333,16 +3333,24 @@ JS_SetTrustedPrincipals(JSRuntime* rt, J
 extern JS_PUBLIC_API(void)
 JS_InitDestroyPrincipalsCallback(JSRuntime* rt, JSDestroyPrincipalsOp destroyPrincipals)
 {
     MOZ_ASSERT(destroyPrincipals);
     MOZ_ASSERT(!rt->destroyPrincipals);
     rt->destroyPrincipals = destroyPrincipals;
 }
 
+extern JS_PUBLIC_API(void)
+JS_InitReadPrincipalsCallback(JSRuntime* rt, JSReadPrincipalsOp read)
+{
+    MOZ_ASSERT(read);
+    MOZ_ASSERT(!rt->readPrincipals);
+    rt->readPrincipals = read;
+}
+
 JS_PUBLIC_API(JSFunction*)
 JS_NewFunction(JSContext* cx, JSNative native, unsigned nargs, unsigned flags,
                const char* name)
 {
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
 
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -209,30 +209,35 @@ NewGlobalObject(JSContext* cx, JS::Compa
  * set bits in P are a superset of those in Q. Thus, the principal 0 is
  * subsumed by everything, and the principal ~0 subsumes everything.
  *
  * As a special case, a null pointer as a principal is treated like 0xffff.
  *
  * The 'newGlobal' function takes an option indicating which principal the
  * new global should have; 'evaluate' does for the new code.
  */
-class ShellPrincipals: public JSPrincipals {
+class ShellPrincipals final : public JSPrincipals {
     uint32_t bits;
 
     static uint32_t getBits(JSPrincipals* p) {
         if (!p)
             return 0xffff;
         return static_cast<ShellPrincipals*>(p)->bits;
     }
 
   public:
     explicit ShellPrincipals(uint32_t bits, int32_t refcount = 0) : bits(bits) {
         this->refcount = refcount;
     }
 
+    bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
+        MOZ_ASSERT(false, "not implemented");
+        return false;
+    }
+
     static void destroy(JSPrincipals* principals) {
         MOZ_ASSERT(principals != &fullyTrusted);
         MOZ_ASSERT(principals->refcount == 0);
         js_delete(static_cast<const ShellPrincipals*>(principals));
     }
 
     static bool subsumes(JSPrincipals* first, JSPrincipals* second) {
         uint32_t firstBits  = getBits(first);
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -191,16 +191,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     data(nullptr),
     signalHandlersInstalled_(false),
     canUseSignalHandlers_(false),
     defaultFreeOp_(thisFromCtor()),
     debuggerMutations(0),
     securityCallbacks(const_cast<JSSecurityCallbacks*>(&NullSecurityCallbacks)),
     DOMcallbacks(nullptr),
     destroyPrincipals(nullptr),
+    readPrincipals(nullptr),
     errorReporter(nullptr),
     linkedAsmJSModules(nullptr),
     propertyRemovals(0),
 #if !EXPOSE_INTL_API
     thousandsSeparator(0),
     decimalSeparator(0),
     numGrouping(0),
 #endif
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1153,16 +1153,17 @@ struct JSRuntime : public JS::shadow::Ru
         return &defaultFreeOp_;
     }
 
     uint32_t            debuggerMutations;
 
     const JSSecurityCallbacks* securityCallbacks;
     const js::DOMCallbacks* DOMcallbacks;
     JSDestroyPrincipalsOp destroyPrincipals;
+    JSReadPrincipalsOp readPrincipals;
 
     /* Optional error reporter. */
     JSErrorReporter     errorReporter;
 
     /* AsmJSCache callbacks are runtime-wide. */
     JS::AsmJSCacheOps   asmJSCacheOps;
 
     /* Head of the linked list of linked asm.js modules. */
--- a/js/src/vm/SavedFrame.h
+++ b/js/src/vm/SavedFrame.h
@@ -190,16 +190,21 @@ struct ReconstructedSavedFramePrincipals
 {
     explicit ReconstructedSavedFramePrincipals()
         : JSPrincipals()
     {
         MOZ_ASSERT(is(this));
         this->refcount = 1;
     }
 
+    bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
+        MOZ_ASSERT(false, "ReconstructedSavedFramePrincipals should never be exposed to embedders");
+        return false;
+    }
+
     static ReconstructedSavedFramePrincipals IsSystem;
     static ReconstructedSavedFramePrincipals IsNotSystem;
 
     // Return true if the given JSPrincipals* points to one of the
     // ReconstructedSavedFramePrincipals singletons, false otherwise.
     static bool is(JSPrincipals* p) { return p == &IsSystem || p == &IsNotSystem;}
 
     // Get the appropriate ReconstructedSavedFramePrincipals singleton for the