Merge mozilla-central to autoland. a=merge CLOSED TREE
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Sat, 27 Apr 2019 12:51:05 +0300
changeset 530459 ecc8397871fb7b4e5640f38fa81670f1da352a5a
parent 530458 429ab41a30c4284e2db090abf28353536b44f862 (current diff)
parent 530453 7bdb46cc5ec6221c8df10f0a239dc4faf457488d (diff)
child 530460 08d39c306ffbbb2d5f9cb645e3a4103f2656aaa9
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge CLOSED TREE
--- a/dom/tests/browser/browser_noopener.js
+++ b/dom/tests/browser/browser_noopener.js
@@ -92,17 +92,17 @@ async function doAllTests() {
   await doTests(true, false);
 
   // Non-private window with container
   await doTests(false, true);
 }
 
 // This test takes a really long time, especially in debug builds, as it is
 // constant starting and stopping processes, and opens a new window ~144 times.
-requestLongerTimeout(25);
+requestLongerTimeout(30);
 
 add_task(async function prepare() {
   await SpecialPowers.pushPrefEnv({set: [["dom.window.open.noreferrer.enabled", true]]});
 });
 
 add_task(async function newtab_sameproc() {
   await SpecialPowers.pushPrefEnv({set: [[OPEN_NEWWINDOW_PREF, OPEN_NEWTAB],
                                          [NOOPENER_NEWPROC_PREF, false]]});
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -1268,9 +1268,15 @@ void wr_transaction_notification_notifie
                                           mozilla::wr::Checkpoint aWhen) {
   auto handler = reinterpret_cast<mozilla::wr::NotificationHandler*>(aHandler);
   handler->Notify(aWhen);
   // TODO: it would be better to get a callback when the object is destroyed on
   // the rust side and delete then.
   delete handler;
 }
 
+void wr_register_thread_local_arena() {
+#ifdef MOZ_MEMORY
+  jemalloc_thread_local_arena(true);
+#endif
+}
+
 }  // extern C
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -982,16 +982,17 @@ impl AsyncPropertySampler for SamplerCal
     fn deregister(&self) {
         unsafe { apz_deregister_sampler(self.window_id) }
     }
 }
 
 extern "C" {
     fn gecko_profiler_register_thread(name: *const ::std::os::raw::c_char);
     fn gecko_profiler_unregister_thread();
+    fn wr_register_thread_local_arena();
 }
 
 struct GeckoProfilerThreadListener {}
 
 impl GeckoProfilerThreadListener {
     pub fn new() -> GeckoProfilerThreadListener {
         GeckoProfilerThreadListener{}
     }
@@ -1021,16 +1022,17 @@ pub unsafe extern "C" fn wr_thread_pool_
     // with high worker counts and extra overhead because of rayon and font
     // management.
     let num_threads = num_cpus::get().max(2).min(8);
 
     let worker = rayon::ThreadPoolBuilder::new()
         .thread_name(|idx|{ format!("WRWorker#{}", idx) })
         .num_threads(num_threads)
         .start_handler(|idx| {
+            wr_register_thread_local_arena();
             let name = format!("WRWorker#{}", idx);
             register_thread_with_profiler(name.clone());
             gecko_profiler_register_thread(CString::new(name).unwrap().as_ptr());
         })
         .exit_handler(|_idx| {
             gecko_profiler_unregister_thread();
         })
         .build();
--- a/image/ImageOps.cpp
+++ b/image/ImageOps.cpp
@@ -105,16 +105,17 @@ ImageOps::CreateImageBuffer(already_AddR
   // Prepare the input stream.
   if (!NS_InputStreamIsBuffered(inputStream)) {
     nsCOMPtr<nsIInputStream> bufStream;
     rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
                                    inputStream.forget(), 1024);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return nullptr;
     }
+    inputStream = std::move(bufStream);
   }
 
   // Figure out how much data we've been passed.
   uint64_t length;
   rv = inputStream->Available(&length);
   if (NS_FAILED(rv) || length > UINT32_MAX) {
     return nullptr;
   }
--- a/js/src/jit/arm64/vixl/MozCpu-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozCpu-vixl.cpp
@@ -94,22 +94,24 @@ void CPU::EnsureIAndDCacheCoherency(void
   uintptr_t end = start + length;
 
   do {
     __asm__ __volatile__ (
       // Clean each line of the D cache containing the target data.
       //
       // dc       : Data Cache maintenance
       //     c    : Clean
+      //      i   : Invalidate
       //      va  : by (Virtual) Address
-      //        u : to the point of Unification
-      // The point of unification for a processor is the point by which the
-      // instruction and data caches are guaranteed to see the same copy of a
-      // memory location. See ARM DDI 0406B page B2-12 for more information.
-      "   dc    cvau, %[dline]\n"
+      //        c : to the point of Coherency
+      // Original implementation used cvau, but changed to civac due to
+      // errata on Cortex-A53 819472, 826319, 827319 and 824069.
+      // See ARM DDI 0406B page B2-12 for more information.
+      //
+      "   dc    civac, %[dline]\n"
       :
       : [dline] "r" (dline)
       // This code does not write to memory, but the "memory" dependency
       // prevents GCC from reordering the code.
       : "memory");
     dline += dsize;
   } while (dline < end);
 
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -396,21 +396,29 @@ namespace js {
  * with the runtime, passing the code's URL, and hope that it will be able to
  * find the source.
  */
 class SourceHook {
  public:
   virtual ~SourceHook() {}
 
   /**
-   * Set |*src| and |*length| to refer to the source code for |filename|.
-   * On success, the caller owns the buffer to which |*src| points, and
-   * should use JS_free to free it.
+   * Attempt to load the source for |filename|.
+   *
+   * On success, return true and store an owning pointer to the UTF-8 or UTF-16
+   * contents of the file in whichever of |twoByteSource| or |utf8Source| is
+   * non-null.  (Exactly one of these will be non-null.)  This pointer must be
+   * |js_free|'d when it is no longer needed.
+   *
+   * On failure, return false.  The contents of whichever of |twoByteSource| or
+   * |utf8Source| was initially non-null are unspecified and must not be
+   * |js_free|'d.
    */
-  virtual bool load(JSContext* cx, const char* filename, char16_t** src,
+  virtual bool load(JSContext* cx, const char* filename,
+                    char16_t** twoByteSource, char** utf8Source,
                     size_t* length) = 0;
 };
 
 /**
  * Have |cx| use |hook| to retrieve lazily-retrieved source code. See the
  * comments for SourceHook. The context takes ownership of the hook, and
  * will delete it when the context itself is deleted, or when a new hook is
  * set.
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -6409,18 +6409,21 @@ static bool GetSelfHostedValue(JSContext
 
 class ShellSourceHook : public SourceHook {
   // The function we should call to lazily retrieve source code.
   PersistentRootedFunction fun;
 
  public:
   ShellSourceHook(JSContext* cx, JSFunction& fun) : fun(cx, &fun) {}
 
-  bool load(JSContext* cx, const char* filename, char16_t** src,
-            size_t* length) override {
+  bool load(JSContext* cx, const char* filename, char16_t** twoByteSource,
+            char** utf8Source, size_t* length) override {
+    MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr),
+               "must be called requesting only one of UTF-8 or UTF-16 source");
+
     RootedString str(cx, JS_NewStringCopyZ(cx, filename));
     if (!str) {
       return false;
     }
     RootedValue filenameValue(cx, StringValue(str));
 
     RootedValue result(cx);
     if (!Call(cx, UndefinedHandleValue, fun, HandleValueArray(filenameValue),
@@ -6428,28 +6431,46 @@ class ShellSourceHook : public SourceHoo
       return false;
     }
 
     str = JS::ToString(cx, result);
     if (!str) {
       return false;
     }
 
-    *length = JS_GetStringLength(str);
-    *src = cx->pod_malloc<char16_t>(*length);
-    if (!*src) {
-      return false;
-    }
-
-    JSLinearString* linear = str->ensureLinear(cx);
-    if (!linear) {
-      return false;
-    }
-
-    CopyChars(*src, *linear);
+    Rooted<JSFlatString*> flat(cx, str->ensureFlat(cx));
+    if (!flat) {
+      return false;
+    }
+
+    if (twoByteSource) {
+      *length = JS_GetStringLength(flat);
+
+      *twoByteSource = cx->pod_malloc<char16_t>(*length);
+      if (!*twoByteSource) {
+        return false;
+      }
+
+      CopyChars(*twoByteSource, *flat);
+    } else {
+      MOZ_ASSERT(utf8Source != nullptr);
+
+      *length = JS::GetDeflatedUTF8StringLength(flat);
+
+      *utf8Source = cx->pod_malloc<char>(*length);
+      if (!*utf8Source) {
+        return false;
+      }
+
+      size_t dstLen = *length;
+      JS::DeflateStringToUTF8Buffer(
+          flat, mozilla::RangedPtr<char>(*utf8Source, *length), &dstLen);
+      MOZ_ASSERT(dstLen == *length);
+    }
+
     return true;
   }
 };
 
 static bool WithSourceHook(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   RootedObject callee(cx, &args.callee());
 
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -1667,52 +1667,77 @@ class ScriptSource::LoadSourceMatcher {
     return sourceAlreadyLoaded();
   }
 
   template <typename Unit>
   bool operator()(const Uncompressed<Unit>&) const {
     return sourceAlreadyLoaded();
   }
 
-  bool operator()(const Missing&) const { return tryLoadingSource(); }
-
-  bool operator()(const BinAST&) const { return tryLoadingSource(); }
+  template <typename Unit>
+  bool operator()(const Retrievable<Unit>&) {
+    // Establish the default outcome first.
+    *loaded_ = false;
+
+    MOZ_ASSERT(ss_->sourceRetrievable(),
+               "should be retrievable if Retrievable");
+
+    if (!cx_->runtime()->sourceHook.ref()) {
+      return true;
+    }
+
+    // The argument here is just for overloading -- its value doesn't matter.
+    if (!tryLoadAndSetSource(Unit('0'))) {
+      return false;
+    }
+
+    *loaded_ = true;
+    return true;
+  }
+
+  bool operator()(const Missing&) const {
+    MOZ_ASSERT(!ss_->sourceRetrievable(),
+               "should have Retrievable<Unit> source, not Missing source, if "
+               "retrievable");
+    *loaded_ = false;
+    return true;
+  }
+
+  bool operator()(const BinAST&) const {
+    MOZ_ASSERT(!ss_->sourceRetrievable(), "binast source is never retrievable");
+    *loaded_ = false;
+    return true;
+  }
 
  private:
   bool sourceAlreadyLoaded() const {
     *loaded_ = true;
     return true;
   }
 
-  bool tryLoadingSource() const {
-    // Establish the default outcome first.
-    *loaded_ = false;
-
-    if (!cx_->runtime()->sourceHook.ref() || !ss_->sourceRetrievable()) {
-      return true;
-    }
-
-    char16_t* src = nullptr;
+  bool tryLoadAndSetSource(const Utf8Unit&) const {
+    char* utf8Source;
     size_t length;
-    if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), &src,
-                                          &length)) {
-      return false;
-    }
-    if (!src) {
-      return true;
-    }
-
-    // XXX On-demand source is currently only UTF-16.  Perhaps it should be
-    //     changed to UTF-8, or UTF-8 be allowed in addition to UTF-16?
-    if (!ss_->setRetrievedSource(cx_, EntryUnits<char16_t>(src), length)) {
-      return false;
-    }
-
-    *loaded_ = true;
-    return true;
+    return cx_->runtime()->sourceHook->load(cx_, ss_->filename(), nullptr,
+                                            &utf8Source, &length) &&
+           utf8Source &&
+           ss_->setRetrievedSource(
+               cx_,
+               EntryUnits<Utf8Unit>(reinterpret_cast<Utf8Unit*>(utf8Source)),
+               length);
+  }
+
+  bool tryLoadAndSetSource(const char16_t&) const {
+    char16_t* utf16Source;
+    size_t length;
+    return cx_->runtime()->sourceHook->load(cx_, ss_->filename(), &utf16Source,
+                                            nullptr, &length) &&
+           utf16Source &&
+           ss_->setRetrievedSource(cx_, EntryUnits<char16_t>(utf16Source),
+                                   length);
   }
 };
 
 /* static */
 bool ScriptSource::loadSource(JSContext* cx, ScriptSource* ss, bool* loaded) {
   return ss->data.match(LoadSourceMatcher(cx, ss, loaded));
 }
 
@@ -1884,18 +1909,21 @@ const Unit* ScriptSource::units(JSContex
     const Unit* units = data.as<Uncompressed<Unit>>().units();
     if (!units) {
       return nullptr;
     }
     return units + begin;
   }
 
   if (data.is<Missing>()) {
-    MOZ_CRASH(
-        "ScriptSource::units() on ScriptSource with SourceType = Missing");
+    MOZ_CRASH("ScriptSource::units() on ScriptSource with missing source");
+  }
+
+  if (data.is<Retrievable<Unit>>()) {
+    MOZ_CRASH("ScriptSource::units() on ScriptSource with retrievable source");
   }
 
   MOZ_ASSERT(data.is<Compressed<Unit>>());
 
   // Determine first/last chunks, the offset (in bytes) into the first chunk
   // of the requested units, and the number of bytes in the last chunk.
   //
   // Note that first and last chunk sizes are miscomputed and *must not be
@@ -2091,18 +2119,16 @@ JSFlatString* ScriptSource::functionBody
       parameterListEnd_ + (sizeof(FunctionConstructorMedialSigils) - 1);
   size_t stop = length() - (sizeof(FunctionConstructorFinalBrace) - 1);
   return substring(cx, start, stop);
 }
 
 template <typename Unit>
 MOZ_MUST_USE bool ScriptSource::setUncompressedSourceHelper(
     JSContext* cx, EntryUnits<Unit>&& source, size_t length) {
-  MOZ_ASSERT(data.is<Missing>());
-
   auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
 
   auto uniqueChars = SourceTypeTraits<Unit>::toCacheable(std::move(source));
   auto deduped = cache.getOrCreate(std::move(uniqueChars), length);
   if (!deduped) {
     ReportOutOfMemory(cx);
     return false;
   }
@@ -2111,60 +2137,69 @@ MOZ_MUST_USE bool ScriptSource::setUncom
   return true;
 }
 
 template <typename Unit>
 MOZ_MUST_USE bool ScriptSource::setRetrievedSource(JSContext* cx,
                                                    EntryUnits<Unit>&& source,
                                                    size_t length) {
   MOZ_ASSERT(sourceRetrievable_);
-  MOZ_ASSERT(data.is<Missing>(),
-             "retrievable source must be indicated as missing");
+  MOZ_ASSERT(data.is<Retrievable<Unit>>(),
+             "retrieved source can only overwrite the corresponding "
+             "retrievable source");
   return setUncompressedSourceHelper(cx, std::move(source), length);
 }
 
 #if defined(JS_BUILD_BINAST)
 
 MOZ_MUST_USE bool ScriptSource::setBinASTSourceCopy(JSContext* cx,
                                                     const uint8_t* buf,
                                                     size_t len) {
+  MOZ_ASSERT(data.is<Missing>());
+
   auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
   auto deduped = cache.getOrCreate(reinterpret_cast<const char*>(buf), len);
   if (!deduped) {
     ReportOutOfMemory(cx);
     return false;
   }
-  MOZ_ASSERT(data.is<Missing>());
+
   data = SourceType(BinAST(std::move(*deduped)));
   return true;
 }
 
-MOZ_MUST_USE bool ScriptSource::setBinASTSource(JSContext* cx,
-                                                UniqueChars&& buf, size_t len) {
+MOZ_MUST_USE bool ScriptSource::initializeBinAST(
+    JSContext* cx, UniqueChars&& buf, size_t len,
+    UniquePtr<frontend::BinASTSourceMetadata> metadata) {
+  MOZ_ASSERT(data.is<Missing>(),
+             "should only be initializing a fresh ScriptSource");
+  MOZ_ASSERT(binASTMetadata_ == nullptr, "shouldn't have BinAST metadata yet");
+
   auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
   auto deduped = cache.getOrCreate(std::move(buf), len);
   if (!deduped) {
     ReportOutOfMemory(cx);
     return false;
   }
-  MOZ_ASSERT(data.is<Missing>());
+
   data = SourceType(BinAST(std::move(*deduped)));
+  binASTMetadata_ = std::move(metadata);
   return true;
 }
 
 const uint8_t* ScriptSource::binASTSource() {
   MOZ_ASSERT(hasBinASTSource());
   return reinterpret_cast<const uint8_t*>(data.as<BinAST>().string.chars());
 }
 
 #endif /* JS_BUILD_BINAST */
 
 bool ScriptSource::tryCompressOffThread(JSContext* cx) {
   if (!hasUncompressedSource()) {
-    // This excludes already-compressed, missing, and BinAST source.
+    // This excludes compressed, missing, retrievable, and BinAST source.
     return true;
   }
 
   // There are several cases where source compression is not a good idea:
   //  - If the script is tiny, then compression will save little or no space.
   //  - If there is only one core, then compression will contend with JS
   //    execution (which hurts benchmarketing).
   //
@@ -2251,16 +2286,17 @@ bool ScriptSource::assignSource(JSContex
              "source assignment should only occur on fresh ScriptSources");
 
   if (cx->realm()->behaviors().discardSource()) {
     return true;
   }
 
   if (options.sourceIsLazy) {
     sourceRetrievable_ = true;
+    data = SourceType(Retrievable<Unit>());
     return true;
   }
 
   JSRuntime* runtime = cx->zone()->runtimeFromAnyThread();
   auto& cache = runtime->sharedImmutableStrings();
   auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&srcBuf]() {
     using CharT = typename SourceTypeTraits<Unit>::CharT;
     return srcBuf.ownsUnits()
@@ -2281,19 +2317,21 @@ template bool ScriptSource::assignSource
                                          SourceText<char16_t>& srcBuf);
 template bool ScriptSource::assignSource(JSContext* cx,
                                          const ReadOnlyCompileOptions& options,
                                          SourceText<Utf8Unit>& srcBuf);
 
 void ScriptSource::trace(JSTracer* trc) {
 #ifdef JS_BUILD_BINAST
   if (binASTMetadata_) {
+    MOZ_ASSERT(data.is<BinAST>());
     binASTMetadata_->trace(trc);
   }
 #else
+  MOZ_ASSERT(!data.is<BinAST>());
   MOZ_ASSERT(!binASTMetadata_);
 #endif  // JS_BUILD_BINAST
 }
 
 static MOZ_MUST_USE bool reallocUniquePtr(UniqueChars& unique, size_t size) {
   auto newPtr = static_cast<char*>(js_realloc(unique.get(), size));
   if (!newPtr) {
     return false;
@@ -2387,18 +2425,18 @@ struct SourceCompressionTask::PerformTas
   template <typename Unit>
   void operator()(const ScriptSource::Uncompressed<Unit>&) {
     task_->workEncodingSpecific<Unit>();
   }
 
   template <typename T>
   void operator()(const T&) {
     MOZ_CRASH(
-        "why are we compressing missing, already-compressed, or "
-        "BinAST source?");
+        "why are we compressing missing, missing-but-retrievable, "
+        "already-compressed, or BinAST source?");
   }
 };
 
 void ScriptSource::performTaskWork(SourceCompressionTask* task) {
   MOZ_ASSERT(hasUncompressedSource());
   data.match(SourceCompressionTask::PerformTaskWork(task));
 }
 
@@ -2587,168 +2625,245 @@ XDRResult ScriptSource::xdrUncompressedS
   SourceEncoder<char16_t> encoder(xdr, this, uncompressedLength);
   return encoder.encode();
 }
 
 }  // namespace js
 
 template <XDRMode mode>
 /* static */
-XDRResult ScriptSource::XDR(XDRState<mode>* xdr,
-                            const mozilla::Maybe<JS::CompileOptions>& options,
-                            MutableHandle<ScriptSourceHolder> holder) {
-  JSContext* cx = xdr->cx();
-  ScriptSource* ss = nullptr;
-
+XDRResult ScriptSource::xdrData(XDRState<mode>* const xdr,
+                                ScriptSource* const ss) {
+  // Retrievability is kept outside |ScriptSource::data| (and not solely as
+  // distinct variant types within it) because retrievable compressed or
+  // uncompressed data need not be XDR'd.
+  uint8_t retrievable;
   if (mode == XDR_ENCODE) {
-    ss = holder.get().get();
-  } else {
-    // Allocate a new ScriptSource and root it with the holder.
-    ss = cx->new_<ScriptSource>();
-    if (!ss) {
-      return xdr->fail(JS::TranscodeResult_Throw);
+    retrievable = ss->sourceRetrievable_;
+  }
+  MOZ_TRY(xdr->codeUint8(&retrievable));
+  if (mode == XDR_DECODE) {
+    ss->sourceRetrievable_ = retrievable != 0;
+  }
+
+  // The order here corresponds to the type order in |ScriptSource::SourceType|
+  // for simplicity, but it isn't truly necessary that it do so.
+  enum class DataType {
+    CompressedUtf8,
+    UncompressedUtf8,
+    CompressedUtf16,
+    UncompressedUtf16,
+    RetrievableUtf8,
+    RetrievableUtf16,
+    Missing,
+    BinAST,
+  };
+
+  DataType tag;
+  {
+    // This is terrible, but we can't do better.  When |mode == XDR_DECODE| we
+    // don't have a |ScriptSource::data| |Variant| to match -- the entire XDR
+    // idiom for tagged unions depends on coding a tag-number, then the
+    // corresponding tagged data.  So we must manually define a tag-enum, code
+    // it, then switch on it (and ignore the |Variant::match| API).
+    class XDRDataTag {
+     public:
+      DataType operator()(const Compressed<Utf8Unit>&) {
+        return DataType::CompressedUtf8;
+      }
+      DataType operator()(const Uncompressed<Utf8Unit>&) {
+        return DataType::UncompressedUtf8;
+      }
+      DataType operator()(const Compressed<char16_t>&) {
+        return DataType::CompressedUtf16;
+      }
+      DataType operator()(const Uncompressed<char16_t>&) {
+        return DataType::UncompressedUtf16;
+      }
+      DataType operator()(const Retrievable<Utf8Unit>&) {
+        return DataType::RetrievableUtf8;
+      }
+      DataType operator()(const Retrievable<char16_t>&) {
+        return DataType::RetrievableUtf16;
+      }
+      DataType operator()(const Missing&) { return DataType::Missing; }
+      DataType operator()(const BinAST&) { return DataType::BinAST; }
+    };
+
+    uint8_t type;
+    if (mode == XDR_ENCODE) {
+      type = static_cast<uint8_t>(ss->data.match(XDRDataTag()));
     }
-    holder.get().reset(ss);
-
-    // We use this CompileOptions only to initialize the ScriptSourceObject.
-    // Most CompileOptions fields aren't used by ScriptSourceObject, and those
-    // that are (element; elementAttributeName) aren't preserved by XDR. So
-    // this can be simple.
-    if (!ss->initFromOptions(cx, *options)) {
-      return xdr->fail(JS::TranscodeResult_Throw);
+    MOZ_TRY(xdr->codeUint8(&type));
+
+    if (type > static_cast<uint8_t>(DataType::BinAST)) {
+      // Fail in debug, but only soft-fail in release, if the type is invalid.
+      MOZ_ASSERT_UNREACHABLE("bad tag");
+      return xdr->fail(JS::TranscodeResult_Failure_BadDecode);
     }
-  }
-
-  uint8_t hasSource = ss->hasSourceText();
-  MOZ_TRY(xdr->codeUint8(&hasSource));
-
-  uint8_t hasBinSource = ss->hasBinASTSource();
-  MOZ_TRY(xdr->codeUint8(&hasBinSource));
-
-  uint8_t retrievable = ss->sourceRetrievable_;
-  MOZ_TRY(xdr->codeUint8(&retrievable));
-  ss->sourceRetrievable_ = retrievable;
-
-  if ((hasSource || hasBinSource) && !retrievable) {
+
+    tag = static_cast<DataType>(type);
+  }
+
+  auto CodeCompressedData = [xdr, ss, &retrievable](auto unit) -> XDRResult {
+    using Unit = decltype(unit);
+
+    static_assert(std::is_same<Unit, Utf8Unit>::value ||
+                      std::is_same<Unit, char16_t>::value,
+                  "should handle UTF-8 and UTF-16");
+
+    if (mode == XDR_ENCODE) {
+      MOZ_ASSERT(ss->data.is<Compressed<Unit>>());
+    }
+
+    if (retrievable) {
+      // It's unnecessary to code compressed data if it can just be retrieved
+      // using the source hook.
+      if (mode == XDR_DECODE) {
+        ss->data = SourceType(Retrievable<Unit>());
+      }
+      return Ok();
+    }
+
     uint32_t uncompressedLength = 0;
     if (mode == XDR_ENCODE) {
-      uncompressedLength = ss->length();
+      uncompressedLength = ss->data.as<Compressed<Unit>>().uncompressedLength;
     }
     MOZ_TRY(xdr->codeUint32(&uncompressedLength));
 
-    if (hasBinSource) {
-      if (mode == XDR_DECODE) {
-#if defined(JS_BUILD_BINAST)
-        auto bytes = xdr->cx()->template make_pod_array<char>(
-            Max<size_t>(uncompressedLength, 1));
-        if (!bytes) {
-          return xdr->fail(JS::TranscodeResult_Throw);
-        }
-        MOZ_TRY(xdr->codeBytes(bytes.get(), uncompressedLength));
-
-        if (!ss->setBinASTSource(xdr->cx(), std::move(bytes),
-                                 uncompressedLength)) {
-          return xdr->fail(JS::TranscodeResult_Throw);
-        }
-#else
-        MOZ_ASSERT(mode != XDR_ENCODE);
+    uint32_t compressedLength;
+    if (mode == XDR_ENCODE) {
+      compressedLength = ss->data.as<Compressed<Unit>>().raw.length();
+    }
+    MOZ_TRY(xdr->codeUint32(&compressedLength));
+
+    if (mode == XDR_DECODE) {
+      // Compressed data is always single-byte chars.
+      auto bytes = xdr->cx()->template make_pod_array<char>(compressedLength);
+      if (!bytes) {
         return xdr->fail(JS::TranscodeResult_Throw);
-#endif /* JS_BUILD_BINAST */
-      } else {
-        void* bytes = ss->binASTData();
-        MOZ_TRY(xdr->codeBytes(bytes, uncompressedLength));
+      }
+      MOZ_TRY(xdr->codeBytes(bytes.get(), compressedLength));
+
+      if (!ss->initializeWithCompressedSource<Unit>(xdr->cx(), std::move(bytes),
+                                                    compressedLength,
+                                                    uncompressedLength)) {
+        return xdr->fail(JS::TranscodeResult_Throw);
       }
     } else {
-      // A compressed length of 0 indicates source is uncompressed
-      uint32_t compressedLength;
-      if (mode == XDR_ENCODE) {
-        compressedLength = ss->compressedLengthOrZero();
-      }
-      MOZ_TRY(xdr->codeUint32(&compressedLength));
-
-      uint8_t srcCharSize;
-      if (mode == XDR_ENCODE) {
-        srcCharSize = ss->sourceCharSize();
-      }
-      MOZ_TRY(xdr->codeUint8(&srcCharSize));
-
-      if (srcCharSize != 1 && srcCharSize != 2) {
-        // Fail in debug, but only soft-fail in release, if the source-char
-        // size is invalid.
-        MOZ_ASSERT_UNREACHABLE("bad XDR source chars size");
-        return xdr->fail(JS::TranscodeResult_Failure_BadDecode);
+      void* bytes =
+          const_cast<char*>(ss->data.as<Compressed<Unit>>().raw.chars());
+      MOZ_TRY(xdr->codeBytes(bytes, compressedLength));
+    }
+
+    return Ok();
+  };
+
+  auto CodeUncompressedData = [xdr, ss, &retrievable](auto unit) -> XDRResult {
+    using Unit = decltype(unit);
+
+    static_assert(std::is_same<Unit, Utf8Unit>::value ||
+                      std::is_same<Unit, char16_t>::value,
+                  "should handle UTF-8 and UTF-16");
+
+    if (mode == XDR_ENCODE) {
+      MOZ_ASSERT(ss->data.is<Uncompressed<Unit>>());
+    }
+
+    if (retrievable) {
+      // It's unnecessary to code uncompressed data if it can just be retrieved
+      // using the source hook.
+      if (mode == XDR_DECODE) {
+        ss->data = SourceType(Retrievable<Unit>());
       }
-
-      if (compressedLength) {
-        if (mode == XDR_DECODE) {
-          // Compressed data is always single-byte chars.
-          auto bytes =
-              xdr->cx()->template make_pod_array<char>(compressedLength);
-          if (!bytes) {
-            return xdr->fail(JS::TranscodeResult_Throw);
-          }
-          MOZ_TRY(xdr->codeBytes(bytes.get(), compressedLength));
-
-          if (!(srcCharSize == 1 ? ss->initializeWithCompressedSource<Utf8Unit>(
-                                       xdr->cx(), std::move(bytes),
-                                       compressedLength, uncompressedLength)
-                                 : ss->initializeWithCompressedSource<char16_t>(
-                                       xdr->cx(), std::move(bytes),
-                                       compressedLength, uncompressedLength))) {
-            return xdr->fail(JS::TranscodeResult_Throw);
-          }
-        } else {
-          void* bytes = srcCharSize == 1 ? ss->compressedData<Utf8Unit>()
-                                         : ss->compressedData<char16_t>();
-          MOZ_TRY(xdr->codeBytes(bytes, compressedLength));
-        }
-      } else {
-        MOZ_TRY(
-            ss->xdrUncompressedSource(xdr, srcCharSize, uncompressedLength));
+      return Ok();
+    }
+
+    uint32_t uncompressedLength = 0;
+    if (mode == XDR_ENCODE) {
+      uncompressedLength = ss->data.as<Uncompressed<Unit>>().length();
+    }
+    MOZ_TRY(xdr->codeUint32(&uncompressedLength));
+
+    return ss->xdrUncompressedSource(xdr, sizeof(Unit), uncompressedLength);
+  };
+
+  auto CodeBinASTData = [xdr, ss]() -> XDRResult {
+#if !defined(JS_BUILD_BINAST)
+    return xdr->fail(JS::TranscodeResult_Throw);
+#else
+    // XDR the length of the BinAST data.
+    uint32_t binASTLength;
+    if (mode == XDR_ENCODE) {
+      binASTLength = ss->data.as<BinAST>().string.length();
+    }
+    MOZ_TRY(xdr->codeUint32(&binASTLength));
+
+    // XDR the BinAST data.
+    UniquePtr<char[], JS::FreePolicy> bytes;
+    if (mode == XDR_DECODE) {
+      bytes = xdr->cx()->template make_pod_array<char>(
+          Max<size_t>(binASTLength, 1));
+      if (!bytes) {
+        return xdr->fail(JS::TranscodeResult_Throw);
       }
+      MOZ_TRY(xdr->codeBytes(bytes.get(), binASTLength));
+    } else {
+      void* bytes = ss->binASTData();
+      MOZ_TRY(xdr->codeBytes(bytes, binASTLength));
     }
 
-    uint8_t hasMetadata = !!ss->binASTMetadata_;
+    // XDR any BinAST metadata.
+    uint8_t hasMetadata;
+    if (mode == XDR_ENCODE) {
+      hasMetadata = ss->binASTMetadata_ != nullptr;
+    }
     MOZ_TRY(xdr->codeUint8(&hasMetadata));
+
+    UniquePtr<frontend::BinASTSourceMetadata> freshMetadata;
     if (hasMetadata) {
-#if defined(JS_BUILD_BINAST)
-      UniquePtr<frontend::BinASTSourceMetadata>& binASTMetadata =
-          ss->binASTMetadata_;
+      // If we're decoding, we decode into fresh metadata.  If we're encoding,
+      // we encode *from* the stored metadata.
+      auto& binASTMetadata =
+          mode == XDR_DECODE ? freshMetadata : ss->binASTMetadata_;
+
       uint32_t numBinASTKinds;
       uint32_t numStrings;
       if (mode == XDR_ENCODE) {
         numBinASTKinds = binASTMetadata->numBinASTKinds();
         numStrings = binASTMetadata->numStrings();
       }
       MOZ_TRY(xdr->codeUint32(&numBinASTKinds));
       MOZ_TRY(xdr->codeUint32(&numStrings));
 
       if (mode == XDR_DECODE) {
         // Use calloc, since we're storing this immediately, and filling it
         // might GC, to avoid marking bogus atoms.
-        auto metadata = static_cast<frontend::BinASTSourceMetadata*>(
-            js_calloc(frontend::BinASTSourceMetadata::totalSize(numBinASTKinds,
-                                                                numStrings)));
-        if (!metadata) {
+        void* mem = js_calloc(frontend::BinASTSourceMetadata::totalSize(
+            numBinASTKinds, numStrings));
+        if (!mem) {
           return xdr->fail(JS::TranscodeResult_Throw);
         }
-        new (metadata)
+
+        auto metadata = new (mem)
             frontend::BinASTSourceMetadata(numBinASTKinds, numStrings);
-        ss->setBinASTSourceMetadata(metadata);
+        binASTMetadata.reset(metadata);
       }
 
+      frontend::BinASTKind* binASTKindBase = binASTMetadata->binASTKindBase();
       for (uint32_t i = 0; i < numBinASTKinds; i++) {
-        frontend::BinASTKind* binASTKindBase = binASTMetadata->binASTKindBase();
         MOZ_TRY(xdr->codeEnum32(&binASTKindBase[i]));
       }
 
       RootedAtom atom(xdr->cx());
       JSAtom** atomsBase = binASTMetadata->atomsBase();
       auto slices = binASTMetadata->sliceBase();
-      auto sourceBase = reinterpret_cast<const char*>(ss->binASTSource());
+      const char* sourceBase = mode == XDR_ENCODE
+                                   ? bytes.get()
+                                   : ss->data.as<BinAST>().string.chars();
 
       for (uint32_t i = 0; i < numStrings; i++) {
         uint8_t isNull;
         if (mode == XDR_ENCODE) {
           atom = binASTMetadata->getAtom(i);
           isNull = !atom;
         }
         MOZ_TRY(xdr->codeUint8(&isNull));
@@ -2772,23 +2887,113 @@ XDRResult ScriptSource::XDR(XDRState<mod
         MOZ_TRY(xdr->codeUint64(&sliceOffset));
         MOZ_TRY(xdr->codeUint32(&sliceLen));
 
         if (mode == XDR_DECODE) {
           new (&slices[i]) frontend::BinASTSourceMetadata::CharSlice(
               sourceBase + sliceOffset, sliceLen);
         }
       }
-#else
-      // No BinAST, no BinASTMetadata
-      MOZ_ASSERT(mode != XDR_ENCODE);
+    }
+
+    if (mode == XDR_DECODE) {
+      if (!ss->initializeBinAST(xdr->cx(), std::move(bytes), binASTLength,
+                                std::move(freshMetadata))) {
+        return xdr->fail(JS::TranscodeResult_Throw);
+      }
+    } else {
+      MOZ_ASSERT(freshMetadata == nullptr);
+    }
+
+    return Ok();
+#endif  // !defined(JS_BUILD_BINAST)
+  };
+
+  switch (tag) {
+    case DataType::CompressedUtf8:
+      // The argument here is just for overloading -- its value doesn't matter.
+      return CodeCompressedData(Utf8Unit('0'));
+
+    case DataType::UncompressedUtf8:
+      // The argument here is just for overloading -- its value doesn't matter.
+      return CodeUncompressedData(Utf8Unit('0'));
+
+    case DataType::CompressedUtf16:
+      // The argument here is just for overloading -- its value doesn't matter.
+      return CodeCompressedData(char16_t('0'));
+
+    case DataType::UncompressedUtf16:
+      // The argument here is just for overloading -- its value doesn't matter.
+      return CodeUncompressedData(char16_t('0'));
+
+    case DataType::Missing: {
+      MOZ_ASSERT(ss->data.is<Missing>(),
+                 "ScriptSource::data is initialized as missing, so neither "
+                 "encoding nor decoding has to change anything");
+
+      // There's no data to XDR for missing source.
+      break;
+    }
+
+    case DataType::RetrievableUtf8: {
+      if (mode == XDR_DECODE) {
+        MOZ_ASSERT(ss->data.is<Missing>());
+        ss->data = SourceType(Retrievable<Utf8Unit>());
+      }
+      return Ok();
+    }
+
+    case DataType::RetrievableUtf16: {
+      if (mode == XDR_DECODE) {
+        MOZ_ASSERT(ss->data.is<Missing>());
+        ss->data = SourceType(Retrievable<char16_t>());
+      }
+      return Ok();
+    }
+
+    case DataType::BinAST:
+      return CodeBinASTData();
+  }
+
+  // The range-check on |type| far above ought ensure the above |switch| is
+  // exhaustive and all cases will return, but not all compilers understand
+  // this.  Make the Missing case break to here so control obviously never flows
+  // off the end.
+  MOZ_ASSERT(tag == DataType::Missing);
+  return Ok();
+}
+
+template <XDRMode mode>
+/* static */
+XDRResult ScriptSource::XDR(XDRState<mode>* xdr,
+                            const mozilla::Maybe<JS::CompileOptions>& options,
+                            MutableHandle<ScriptSourceHolder> holder) {
+  JSContext* cx = xdr->cx();
+  ScriptSource* ss = nullptr;
+
+  if (mode == XDR_ENCODE) {
+    ss = holder.get().get();
+  } else {
+    // Allocate a new ScriptSource and root it with the holder.
+    ss = cx->new_<ScriptSource>();
+    if (!ss) {
       return xdr->fail(JS::TranscodeResult_Throw);
-#endif  // JS_BUILD_BINAST
     }
-  }
+    holder.get().reset(ss);
+
+    // We use this CompileOptions only to initialize the ScriptSourceObject.
+    // Most CompileOptions fields aren't used by ScriptSourceObject, and those
+    // that are (element; elementAttributeName) aren't preserved by XDR. So
+    // this can be simple.
+    if (!ss->initFromOptions(cx, *options)) {
+      return xdr->fail(JS::TranscodeResult_Throw);
+    }
+  }
+
+  MOZ_TRY(xdrData(xdr, ss));
 
   uint8_t haveSourceMap = ss->hasSourceMapURL();
   MOZ_TRY(xdr->codeUint8(&haveSourceMap));
 
   if (haveSourceMap) {
     UniqueTwoByteChars& sourceMapURL(ss->sourceMapURL_);
     uint32_t sourceMapURLLen =
         (mode == XDR_DECODE) ? 0 : js_strlen(sourceMapURL.get());
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -503,52 +503,72 @@ class ScriptSource {
                   mozilla::recordreplay::Behavior::DontPreserve>
       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.
 
   // Indicate which field in the |data| union is active.
-  struct Missing {};
-
+
+  // Uncompressed source text.
   template <typename Unit>
   class Uncompressed {
     typename SourceTypeTraits<Unit>::SharedImmutableString string_;
 
    public:
     explicit Uncompressed(
         typename SourceTypeTraits<Unit>::SharedImmutableString str)
         : string_(std::move(str)) {}
 
     const Unit* units() const { return SourceTypeTraits<Unit>::units(string_); }
 
     size_t length() const { return string_.length(); }
   };
 
+  // Compressed source text.
   template <typename Unit>
   struct Compressed {
     // Single-byte compressed text, regardless whether the original text
     // was single-byte or two-byte.
     SharedImmutableString raw;
     size_t uncompressedLength;
 
     Compressed(SharedImmutableString raw, size_t uncompressedLength)
         : raw(std::move(raw)), uncompressedLength(uncompressedLength) {}
   };
 
+  // Source that can be retrieved using the registered source hook.  |Unit|
+  // records the source type so that source-text coordinates in functions and
+  // scripts that depend on this |ScriptSource| are correct.
+  template <typename Unit>
+  struct Retrievable {
+    // The source hook and script URL required to retrieve source are stored
+    // elsewhere, so nothing is needed here.  It'd be better hygiene to store
+    // something source-hook-like in each |ScriptSource| that needs it, but that
+    // requires reimagining a source-hook API that currently depends on source
+    // hooks being uniquely-owned pointers...
+  };
+
+  // Missing source text that isn't retrievable using the source hook.  (All
+  // ScriptSources initially begin in this state.  Users that are compiling
+  // source text will overwrite |data| to store a different state.)
+  struct Missing {};
+
+  // BinAST source.
   struct BinAST {
     SharedImmutableString string;
     explicit BinAST(SharedImmutableString&& str) : string(std::move(str)) {}
   };
 
   using SourceType =
       mozilla::Variant<Compressed<mozilla::Utf8Unit>,
                        Uncompressed<mozilla::Utf8Unit>, Compressed<char16_t>,
-                       Uncompressed<char16_t>, Missing, BinAST>;
+                       Uncompressed<char16_t>, Retrievable<mozilla::Utf8Unit>,
+                       Retrievable<char16_t>, Missing, BinAST>;
   SourceType data;
 
   // If the GC attempts to call convertToCompressedSource with PinnedUnits
   // present, the first PinnedUnits (that is, bottom of the stack) will set
   // the compressed chars upon destruction.
   PinnedUnitsBase* pinnedUnitsStack_;
   mozilla::MaybeOneOf<Compressed<mozilla::Utf8Unit>, Compressed<char16_t>>
       pendingCompressed_;
@@ -618,20 +638,27 @@ class ScriptSource {
   // unique anymore.
   uint32_t id_;
 
   // How many ids have been handed out to sources.
   static mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent,
                          mozilla::recordreplay::Behavior::DontPreserve>
       idCount_;
 
-  // True if we can call JSRuntime::sourceHook to load the source on
-  // demand. If sourceRetrievable_ and hasSourceText() are false, it is not
-  // possible to get source at all.
+  // If this field is true, we can call JSRuntime::sourceHook to load the source
+  // on demand.  Thus if this contains compressed/uncompressed data, we don't
+  // have to preserve it while we're not using it, because we can use the source
+  // hook to load it when we need it.
+  //
+  // This field is always true for retrievable source.  It *may* be true for
+  // compressed/uncompressed source (if retrievable source was rewritten to
+  // compressed/uncompressed source loaded using the source hook).  It is always
+  // false for missing or BinAST source.
   bool sourceRetrievable_ : 1;
+
   bool hasIntroductionOffset_ : 1;
   bool containsAsmJS_ : 1;
 
   UniquePtr<frontend::BinASTSourceMetadata> binASTMetadata_;
 
   template <typename Unit>
   const Unit* chunkUnits(JSContext* cx,
                          UncompressedSourceCache::AutoHoldEntry& holder,
@@ -791,16 +818,21 @@ class ScriptSource {
       return true;
     }
 
     template <typename Unit>
     bool operator()(const Compressed<Unit>&) {
       return false;
     }
 
+    template <typename Unit>
+    bool operator()(const Retrievable<Unit>&) {
+      return false;
+    }
+
     bool operator()(const BinAST&) { return false; }
 
     bool operator()(const Missing&) { return false; }
   };
 
  public:
   bool hasUncompressedSource() const {
     return data.match(HasUncompressedSource());
@@ -819,16 +851,21 @@ class ScriptSource {
       return true;
     }
 
     template <typename Unit>
     bool operator()(const Uncompressed<Unit>&) {
       return false;
     }
 
+    template <typename Unit>
+    bool operator()(const Retrievable<Unit>&) {
+      return false;
+    }
+
     bool operator()(const BinAST&) { return false; }
 
     bool operator()(const Missing&) { return false; }
   };
 
  public:
   bool hasCompressedSource() const { return data.match(HasCompressedSource()); }
 
@@ -864,110 +901,65 @@ class ScriptSource {
 
  public:
   template <typename Unit>
   bool hasSourceType() const {
     return data.match(SourceTypeMatcher<Unit>());
   }
 
  private:
-  struct SourceCharSizeMatcher {
-    template <template <typename C> class Data, typename Unit>
-    uint8_t operator()(const Data<Unit>& data) {
-      static_assert(std::is_same<Unit, mozilla::Utf8Unit>::value ||
-                        std::is_same<Unit, char16_t>::value,
-                    "should only have UTF-8 or UTF-16 source char");
-      return sizeof(Unit);
-    }
-
-    uint8_t operator()(const BinAST&) {
-      MOZ_CRASH("BinAST source has no source-char size");
-      return 0;
-    }
-
-    uint8_t operator()(const Missing&) {
-      MOZ_CRASH("missing source has no source-char size");
-      return 0;
-    }
-  };
-
- public:
-  uint8_t sourceCharSize() const { return data.match(SourceCharSizeMatcher()); }
-
- private:
   struct UncompressedLengthMatcher {
     template <typename Unit>
     size_t operator()(const Uncompressed<Unit>& u) {
       return u.length();
     }
 
     template <typename Unit>
     size_t operator()(const Compressed<Unit>& u) {
       return u.uncompressedLength;
     }
 
+    template <typename Unit>
+    size_t operator()(const Retrievable<Unit>&) {
+      MOZ_CRASH("ScriptSource::length on a missing-but-retrievable source");
+      return 0;
+    }
+
     size_t operator()(const BinAST& b) { return b.string.length(); }
 
     size_t operator()(const Missing& m) {
       MOZ_CRASH("ScriptSource::length on a missing source");
       return 0;
     }
   };
 
  public:
   size_t length() const {
     MOZ_ASSERT(hasSourceText() || hasBinASTSource());
     return data.match(UncompressedLengthMatcher());
   }
 
- private:
-  struct CompressedLengthOrZeroMatcher {
-    template <typename Unit>
-    size_t operator()(const Uncompressed<Unit>&) {
-      return 0;
-    }
-
-    template <typename Unit>
-    size_t operator()(const Compressed<Unit>& c) {
-      return c.raw.length();
-    }
-
-    size_t operator()(const BinAST&) {
-      MOZ_CRASH("trying to get compressed length for BinAST data");
-      return 0;
-    }
-
-    size_t operator()(const Missing&) {
-      MOZ_CRASH("missing source data");
-      return 0;
-    }
-  };
-
- public:
-  size_t compressedLengthOrZero() const {
-    return data.match(CompressedLengthOrZeroMatcher());
-  }
-
   JSFlatString* substring(JSContext* cx, size_t start, size_t stop);
   JSFlatString* substringDontDeflate(JSContext* cx, size_t start, size_t stop);
 
   MOZ_MUST_USE bool appendSubstring(JSContext* cx, js::StringBuffer& buf,
                                     size_t start, size_t stop);
 
   bool isFunctionBody() { return parameterListEnd_ != 0; }
   JSFlatString* functionBodyString(JSContext* cx);
 
   void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                               JS::ScriptSourceInfo* info) const;
 
  private:
-  // Overwrites |data| with the uncompressed data from |source|.  (This function
-  // currently asserts |data.is<Missing>()|, but callers should assert it as
-  // well, because this function shortly will be used in other cases and the
-  // assertion will have to be removed.)
+  // Overwrites |data| with the uncompressed data from |source|.
+  //
+  // This function asserts nothing about |data|.  Users should use assertions to
+  // double-check their own understandings of the |data| state transition being
+  // performed.
   template <typename Unit>
   MOZ_MUST_USE bool setUncompressedSourceHelper(JSContext* cx,
                                                 EntryUnits<Unit>&& source,
                                                 size_t length);
 
  public:
   // Initialize a fresh |ScriptSource| with uncompressed source.
   template <typename Unit>
@@ -1004,21 +996,22 @@ class ScriptSource {
    * Do not take ownership of the given `buf`. Store the canonical, shared
    * and de-duplicated version. If there is no extant shared version of
    * `buf`, make a copy.
    */
   MOZ_MUST_USE bool setBinASTSourceCopy(JSContext* cx, const uint8_t* buf,
                                         size_t len);
 
   /*
-   * Take ownership of the given `buf` and return the canonical, shared and
-   * de-duplicated version.
+   * Initialize this as containing BinAST data for |buf|/|len|, using a shared,
+   * deduplicated version of |buf| if necessary.
    */
-  MOZ_MUST_USE bool setBinASTSource(JSContext* cx, UniqueChars&& buf,
-                                    size_t len);
+  MOZ_MUST_USE bool initializeBinAST(
+      JSContext* cx, UniqueChars&& buf, size_t len,
+      UniquePtr<frontend::BinASTSourceMetadata> metadata);
 
   const uint8_t* binASTSource();
 
 #endif /* JS_BUILD_BINAST */
 
  private:
   void performTaskWork(SourceCompressionTask* task);
 
@@ -1034,32 +1027,35 @@ class ScriptSource {
     void operator()(const Uncompressed<Unit>&) {
       source_->convertToCompressedSource<Unit>(std::move(compressed_),
                                                source_->length());
     }
 
     template <typename Unit>
     void operator()(const Compressed<Unit>&) {
       MOZ_CRASH(
-          "can't set compressed source when source is already "
-          "compressed -- ScriptSource::tryCompressOffThread "
-          "shouldn't have queued up this task?");
+          "can't set compressed source when source is already compressed -- "
+          "ScriptSource::tryCompressOffThread shouldn't have queued up this "
+          "task?");
+    }
+
+    template <typename Unit>
+    void operator()(const Retrievable<Unit>&) {
+      MOZ_CRASH("shouldn't compressing unloaded-but-retrievable source");
     }
 
     void operator()(const BinAST&) {
-      MOZ_CRASH(
-          "doesn't make sense to set compressed source for BinAST "
-          "data");
+      MOZ_CRASH("doesn't make sense to set compressed source for BinAST data");
     }
 
     void operator()(const Missing&) {
       MOZ_CRASH(
-          "doesn't make sense to set compressed source for "
-          "missing source -- ScriptSource::tryCompressOffThread "
-          "shouldn't have queued up this task?");
+          "doesn't make sense to set compressed source for missing source -- "
+          "ScriptSource::tryCompressOffThread shouldn't have queued up this "
+          "task?");
     }
   };
 
   void convertToCompressedSourceFromTask(SharedImmutableString compressed);
 
  private:
   // It'd be better to make this function take <XDRMode, Unit>, as both
   // specializations of this function contain nested Unit-parametrized
@@ -1144,16 +1140,22 @@ class ScriptSource {
 
   const mozilla::TimeStamp parseEnded() const { return parseEnded_; }
   // Inform `this` source that it has been fully parsed.
   void recordParseEnded() {
     MOZ_ASSERT(parseEnded_.IsNull());
     parseEnded_ = ReallyNow();
   }
 
+ private:
+  template <XDRMode mode>
+  static MOZ_MUST_USE XDRResult xdrData(XDRState<mode>* const xdr,
+                                        ScriptSource* const ss);
+
+ public:
   template <XDRMode mode>
   static MOZ_MUST_USE XDRResult
   XDR(XDRState<mode>* xdr, const mozilla::Maybe<JS::CompileOptions>& options,
       MutableHandle<ScriptSourceHolder> ss);
 
   void trace(JSTracer* trc);
 };
 
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Per JSRuntime object */
 
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/UniquePtr.h"
 
 #include "xpcprivate.h"
 #include "xpcpublic.h"
 #include "XPCWrapper.h"
 #include "XPCJSMemoryReporter.h"
 #include "XrayWrapper.h"
@@ -2792,17 +2793,24 @@ static bool PreserveWrapper(JSContext* c
   MOZ_ASSERT(cx);
   MOZ_ASSERT(obj);
   MOZ_ASSERT(mozilla::dom::IsDOMObject(obj));
 
   return mozilla::dom::TryPreserveWrapper(obj);
 }
 
 static nsresult ReadSourceFromFilename(JSContext* cx, const char* filename,
-                                       char16_t** src, size_t* len) {
+                                       char16_t** twoByteSource,
+                                       char** utf8Source, size_t* len) {
+  MOZ_ASSERT(*len == 0);
+  MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr),
+             "must be called requesting only one of UTF-8 or UTF-16 source");
+  MOZ_ASSERT_IF(twoByteSource, !*twoByteSource);
+  MOZ_ASSERT_IF(utf8Source, !*utf8Source);
+
   nsresult rv;
 
   // mozJSSubScriptLoader prefixes the filenames of the scripts it loads with
   // the filename of its caller. Axe that if present.
   const char* arrow;
   while ((arrow = strstr(filename, " -> "))) {
     filename = arrow + strlen(" -> ");
   }
@@ -2847,70 +2855,93 @@ static nsresult ReadSourceFromFilename(J
 
   // Technically, this should be SIZE_MAX, but we don't run on machines
   // where that would be less than UINT32_MAX, and the latter is already
   // well beyond a reasonable limit.
   if (rawLen > UINT32_MAX) {
     return NS_ERROR_FILE_TOO_BIG;
   }
 
-  // Allocate an internal buf the size of the file.
-  auto buf = MakeUniqueFallible<unsigned char[]>(rawLen);
+  // Allocate a buffer the size of the file to initially fill with the UTF-8
+  // contents of the file.  Use the JS allocator so that if UTF-8 source was
+  // requested, we can return this memory directly.
+  JS::UniqueChars buf(js_pod_malloc<char>(rawLen));
   if (!buf) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  unsigned char* ptr = buf.get();
-  unsigned char* end = ptr + rawLen;
+  char* ptr = buf.get();
+  char* end = ptr + rawLen;
   while (ptr < end) {
     uint32_t bytesRead;
-    rv =
-        scriptStream->Read(reinterpret_cast<char*>(ptr), end - ptr, &bytesRead);
+    rv = scriptStream->Read(ptr, PointerRangeSize(ptr, end), &bytesRead);
     if (NS_FAILED(rv)) {
       return rv;
     }
     MOZ_ASSERT(bytesRead > 0, "stream promised more bytes before EOF");
     ptr += bytesRead;
   }
 
-  rv = ScriptLoader::ConvertToUTF16(scriptChannel, buf.get(), rawLen,
-                                    NS_LITERAL_STRING("UTF-8"), nullptr, *src,
-                                    *len);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!*src) {
-    return NS_ERROR_FAILURE;
+  size_t bytesAllocated;
+  if (utf8Source) {
+    // |buf| is already UTF-8, so we can directly return it.
+    *len = bytesAllocated = rawLen;
+    *utf8Source = buf.release();
+  } else {
+    MOZ_ASSERT(twoByteSource != nullptr);
+
+    // |buf| can't be directly returned -- convert it to UTF-16.
+
+    // On success this overwrites |*twoByteSource| and |*len|.
+    rv = ScriptLoader::ConvertToUTF16(
+        scriptChannel, reinterpret_cast<const unsigned char*>(buf.get()),
+        rawLen, NS_LITERAL_STRING("UTF-8"), nullptr, *twoByteSource, *len);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!*twoByteSource) {
+      return NS_ERROR_FAILURE;
+    }
+
+    bytesAllocated = *len * sizeof(char16_t);
   }
 
   // Historically this method used JS_malloc() which updates the GC memory
-  // accounting.  Since ConvertToUTF16() now uses js_malloc() instead we
-  // update the accounting manually after the fact.
-  JS_updateMallocCounter(cx, *len);
+  // accounting.  Since ConvertToUTF16() and js::MakeUnique now use js_malloc()
+  // instead we update the accounting manually after the fact.
+  JS_updateMallocCounter(cx, bytesAllocated);
 
   return NS_OK;
 }
 
 // The JS engine calls this object's 'load' member function when it needs
 // the source for a chrome JS function. See the comment in the XPCJSRuntime
 // constructor.
 class XPCJSSourceHook : public js::SourceHook {
-  bool load(JSContext* cx, const char* filename, char16_t** src,
-            size_t* length) override {
-    *src = nullptr;
+  bool load(JSContext* cx, const char* filename, char16_t** twoByteSource,
+            char** utf8Source, size_t* length) override {
+    MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr),
+               "must be called requesting only one of UTF-8 or UTF-16 source");
+
     *length = 0;
+    if (twoByteSource) {
+      *twoByteSource = nullptr;
+    } else {
+      *utf8Source = nullptr;
+    }
 
     if (!nsContentUtils::IsSystemCaller(cx)) {
       return true;
     }
 
     if (!filename) {
       return true;
     }
 
-    nsresult rv = ReadSourceFromFilename(cx, filename, src, length);
+    nsresult rv =
+        ReadSourceFromFilename(cx, filename, twoByteSource, utf8Source, length);
     if (NS_FAILED(rv)) {
       xpc::Throw(cx, rv);
       return false;
     }
 
     return true;
   }
 };