Merge inbound to mozilla-central. a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Tue, 16 Oct 2018 12:31:18 +0300
changeset 497184 9079bbe837184ed183b133a374753865b6768bc4
parent 497161 ba7c1249b79b39df4be295ed0c65a32bc7eaa932 (current diff)
parent 497177 7a0bdd661bbce9ada9dc88bd614b9a5c706d2730 (diff)
child 497185 dc75dad8f69b572650ddb2c4a0ea06661135837f
child 497190 db589d2f37284b67611b56d969b4bc9442697002
push id9996
push userarchaeopteryx@coole-files.de
push dateThu, 18 Oct 2018 18:37:15 +0000
treeherdermozilla-beta@8efe26839243 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
9079bbe83718 / 64.0a1 / 20181016100106 / files
nightly linux64
9079bbe83718 / 64.0a1 / 20181016100106 / files
nightly mac
9079bbe83718 / 64.0a1 / 20181016100106 / files
nightly win32
9079bbe83718 / 64.0a1 / 20181016100106 / files
nightly win64
9079bbe83718 / 64.0a1 / 20181016100106 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
js/src/frontend/ParseNode-inl.h
security/nss/automation/taskcluster/windows/setup32.sh
security/nss/automation/taskcluster/windows/setup64.sh
testing/mozharness/configs/builds/releng_sub_windows_configs/32_mingw_debug.py
--- a/dom/base/Location.cpp
+++ b/dom/base/Location.cpp
@@ -56,117 +56,117 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Location, mInnerWindow)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Location)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Location)
 
-nsresult
-Location::CheckURL(nsIURI* aURI, nsDocShellLoadInfo** aLoadInfo)
+already_AddRefed<nsDocShellLoadInfo>
+Location::CheckURL(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal,
+                   ErrorResult& aRv)
 {
-  *aLoadInfo = nullptr;
-
   nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
-  NS_ENSURE_TRUE(docShell, NS_ERROR_NOT_AVAILABLE);
+  if (NS_WARN_IF(!docShell)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
 
   nsCOMPtr<nsIPrincipal> triggeringPrincipal;
   nsCOMPtr<nsIURI> sourceURI;
   net::ReferrerPolicy referrerPolicy = net::RP_Unset;
 
-  if (JSContext *cx = nsContentUtils::GetCurrentJSContext()) {
-    // No cx means that there's no JS running, or at least no JS that
-    // was run through code that properly pushed a context onto the
-    // context stack (as all code that runs JS off of web pages
-    // does). We won't bother with security checks in this case, but
-    // we need to create the loadinfo etc.
-
-    // Get security manager.
-    nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
-    NS_ENSURE_STATE(ssm);
-
-    // Check to see if URI is allowed.
-    nsresult rv = ssm->CheckLoadURIFromScript(cx, aURI);
-    NS_ENSURE_SUCCESS(rv, rv);
+  // Get security manager.
+  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+  if (NS_WARN_IF(!ssm)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
 
-    // Make the load's referrer reflect changes to the document's URI caused by
-    // push/replaceState, if possible.  First, get the document corresponding to
-    // fp.  If the document's original URI (i.e. its URI before
-    // push/replaceState) matches the principal's URI, use the document's
-    // current URI as the referrer.  If they don't match, use the principal's
-    // URI.
-    //
-    // The triggering principal for this load should be the principal of the
-    // incumbent document (which matches where the referrer information is
-    // coming from) when there is an incumbent document, and the subject
-    // principal otherwise.  Note that the URI in the triggering principal
-    // may not match the referrer URI in various cases, notably including
-    // the cases when the incumbent document's document URI was modified
-    // after the document was loaded.
+  // Check to see if URI is allowed.
+  nsresult rv = ssm->CheckLoadURIWithPrincipal(&aSubjectPrincipal, aURI,
+                                               nsIScriptSecurityManager::STANDARD);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    nsAutoCString spec;
+    aURI->GetSpec(spec);
+    aRv.ThrowTypeError<MSG_URL_NOT_LOADABLE>(NS_ConvertUTF8toUTF16(spec));
+    return nullptr;
+  }
+
+  // Make the load's referrer reflect changes to the document's URI caused by
+  // push/replaceState, if possible.  First, get the document corresponding to
+  // fp.  If the document's original URI (i.e. its URI before
+  // push/replaceState) matches the principal's URI, use the document's
+  // current URI as the referrer.  If they don't match, use the principal's
+  // URI.
+  //
+  // The triggering principal for this load should be the principal of the
+  // incumbent document (which matches where the referrer information is
+  // coming from) when there is an incumbent document, and the subject
+  // principal otherwise.  Note that the URI in the triggering principal
+  // may not match the referrer URI in various cases, notably including
+  // the cases when the incumbent document's document URI was modified
+  // after the document was loaded.
 
-    nsCOMPtr<nsPIDOMWindowInner> incumbent =
-      do_QueryInterface(mozilla::dom::GetIncumbentGlobal());
-    nsCOMPtr<nsIDocument> doc = incumbent ? incumbent->GetDoc() : nullptr;
+  nsCOMPtr<nsPIDOMWindowInner> incumbent =
+    do_QueryInterface(mozilla::dom::GetIncumbentGlobal());
+  nsCOMPtr<nsIDocument> doc = incumbent ? incumbent->GetDoc() : nullptr;
 
-    if (doc) {
-      nsCOMPtr<nsIURI> docOriginalURI, docCurrentURI, principalURI;
-      docOriginalURI = doc->GetOriginalURI();
-      docCurrentURI = doc->GetDocumentURI();
-      rv = doc->NodePrincipal()->GetURI(getter_AddRefs(principalURI));
-      NS_ENSURE_SUCCESS(rv, rv);
+  if (doc) {
+    nsCOMPtr<nsIURI> docOriginalURI, docCurrentURI, principalURI;
+    docOriginalURI = doc->GetOriginalURI();
+    docCurrentURI = doc->GetDocumentURI();
+    rv = doc->NodePrincipal()->GetURI(getter_AddRefs(principalURI));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      aRv.Throw(rv);
+      return nullptr;
+    }
 
-      triggeringPrincipal = doc->NodePrincipal();
-      referrerPolicy = doc->GetReferrerPolicy();
+    triggeringPrincipal = doc->NodePrincipal();
+    referrerPolicy = doc->GetReferrerPolicy();
 
-      bool urisEqual = false;
-      if (docOriginalURI && docCurrentURI && principalURI) {
-        principalURI->Equals(docOriginalURI, &urisEqual);
-      }
-      if (urisEqual) {
-        sourceURI = docCurrentURI;
-      }
-      else {
-        // Use principalURI as long as it is not an NullPrincipalURI.  We
-        // could add a method such as GetReferrerURI to principals to make this
-        // cleaner, but given that we need to start using Source Browsing
-        // Context for referrer (see Bug 960639) this may be wasted effort at
-        // this stage.
-        if (principalURI) {
-          bool isNullPrincipalScheme;
-          rv = principalURI->SchemeIs(NS_NULLPRINCIPAL_SCHEME,
-                                     &isNullPrincipalScheme);
-          if (NS_SUCCEEDED(rv) && !isNullPrincipalScheme) {
-            sourceURI = principalURI;
-          }
+    bool urisEqual = false;
+    if (docOriginalURI && docCurrentURI && principalURI) {
+      principalURI->Equals(docOriginalURI, &urisEqual);
+    }
+    if (urisEqual) {
+      sourceURI = docCurrentURI;
+    }
+    else {
+      // Use principalURI as long as it is not an NullPrincipalURI.  We
+      // could add a method such as GetReferrerURI to principals to make this
+      // cleaner, but given that we need to start using Source Browsing
+      // Context for referrer (see Bug 960639) this may be wasted effort at
+      // this stage.
+      if (principalURI) {
+        bool isNullPrincipalScheme;
+        rv = principalURI->SchemeIs(NS_NULLPRINCIPAL_SCHEME,
+                                    &isNullPrincipalScheme);
+        if (NS_SUCCEEDED(rv) && !isNullPrincipalScheme) {
+          sourceURI = principalURI;
         }
       }
     }
-    else {
-      // No document; determine triggeringPrincipal by quering the
-      // subjectPrincipal, wich is the principal of the current JS
-      // compartment, or a null principal in case there is no
-      // compartment yet.
-      triggeringPrincipal = nsContentUtils::SubjectPrincipal();
-    }
+  } else {
+    // No document; just use our subject principal as the triggering principal.
+    triggeringPrincipal = &aSubjectPrincipal;
   }
 
   // Create load info
   RefPtr<nsDocShellLoadInfo> loadInfo = new nsDocShellLoadInfo();
 
   loadInfo->SetTriggeringPrincipal(triggeringPrincipal);
 
   if (sourceURI) {
     loadInfo->SetReferrer(sourceURI);
     loadInfo->SetReferrerPolicy(referrerPolicy);
   }
 
-  loadInfo.swap(*aLoadInfo);
-
-  return NS_OK;
+  return loadInfo.forget();
 }
 
 nsresult
 Location::GetURI(nsIURI** aURI, bool aGetInnermostURI)
 {
   *aURI = nullptr;
 
   nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
@@ -201,44 +201,47 @@ Location::GetURI(nsIURI** aURI, bool aGe
   NS_ASSERTION(uri, "nsJARURI screwed up?");
 
   nsCOMPtr<nsIURIFixup> urifixup(do_GetService(NS_URIFIXUP_CONTRACTID, &rv));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return urifixup->CreateExposableURI(uri, aURI);
 }
 
-nsresult
-Location::SetURI(nsIURI* aURI, bool aReplace)
+void
+Location::SetURI(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal,
+                 ErrorResult& aRv, bool aReplace)
 {
   nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
   if (docShell) {
-    RefPtr<nsDocShellLoadInfo> loadInfo;
-
-    if(NS_FAILED(CheckURL(aURI, getter_AddRefs(loadInfo))))
-      return NS_ERROR_FAILURE;
+    RefPtr<nsDocShellLoadInfo> loadInfo =
+      CheckURL(aURI, aSubjectPrincipal, aRv);
+    if (aRv.Failed()) {
+      return;
+    }
 
     if (aReplace) {
       loadInfo->SetLoadType(LOAD_STOP_CONTENT_AND_REPLACE);
     } else {
       loadInfo->SetLoadType(LOAD_STOP_CONTENT);
     }
 
     // Get the incumbent script's browsing context to set as source.
     nsCOMPtr<nsPIDOMWindowInner> sourceWindow =
       do_QueryInterface(mozilla::dom::GetIncumbentGlobal());
     if (sourceWindow) {
       loadInfo->SetSourceDocShell(sourceWindow->GetDocShell());
     }
 
-    return docShell->LoadURI(aURI, loadInfo,
-                             nsIWebNavigation::LOAD_FLAGS_NONE, true);
+    nsresult rv = docShell->LoadURI(aURI, loadInfo,
+                                    nsIWebNavigation::LOAD_FLAGS_NONE, true);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      aRv.Throw(rv);
+    }
   }
-
-  return NS_OK;
 }
 
 void
 Location::GetHash(nsAString& aHash,
                   nsIPrincipal& aSubjectPrincipal,
                   ErrorResult& aRv)
 {
   if (!CallerSubsumes(&aSubjectPrincipal)) {
@@ -300,17 +303,17 @@ Location::SetHash(const nsAString& aHash
 
   aRv = NS_MutateURI(uri)
           .SetRef(hash)
           .Finalize(uri);
   if (NS_WARN_IF(aRv.Failed()) || !uri) {
     return;
   }
 
-  aRv = SetURI(uri);
+  SetURI(uri, aSubjectPrincipal, aRv);
 }
 
 void
 Location::GetHost(nsAString& aHost,
                   nsIPrincipal& aSubjectPrincipal,
                   ErrorResult& aRv)
 {
   if (!CallerSubsumes(&aSubjectPrincipal)) {
@@ -354,17 +357,17 @@ Location::SetHost(const nsAString& aHost
 
   aRv = NS_MutateURI(uri)
           .SetHostPort(NS_ConvertUTF16toUTF8(aHost))
           .Finalize(uri);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
-  aRv = SetURI(uri);
+  SetURI(uri, aSubjectPrincipal, aRv);
 }
 
 void
 Location::GetHostname(nsAString& aHostname,
                       nsIPrincipal& aSubjectPrincipal,
                       ErrorResult& aRv)
 {
   if (!CallerSubsumes(&aSubjectPrincipal)) {
@@ -399,17 +402,17 @@ Location::SetHostname(const nsAString& a
 
   aRv = NS_MutateURI(uri)
           .SetHost(NS_ConvertUTF16toUTF8(aHostname))
           .Finalize(uri);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
-  aRv = SetURI(uri);
+  SetURI(uri, aSubjectPrincipal, aRv);
 }
 
 nsresult
 Location::GetHref(nsAString& aHref)
 {
   aHref.Truncate();
 
   nsCOMPtr<nsIURI> uri;
@@ -425,33 +428,35 @@ Location::GetHref(nsAString& aHref)
   }
 
   AppendUTF8toUTF16(uriString, aHref);
   return NS_OK;
 }
 
 void
 Location::SetHref(const nsAString& aHref,
+                  nsIPrincipal& aSubjectPrincipal,
                   ErrorResult& aRv)
 {
-  DoSetHref(aHref, false, aRv);
+  DoSetHref(aHref, aSubjectPrincipal, false, aRv);
 }
 
 void
-Location::DoSetHref(const nsAString& aHref, bool aReplace, ErrorResult& aRv)
+Location::DoSetHref(const nsAString& aHref, nsIPrincipal& aSubjectPrincipal,
+                    bool aReplace, ErrorResult& aRv)
 {
   // Get the source of the caller
   nsCOMPtr<nsIURI> base = GetSourceBaseURL();
-
-  aRv = SetHrefWithBase(aHref, base, aReplace);
+  SetHrefWithBase(aHref, base, aSubjectPrincipal, aReplace, aRv);
 }
 
-nsresult
+void
 Location::SetHrefWithBase(const nsAString& aHref, nsIURI* aBase,
-                          bool aReplace)
+                          nsIPrincipal& aSubjectPrincipal,
+                          bool aReplace, ErrorResult& aRv)
 {
   nsresult result;
   nsCOMPtr<nsIURI> newUri;
 
   nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
 
   if (nsIDocument* doc = GetEntryDocument()) {
     result = NS_NewURI(getter_AddRefs(newUri), aHref,
@@ -483,19 +488,21 @@ Location::SetHrefWithBase(const nsAStrin
         // since we only want to replace if the location is set by a
         // <script> tag in the same window.  See bug 178729.
         nsCOMPtr<nsIScriptGlobalObject> ourGlobal =
           docShell ? docShell->GetScriptGlobalObject() : nullptr;
         inScriptTag = (ourGlobal == scriptContext->GetGlobalObject());
       }
     }
 
-    return SetURI(newUri, aReplace || inScriptTag);
+    SetURI(newUri, aSubjectPrincipal, aRv, aReplace || inScriptTag);
+    return;
   }
-  return result;
+
+  aRv.Throw(result);
 }
 
 void
 Location::GetOrigin(nsAString& aOrigin,
                     nsIPrincipal& aSubjectPrincipal,
                     ErrorResult& aRv)
 {
   if (!CallerSubsumes(&aSubjectPrincipal)) {
@@ -566,17 +573,17 @@ Location::SetPathname(const nsAString& a
 
   nsresult rv = NS_MutateURI(uri)
                   .SetFilePath(NS_ConvertUTF16toUTF8(aPathname))
                   .Finalize(uri);
   if (NS_FAILED(rv)) {
     return;
   }
 
-  aRv = SetURI(uri);
+  SetURI(uri, aSubjectPrincipal, aRv);
 }
 
 void
 Location::GetPort(nsAString& aPort,
                   nsIPrincipal& aSubjectPrincipal,
                   ErrorResult& aRv)
 {
   if (!CallerSubsumes(&aSubjectPrincipal)) {
@@ -635,17 +642,17 @@ Location::SetPort(const nsAString& aPort
 
   aRv = NS_MutateURI(uri)
           .SetPort(port)
           .Finalize(uri);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
-  aRv = SetURI(uri);
+  SetURI(uri, aSubjectPrincipal, aRv);
 }
 
 void
 Location::GetProtocol(nsAString& aProtocol,
                       nsIPrincipal& aSubjectPrincipal,
                       ErrorResult& aRv)
 {
   if (!CallerSubsumes(&aSubjectPrincipal)) {
@@ -732,17 +739,17 @@ Location::SetProtocol(const nsAString& a
     return;
   }
 
   if (!isHttp && !isHttps) {
     // No-op, per spec.
     return;
   }
 
-  aRv = SetURI(uri);
+  SetURI(uri, aSubjectPrincipal, aRv);
 }
 
 void
 Location::GetSearch(nsAString& aSearch,
                     nsIPrincipal& aSubjectPrincipal,
                     ErrorResult& aRv)
 {
   if (!CallerSubsumes(&aSubjectPrincipal)) {
@@ -797,17 +804,17 @@ Location::SetSearch(const nsAString& aSe
     aRv = NS_MutateURI(uri)
             .SetQuery(NS_ConvertUTF16toUTF8(aSearch))
             .Finalize(uri);
   }
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
-  aRv = SetURI(uri);
+  SetURI(uri, aSubjectPrincipal, aRv);
 }
 
 nsresult
 Location::Reload(bool aForceget)
 {
   nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
   nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell));
   nsCOMPtr<nsPIDOMWindowOuter> window = docShell ? docShell->GetWindow()
@@ -853,30 +860,30 @@ Location::Reload(bool aForceget)
   return rv;
 }
 
 void
 Location::Replace(const nsAString& aUrl,
                   nsIPrincipal& aSubjectPrincipal,
                   ErrorResult& aRv)
 {
-  DoSetHref(aUrl, true, aRv);
+  DoSetHref(aUrl, aSubjectPrincipal, true, aRv);
 }
 
 void
 Location::Assign(const nsAString& aUrl,
                  nsIPrincipal& aSubjectPrincipal,
                  ErrorResult& aRv)
 {
   if (!CallerSubsumes(&aSubjectPrincipal)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
-  DoSetHref(aUrl, false, aRv);
+  DoSetHref(aUrl, aSubjectPrincipal, false, aRv);
 }
 
 already_AddRefed<nsIURI>
 Location::GetSourceBaseURL()
 {
   nsIDocument* doc = GetEntryDocument();
   // If there's no entry document, we either have no Script Entry Point or one
   // that isn't a DOM Window.  This doesn't generally happen with the DOM, but
--- a/dom/base/Location.h
+++ b/dom/base/Location.h
@@ -63,16 +63,17 @@ public:
       aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
       return;
     }
 
     aError = GetHref(aHref);
   }
 
   void SetHref(const nsAString& aHref,
+               nsIPrincipal& aSubjectPrincipal,
                ErrorResult& aError);
 
   void GetOrigin(nsAString& aOrigin,
                  nsIPrincipal& aSubjectPrincipal,
                  ErrorResult& aError);
 
   void GetProtocol(nsAString& aProtocol,
                    nsIPrincipal& aSubjectPrincipal,
@@ -161,27 +162,37 @@ protected:
   virtual ~Location();
 
   // In the case of jar: uris, we sometimes want the place the jar was
   // fetched from as the URI instead of the jar: uri itself.  Pass in
   // true for aGetInnermostURI when that's the case.
   // Note, this method can return NS_OK with a null value for aURL. This happens
   // if the docShell is null.
   nsresult GetURI(nsIURI** aURL, bool aGetInnermostURI = false);
-  nsresult SetURI(nsIURI* aURL, bool aReplace = false);
-  nsresult SetHrefWithBase(const nsAString& aHref, nsIURI* aBase,
-                           bool aReplace);
+  void SetURI(nsIURI* aURL, nsIPrincipal& aSubjectPrincipal,
+              ErrorResult& aRv, bool aReplace = false);
+  void SetHrefWithBase(const nsAString& aHref, nsIURI* aBase,
+                       nsIPrincipal& aSubjectPrincipal,
+                       bool aReplace, ErrorResult& aRv);
 
   // Helper for Assign/SetHref/Replace
-  void DoSetHref(const nsAString& aHref, bool aReplace, ErrorResult& aRv);
+  void DoSetHref(const nsAString& aHref, nsIPrincipal& aSubjectPrincipal,
+                 bool aReplace, ErrorResult& aRv);
 
   // Get the base URL we should be using for our relative URL
   // resolution for SetHref/Assign/Replace.
   already_AddRefed<nsIURI> GetSourceBaseURL();
-  nsresult CheckURL(nsIURI *url, nsDocShellLoadInfo** aLoadInfo);
+
+  // Check whether it's OK to load the given url with the given subject
+  // principal, and if so construct the right nsDocShellLoadInfo for the load
+  // and return it.
+  already_AddRefed<nsDocShellLoadInfo> CheckURL(nsIURI *url,
+                                                nsIPrincipal& aSubjectPrincipal,
+                                                ErrorResult& aRv);
+
   bool CallerSubsumes(nsIPrincipal* aSubjectPrincipal);
 
   nsString mCachedHash;
   nsCOMPtr<nsPIDOMWindowInner> mInnerWindow;
   nsWeakPtr mDocShell;
 };
 
 } // dom namespace
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -111,8 +111,9 @@ MSG_DEF(MSG_INVALID_AUDIOPARAM_METHOD_ST
 MSG_DEF(MSG_INVALID_AUDIOPARAM_METHOD_END_TIME_ERROR, 0, JSEXN_RANGEERR, "The end time for an AudioParam method must be non-negative.")
 MSG_DEF(MSG_INVALID_AUDIOPARAM_EXPONENTIAL_VALUE_ERROR, 0, JSEXN_RANGEERR, "The value passed to exponentialRampToValueAtTime must be positive.")
 MSG_DEF(MSG_INVALID_AUDIOPARAM_EXPONENTIAL_CONSTANT_ERROR, 0, JSEXN_RANGEERR, "The exponential constant passed to setTargetAtTime must be non-negative.")
 MSG_DEF(MSG_VALUE_OUT_OF_RANGE, 1, JSEXN_RANGEERR, "The value for the {0} is outside the valid range.")
 MSG_DEF(MSG_INVALID_PANNERNODE_REFDISTANCE_ERROR, 0, JSEXN_RANGEERR, "The refDistance value passed to PannerNode must not be negative.")
 MSG_DEF(MSG_INVALID_PANNERNODE_MAXDISTANCE_ERROR, 0, JSEXN_RANGEERR, "The maxDistance value passed to PannerNode must be positive.")
 MSG_DEF(MSG_INVALID_PANNERNODE_ROLLOFF_ERROR, 0, JSEXN_RANGEERR, "The rolloffFactor value passed to PannerNode must not be negative.")
 MSG_DEF(MSG_NOT_ARRAY_NOR_UNDEFINED, 1, JSEXN_TYPEERR, "{0} is neither an array nor undefined.")
+MSG_DEF(MSG_URL_NOT_LOADABLE, 1, JSEXN_TYPEERR, "Access to '{0}' from script denied.")
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -2407,18 +2407,18 @@ ScriptLoader::EvaluateScript(ScriptLoadR
                                               aRequest->ScriptBinASTData().length(),
                                               &script);
               } else {
                 MOZ_ASSERT(aRequest->IsTextSource());
                 auto srcBuf = GetScriptSource(cx, aRequest);
 
                 if (srcBuf) {
                   if (recordreplay::IsRecordingOrReplaying()) {
-                    recordreplay::NoteContentParse(this, options.filename(), "application/javascript",
-                                                   srcBuf->get(), srcBuf->length());
+                    recordreplay::NoteContentParse16(this, options.filename(), "application/javascript",
+                                                     srcBuf->get(), srcBuf->length());
                   }
                   rv = exec.CompileAndExec(options, *srcBuf, &script);
                 } else {
                   rv = NS_ERROR_OUT_OF_MEMORY;
                 }
               }
             }
           }
--- a/dom/webidl/Location.webidl
+++ b/dom/webidl/Location.webidl
@@ -15,17 +15,17 @@
 interface Location {
   // Bug 824857: no support for stringifier attributes yet.
   //  stringifier attribute USVString href;
 
   // Bug 824857 should remove this.
   [Throws, NeedsSubjectPrincipal]
   stringifier;
 
-  [Throws, CrossOriginWritable, GetterNeedsSubjectPrincipal]
+  [Throws, CrossOriginWritable, NeedsSubjectPrincipal]
            attribute USVString href;
   [Throws, NeedsSubjectPrincipal]
   readonly attribute USVString origin;
   [Throws, NeedsSubjectPrincipal]
            attribute USVString protocol;
   [Throws, NeedsSubjectPrincipal]
            attribute USVString host;
   [Throws, NeedsSubjectPrincipal]
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -11,25 +11,25 @@
 #include "mozilla/Move.h"
 
 #include <stdlib.h>
 
 #include "jspubtd.h"
 
 #include "builtin/Array.h"
 #include "builtin/Reflect.h"
+#include "frontend/ParseNode.h"
 #include "frontend/Parser.h"
 #include "frontend/TokenStream.h"
 #include "js/CharacterEncoding.h"
 #include "js/StableStringChars.h"
 #include "vm/JSAtom.h"
 #include "vm/JSObject.h"
 #include "vm/RegExpObject.h"
 
-#include "frontend/ParseNode-inl.h"
 #include "vm/JSObject-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 
 using JS::AutoStableStringChars;
 using JS::AutoValueArray;
 using JS::CompileOptions;
--- a/js/src/frontend/BinSource-auto.cpp
+++ b/js/src/frontend/BinSource-auto.cpp
@@ -15,23 +15,23 @@
 #include "mozilla/Move.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Vector.h"
 
 #include "frontend/BinSource-macros.h"
 #include "frontend/BinSource.h"
 #include "frontend/BinTokenReaderTester.h"
 #include "frontend/FullParseHandler.h"
+#include "frontend/ParseNode.h"
 #include "frontend/Parser.h"
 #include "frontend/SharedContext.h"
 
 #include "vm/RegExpObject.h"
 
 #include "frontend/ParseContext-inl.h"
-#include "frontend/ParseNode-inl.h"
 
 namespace js {
 namespace frontend {
 
 // Compare a bunch of `uint8_t` values (as returned by the tokenizer_) with
 // a string literal (and ONLY a string literal).
 template<typename Tok, size_t N>
 bool operator==(const typename Tok::Chars& left, const char (&right)[N]) {
@@ -2838,18 +2838,19 @@ BinASTParser<Tok>::parseInterfaceCallExp
                 parseContext_->functionBox()->usesApply = true;
         } else if (prop == cx_->names().call) {
             op = JSOP_FUNCALL;
         }
     }
 
     // Check for direct calls to `eval`.
     if (factory_.isEvalName(callee, cx_)) {
-        if (!parseContext_->varScope().lookupDeclaredNameForAdd(callee->name())
-         && !parseContext_->innermostScope()->lookupDeclaredNameForAdd(callee->name())) {
+        if (!parseContext_->varScope().lookupDeclaredNameForAdd(cx_->names().eval)
+         && !parseContext_->innermostScope()->lookupDeclaredNameForAdd(cx_->names().eval))
+        {
             // This is a direct call to `eval`.
             if (!parseContext_->sc()->hasDirectEval()) {
                 return raiseMissingDirectEvalInAssertedScope();
             }
 
             op = parseContext_->sc()->strict() ? JSOP_STRICTEVAL : JSOP_EVAL;
         }
     }
@@ -4540,17 +4541,17 @@ BinASTParser<Tok>::parseInterfaceShortha
     const BinField expected_fields[1] = { BinField::Name };
     MOZ_TRY(tokenizer_->checkFields(kind, fields, expected_fields));
 #endif // defined(DEBUG)
 
     BINJS_MOZ_TRY_DECL(name, parseIdentifierExpression());
 
     MOZ_ASSERT(name->isKind(ParseNodeKind::Name));
     MOZ_ASSERT(!factory_.isUsableAsObjectPropertyName(name));
-    BINJS_TRY_DECL(propName, factory_.newObjectLiteralPropertyName(name->name(), tokenizer_->pos(start)));
+    BINJS_TRY_DECL(propName, factory_.newObjectLiteralPropertyName(name->template as<NameNode>().name(), tokenizer_->pos(start)));
 
     BINJS_TRY_DECL(result, factory_.newObjectMethodOrPropertyDefinition(propName, name, AccessorType::None));
     result->setKind(ParseNodeKind::Shorthand);
     return result;
 }
 
 template<typename Tok> JS::Result<ParseNode*>
 BinASTParser<Tok>::parseInterfaceSpreadElement(const size_t start, const BinKind kind, const BinFields& fields)
--- a/js/src/frontend/BinSource.cpp
+++ b/js/src/frontend/BinSource.cpp
@@ -12,24 +12,24 @@
 #include "mozilla/Move.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Vector.h"
 
 #include "frontend/BinSource-macros.h"
 #include "frontend/BinTokenReaderTester.h"
 #include "frontend/FullParseHandler.h"
+#include "frontend/ParseNode.h"
 #include "frontend/Parser.h"
 #include "frontend/SharedContext.h"
 
 #include "js/Result.h"
 #include "vm/RegExpObject.h"
 
 #include "frontend/ParseContext-inl.h"
-#include "frontend/ParseNode-inl.h"
 #include "vm/JSContext-inl.h"
 
 // # About compliance with EcmaScript
 //
 // For the moment, this parser implements ES5. Future versions will be extended
 // to ES6 and further on.
 //
 // By design, it does NOT implement Annex B.3.3. If possible, we would like
@@ -553,17 +553,17 @@ BinASTParser<Tok>::checkPositionalParame
 
             JSAtom* name = positionalParams.get()[i];
             if (!name) {
                 // Step 2.a.ii.1.
                 return raiseError("Expected destructuring/rest parameter per AssertedParameterScope.paramNames, got positional parameter");
             }
 
             // Step 2.a.i.
-            if (param->name() != name) {
+            if (param->as<NameNode>().name() != name) {
                 // Step 2.a.ii.1.
                 return raiseError("Name mismatch between AssertedPositionalParameterName in AssertedParameterScope.paramNames and actual parameter");
             }
 
             // Step 2.a.i.1.
             // Implicitly done.
         } else {
             // Destructuring parameter.
--- a/js/src/frontend/BinSource.yaml
+++ b/js/src/frontend/BinSource.yaml
@@ -33,23 +33,23 @@ cpp:
         #include "mozilla/Move.h"
         #include "mozilla/PodOperations.h"
         #include "mozilla/Vector.h"
 
         #include "frontend/BinSource-macros.h"
         #include "frontend/BinSource.h"
         #include "frontend/BinTokenReaderTester.h"
         #include "frontend/FullParseHandler.h"
+        #include "frontend/ParseNode.h"
         #include "frontend/Parser.h"
         #include "frontend/SharedContext.h"
 
         #include "vm/RegExpObject.h"
 
         #include "frontend/ParseContext-inl.h"
-        #include "frontend/ParseNode-inl.h"
 
         namespace js {
         namespace frontend {
 
         // Compare a bunch of `uint8_t` values (as returned by the tokenizer_) with
         // a string literal (and ONLY a string literal).
         template<typename Tok, size_t N>
         bool operator==(const typename Tok::Chars& left, const char (&right)[N]) {
@@ -501,18 +501,19 @@ CallExpression:
                     parseContext_->functionBox()->usesApply = true;
             } else if (prop == cx_->names().call) {
                 op = JSOP_FUNCALL;
             }
         }
 
         // Check for direct calls to `eval`.
         if (factory_.isEvalName(callee, cx_)) {
-            if (!parseContext_->varScope().lookupDeclaredNameForAdd(callee->name())
-             && !parseContext_->innermostScope()->lookupDeclaredNameForAdd(callee->name())) {
+            if (!parseContext_->varScope().lookupDeclaredNameForAdd(cx_->names().eval)
+             && !parseContext_->innermostScope()->lookupDeclaredNameForAdd(cx_->names().eval))
+            {
                 // This is a direct call to `eval`.
                 if (!parseContext_->sc()->hasDirectEval()) {
                     return raiseMissingDirectEvalInAssertedScope();
                 }
 
                 op = parseContext_->sc()->strict() ? JSOP_STRICTEVAL : JSOP_EVAL;
             }
         }
@@ -1108,17 +1109,17 @@ Script:
     build:
         MOZ_TRY(checkClosedVars(parseContext_->varScope()));
         BINJS_MOZ_TRY_DECL(result, appendDirectivesToBody(/* body = */ statements, /* directives = */ directives));
 
 ShorthandProperty:
     build: |
         MOZ_ASSERT(name->isKind(ParseNodeKind::Name));
         MOZ_ASSERT(!factory_.isUsableAsObjectPropertyName(name));
-        BINJS_TRY_DECL(propName, factory_.newObjectLiteralPropertyName(name->name(), tokenizer_->pos(start)));
+        BINJS_TRY_DECL(propName, factory_.newObjectLiteralPropertyName(name->template as<NameNode>().name(), tokenizer_->pos(start)));
 
         BINJS_TRY_DECL(result, factory_.newObjectMethodOrPropertyDefinition(propName, name, AccessorType::None));
         result->setKind(ParseNodeKind::Shorthand);
 
 SwitchCase:
     type-ok:
         CaseClause*
     build: |
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -31,16 +31,17 @@
 #include "frontend/ElemOpEmitter.h"
 #include "frontend/EmitterScope.h"
 #include "frontend/ExpressionStatementEmitter.h"
 #include "frontend/ForInEmitter.h"
 #include "frontend/ForOfEmitter.h"
 #include "frontend/ForOfLoopControl.h"
 #include "frontend/IfEmitter.h"
 #include "frontend/NameOpEmitter.h"
+#include "frontend/ParseNode.h"
 #include "frontend/Parser.h"
 #include "frontend/PropOpEmitter.h"
 #include "frontend/SwitchEmitter.h"
 #include "frontend/TDZCheckCache.h"
 #include "frontend/TryEmitter.h"
 #include "frontend/WhileEmitter.h"
 #include "js/CompileOptions.h"
 #include "vm/BytecodeUtil.h"
@@ -48,17 +49,16 @@
 #include "vm/GeneratorObject.h"
 #include "vm/JSAtom.h"
 #include "vm/JSContext.h"
 #include "vm/JSFunction.h"
 #include "vm/JSScript.h"
 #include "vm/Stack.h"
 #include "wasm/AsmJS.h"
 
-#include "frontend/ParseNode-inl.h"
 #include "vm/JSObject-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 
 using mozilla::AssertedCast;
 using mozilla::DebugOnly;
 using mozilla::Maybe;
@@ -1711,19 +1711,19 @@ BytecodeEmitter::emitGetNameAtLocation(J
     if (!noe.emitGet()) {
         return false;
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitGetName(ParseNode* pn)
-{
-    return emitGetName(pn->name());
+BytecodeEmitter::emitGetName(NameNode* name)
+{
+    return emitGetName(name->name());
 }
 
 bool
 BytecodeEmitter::emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc)
 {
     // Dynamic accesses have TDZ checks built into their VM code and should
     // never emit explicit TDZ checks.
     MOZ_ASSERT(loc.hasKnownSlot());
@@ -2216,17 +2216,17 @@ bool
 BytecodeEmitter::emitSetThis(BinaryNode* setThisNode)
 {
     // ParseNodeKind::SetThis is used to update |this| after a super() call
     // in a derived class constructor.
 
     MOZ_ASSERT(setThisNode->isKind(ParseNodeKind::SetThis));
     MOZ_ASSERT(setThisNode->left()->isKind(ParseNodeKind::Name));
 
-    RootedAtom name(cx, setThisNode->left()->name());
+    RootedAtom name(cx, setThisNode->left()->as<NameNode>().name());
 
     // The 'this' binding is not lexical, but due to super() semantics this
     // initialization needs to be treated as a lexical one.
     NameLocation loc = lookupName(name);
     NameLocation lexicalLoc;
     if (loc.kind() == NameLocation::Kind::FrameSlot) {
         lexicalLoc = NameLocation::FrameSlot(BindingKind::Let, loc.frameSlot());
     } else if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) {
@@ -2553,17 +2553,17 @@ BytecodeEmitter::emitSetOrInitializeDest
         // Per its post-condition, emitDestructuringOps has left the
         // to-be-destructured value on top of the stack.
         if (!emit1(JSOP_POP)) {
             return false;
         }
     } else {
         switch (target->getKind()) {
           case ParseNodeKind::Name: {
-            RootedAtom name(cx, target->name());
+            RootedAtom name(cx, target->as<NameNode>().name());
             NameLocation loc;
             NameOpEmitter::Kind kind;
             switch (flav) {
               case DestructuringDeclaration:
                 loc = lookupName(name);
                 kind = NameOpEmitter::Kind::Initialize;
                 break;
               case DestructuringFormalParameterInVarScope: {
@@ -3022,17 +3022,17 @@ bool
 BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern)
 {
     if (!emitTree(initializer)) {
         return false;
     }
 
     if (initializer->isDirectRHSAnonFunction()) {
         MOZ_ASSERT(!pattern->isInParens());
-        RootedAtom name(cx, pattern->name());
+        RootedAtom name(cx, pattern->as<NameNode>().name());
         if (!setOrEmitSetFunName(initializer, name)) {
             return false;
         }
     }
 
     return true;
 }
 
@@ -3764,26 +3764,27 @@ BytecodeEmitter::emitDeclarationList(Lis
             if (!emitDestructuringOps(pattern, DestructuringDeclaration)) {
                 return false;
             }
 
             if (!emit1(JSOP_POP)) {
                 return false;
             }
         } else {
-            if (!emitSingleDeclaration(declList, decl, decl->as<NameNode>().initializer())) {
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-bool
-BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl,
+            NameNode* name = &decl->as<NameNode>();
+            if (!emitSingleDeclaration(declList, name, name->initializer())) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+bool
+BytecodeEmitter::emitSingleDeclaration(ListNode* declList, NameNode* decl,
                                        ParseNode* initializer)
 {
     MOZ_ASSERT(decl->isKind(ParseNodeKind::Name));
 
     // Nothing to do for initializer-less 'var' declarations, as there's no TDZ.
     if (!initializer && declList->isKind(ParseNodeKind::Var)) {
         return true;
     }
@@ -3860,35 +3861,36 @@ CompoundAssignmentParseNodeKindToJSOp(Pa
 bool
 BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs)
 {
     bool isCompound = compoundOp != JSOP_NOP;
 
     // Name assignments are handled separately because choosing ops and when
     // to emit BINDNAME is involved and should avoid duplication.
     if (lhs->isKind(ParseNodeKind::Name)) {
+        NameNode* nameNode = &lhs->as<NameNode>();
+        RootedAtom name(cx, nameNode->name());
         NameOpEmitter noe(this,
-                          lhs->name(),
+                          name,
                           isCompound
                           ? NameOpEmitter::Kind::CompoundAssignment
                           : NameOpEmitter::Kind::SimpleAssignment);
         if (!noe.prepareForRhs()) {                       // ENV? VAL?
             return false;
         }
 
         // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on
         // the top of the stack and we need to pick the right RHS value.
         uint8_t offset = noe.emittedBindOp() ? 2 : 1;
         if (!EmitAssignmentRhs(this, rhs, offset)) {      // ENV? VAL? RHS
             return false;
         }
         if (rhs && rhs->isDirectRHSAnonFunction()) {
-            MOZ_ASSERT(!lhs->isInParens());
+            MOZ_ASSERT(!nameNode->isInParens());
             MOZ_ASSERT(!isCompound);
-            RootedAtom name(cx, lhs->name());
             if (!setOrEmitSetFunName(rhs, name)) {         // ENV? VAL? RHS
                 return false;
             }
         }
 
         // Emit the compound assignment op if there is one.
         if (isCompound) {
             if (!emit1(compoundOp)) {                     // ENV? VAL
@@ -4231,23 +4233,23 @@ ParseNode::getConstantValue(JSContext* c
       }
       default:
         MOZ_CRASH("Unexpected node");
     }
     return false;
 }
 
 bool
-BytecodeEmitter::emitSingletonInitialiser(ParseNode* pn)
+BytecodeEmitter::emitSingletonInitialiser(ListNode* objOrArray)
 {
     NewObjectKind newKind =
-        (pn->getKind() == ParseNodeKind::Object) ? SingletonObject : TenuredObject;
+        (objOrArray->getKind() == ParseNodeKind::Object) ? SingletonObject : TenuredObject;
 
     RootedValue value(cx);
-    if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value, nullptr, 0, newKind)) {
+    if (!objOrArray->getConstantValue(cx, ParseNode::AllowObjects, &value, nullptr, 0, newKind)) {
         return false;
     }
 
     MOZ_ASSERT_IF(newKind == SingletonObject, value.toObject().isSingleton());
 
     ObjectBox* objbox = parser->newObjectBox(&value.toObject());
     if (!objbox) {
         return false;
@@ -4322,17 +4324,17 @@ BytecodeEmitter::emitCatch(BinaryNode* c
                 return false;
             }
             if (!emit1(JSOP_POP)) {
                 return false;
             }
             break;
 
           case ParseNodeKind::Name:
-            if (!emitLexicalInitialization(param)) {
+            if (!emitLexicalInitialization(&param->as<NameNode>())) {
                 return false;
             }
             if (!emit1(JSOP_POP)) {
                 return false;
             }
             break;
 
           default:
@@ -4904,17 +4906,18 @@ BytecodeEmitter::emitInitializeForInOrOf
     if (!updateSourceCoordNotes(target->pn_pos.begin)) {
         return false;
     }
 
     MOZ_ASSERT(target->isForLoopDeclaration());
     target = parser->astGenerator().singleBindingFromDeclaration(&target->as<ListNode>());
 
     if (target->isKind(ParseNodeKind::Name)) {
-        NameOpEmitter noe(this, target->name(), NameOpEmitter::Kind::Initialize);
+        NameNode* nameNode = &target->as<NameNode>();
+        NameOpEmitter noe(this, nameNode->name(), NameOpEmitter::Kind::Initialize);
         if (!noe.prepareForRhs()) {
             return false;
         }
         if (noe.emittedBindOp()) {
             // Per-iteration initialization in for-in/of loops computes the
             // iteration value *before* initializing.  Thus the initializing
             // value may be buried under a bind-specific value on the stack.
             // Swap it to the top of the stack.
@@ -4960,18 +4963,17 @@ BytecodeEmitter::emitForOf(ForNode* forO
 
     ParseNode* forHeadExpr = forOfHead->kid3();
 
     // Certain builtins (e.g. Array.from) are implemented in self-hosting
     // as for-of loops.
     bool allowSelfHostedIter = false;
     if (emitterMode == BytecodeEmitter::SelfHosting &&
         forHeadExpr->isKind(ParseNodeKind::Call) &&
-        forHeadExpr->as<BinaryNode>().left()->name() == cx->names().allowContentIter)
-    {
+        forHeadExpr->as<BinaryNode>().left()->isName(cx->names().allowContentIter)) {
         allowSelfHostedIter = true;
     }
 
     ForOfEmitter forOf(this, headLexicalEmitterScope, allowSelfHostedIter, iterKind);
 
     if (!forOf.emitIterated()) {                          //
         return false;
     }
@@ -5031,17 +5033,18 @@ BytecodeEmitter::emitForIn(ForNode* forI
             if (ParseNode* initializer = decl->as<NameNode>().initializer()) {
                 MOZ_ASSERT(forInTarget->isKind(ParseNodeKind::Var),
                            "for-in initializers are only permitted for |var| declarations");
 
                 if (!updateSourceCoordNotes(decl->pn_pos.begin)) {
                     return false;
                 }
 
-                NameOpEmitter noe(this, decl->name(), NameOpEmitter::Kind::Initialize);
+                NameNode* nameNode = &decl->as<NameNode>();
+                NameOpEmitter noe(this, nameNode->name(), NameOpEmitter::Kind::Initialize);
                 if (!noe.prepareForRhs()) {
                     return false;
                 }
                 if (!emitInitializer(initializer, decl)) {
                     return false;
                 }
                 if (!noe.emitAssignment()) {
                     return false;
@@ -5612,23 +5615,22 @@ BytecodeEmitter::emitContinue(PropertyNa
         }
     } else {
         target = findInnermostNestableControl<LoopControl>();
     }
     return emitGoto(target, &target->continues, SRC_CONTINUE);
 }
 
 bool
-BytecodeEmitter::emitGetFunctionThis(ParseNode* pn)
+BytecodeEmitter::emitGetFunctionThis(NameNode* thisName)
 {
     MOZ_ASSERT(sc->thisBinding() == ThisBinding::Function);
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::Name));
-    MOZ_ASSERT(pn->name() == cx->names().dotThis);
-
-    return emitGetFunctionThis(Some(pn->pn_pos.begin));
+    MOZ_ASSERT(thisName->isName(cx->names().dotThis));
+
+    return emitGetFunctionThis(Some(thisName->pn_pos.begin));
 }
 
 bool
 BytecodeEmitter::emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset)
 {
     if (offset) {
         if (!updateLineNumberNotes(*offset)) {
             return false;
@@ -5646,23 +5648,25 @@ BytecodeEmitter::emitGetFunctionThis(con
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitGetThisForSuperBase(UnaryNode* superBase)
 {
     MOZ_ASSERT(superBase->isKind(ParseNodeKind::SuperBase));
-    return emitGetFunctionThis(superBase->kid());         // THIS
+    NameNode* nameNode = &superBase->kid()->as<NameNode>();
+    return emitGetFunctionThis(nameNode);                 // THIS
 }
 
 bool
 BytecodeEmitter::emitThisLiteral(ThisLiteral* pn)
 {
-    if (ParseNode* thisName = pn->kid()) {
+    if (ParseNode* kid = pn->kid()) {
+        NameNode* thisName = &kid->as<NameNode>();
         return emitGetFunctionThis(thisName);             // THIS
     }
 
     if (sc->thisBinding() == ThisBinding::Module) {
         return emit1(JSOP_UNDEFINED);                     // UNDEF
     }
 
     MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global);
@@ -6515,17 +6519,17 @@ BytecodeEmitter::emitSelfHostedCallFunct
     // invokes the callee with the correct |this| object and arguments.
     // callFunction(fun, thisArg, arg0, arg1) thus becomes:
     // - emit lookup for fun
     // - emit lookup for thisArg
     // - emit lookups for arg0, arg1
     //
     // argc is set to the amount of actually emitted args and the
     // emitting of args below is disabled by setting emitArgs to false.
-    ParseNode* calleeNode = callNode->left();
+    NameNode* calleeNode = &callNode->left()->as<NameNode>();
     ListNode* argsList = &callNode->right()->as<ListNode>();
 
     const char* errorName = SelfHostedCallFunctionName(calleeNode->name(), cx);
 
     if (argsList->count() < 2) {
         reportError(callNode, JSMSG_MORE_ARGS_NEEDED, errorName, "2", "s");
         return false;
     }
@@ -6535,18 +6539,17 @@ BytecodeEmitter::emitSelfHostedCallFunct
         reportError(callNode, JSMSG_NOT_CONSTRUCTOR, errorName);
         return false;
     }
 
     bool constructing = calleeNode->name() == cx->names().constructContentFunction;
     ParseNode* funNode = argsList->head();
     if (constructing) {
         callOp = JSOP_NEW;
-    } else if (funNode->getKind() == ParseNodeKind::Name &&
-               funNode->name() == cx->names().std_Function_apply) {
+    } else if (funNode->isName(cx->names().std_Function_apply)) {
         callOp = JSOP_FUNAPPLY;
     }
 
     if (!emitTree(funNode)) {
         return false;
     }
 
 #ifdef DEBUG
@@ -6730,42 +6733,42 @@ BytecodeEmitter::emitSelfHostedGetProper
     if (!emitTree(objNode)) {
         return false;
     }
 
     return emitElemOpBase(JSOP_GETELEM_SUPER);
 }
 
 bool
-BytecodeEmitter::isRestParameter(ParseNode* pn)
+BytecodeEmitter::isRestParameter(ParseNode* expr)
 {
     if (!sc->isFunctionBox()) {
         return false;
     }
 
     FunctionBox* funbox = sc->asFunctionBox();
     RootedFunction fun(cx, funbox->function());
     if (!funbox->hasRest()) {
         return false;
     }
 
-    if (!pn->isKind(ParseNodeKind::Name)) {
-        if (emitterMode == BytecodeEmitter::SelfHosting && pn->isKind(ParseNodeKind::Call)) {
-            BinaryNode* callNode = &pn->as<BinaryNode>();
+    if (!expr->isKind(ParseNodeKind::Name)) {
+        if (emitterMode == BytecodeEmitter::SelfHosting &&
+            expr->isKind(ParseNodeKind::Call))
+        {
+            BinaryNode* callNode = &expr->as<BinaryNode>();
             ParseNode* calleeNode = callNode->left();
-            if (calleeNode->getKind() == ParseNodeKind::Name &&
-                calleeNode->name() == cx->names().allowContentIter)
-            {
+            if (calleeNode->isName(cx->names().allowContentIter)) {
                 return isRestParameter(callNode->right()->as<ListNode>().head());
             }
         }
         return false;
     }
 
-    JSAtom* name = pn->name();
+    JSAtom* name = expr->as<NameNode>().name();
     Maybe<NameLocation> paramLoc = locationOfNameBoundInFunctionScope(name);
     if (paramLoc && lookupName(name) == *paramLoc) {
         FunctionScope::Data* bindings = funbox->functionScopeBindings();
         if (bindings->nonPositionalFormalStart > 0) {
             // |paramName| can be nullptr when the rest destructuring syntax is
             // used: `function f(...[]) {}`.
             JSAtom* paramName =
                 bindings->trailingNames[bindings->nonPositionalFormalStart - 1].name();
@@ -6776,18 +6779,18 @@ BytecodeEmitter::isRestParameter(ParseNo
     return false;
 }
 
 bool
 BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call, CallOrNewEmitter& cone)
 {
     switch (callee->getKind()) {
       case ParseNodeKind::Name:
-        if (!cone.emitNameCallee(callee->name())) {       // CALLEE THIS
-            return false;
+        if (!cone.emitNameCallee(callee->as<NameNode>().name())) {
+            return false;                                 // CALLEE THIS
         }
         break;
       case ParseNodeKind::Dot: {
         MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         PropertyAccess* prop = &callee->as<PropertyAccess>();
         bool isSuper = prop->isSuper();
 
         PropOpEmitter& poe = cone.prepareForPropCallee(isSuper);
@@ -6953,17 +6956,17 @@ BytecodeEmitter::emitCallOrNew(BinaryNod
     ListNode* argsList = &callNode->right()->as<ListNode>();
     bool isSpread = JOF_OPTYPE(callNode->getOp()) == JOF_BYTE;
     if (calleeNode->isKind(ParseNodeKind::Name) && emitterMode == BytecodeEmitter::SelfHosting &&
         !isSpread)
     {
         // Calls to "forceInterpreter", "callFunction",
         // "callContentFunction", or "resumeGenerator" in self-hosted
         // code generate inline bytecode.
-        PropertyName* calleeName = calleeNode->name();
+        PropertyName* calleeName = calleeNode->as<NameNode>().name();
         if (calleeName == cx->names().callFunction ||
             calleeName == cx->names().callContentFunction ||
             calleeName == cx->names().constructContentFunction)
         {
             return emitSelfHostedCallFunction(callNode);
         }
         if (calleeName == cx->names().resumeGenerator) {
             return emitSelfHostedResumeGenerator(callNode);
@@ -7660,18 +7663,17 @@ BytecodeEmitter::emitArray(ParseNode* ar
             }
         } else {
             ParseNode* expr;
             if (elem->isKind(ParseNodeKind::Spread)) {
                 expr = elem->as<UnaryNode>().kid();
 
                 if (emitterMode == BytecodeEmitter::SelfHosting &&
                     expr->isKind(ParseNodeKind::Call) &&
-                    expr->as<BinaryNode>().left()->name() == cx->names().allowContentIter)
-                {
+                    expr->as<BinaryNode>().left()->isName(cx->names().allowContentIter)) {
                     allowSelfHostedIter = true;
                 }
             } else {
                 expr = elem;
             }
             if (!emitTree(expr)) {                                       // ARRAY INDEX? VALUE
                 return false;
             }
@@ -7950,17 +7952,17 @@ BytecodeEmitter::emitFunctionFormalParam
             {
                 return false;
             }
 
             if (!emit1(JSOP_POP)) {
                 return false;
             }
         } else if (hasParameterExprs || isRest) {
-            RootedAtom paramName(cx, bindingElement->name());
+            RootedAtom paramName(cx, bindingElement->as<NameNode>().name());
             NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope);
             NameOpEmitter noe(this, paramName, paramLoc, NameOpEmitter::Kind::Initialize);
             if (!noe.prepareForRhs()) {
                 return false;
             }
             if (hasParameterExprs) {
                 // If we had an initializer or a rest parameter, the value is
                 // already on the stack.
@@ -8097,19 +8099,19 @@ BytecodeEmitter::emitFunctionBody(ParseN
             return false;
         }
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitLexicalInitialization(ParseNode* pn)
-{
-    NameOpEmitter noe(this, pn->name(), NameOpEmitter::Kind::Initialize);
+BytecodeEmitter::emitLexicalInitialization(NameNode* name)
+{
+    NameOpEmitter noe(this, name->name(), NameOpEmitter::Kind::Initialize);
     if (!noe.prepareForRhs()) {
         return false;
     }
 
     // The caller has pushed the RHS to the top of the stack. Assert that the
     // name is lexical and no BIND[G]NAME ops were emitted.
     MOZ_ASSERT(noe.loc().isLexical());
     MOZ_ASSERT(!noe.emittedBindOp());
@@ -8321,29 +8323,28 @@ BytecodeEmitter::emitClass(ClassNode* cl
         return false;
     }
 
     if (!emit1(JSOP_POP)) {                                     // ... CONSTRUCTOR
         return false;
     }
 
     if (names) {
-        ParseNode* innerName = names->innerBinding();
+        NameNode* innerName = names->innerBinding();
         if (!emitLexicalInitialization(innerName)) {            // ... CONSTRUCTOR
             return false;
         }
 
         // Pop the inner scope.
         if (!emitterScope->leave(this)) {
             return false;
         }
         emitterScope.reset();
 
-        ParseNode* outerName = names->outerBinding();
-        if (outerName) {
+        if (NameNode* outerName = names->outerBinding()) {
             if (!emitLexicalInitialization(outerName)) {        // ... CONSTRUCTOR
                 return false;
             }
             // Only class statements make outer bindings, and they do not leave
             // themselves on the stack.
             if (!emit1(JSOP_POP)) {                             // ...
                 return false;
             }
@@ -8363,17 +8364,17 @@ BytecodeEmitter::emitExportDefault(Binar
     MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportDefault));
 
     ParseNode* nameNode = exportNode->left();
     if (!emitTree(nameNode)) {
         return false;
     }
 
     if (ParseNode* binding = exportNode->right()) {
-        if (!emitLexicalInitialization(binding)) {
+        if (!emitLexicalInitialization(&binding->as<NameNode>())) {
             return false;
         }
 
         if (nameNode->isDirectRHSAnonFunction()) {
             HandlePropertyName name = cx->names().default_;
             if (!setOrEmitSetFunName(nameNode, name)) {
                 return false;
             }
@@ -8799,17 +8800,17 @@ BytecodeEmitter::emitTree(ParseNode* pn,
 
       case ParseNodeKind::Object:
         if (!emitObject(&pn->as<ListNode>())) {
             return false;
         }
         break;
 
       case ParseNodeKind::Name:
-        if (!emitGetName(pn)) {
+        if (!emitGetName(&pn->as<NameNode>())) {
             return false;
         }
         break;
 
       case ParseNodeKind::TemplateStringList:
         if (!emitTemplateString(&pn->as<ListNode>())) {
             return false;
         }
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -12,16 +12,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/Span.h"
 
 #include "ds/InlineTable.h"
 #include "frontend/BCEParserHandle.h"
 #include "frontend/EitherParser.h"
 #include "frontend/JumpList.h"
 #include "frontend/NameFunctions.h"
+#include "frontend/ParseNode.h"
 #include "frontend/SharedContext.h"
 #include "frontend/SourceNotes.h"
 #include "frontend/ValueUsage.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/Interpreter.h"
 #include "vm/Iteration.h"
 
 namespace js {
@@ -516,17 +517,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool emitUint32Operand(JSOp op, uint32_t operand);
 
     // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand.
     MOZ_MUST_USE bool emitN(JSOp op, size_t extra, ptrdiff_t* offset = nullptr);
 
     MOZ_MUST_USE bool emitNumberOp(double dval);
 
     MOZ_MUST_USE bool emitThisLiteral(ThisLiteral* pn);
-    MOZ_MUST_USE bool emitGetFunctionThis(ParseNode* pn);
+    MOZ_MUST_USE bool emitGetFunctionThis(NameNode* thisName);
     MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset);
     MOZ_MUST_USE bool emitGetThisForSuperBase(UnaryNode* superBase);
     MOZ_MUST_USE bool emitSetThis(BinaryNode* setThisNode);
     MOZ_MUST_USE bool emitCheckDerivedClassConstructorReturn();
 
     // Handle jump opcodes and jump targets.
     MOZ_MUST_USE bool emitJumpTarget(JumpTarget* target);
     MOZ_MUST_USE bool emitJumpNoFallthrough(JSOp op, JumpList* jump);
@@ -580,28 +581,28 @@ struct MOZ_STACK_CLASS BytecodeEmitter
 
     MOZ_MUST_USE bool emitArgOp(JSOp op, uint16_t slot);
     MOZ_MUST_USE bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec);
 
     MOZ_MUST_USE bool emitGetNameAtLocation(JSAtom* name, const NameLocation& loc);
     MOZ_MUST_USE bool emitGetName(JSAtom* name) {
         return emitGetNameAtLocation(name, lookupName(name));
     }
-    MOZ_MUST_USE bool emitGetName(ParseNode* pn);
+    MOZ_MUST_USE bool emitGetName(NameNode* name);
 
     MOZ_MUST_USE bool emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc);
 
     MOZ_MUST_USE bool emitNameIncDec(UnaryNode* incDec);
 
     MOZ_MUST_USE bool emitDeclarationList(ListNode* declList);
-    MOZ_MUST_USE bool emitSingleDeclaration(ParseNode* declList, ParseNode* decl,
+    MOZ_MUST_USE bool emitSingleDeclaration(ListNode* declList, NameNode* decl,
                                             ParseNode* initializer);
 
     MOZ_MUST_USE bool emitNewInit();
-    MOZ_MUST_USE bool emitSingletonInitialiser(ParseNode* pn);
+    MOZ_MUST_USE bool emitSingletonInitialiser(ListNode* objOrArray);
 
     MOZ_MUST_USE bool emitPrepareIteratorResult();
     MOZ_MUST_USE bool emitFinishIteratorResult(bool done);
     MOZ_MUST_USE bool iteratorResultShape(unsigned* shape);
 
     MOZ_MUST_USE bool emitGetDotGeneratorInInnermostScope() {
         return emitGetDotGeneratorInScope(*innermostEmitterScope());
     }
@@ -756,17 +757,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool emitSequenceExpr(ListNode* node,
                                        ValueUsage valueUsage = ValueUsage::WantValue);
 
     MOZ_NEVER_INLINE MOZ_MUST_USE bool emitIncOrDec(UnaryNode* incDec);
 
     MOZ_MUST_USE bool emitConditionalExpression(ConditionalExpression& conditional,
                                                 ValueUsage valueUsage = ValueUsage::WantValue);
 
-    bool isRestParameter(ParseNode* pn);
+    bool isRestParameter(ParseNode* expr);
 
     MOZ_MUST_USE bool emitArguments(ListNode* argsList, bool isCall, bool isSpread,
                                     CallOrNewEmitter& cone);
     MOZ_MUST_USE bool emitCallOrNew(BinaryNode* callNode,
                                     ValueUsage valueUsage = ValueUsage::WantValue);
     MOZ_MUST_USE bool emitSelfHostedCallFunction(BinaryNode* callNode);
     MOZ_MUST_USE bool emitSelfHostedResumeGenerator(BinaryNode* callNode);
     MOZ_MUST_USE bool emitSelfHostedForceInterpreter();
@@ -787,18 +788,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool emitInitializeForInOrOfTarget(TernaryNode* forHead);
 
     MOZ_MUST_USE bool emitBreak(PropertyName* label);
     MOZ_MUST_USE bool emitContinue(PropertyName* label);
 
     MOZ_MUST_USE bool emitFunctionFormalParametersAndBody(ListNode* paramsBody);
     MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody);
     MOZ_MUST_USE bool emitInitializeFunctionSpecialNames();
-    MOZ_MUST_USE bool emitFunctionBody(ParseNode* pn);
-    MOZ_MUST_USE bool emitLexicalInitialization(ParseNode* pn);
+    MOZ_MUST_USE bool emitFunctionBody(ParseNode* funBody);
+    MOZ_MUST_USE bool emitLexicalInitialization(NameNode* name);
 
     // Emit bytecode for the spread operator.
     //
     // emitSpread expects the current index (I) of the array, the array itself
     // and the iterator to be on the stack in that order (iterator on the bottom).
     // It will pop the iterator and I, then iterate over the iterator by calling
     // |.next()| and put the results into the I-th element of array with
     // incrementing I, then push the result I (it will be original I +
--- a/js/src/frontend/CallOrNewEmitter.h
+++ b/js/src/frontend/CallOrNewEmitter.h
@@ -37,18 +37,17 @@ class MOZ_RAII AutoEmittingRunOnceLambda
 // Class for emitting bytecode for call or new expression.
 //
 // Usage: (check for the return value is omitted for simplicity)
 //
 //   `print(arg);`
 //     CallOrNewEmitter cone(this, JSOP_CALL,
 //                           CallOrNewEmitter::ArgumentsKind::Other,
 //                           ValueUsage::WantValue);
-//     cone.emitNameCallee();
-//     emit(print);
+//     cone.emitNameCallee(print);
 //     cone.emitThis();
 //     cone.prepareForNonSpreadArguments();
 //     emit(arg);
 //     cone.emitEnd(1, Some(offset_of_callee));
 //
 //   `callee.prop(arg1, arg2);`
 //     CallOrNewEmitter cone(this, JSOP_CALL,
 //                           CallOrNewEmitter::ArgumentsKind::Other,
@@ -103,45 +102,44 @@ class MOZ_RAII AutoEmittingRunOnceLambda
 //     cone.prepareForNonSpreadArguments();
 //     emit(arg);
 //     cone.emitEnd(1, Some(offset_of_callee));
 //
 //   `print(...arg);`
 //     CallOrNewEmitter cone(this, JSOP_SPREADCALL,
 //                           CallOrNewEmitter::ArgumentsKind::Other,
 //                           ValueUsage::WantValue);
-//     cone.emitNameCallee();
-//     emit(print);
+//     cone.emitNameCallee(print);
 //     cone.emitThis();
-//     if (cone.wantSpreadOperand())
+//     if (cone.wantSpreadOperand()) {
 //       emit(arg)
+//     }
 //     cone.emitSpreadArgumentsTest();
 //     emit([...arg]);
 //     cone.emitEnd(1, Some(offset_of_callee));
 //
 //   `print(...rest);`
 //   where `rest` is rest parameter
 //     CallOrNewEmitter cone(this, JSOP_SPREADCALL,
 //                           CallOrNewEmitter::ArgumentsKind::SingleSpreadRest,
 //                           ValueUsage::WantValue);
-//     cone.emitNameCallee();
-//     emit(print);
+//     cone.emitNameCallee(print);
 //     cone.emitThis();
-//     if (cone.wantSpreadOperand())
+//     if (cone.wantSpreadOperand()) {
 //       emit(arg)
+//     }
 //     cone.emitSpreadArgumentsTest();
 //     emit([...arg]);
 //     cone.emitEnd(1, Some(offset_of_callee));
 //
 //   `new f(arg);`
 //     CallOrNewEmitter cone(this, JSOP_NEW,
 //                           CallOrNewEmitter::ArgumentsKind::Other,
 //                           ValueUsage::WantValue);
-//     cone.emitNameCallee();
-//     emit(f);
+//     cone.emitNameCallee(f);
 //     cone.emitThis();
 //     cone.prepareForNonSpreadArguments();
 //     emit(arg);
 //     cone.emitEnd(1, Some(offset_of_callee));
 //
 class MOZ_STACK_CLASS CallOrNewEmitter
 {
   public:
deleted file mode 100644
--- a/js/src/frontend/ParseNode-inl.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: set ts=8 sts=4 et sw=4 tw=99:
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef frontend_ParseNode_inl_h
-#define frontend_ParseNode_inl_h
-
-#include "frontend/ParseNode.h"
-
-#include "frontend/SharedContext.h"
-
-namespace js {
-namespace frontend {
-
-inline PropertyName*
-ParseNode::name() const
-{
-    MOZ_ASSERT(isKind(ParseNodeKind::Function) || isKind(ParseNodeKind::Name));
-    JSAtom* atom = isKind(ParseNodeKind::Function)
-                   ? as<CodeNode>().funbox()->function()->explicitName()
-                   : as<NameNode>().atom();
-    return atom->asPropertyName();
-}
-
-} /* namespace frontend */
-} /* namespace js */
-
-#endif /* frontend_ParseNode_inl_h */
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -1,15 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "frontend/ParseNode-inl.h"
+#include "frontend/ParseNode.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/FloatingPoint.h"
 
 #include "jsnum.h"
 
 #include "frontend/Parser.h"
 
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -636,16 +636,17 @@ class ParseNode
     ParseNodeArity getArity() const        { return ParseNodeArity(pn_arity); }
     bool isArity(ParseNodeArity a) const   { return getArity() == a; }
     void setArity(ParseNodeArity a)        { pn_arity = a; }
 
     bool isBinaryOperation() const {
         ParseNodeKind kind = getKind();
         return ParseNodeKind::BinOpFirst <= kind && kind <= ParseNodeKind::BinOpLast;
     }
+    inline bool isName(PropertyName* name) const;
 
     /* Boolean attributes. */
     bool isInParens() const                { return pn_parens; }
     bool isLikelyIIFE() const              { return isInParens(); }
     void setInParens(bool enabled)         { pn_parens = enabled; }
 
     bool isDirectRHSAnonFunction() const {
         return pn_rhs_anon_fun;
@@ -735,19 +736,16 @@ class ParseNode
     /*
      * If |left| is a list of the given kind/left-associative op, append
      * |right| to it and return |left|.  Otherwise return a [left, right] list.
      */
     static ParseNode*
     appendOrCreateList(ParseNodeKind kind, ParseNode* left, ParseNode* right,
                        FullParseHandler* handler, ParseContext* pc);
 
-    // include "ParseNode-inl.h" for these methods.
-    inline PropertyName* name() const;
-
     /* True if pn is a parsenode representing a literal constant. */
     bool isLiteral() const {
         return isKind(ParseNodeKind::Number) ||
                isKind(ParseNodeKind::String) ||
                isKind(ParseNodeKind::True) ||
                isKind(ParseNodeKind::False) ||
                isKind(ParseNodeKind::Null) ||
                isKind(ParseNodeKind::RawUndefined);
@@ -836,16 +834,21 @@ class NameNode : public ParseNode
 #ifdef DEBUG
     void dump(GenericPrinter& out, int indent);
 #endif
 
     JSAtom* atom() const {
         return pn_u.name.atom;
     }
 
+    PropertyName* name() const {
+        MOZ_ASSERT(isKind(ParseNodeKind::Name));
+        return atom()->asPropertyName();
+    }
+
     ParseNode* initializer() const {
         return pn_u.name.initOrStmt;
     }
 
     void setAtom(JSAtom* atom) {
         pn_u.name.atom = atom;
     }
 
@@ -854,16 +857,22 @@ class NameNode : public ParseNode
     }
 
     // Methods used by FoldConstants.cpp.
     ParseNode** unsafeInitializerReference() {
         return &pn_u.name.initOrStmt;
     }
 };
 
+inline bool
+ParseNode::isName(PropertyName* name) const
+{
+    return getKind() == ParseNodeKind::Name && as<NameNode>().name() == name;
+}
+
 class UnaryNode : public ParseNode
 {
   public:
     UnaryNode(ParseNodeKind kind, const TokenPos& pos, ParseNode* kid)
       : ParseNode(kind, JSOP_NOP, PN_UNARY, pos)
     {
         pn_u.unary.kid = kid;
     }
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -30,29 +30,29 @@
 
 #include "jsnum.h"
 #include "jstypes.h"
 
 #include "builtin/ModuleObject.h"
 #include "builtin/SelfHostingDefines.h"
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/FoldConstants.h"
+#include "frontend/ParseNode.h"
 #include "frontend/TokenStream.h"
 #include "irregexp/RegExpParser.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/JSAtom.h"
 #include "vm/JSContext.h"
 #include "vm/JSFunction.h"
 #include "vm/JSScript.h"
 #include "vm/RegExpObject.h"
 #include "vm/StringType.h"
 #include "wasm/AsmJS.h"
 
 #include "frontend/ParseContext-inl.h"
-#include "frontend/ParseNode-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 
 using namespace js;
 
 using mozilla::Maybe;
 using mozilla::Nothing;
 using mozilla::PodCopy;
 using mozilla::PodZero;
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -843,16 +843,25 @@ class SourceCompressionTask
     bool shouldCancel() const {
         // If the refcount is exactly 1, then nothing else is holding on to the
         // ScriptSource, so no reason to compress it and we should cancel the task.
         return sourceHolder_.get()->refs == 1;
     }
 
     void work();
     void complete();
+
+  private:
+    struct PerformTaskWork;
+    friend struct PerformTaskWork;
+
+    // The work algorithm, aware whether it's compressing one-byte UTF-8 source
+    // text or UTF-16, for CharT either Utf8Unit or char16_t.  Invoked by
+    // work() after doing a type-test of the ScriptSource*.
+    template<typename CharT> void workEncodingSpecific();
 };
 
 // A PromiseHelperTask is an OffThreadPromiseTask that executes a single job on
 // a helper thread. Derived classes do their helper-thread work by implementing
 // execute().
 struct PromiseHelperTask : OffThreadPromiseTask
 {
     PromiseHelperTask(JSContext* cx, Handle<PromiseObject*> promise)
--- a/js/src/vm/JSFunction.cpp
+++ b/js/src/vm/JSFunction.cpp
@@ -9,16 +9,17 @@
  */
 
 #include "vm/JSFunction-inl.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Range.h"
+#include "mozilla/Utf8.h"
 
 #include <string.h>
 
 #include "jsapi.h"
 #include "jstypes.h"
 
 #include "builtin/Array.h"
 #include "builtin/Eval.h"
@@ -60,16 +61,17 @@
 #include "vm/Stack-inl.h"
 
 using namespace js;
 
 using mozilla::ArrayLength;
 using mozilla::CheckedInt;
 using mozilla::Maybe;
 using mozilla::Some;
+using mozilla::Utf8Unit;
 
 using JS::AutoStableStringChars;
 using JS::CompileOptions;
 using JS::SourceBufferHolder;
 
 static bool
 fun_enumerate(JSContext* cx, HandleObject obj)
 {
@@ -1777,29 +1779,46 @@ JSFunction::createScriptForLazilyInterpr
 #else
             MOZ_CRASH("Trying to delazify BinAST function in non-BinAST build");
 #endif /*JS_BUILD_BINAST */
         } else {
             MOZ_ASSERT(lazy->scriptSource()->hasSourceText());
 
             // Parse and compile the script from source.
             UncompressedSourceCache::AutoHoldEntry holder;
-            ScriptSource::PinnedChars chars(cx, lazy->scriptSource(), holder,
-                                            lazy->sourceStart(), lazyLength);
-            if (!chars.get()) {
-                return false;
-            }
-
-            if (!frontend::CompileLazyFunction(cx, lazy, chars.get(), lazyLength)) {
-		// The frontend shouldn't fail after linking the function and the
-		// non-lazy script together.
-                MOZ_ASSERT(fun->isInterpretedLazy());
-                MOZ_ASSERT(fun->lazyScript() == lazy);
-                MOZ_ASSERT(!lazy->hasScript());
-                return false;
+
+            if (lazy->scriptSource()->hasSourceType<Utf8Unit>()) {
+                // UTF-8 source text.
+                ScriptSource::PinnedChars<Utf8Unit> chars(cx, lazy->scriptSource(), holder,
+                                                          lazy->sourceStart(), lazyLength);
+                if (!chars.get()) {
+                    return false;
+                }
+
+                // XXX There are no UTF-8 ScriptSources now, so just crash so this
+                //     gets filled in later.
+                MOZ_CRASH("UTF-8 lazy function compilation not implemented yet");
+            } else {
+                MOZ_ASSERT(lazy->scriptSource()->hasSourceType<char16_t>());
+
+                // UTF-16 source text.
+                ScriptSource::PinnedChars<char16_t> chars(cx, lazy->scriptSource(), holder,
+                                                          lazy->sourceStart(), lazyLength);
+                if (!chars.get()) {
+                    return false;
+                }
+
+                if (!frontend::CompileLazyFunction(cx, lazy, chars.get(), lazyLength)) {
+                    // The frontend shouldn't fail after linking the function and the
+                    // non-lazy script together.
+                    MOZ_ASSERT(fun->isInterpretedLazy());
+                    MOZ_ASSERT(fun->lazyScript() == lazy);
+                    MOZ_ASSERT(!lazy->hasScript());
+                    return false;
+                }
             }
         }
 
         script = fun->nonLazyScript();
 
         // Remember the compiled script on the lazy script itself, in case
         // there are clones of the function still pointing to the lazy script.
         if (!lazy->maybeScript()) {
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -17,16 +17,17 @@
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Vector.h"
 
 #include <algorithm>
 #include <new>
 #include <string.h>
+#include <type_traits>
 #include <utility>
 
 #include "jsapi.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/BytecodeEmitter.h"
@@ -68,16 +69,19 @@
 #include "vm/NativeObject-inl.h"
 #include "vm/SharedImmutableStringsCache-inl.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
 
 using mozilla::Maybe;
 using mozilla::PodCopy;
+using mozilla::PointerRangeSize;
+using mozilla::Utf8AsUnsignedChars;
+using mozilla::Utf8Unit;
 
 using JS::CompileOptions;
 using JS::ReadOnlyCompileOptions;
 using JS::SourceBufferHolder;
 
 template<XDRMode mode>
 XDRResult
 js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp)
@@ -1561,17 +1565,20 @@ JSScript::loadSource(JSContext* cx, Scri
     char16_t* src = nullptr;
     size_t length;
     if (!cx->runtime()->sourceHook->load(cx, ss->filename(), &src, &length)) {
         return false;
     }
     if (!src) {
         return true;
     }
-    if (!ss->setSource(cx, UniqueTwoByteChars(src), length)) {
+
+    // 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->setSource(cx, EntryChars<char16_t>(src), length)) {
         return false;
     }
 
     *worked = true;
     return true;
 }
 
 /* static */ JSFlatString*
@@ -1583,103 +1590,63 @@ JSScript::sourceData(JSContext* cx, Hand
 
 bool
 JSScript::appendSourceDataForToString(JSContext* cx, StringBuffer& buf)
 {
     MOZ_ASSERT(scriptSource()->hasSourceText());
     return scriptSource()->appendSubstring(cx, buf, toStringStart(), toStringEnd());
 }
 
-UncompressedSourceCache::AutoHoldEntry::AutoHoldEntry()
-  : cache_(nullptr), sourceChunk_()
-{
-}
-
-void
-UncompressedSourceCache::AutoHoldEntry::holdEntry(UncompressedSourceCache* cache,
-                                                  const ScriptSourceChunk& sourceChunk)
-{
-    // Initialise the holder for a specific cache and script source. This will
-    // hold on to the cached source chars in the event that the cache is purged.
-    MOZ_ASSERT(!cache_ && !sourceChunk_.valid() && !charsToFree_);
-    cache_ = cache;
-    sourceChunk_ = sourceChunk;
-}
-
-void
-UncompressedSourceCache::AutoHoldEntry::holdChars(UniqueTwoByteChars chars)
-{
-    MOZ_ASSERT(!cache_ && !sourceChunk_.valid() && !charsToFree_);
-    charsToFree_ = std::move(chars);
-}
-
-void
-UncompressedSourceCache::AutoHoldEntry::deferDelete(UniqueTwoByteChars chars)
-{
-    // Take ownership of source chars now the cache is being purged. Remove our
-    // reference to the ScriptSource which might soon be destroyed.
-    MOZ_ASSERT(cache_ && sourceChunk_.valid() && !charsToFree_);
-    cache_ = nullptr;
-    sourceChunk_ = ScriptSourceChunk();
-    charsToFree_ = std::move(chars);
-}
-
-UncompressedSourceCache::AutoHoldEntry::~AutoHoldEntry()
-{
-    if (cache_) {
-        MOZ_ASSERT(sourceChunk_.valid());
-        cache_->releaseEntry(*this);
-    }
-}
-
 void
 UncompressedSourceCache::holdEntry(AutoHoldEntry& holder, const ScriptSourceChunk& ssc)
 {
     MOZ_ASSERT(!holder_);
     holder.holdEntry(this, ssc);
     holder_ = &holder;
 }
 
 void
 UncompressedSourceCache::releaseEntry(AutoHoldEntry& holder)
 {
     MOZ_ASSERT(holder_ == &holder);
     holder_ = nullptr;
 }
 
-const char16_t*
+template<typename CharT>
+const CharT*
 UncompressedSourceCache::lookup(const ScriptSourceChunk& ssc, AutoHoldEntry& holder)
 {
     MOZ_ASSERT(!holder_);
+    MOZ_ASSERT(ssc.ss->compressedSourceIs<CharT>());
+
     if (!map_) {
         return nullptr;
     }
+
     if (Map::Ptr p = map_->lookup(ssc)) {
         holdEntry(holder, ssc);
-        return p->value().get();
-    }
+        return static_cast<const CharT*>(p->value().get());
+    }
+
     return nullptr;
 }
 
 bool
-UncompressedSourceCache::put(const ScriptSourceChunk& ssc, UniqueTwoByteChars str,
-                             AutoHoldEntry& holder)
+UncompressedSourceCache::put(const ScriptSourceChunk& ssc, SourceData data, AutoHoldEntry& holder)
 {
     MOZ_ASSERT(!holder_);
 
     if (!map_) {
-        UniquePtr<Map> map = MakeUnique<Map>();
-        if (!map) {
+        map_ = MakeUnique<Map>();
+        if (!map_) {
             return false;
         }
-
-        map_ = std::move(map);
-    }
-
-    if (!map_->put(ssc, std::move(str))) {
+    }
+
+    if (!map_->put(ssc, std::move(data))) {
         return false;
     }
 
     holdEntry(holder, ssc);
     return true;
 }
 
 void
@@ -1691,263 +1658,334 @@ UncompressedSourceCache::purge()
 
     for (Map::Range r = map_->all(); !r.empty(); r.popFront()) {
         if (holder_ && r.front().key() == holder_->sourceChunk()) {
             holder_->deferDelete(std::move(r.front().value()));
             holder_ = nullptr;
         }
     }
 
-    map_.reset();
+    map_ = nullptr;
 }
 
 size_t
 UncompressedSourceCache::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
 {
     size_t n = 0;
     if (map_ && !map_->empty()) {
         n += map_->shallowSizeOfIncludingThis(mallocSizeOf);
         for (Map::Range r = map_->all(); !r.empty(); r.popFront()) {
             n += mallocSizeOf(r.front().value().get());
         }
     }
     return n;
 }
 
-const char16_t*
+template<typename CharT>
+const CharT*
 ScriptSource::chunkChars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder,
                          size_t chunk)
 {
-    const Compressed& c = data.as<Compressed>();
+    const Compressed<CharT>& c = data.as<Compressed<CharT>>();
 
     ScriptSourceChunk ssc(this, chunk);
-    if (const char16_t* decompressed = cx->caches().uncompressedSourceCache.lookup(ssc, holder)) {
+    if (const CharT* decompressed = cx->caches().uncompressedSourceCache.lookup<CharT>(ssc, holder)) {
         return decompressed;
     }
 
-    size_t totalLengthInBytes = length() * sizeof(char16_t);
+    size_t totalLengthInBytes = length() * sizeof(CharT);
     size_t chunkBytes = Compressor::chunkSize(totalLengthInBytes, chunk);
 
-    MOZ_ASSERT((chunkBytes % sizeof(char16_t)) == 0);
-    const size_t lengthWithNull = (chunkBytes / sizeof(char16_t)) + 1;
-    UniqueTwoByteChars decompressed(js_pod_malloc<char16_t>(lengthWithNull));
+    MOZ_ASSERT((chunkBytes % sizeof(CharT)) == 0);
+    const size_t lengthWithNull = (chunkBytes / sizeof(CharT)) + 1;
+    EntryChars<CharT> decompressed(js_pod_malloc<CharT>(lengthWithNull));
     if (!decompressed) {
         JS_ReportOutOfMemory(cx);
         return nullptr;
     }
 
-    if (!DecompressStringChunk((const unsigned char*) c.raw.chars(),
+    // Compression treats input and output memory as plain ol' bytes. These
+    // reinterpret_cast<>s accord exactly with that.
+    if (!DecompressStringChunk(reinterpret_cast<const unsigned char*>(c.raw.chars()),
                                chunk,
                                reinterpret_cast<unsigned char*>(decompressed.get()),
                                chunkBytes))
     {
         JS_ReportOutOfMemory(cx);
         return nullptr;
     }
 
-    decompressed[lengthWithNull - 1] = '\0';
-
-    const char16_t* ret = decompressed.get();
-    if (!cx->caches().uncompressedSourceCache.put(ssc, std::move(decompressed), holder)) {
+    decompressed[lengthWithNull - 1] = CharT('\0');
+
+    const CharT* ret = decompressed.get();
+    if (!cx->caches().uncompressedSourceCache.put(ssc, ToSourceData(std::move(decompressed)),
+                                                  holder))
+    {
         JS_ReportOutOfMemory(cx);
         return nullptr;
     }
     return ret;
 }
 
-ScriptSource::PinnedChars::PinnedChars(JSContext* cx, ScriptSource* source,
-                                       UncompressedSourceCache::AutoHoldEntry& holder,
-                                       size_t begin, size_t len)
-  : stack_(nullptr),
-    prev_(nullptr),
-    source_(source)
+template<typename CharT>
+void
+ScriptSource::movePendingCompressedSource()
 {
-    chars_ = source->chars(cx, holder, begin, len);
-    if (chars_) {
-        stack_ = &source->pinnedCharsStack_;
-        prev_ = *stack_;
-        *stack_ = this;
-    }
+    if (pendingCompressed_.empty()) {
+        return;
+    }
+
+    Compressed<CharT>& pending = pendingCompressed_.ref<Compressed<CharT>>();
+
+    MOZ_ASSERT(!hasCompressedSource());
+    MOZ_ASSERT_IF(hasUncompressedSource(),
+                  pending.uncompressedLength == length());
+
+    data = SourceType(std::move(pending));
+    pendingCompressed_.destroy();
 }
 
-ScriptSource::PinnedChars::~PinnedChars()
+template<typename CharT>
+ScriptSource::PinnedChars<CharT>::~PinnedChars()
 {
     if (chars_) {
         MOZ_ASSERT(*stack_ == this);
         *stack_ = prev_;
         if (!prev_) {
-            source_->movePendingCompressedSource();
+            source_->movePendingCompressedSource<CharT>();
         }
     }
 }
 
-void
-ScriptSource::movePendingCompressedSource()
-{
-    if (!pendingCompressed_) {
-        return;
-    }
-
-    MOZ_ASSERT(data.is<Missing>() || data.is<Uncompressed>());
-    MOZ_ASSERT_IF(data.is<Uncompressed>(),
-                  data.as<Uncompressed>().string.length() ==
-                  pendingCompressed_->uncompressedLength);
-
-    data = SourceType(Compressed(std::move(pendingCompressed_->raw),
-                                 pendingCompressed_->uncompressedLength));
-    pendingCompressed_ = mozilla::Nothing();
-}
-
-const char16_t*
+template<typename CharT>
+const CharT*
 ScriptSource::chars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder,
                     size_t begin, size_t len)
 {
+    MOZ_ASSERT(begin <= length());
     MOZ_ASSERT(begin + len <= length());
 
-    if (data.is<Uncompressed>()) {
-        const char16_t* chars = data.as<Uncompressed>().string.chars();
+    if (data.is<Uncompressed<CharT>>()) {
+        const CharT* chars = data.as<Uncompressed<CharT>>().chars();
         if (!chars) {
             return nullptr;
         }
         return chars + begin;
     }
 
     if (data.is<Missing>()) {
         MOZ_CRASH("ScriptSource::chars() on ScriptSource with SourceType = Missing");
     }
 
-    MOZ_ASSERT(data.is<Compressed>());
+    MOZ_ASSERT(data.is<Compressed<CharT>>());
 
     // Determine which chunk(s) we are interested in, and the offsets within
     // these chunks.
     size_t firstChunk, lastChunk;
     size_t firstChunkOffset, lastChunkOffset;
     MOZ_ASSERT(len > 0);
-    Compressor::toChunkOffset(begin * sizeof(char16_t), &firstChunk, &firstChunkOffset);
-    Compressor::toChunkOffset((begin + len - 1) * sizeof(char16_t), &lastChunk, &lastChunkOffset);
-
-    MOZ_ASSERT(firstChunkOffset % sizeof(char16_t) == 0);
-    size_t firstChar = firstChunkOffset / sizeof(char16_t);
+    Compressor::toChunkOffset(begin * sizeof(CharT), &firstChunk, &firstChunkOffset);
+    Compressor::toChunkOffset((begin + len - 1) * sizeof(CharT), &lastChunk, &lastChunkOffset);
+
+    MOZ_ASSERT(firstChunkOffset % sizeof(CharT) == 0);
+    size_t firstChar = firstChunkOffset / sizeof(CharT);
 
     if (firstChunk == lastChunk) {
-        const char16_t* chars = chunkChars(cx, holder, firstChunk);
+        const CharT* chars = chunkChars<CharT>(cx, holder, firstChunk);
         if (!chars) {
             return nullptr;
         }
+
         return chars + firstChar;
     }
 
     // We need multiple chunks. Allocate a (null-terminated) buffer to hold
     // |len| chars and copy uncompressed chars from the chunks into it. We use
     // chunkChars() so we benefit from chunk caching by UncompressedSourceCache.
 
     MOZ_ASSERT(firstChunk < lastChunk);
 
     size_t lengthWithNull = len + 1;
-    UniqueTwoByteChars decompressed(js_pod_malloc<char16_t>(lengthWithNull));
+    EntryChars<CharT> decompressed(js_pod_malloc<CharT>(lengthWithNull));
     if (!decompressed) {
         JS_ReportOutOfMemory(cx);
         return nullptr;
     }
 
-    size_t totalLengthInBytes = length() * sizeof(char16_t);
-    char16_t* cursor = decompressed.get();
+    size_t totalLengthInBytes = length() * sizeof(CharT);
+    CharT* cursor = decompressed.get();
 
     for (size_t i = firstChunk; i <= lastChunk; i++) {
         UncompressedSourceCache::AutoHoldEntry chunkHolder;
-        const char16_t* chars = chunkChars(cx, chunkHolder, i);
+        const CharT* chars = chunkChars<CharT>(cx, chunkHolder, i);
         if (!chars) {
             return nullptr;
         }
 
-        size_t numChars = Compressor::chunkSize(totalLengthInBytes, i) / sizeof(char16_t);
+        size_t numChars = Compressor::chunkSize(totalLengthInBytes, i) / sizeof(CharT);
         if (i == firstChunk) {
             MOZ_ASSERT(firstChar < numChars);
             chars += firstChar;
             numChars -= firstChar;
         } else if (i == lastChunk) {
-            size_t numCharsNew = lastChunkOffset / sizeof(char16_t) + 1;
+            size_t numCharsNew = lastChunkOffset / sizeof(CharT) + 1;
             MOZ_ASSERT(numCharsNew <= numChars);
             numChars = numCharsNew;
         }
         mozilla::PodCopy(cursor, chars, numChars);
         cursor += numChars;
     }
 
-    *cursor++ = '\0';
-    MOZ_ASSERT(size_t(cursor - decompressed.get()) == lengthWithNull);
+    // XXX Bug 1499192: can we remove the null-termination?  It's unclear if
+    //     anyone uses chunk implicit null-termination, chunks can contain
+    //     nulls anyway, and the extra character risks size-class goofs.
+    *cursor++ = CharT('\0');
+    MOZ_ASSERT(PointerRangeSize(decompressed.get(), cursor) == lengthWithNull);
 
     // Transfer ownership to |holder|.
-    const char16_t* ret = decompressed.get();
+    const CharT* ret = decompressed.get();
     holder.holdChars(std::move(decompressed));
     return ret;
 }
 
+template<typename CharT>
+ScriptSource::PinnedChars<CharT>::PinnedChars(JSContext* cx, ScriptSource* source,
+                                              UncompressedSourceCache::AutoHoldEntry& holder,
+                                              size_t begin, size_t len)
+  : PinnedCharsBase(source)
+{
+    MOZ_ASSERT(source->hasSourceType<CharT>(),
+               "must pin chars of source's type");
+
+    chars_ = source->chars<CharT>(cx, holder, begin, len);
+    if (chars_) {
+        stack_ = &source->pinnedCharsStack_;
+        prev_ = *stack_;
+        *stack_ = this;
+    }
+}
+
+template class ScriptSource::PinnedChars<Utf8Unit>;
+template class ScriptSource::PinnedChars<char16_t>;
+
 JSFlatString*
 ScriptSource::substring(JSContext* cx, size_t start, size_t stop)
 {
     MOZ_ASSERT(start <= stop);
+
     size_t len = stop - start;
     UncompressedSourceCache::AutoHoldEntry holder;
-    PinnedChars chars(cx, this, holder, start, len);
+
+    // UTF-8 source text.
+    if (hasSourceType<Utf8Unit>()) {
+        PinnedChars<Utf8Unit> chars(cx, this, holder, start, len);
+        if (!chars.get()) {
+            return nullptr;
+        }
+
+        char* str = SourceTypeTraits<Utf8Unit>::toString(chars.get());
+        return NewStringCopyUTF8N<CanGC>(cx, JS::UTF8Chars(str, len));
+    }
+
+    // UTF-16 source text.
+    PinnedChars<char16_t> chars(cx, this, holder, start, len);
     if (!chars.get()) {
         return nullptr;
     }
+
     return NewStringCopyN<CanGC>(cx, chars.get(), len);
 }
 
 JSFlatString*
 ScriptSource::substringDontDeflate(JSContext* cx, size_t start, size_t stop)
 {
     MOZ_ASSERT(start <= stop);
+
     size_t len = stop - start;
     UncompressedSourceCache::AutoHoldEntry holder;
-    PinnedChars chars(cx, this, holder, start, len);
+
+    // UTF-8 source text.
+    if (hasSourceType<Utf8Unit>()) {
+        PinnedChars<Utf8Unit> chars(cx, this, holder, start, len);
+        if (!chars.get()) {
+            return nullptr;
+        }
+
+        char* str = SourceTypeTraits<Utf8Unit>::toString(chars.get());
+
+        // There doesn't appear to be a non-deflating UTF-8 string creation
+        // function -- but then again, it's not entirely clear how current
+        // callers benefit from non-deflation.
+        return NewStringCopyUTF8N<CanGC>(cx, JS::UTF8Chars(str, len));
+    }
+
+    // UTF-16 source text.
+    PinnedChars<char16_t> chars(cx, this, holder, start, len);
     if (!chars.get()) {
         return nullptr;
     }
+
     return NewStringCopyNDontDeflate<CanGC>(cx, chars.get(), len);
 }
 
 bool
 ScriptSource::appendSubstring(JSContext* cx, StringBuffer& buf, size_t start, size_t stop)
 {
     MOZ_ASSERT(start <= stop);
+
     size_t len = stop - start;
     UncompressedSourceCache::AutoHoldEntry holder;
-    PinnedChars chars(cx, this, holder, start, len);
-    if (!chars.get()) {
+
+    if (hasSourceType<Utf8Unit>()) {
+        MOZ_CRASH("for now");
         return false;
-    }
-    if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) {
-        return false;
-    }
-    return buf.append(chars.get(), len);
+    } else {
+        PinnedChars<char16_t> chars(cx, this, holder, start, len);
+        if (!chars.get()) {
+            return false;
+        }
+        if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) {
+            return false;
+        }
+        return buf.append(chars.get(), len);
+    }
 }
 
 JSFlatString*
 ScriptSource::functionBodyString(JSContext* cx)
 {
     MOZ_ASSERT(isFunctionBody());
 
     size_t start = parameterListEnd_ + (sizeof(FunctionConstructorMedialSigils) - 1);
     size_t stop = length() - (sizeof(FunctionConstructorFinalBrace) - 1);
     return substring(cx, start, stop);
 }
 
+template<typename CharT>
+void
+ScriptSource::setSource(typename SourceTypeTraits<CharT>::SharedImmutableString uncompressed)
+{
+    MOZ_ASSERT(data.is<Missing>());
+    data = SourceType(Uncompressed<CharT>(std::move(uncompressed)));
+}
+
+template<typename CharT>
 MOZ_MUST_USE bool
-ScriptSource::setSource(JSContext* cx, UniqueTwoByteChars&& source, size_t length)
+ScriptSource::setSource(JSContext* cx, EntryChars<CharT>&& source, size_t length)
 {
     auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
-    auto deduped = cache.getOrCreate(std::move(source), length);
+
+    auto uniqueChars = SourceTypeTraits<CharT>::toCacheable(std::move(source));
+    auto deduped = cache.getOrCreate(std::move(uniqueChars), length);
     if (!deduped) {
         ReportOutOfMemory(cx);
         return false;
     }
-    setSource(std::move(*deduped));
+
+    setSource<CharT>(std::move(*deduped));
     return true;
 }
 
 #if defined(JS_BUILD_BINAST)
 
 MOZ_MUST_USE bool
 ScriptSource::setBinASTSourceCopy(JSContext* cx, const uint8_t* buf, size_t len)
 {
@@ -1980,27 +2018,21 @@ const uint8_t*
 ScriptSource::binASTSource()
 {
     MOZ_ASSERT(hasBinASTSource());
     return reinterpret_cast<const uint8_t*>(data.as<BinAST>().string.chars());
 }
 
 #endif /* JS_BUILD_BINAST */
 
-void
-ScriptSource::setSource(SharedImmutableTwoByteString&& string)
-{
-    MOZ_ASSERT(data.is<Missing>());
-    data = SourceType(Uncompressed(std::move(string)));
-}
-
 bool
 ScriptSource::tryCompressOffThread(JSContext* cx)
 {
-    if (!data.is<Uncompressed>()) {
+    if (!hasUncompressedSource()) {
+        // This excludes already-compressed, missing, 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).
     //
@@ -2034,62 +2066,67 @@ ScriptSource::tryCompressOffThread(JSCon
     auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), this);
     if (!task) {
         ReportOutOfMemory(cx);
         return false;
     }
     return EnqueueOffThreadCompression(cx, std::move(task));
 }
 
+template<typename CharT>
+void
+ScriptSource::setCompressedSource(SharedImmutableString raw, size_t uncompressedLength)
+{
+    MOZ_ASSERT(data.is<Missing>() || hasUncompressedSource());
+    MOZ_ASSERT_IF(hasUncompressedSource(), length() == uncompressedLength);
+
+    if (pinnedCharsStack_) {
+        MOZ_ASSERT(pendingCompressed_.empty());
+        pendingCompressed_.construct<Compressed<CharT>>(std::move(raw), uncompressedLength);
+    } else {
+        data = SourceType(Compressed<CharT>(std::move(raw), uncompressedLength));
+    }
+}
+
+template<typename CharT>
 MOZ_MUST_USE bool
-ScriptSource::setCompressedSource(JSContext* cx, UniqueChars&& raw, size_t rawLength,
+ScriptSource::setCompressedSource(JSContext* cx, UniqueChars&& compressed, size_t rawLength,
                                   size_t sourceLength)
 {
-    MOZ_ASSERT(raw);
+    MOZ_ASSERT(compressed);
+
     auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
-    auto deduped = cache.getOrCreate(std::move(raw), rawLength);
+    auto deduped = cache.getOrCreate(std::move(compressed), rawLength);
     if (!deduped) {
         ReportOutOfMemory(cx);
         return false;
     }
-    setCompressedSource(std::move(*deduped), sourceLength);
+
+    setCompressedSource<CharT>(std::move(*deduped), sourceLength);
     return true;
 }
 
-void
-ScriptSource::setCompressedSource(SharedImmutableString&& raw, size_t uncompressedLength)
-{
-    MOZ_ASSERT(data.is<Missing>() || data.is<Uncompressed>());
-    MOZ_ASSERT_IF(data.is<Uncompressed>(),
-                  data.as<Uncompressed>().string.length() == uncompressedLength);
-    if (pinnedCharsStack_) {
-        pendingCompressed_ = mozilla::Some(Compressed(std::move(raw), uncompressedLength));
-    } else {
-        data = SourceType(Compressed(std::move(raw), uncompressedLength));
-    }
-}
-
 bool
 ScriptSource::setSourceCopy(JSContext* cx, SourceBufferHolder& srcBuf)
 {
     MOZ_ASSERT(!hasSourceText());
 
     JSRuntime* runtime = cx->zone()->runtimeFromAnyThread();
     auto& cache = runtime->sharedImmutableStrings();
-    auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&]() {
+    auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&srcBuf]() {
         return srcBuf.ownsChars()
                ? UniqueTwoByteChars(srcBuf.take())
                : DuplicateString(srcBuf.get(), srcBuf.length());
     });
     if (!deduped) {
         ReportOutOfMemory(cx);
         return false;
     }
-    setSource(std::move(*deduped));
-
+
+    setSource<char16_t>(std::move(*deduped));
     return true;
 }
 
 void
 ScriptSource::trace(JSTracer* trc)
 {
 #ifdef JS_BUILD_BINAST
     if (binASTMetadata_) {
@@ -2109,38 +2146,34 @@ reallocUniquePtr(UniqueChars& unique, si
     }
 
     // Since the realloc succeeded, unique is now holding a freed pointer.
     mozilla::Unused << unique.release();
     unique.reset(newPtr);
     return true;
 }
 
+template<typename CharT>
 void
-SourceCompressionTask::work()
+SourceCompressionTask::workEncodingSpecific()
 {
-    if (shouldCancel()) {
-        return;
-    }
-
     ScriptSource* source = sourceHolder_.get();
-    MOZ_ASSERT(source->data.is<ScriptSource::Uncompressed>());
+    MOZ_ASSERT(source->data.is<ScriptSource::Uncompressed<CharT>>());
 
     // Try to keep the maximum memory usage down by only allocating half the
     // size of the string, first.
-    size_t inputBytes = source->length() * sizeof(char16_t);
+    size_t inputBytes = source->length() * sizeof(CharT);
     size_t firstSize = inputBytes / 2;
     UniqueChars compressed(js_pod_malloc<char>(firstSize));
     if (!compressed) {
         return;
     }
 
-    const char16_t* chars = source->data.as<ScriptSource::Uncompressed>().string.chars();
-    Compressor comp(reinterpret_cast<const unsigned char*>(chars),
-                    inputBytes);
+    const CharT* chars = source->data.as<ScriptSource::Uncompressed<CharT>>().chars();
+    Compressor comp(reinterpret_cast<const unsigned char*>(chars), inputBytes);
     if (!comp.init()) {
         return;
     }
 
     comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()), firstSize);
     bool cont = true;
     bool reallocated = false;
     while (cont) {
@@ -2187,22 +2220,68 @@ SourceCompressionTask::work()
     if (shouldCancel()) {
         return;
     }
 
     auto& strings = runtime_->sharedImmutableStrings();
     resultString_ = strings.getOrCreate(std::move(compressed), totalBytes);
 }
 
+struct SourceCompressionTask::PerformTaskWork
+{
+    SourceCompressionTask* const task_;
+
+    explicit PerformTaskWork(SourceCompressionTask* task)
+      : task_(task)
+    {}
+
+    template<typename CharT>
+    void match(const ScriptSource::Uncompressed<CharT>&) {
+        task_->workEncodingSpecific<CharT>();
+    }
+
+    template<typename T>
+    void match (const T&) {
+        MOZ_CRASH("why are we compressing missing, already-compressed, or "
+                  "BinAST source?");
+    }
+};
+
+void
+ScriptSource::performTaskWork(SourceCompressionTask* task)
+{
+    MOZ_ASSERT(hasUncompressedSource());
+    data.match(SourceCompressionTask::PerformTaskWork(task));
+}
+
+void
+SourceCompressionTask::work()
+{
+    if (shouldCancel()) {
+        return;
+    }
+
+    ScriptSource* source = sourceHolder_.get();
+    MOZ_ASSERT(source->hasUncompressedSource());
+
+    source->performTaskWork(this);
+}
+
+void
+ScriptSource::setCompressedSourceFromTask(SharedImmutableString compressed)
+{
+    data.match(SetCompressedSourceFromTask(this, compressed));
+}
+
 void
 SourceCompressionTask::complete()
 {
-    if (!shouldCancel() && resultString_) {
+    if (!shouldCancel() && resultString_.isSome()) {
         ScriptSource* source = sourceHolder_.get();
-        source->setCompressedSource(std::move(*resultString_), source->length());
+        source->setCompressedSourceFromTask(std::move(*resultString_));
     }
 }
 
 void
 ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                      JS::ScriptSourceInfo* info) const
 {
     info->misc += mallocSizeOf(this) +
@@ -2281,62 +2360,116 @@ ScriptSource::xdrFinalizeEncoder(JS::Tra
     auto cleanup = mozilla::MakeScopeExit([&] {
         xdrEncoder_.reset(nullptr);
     });
 
     XDRResult res = xdrEncoder_->linearize(buffer);
     return res.isOk();
 }
 
+template<typename CharT>
+struct SourceDecoder
+{
+    XDRState<XDR_DECODE>* const xdr_;
+    ScriptSource* const scriptSource_;
+    const uint32_t uncompressedLength_;
+
+  public:
+    SourceDecoder(XDRState<XDR_DECODE>* xdr, ScriptSource* scriptSource,
+                  uint32_t uncompressedLength)
+      : xdr_(xdr),
+        scriptSource_(scriptSource),
+        uncompressedLength_(uncompressedLength)
+    {}
+
+    XDRResult decode() {
+        auto sourceChars =
+            xdr_->cx()->make_pod_array<CharT>(Max<size_t>(uncompressedLength_, 1));
+        if (!sourceChars) {
+            return xdr_->fail(JS::TranscodeResult_Throw);
+        }
+
+        MOZ_TRY(xdr_->codeChars(sourceChars.get(), uncompressedLength_));
+
+        if (!scriptSource_->setSource(xdr_->cx(), std::move(sourceChars),
+                                      uncompressedLength_))
+        {
+            return xdr_->fail(JS::TranscodeResult_Throw);
+        }
+
+        return Ok();
+    }
+};
+
+namespace js {
+
+template<>
+XDRResult
+ScriptSource::xdrUncompressedSource<XDR_DECODE>(XDRState<XDR_DECODE>* xdr,
+                                                uint8_t sourceCharSize,
+                                                uint32_t uncompressedLength)
+{
+    MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2);
+
+    if (sourceCharSize == 1) {
+        SourceDecoder<Utf8Unit> decoder(xdr, this, uncompressedLength);
+        return decoder.decode();
+    }
+
+    SourceDecoder<char16_t> decoder(xdr, this, uncompressedLength);
+    return decoder.decode();
+}
+
+} // namespace js
+
+template<typename CharT>
+struct SourceEncoder
+{
+    XDRState<XDR_ENCODE>* const xdr_;
+    ScriptSource* const source_;
+    const uint32_t uncompressedLength_;
+
+    SourceEncoder(XDRState<XDR_ENCODE>* xdr, ScriptSource* source, uint32_t uncompressedLength)
+      : xdr_(xdr),
+        source_(source),
+        uncompressedLength_(uncompressedLength)
+    {}
+
+    XDRResult encode() {
+        CharT* sourceChars = const_cast<CharT*>(source_->uncompressedData<CharT>());
+
+        return xdr_->codeChars(sourceChars, uncompressedLength_);
+    }
+};
+
+namespace js {
+
+template<>
+XDRResult
+ScriptSource::xdrUncompressedSource<XDR_ENCODE>(XDRState<XDR_ENCODE>* xdr,
+                                                uint8_t sourceCharSize,
+                                                uint32_t uncompressedLength)
+{
+    MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2);
+
+    if (sourceCharSize == 1) {
+        SourceEncoder<Utf8Unit> encoder(xdr, this, uncompressedLength);
+        return encoder.encode();
+    }
+
+    SourceEncoder<char16_t> encoder(xdr, this, uncompressedLength);
+    return encoder.encode();
+}
+
+} // namespace js
+
 template<XDRMode mode>
 XDRResult
 ScriptSource::performXDR(XDRState<mode>* xdr)
 {
-    struct CompressedLengthMatcher
-    {
-        size_t match(Uncompressed&) {
-            // Return 0 for uncompressed source so that |if (compressedLength)|
-            // can be used to distinguish compressed and uncompressed source.
-            return 0;
-        }
-
-        size_t match(Compressed& c) {
-            return c.raw.length();
-        }
-
-        size_t match(BinAST&) {
-            return 0;
-        }
-
-        size_t match(Missing&) {
-            MOZ_CRASH("Missing source data in ScriptSource::performXDR");
-            return 0;
-        }
-    };
-
-    struct RawDataMatcher
-    {
-        void* match(Uncompressed& u) {
-            return (void*) u.string.chars();
-        }
-
-        void* match(Compressed& c) {
-            return (void*) c.raw.chars();
-        }
-
-        void* match(BinAST& b) {
-            return (void*) b.string.chars();
-        }
-
-        void* match(Missing&) {
-            MOZ_CRASH("Missing source data in ScriptSource::performXDR");
-            return nullptr;
-        }
-    };
-
     uint8_t hasSource = hasSourceText();
     MOZ_TRY(xdr->codeUint8(&hasSource));
 
     uint8_t hasBinSource = hasBinASTSource();
     MOZ_TRY(xdr->codeUint8(&hasBinSource));
 
     uint8_t retrievable = sourceRetrievable_;
     MOZ_TRY(xdr->codeUint8(&retrievable));
@@ -2344,78 +2477,83 @@ ScriptSource::performXDR(XDRState<mode>*
 
     if ((hasSource || hasBinSource) && !sourceRetrievable_) {
         uint32_t uncompressedLength = 0;
         if (mode == XDR_ENCODE) {
             uncompressedLength = length();
         }
         MOZ_TRY(xdr->codeUint32(&uncompressedLength));
 
-        // A compressed length of 0 indicates source is uncompressed.
+        // A compressed length of 0 indicates source is uncompressed (or is
+        // BinAST if |hasBinSource|).
         uint32_t compressedLength;
         if (mode == XDR_ENCODE) {
-            CompressedLengthMatcher m;
-            compressedLength = data.match(m);
+            compressedLength = compressedLengthOrZero();
         }
         MOZ_TRY(xdr->codeUint32(&compressedLength));
 
-        if (mode == XDR_DECODE) {
-            if (hasBinSource) {
+        uint8_t srcCharSize;
+        if (mode == XDR_ENCODE) {
+            srcCharSize = 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);
+        }
+
+        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 (!setBinASTSource(xdr->cx(), std::move(bytes), uncompressedLength)) {
                     return xdr->fail(JS::TranscodeResult_Throw);
                 }
 #else
                 MOZ_ASSERT(mode != XDR_ENCODE);
                 return xdr->fail(JS::TranscodeResult_Throw);
 #endif /* JS_BUILD_BINAST */
-            } else if (compressedLength) {
-                auto bytes =
-                    xdr->cx()->template make_pod_array<char>(Max<size_t>(compressedLength, 1));
+            } else {
+                void* bytes = binASTData();
+                MOZ_TRY(xdr->codeBytes(bytes, uncompressedLength));
+            }
+        } else 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 (!setCompressedSource(xdr->cx(), std::move(bytes), compressedLength,
-                                         uncompressedLength))
+                if (!(srcCharSize == 1
+                      ? setCompressedSource<Utf8Unit>(xdr->cx(), std::move(bytes),
+                                                      compressedLength, uncompressedLength)
+                      : setCompressedSource<char16_t>(xdr->cx(), std::move(bytes),
+                                                      compressedLength, uncompressedLength)))
                 {
                     return xdr->fail(JS::TranscodeResult_Throw);
                 }
             } else {
-                auto sourceChars =
-                    xdr->cx()->template make_pod_array<char16_t>(Max<size_t>(uncompressedLength,
-                                                                             1));
-                if (!sourceChars) {
-                    return xdr->fail(JS::TranscodeResult_Throw);
-                }
-                MOZ_TRY(xdr->codeChars(sourceChars.get(), uncompressedLength));
-
-                if (!setSource(xdr->cx(), std::move(sourceChars), uncompressedLength)) {
-                    return xdr->fail(JS::TranscodeResult_Throw);
-                }
+                void* bytes = srcCharSize == 1
+                              ? compressedData<Utf8Unit>()
+                              : compressedData<char16_t>();
+                MOZ_TRY(xdr->codeBytes(bytes, compressedLength));
             }
         } else {
-            if (hasBinSource) {
-                void* bytes = data.match(RawDataMatcher());
-                MOZ_TRY(xdr->codeBytes(bytes, uncompressedLength));
-            } else if (compressedLength) {
-                void* bytes = data.match(RawDataMatcher());
-                MOZ_TRY(xdr->codeBytes(bytes, compressedLength));
-            } else {
-                char16_t* sourceChars = static_cast<char16_t*>(data.match(RawDataMatcher()));
-                MOZ_TRY(xdr->codeChars(sourceChars, uncompressedLength));
-            }
+            MOZ_TRY(xdrUncompressedSource(xdr, srcCharSize, uncompressedLength));
         }
 
         uint8_t hasMetadata = !!binASTMetadata_;
         MOZ_TRY(xdr->codeUint8(&hasMetadata));
         if (hasMetadata) {
 #if defined(JS_BUILD_BINAST)
             uint32_t numBinKinds;
             uint32_t numStrings;
@@ -2546,22 +2684,34 @@ ScriptSource::performXDR(XDRState<mode>*
         }
 
         // Note the content of sources decoded when recording or replaying.
         if (mode == XDR_DECODE &&
             hasSourceText() &&
             mozilla::recordreplay::IsRecordingOrReplaying())
         {
             UncompressedSourceCache::AutoHoldEntry holder;
-            ScriptSource::PinnedChars chars(xdr->cx(), this, holder, 0, length());
-            if (!chars.get()) {
-                return xdr->fail(JS::TranscodeResult_Throw);
+
+            if (hasSourceType<Utf8Unit>()) {
+                // UTF-8 source text.
+                ScriptSource::PinnedChars<Utf8Unit> chars(xdr->cx(), this, holder, 0, length());
+                if (!chars.get()) {
+                    return xdr->fail(JS::TranscodeResult_Throw);
+                }
+                mozilla::recordreplay::NoteContentParse8(this, filename(), "application/javascript",
+                                                         chars.get(), length());
+            } else {
+                // UTF-16 source text.
+                ScriptSource::PinnedChars<char16_t> chars(xdr->cx(), this, holder, 0, length());
+                if (!chars.get()) {
+                    return xdr->fail(JS::TranscodeResult_Throw);
+                }
+                mozilla::recordreplay::NoteContentParse16(this, filename(), "application/javascript",
+                                                          chars.get(), length());
             }
-            mozilla::recordreplay::NoteContentParse(this, filename(), "application/javascript",
-                                                    chars.get(), length());
         }
     }
 
     return Ok();
 }
 
 // Format and return a cx->pod_malloc'ed URL for a generated script like:
 //   {filename} line {lineno} > {introducer}
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -7,30 +7,37 @@
 /* JS script descriptor. */
 
 #ifndef vm_JSScript_h
 #define vm_JSScript_h
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/MaybeOneOf.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Span.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Utf8.h"
 #include "mozilla/Variant.h"
 
+#include <type_traits> // std::is_same
+#include <utility> // std::move
+
 #include "jstypes.h"
 
 #include "frontend/BinSourceRuntimeSupport.h"
 #include "frontend/NameAnalysisTypes.h"
 #include "gc/Barrier.h"
 #include "gc/Rooting.h"
 #include "jit/IonCode.h"
 #include "js/CompileOptions.h"
 #include "js/UbiNode.h"
 #include "js/UniquePtr.h"
+#include "js/Utility.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/JSAtom.h"
 #include "vm/NativeObject.h"
 #include "vm/Scope.h"
 #include "vm/Shape.h"
 #include "vm/SharedImmutableStringsCache.h"
 #include "vm/Time.h"
 
@@ -290,27 +297,27 @@ using DebugScriptMap = HashMap<JSScript*
                                UniqueDebugScript,
                                DefaultHasher<JSScript*>,
                                SystemAllocPolicy>;
 
 class ScriptSource;
 
 struct ScriptSourceChunk
 {
-    ScriptSource* ss;
-    uint32_t chunk;
-
-    ScriptSourceChunk()
-      : ss(nullptr), chunk(0)
-    {}
+    ScriptSource* ss = nullptr;
+    uint32_t chunk = 0;
+
+    ScriptSourceChunk() = default;
+
     ScriptSourceChunk(ScriptSource* ss, uint32_t chunk)
       : ss(ss), chunk(chunk)
     {
-        MOZ_ASSERT(valid());;
+        MOZ_ASSERT(valid());
     }
+
     bool valid() const { return ss != nullptr; }
 
     bool operator==(const ScriptSourceChunk& other) const {
         return ss == other.ss && chunk == other.chunk;
     }
 };
 
 struct ScriptSourceChunkHasher
@@ -320,138 +327,274 @@ struct ScriptSourceChunkHasher
     static HashNumber hash(const ScriptSourceChunk& ssc) {
         return mozilla::AddToHash(DefaultHasher<ScriptSource*>::hash(ssc.ss), ssc.chunk);
     }
     static bool match(const ScriptSourceChunk& c1, const ScriptSourceChunk& c2) {
         return c1 == c2;
     }
 };
 
+template<typename CharT>
+using EntryChars = mozilla::UniquePtr<CharT[], JS::FreePolicy>;
+
+// The uncompressed source cache contains *either* UTF-8 source data *or*
+// UTF-16 source data.  ScriptSourceChunk implies a ScriptSource that
+// contains either UTF-8 data or UTF-16 data, so the nature of the key to
+// Map below indicates how each SourceData ought to be interpreted.
+using SourceData = mozilla::UniquePtr<void, JS::FreePolicy>;
+
+template<typename CharT>
+inline SourceData
+ToSourceData(EntryChars<CharT> chars)
+{
+    static_assert(std::is_same<SourceData::DeleterType,
+                               typename EntryChars<CharT>::DeleterType>::value,
+                  "EntryChars and SourceData must share the same deleter "
+                  "type, that need not know the type of the data being freed, "
+                  "for the upcast below to be safe");
+    return SourceData(chars.release());
+}
+
 class UncompressedSourceCache
 {
-    typedef HashMap<ScriptSourceChunk,
-                    UniqueTwoByteChars,
-                    ScriptSourceChunkHasher,
-                    SystemAllocPolicy> Map;
+    using Map = HashMap<ScriptSourceChunk,
+                        SourceData,
+                        ScriptSourceChunkHasher,
+                        SystemAllocPolicy>;
 
   public:
     // Hold an entry in the source data cache and prevent it from being purged on GC.
     class AutoHoldEntry
     {
-        UncompressedSourceCache* cache_;
-        ScriptSourceChunk sourceChunk_;
-        UniqueTwoByteChars charsToFree_;
+        UncompressedSourceCache* cache_ = nullptr;
+        ScriptSourceChunk sourceChunk_ = {};
+        SourceData data_ = nullptr;
+
       public:
-        explicit AutoHoldEntry();
-        ~AutoHoldEntry();
-        void holdChars(UniqueTwoByteChars chars);
+        explicit AutoHoldEntry() = default;
+
+        ~AutoHoldEntry() {
+            if (cache_) {
+                MOZ_ASSERT(sourceChunk_.valid());
+                cache_->releaseEntry(*this);
+            }
+        }
+
+        template<typename CharT>
+        void holdChars(EntryChars<CharT> chars) {
+            MOZ_ASSERT(!cache_);
+            MOZ_ASSERT(!sourceChunk_.valid());
+            MOZ_ASSERT(!data_);
+
+            data_ = ToSourceData(std::move(chars));
+        }
+
       private:
-        void holdEntry(UncompressedSourceCache* cache, const ScriptSourceChunk& sourceChunk);
-        void deferDelete(UniqueTwoByteChars chars);
+        void holdEntry(UncompressedSourceCache* cache, const ScriptSourceChunk& sourceChunk) {
+            // Initialise the holder for a specific cache and script source.
+            // This will hold on to the cached source chars in the event that
+            // the cache is purged.
+            MOZ_ASSERT(!cache_);
+            MOZ_ASSERT(!sourceChunk_.valid());
+            MOZ_ASSERT(!data_);
+
+            cache_ = cache;
+            sourceChunk_ = sourceChunk;
+        }
+
+        void deferDelete(SourceData data) {
+            // Take ownership of source chars now the cache is being purged. Remove our
+            // reference to the ScriptSource which might soon be destroyed.
+            MOZ_ASSERT(cache_);
+            MOZ_ASSERT(sourceChunk_.valid());
+            MOZ_ASSERT(!data_);
+
+            cache_ = nullptr;
+            sourceChunk_ = ScriptSourceChunk();
+
+            data_ = std::move(data);
+        }
+
+
         const ScriptSourceChunk& sourceChunk() const { return sourceChunk_; }
         friend class UncompressedSourceCache;
     };
 
   private:
-    UniquePtr<Map> map_;
-    AutoHoldEntry* holder_;
+    UniquePtr<Map> map_ = nullptr;
+    AutoHoldEntry* holder_ = nullptr;
 
   public:
-    UncompressedSourceCache() : holder_(nullptr) {}
-
-    const char16_t* lookup(const ScriptSourceChunk& ssc, AutoHoldEntry& asp);
-    bool put(const ScriptSourceChunk& ssc, UniqueTwoByteChars chars, AutoHoldEntry& asp);
+    UncompressedSourceCache() = default;
+
+    template<typename CharT>
+    const CharT* lookup(const ScriptSourceChunk& ssc, AutoHoldEntry& asp);
+
+    bool put(const ScriptSourceChunk& ssc, SourceData data, AutoHoldEntry& asp);
 
     void purge();
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
   private:
     void holdEntry(AutoHoldEntry& holder, const ScriptSourceChunk& ssc);
     void releaseEntry(AutoHoldEntry& holder);
 };
 
+template<typename CharT>
+struct SourceTypeTraits;
+
+template<>
+struct SourceTypeTraits<mozilla::Utf8Unit>
+{
+    using SharedImmutableString = js::SharedImmutableString;
+
+    static const mozilla::Utf8Unit* chars(const SharedImmutableString& string) {
+        // Casting |char| data to |Utf8Unit| is safe because |Utf8Unit|
+        // contains a |char|.  See the long comment in |Utf8Unit|'s definition.
+        return reinterpret_cast<const mozilla::Utf8Unit*>(string.chars());
+    }
+
+    static char* toString(const mozilla::Utf8Unit* units) {
+        auto asUnsigned = const_cast<unsigned char*>(mozilla::Utf8AsUnsignedChars(units));
+        return reinterpret_cast<char*>(asUnsigned);
+    }
+
+    static UniqueChars toCacheable(EntryChars<mozilla::Utf8Unit> str) {
+        // The cache only stores strings of |char| or |char16_t|, and right now
+        // it seems best not to gunk up the cache with |Utf8Unit| too.  So
+        // cache |Utf8Unit| strings by interpreting them as |char| strings.
+        char* chars = toString(str.release());
+        return UniqueChars(chars);
+    }
+};
+
+template<>
+struct SourceTypeTraits<char16_t>
+{
+    using SharedImmutableString = js::SharedImmutableTwoByteString;
+
+    static const char16_t* chars(const SharedImmutableString& string) {
+        return string.chars();
+    }
+
+    static char16_t* toString(char16_t* units) {
+        return units;
+    }
+
+    static UniqueTwoByteChars toCacheable(EntryChars<char16_t> str) {
+        return UniqueTwoByteChars(std::move(str));
+    }
+};
+
 class ScriptSource
 {
     friend class SourceCompressionTask;
 
+    class PinnedCharsBase
+    {
+      protected:
+        PinnedCharsBase** stack_ = nullptr;
+        PinnedCharsBase* prev_ = nullptr;
+
+        ScriptSource* source_;
+
+        explicit PinnedCharsBase(ScriptSource* source)
+          : source_(source)
+        {}
+    };
+
   public:
     // Any users that wish to manipulate the char buffer of the ScriptSource
     // needs to do so via PinnedChars for GC safety. A GC may compress
     // ScriptSources. If the source were initially uncompressed, then any raw
     // pointers to the char buffer would now point to the freed, uncompressed
     // chars. This is analogous to Rooted.
-    class PinnedChars
+    template<typename CharT>
+    class PinnedChars : public PinnedCharsBase
     {
-        PinnedChars** stack_;
-        PinnedChars* prev_;
-
-        ScriptSource* source_;
-        const char16_t* chars_;
+        const CharT* chars_;
 
       public:
         PinnedChars(JSContext* cx, ScriptSource* source,
                     UncompressedSourceCache::AutoHoldEntry& holder,
                     size_t begin, size_t len);
 
         ~PinnedChars();
 
-        const char16_t* get() const {
+        const CharT* get() const {
             return chars_;
         }
     };
 
   private:
     mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire,
                     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 { };
 
-    struct Uncompressed
+    template<typename CharT>
+    class Uncompressed
     {
-        SharedImmutableTwoByteString string;
-
-        explicit Uncompressed(SharedImmutableTwoByteString&& str)
-          : string(std::move(str))
-        { }
+        typename SourceTypeTraits<CharT>::SharedImmutableString string_;
+
+
+      public:
+        explicit Uncompressed(typename SourceTypeTraits<CharT>::SharedImmutableString str)
+          : string_(std::move(str))
+        {}
+
+        const CharT* chars() const {
+            return SourceTypeTraits<CharT>::chars(string_);
+        }
+
+        size_t length() const {
+            return string_.length();
+        }
     };
 
+    template<typename CharT>
     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)
+        Compressed(SharedImmutableString raw, size_t uncompressedLength)
+          : raw(std::move(raw)),
+            uncompressedLength(uncompressedLength)
         { }
     };
 
     struct BinAST
     {
         SharedImmutableString string;
         explicit BinAST(SharedImmutableString&& str)
           : string(std::move(str))
         { }
     };
 
-    using SourceType = mozilla::Variant<Missing, Uncompressed, Compressed, BinAST>;
+    using SourceType =
+        mozilla::Variant<Compressed<mozilla::Utf8Unit>, Uncompressed<mozilla::Utf8Unit>,
+                         Compressed<char16_t>, Uncompressed<char16_t>,
+                         Missing,
+                         BinAST>;
     SourceType data;
 
     // If the GC attempts to call setCompressedSource with PinnedChars
     // present, the first PinnedChars (that is, bottom of the stack) will set
     // the compressed chars upon destruction.
-    PinnedChars* pinnedCharsStack_;
-    mozilla::Maybe<Compressed> pendingCompressed_;
+    PinnedCharsBase* pinnedCharsStack_;
+    mozilla::MaybeOneOf<Compressed<mozilla::Utf8Unit>, Compressed<char16_t>> pendingCompressed_;
 
     // The filename of this script.
     UniqueChars filename_;
 
     UniqueTwoByteChars displayURL_;
     UniqueTwoByteChars sourceMapURL_;
     bool mutedErrors_;
 
@@ -507,27 +650,30 @@ class ScriptSource
     // demand. If sourceRetrievable_ and hasSourceText() are false, it is not
     // possible to get source at all.
     bool sourceRetrievable_:1;
     bool hasIntroductionOffset_:1;
     bool containsAsmJS_:1;
 
     UniquePtr<frontend::BinASTSourceMetadata> binASTMetadata_;
 
-    const char16_t* chunkChars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder,
-                               size_t chunk);
+    template<typename CharT>
+    const CharT* chunkChars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder,
+                            size_t chunk);
 
     // Return a string containing the chars starting at |begin| and ending at
     // |begin + len|.
     //
     // Warning: this is *not* GC-safe! Any chars to be handed out should use
     // PinnedChars. See comment below.
-    const char16_t* chars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& asp,
-                          size_t begin, size_t len);
-
+    template<typename CharT>
+    const CharT* chars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& asp,
+                       size_t begin, size_t len);
+
+    template<typename CharT>
     void movePendingCompressedSource();
 
   public:
     // When creating a JSString* from TwoByte source characters, we don't try to
     // to deflate to Latin1 for longer strings, because this can be slow.
     static const size_t SourceDeflateLimit = 100;
 
     explicit ScriptSource()
@@ -563,78 +709,290 @@ class ScriptSource
     MOZ_MUST_USE bool initFromOptions(JSContext* cx,
                                       const JS::ReadOnlyCompileOptions& options,
                                       const mozilla::Maybe<uint32_t>& parameterListEnd = mozilla::Nothing());
     MOZ_MUST_USE bool setSourceCopy(JSContext* cx, JS::SourceBufferHolder& srcBuf);
     void setSourceRetrievable() { sourceRetrievable_ = true; }
     bool sourceRetrievable() const { return sourceRetrievable_; }
     bool hasSourceText() const { return hasUncompressedSource() || hasCompressedSource(); }
     bool hasBinASTSource() const { return data.is<BinAST>(); }
-    bool hasUncompressedSource() const { return data.is<Uncompressed>(); }
-    bool hasCompressedSource() const { return data.is<Compressed>(); }
 
     void setBinASTSourceMetadata(frontend::BinASTSourceMetadata* metadata) {
         MOZ_ASSERT(hasBinASTSource());
         binASTMetadata_.reset(metadata);
     }
     frontend::BinASTSourceMetadata* binASTSourceMetadata() const {
         MOZ_ASSERT(hasBinASTSource());
         return binASTMetadata_.get();
     }
 
+  private:
+    struct UncompressedDataMatcher
+    {
+        template<typename CharT>
+        const void* match(const Uncompressed<CharT>& u) {
+            return u.chars();
+        }
+
+        template<typename T>
+        const void* match(const T&) {
+            MOZ_CRASH("attempting to access uncompressed data in a "
+                      "ScriptSource not containing it");
+            return nullptr;
+        }
+    };
+
+  public:
+    template<typename CharT>
+    const CharT* uncompressedData() {
+        return static_cast<const CharT*>(data.match(UncompressedDataMatcher()));
+    }
+
+  private:
+    struct CompressedDataMatcher
+    {
+        template<typename CharT>
+        char* match(const Compressed<CharT>& c) {
+            return const_cast<char*>(c.raw.chars());
+        }
+
+        template<typename T>
+        char* match(const T&) {
+            MOZ_CRASH("attempting to access compressed data in a ScriptSource "
+                      "not containing it");
+            return nullptr;
+        }
+    };
+
+  public:
+    template<typename CharT>
+    char* compressedData() {
+        return data.match(CompressedDataMatcher());
+    }
+
+  private:
+    struct BinASTDataMatcher
+    {
+        void* match(const BinAST& b) {
+            return const_cast<char*>(b.string.chars());
+        }
+
+        void notBinAST() {
+            MOZ_CRASH("ScriptSource isn't backed by BinAST data");
+        }
+
+        template<typename T>
+        void* match(const T&) {
+            notBinAST();
+            return nullptr;
+        }
+    };
+
+  public:
+    void* binASTData() {
+        return data.match(BinASTDataMatcher());
+    }
+
+  private:
+    struct HasUncompressedSource
+    {
+        template<typename CharT>
+        bool match(const Uncompressed<CharT>&) { return true; }
+
+        template<typename CharT>
+        bool match(const Compressed<CharT>&) { return false; }
+
+        bool match(const BinAST&) { return false; }
+
+        bool match(const Missing&) { return false; }
+    };
+
+  public:
+    bool hasUncompressedSource() const {
+        return data.match(HasUncompressedSource());
+    }
+
+    template<typename CharT>
+    bool uncompressedSourceIs() const {
+        MOZ_ASSERT(hasUncompressedSource());
+        return data.is<Uncompressed<CharT>>();
+    }
+
+  private:
+    struct HasCompressedSource
+    {
+        template<typename CharT>
+        bool match(const Compressed<CharT>&) { return true; }
+
+        template<typename CharT>
+        bool match(const Uncompressed<CharT>&) { return false; }
+
+        bool match(const BinAST&) { return false; }
+
+        bool match(const Missing&) { return false; }
+    };
+
+  public:
+    bool hasCompressedSource() const {
+        return data.match(HasCompressedSource());
+    }
+
+    template<typename CharT>
+    bool compressedSourceIs() const {
+        MOZ_ASSERT(hasCompressedSource());
+        return data.is<Compressed<CharT>>();
+    }
+
+  private:
+    template<typename CharT>
+    struct SourceTypeMatcher
+    {
+        template<template<typename C> class Data>
+        bool match(const Data<CharT>&) {
+            return true;
+        }
+
+        template<template<typename C> class Data, typename NotCharT>
+        bool match(const Data<NotCharT>&) {
+            return false;
+        }
+
+        bool match(const BinAST&) {
+            MOZ_CRASH("doesn't make sense to ask source type of BinAST data");
+            return false;
+        }
+
+        bool match(const Missing&) {
+            MOZ_CRASH("doesn't make sense to ask source type when missing");
+            return false;
+        }
+    };
+
+  public:
+    template<typename CharT>
+    bool hasSourceType() const {
+        return data.match(SourceTypeMatcher<CharT>());
+    }
+
+  private:
+    struct SourceCharSizeMatcher
+    {
+        template<template<typename C> class Data, typename CharT>
+        uint8_t match(const Data<CharT>& data) {
+            static_assert(std::is_same<CharT, mozilla::Utf8Unit>::value ||
+                          std::is_same<CharT, char16_t>::value,
+                          "should only have UTF-8 or UTF-16 source char");
+            return sizeof(CharT);
+        }
+
+        uint8_t match(const BinAST&) {
+            MOZ_CRASH("BinAST source has no source-char size");
+            return 0;
+        }
+
+        uint8_t match(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 CharT>
+        size_t match(const Uncompressed<CharT>& u) {
+            return u.length();
+        }
+
+        template<typename CharT>
+        size_t match(const Compressed<CharT>& u) {
+            return u.uncompressedLength;
+        }
+
+        size_t match(const BinAST& b) {
+            return b.string.length();
+        }
+
+        size_t match(const Missing& m) {
+            MOZ_CRASH("ScriptSource::length on a missing source");
+            return 0;
+        }
+    };
+
+  public:
     size_t length() const {
-        struct LengthMatcher
-        {
-            size_t match(const Uncompressed& u) {
-                return u.string.length();
-            }
-
-            size_t match(const Compressed& c) {
-                return c.uncompressedLength;
-            }
-
-            size_t match(const BinAST& b) {
-                return b.string.length();
-            }
-
-            size_t match(const Missing& m) {
-                MOZ_CRASH("ScriptSource::length on a missing source");
-                return 0;
-            }
-        };
-
         MOZ_ASSERT(hasSourceText() || hasBinASTSource());
-        return data.match(LengthMatcher());
+        return data.match(UncompressedLengthMatcher());
+    }
+
+  private:
+    struct CompressedLengthOrZeroMatcher
+    {
+        template<typename CharT>
+        size_t match(const Uncompressed<CharT>&) {
+            return 0;
+        }
+
+        template<typename CharT>
+        size_t match(const Compressed<CharT>& c) {
+            return c.raw.length();
+        }
+
+        size_t match(const BinAST&) {
+            MOZ_CRASH("trying to get compressed length for BinAST data");
+            return 0;
+        }
+
+        size_t match(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;
 
+    template<typename CharT>
     MOZ_MUST_USE bool setSource(JSContext* cx,
-                                UniqueTwoByteChars&& source,
+                                EntryChars<CharT>&& source,
                                 size_t length);
-    void setSource(SharedImmutableTwoByteString&& string);
+
+    template<typename CharT>
+    void setSource(typename SourceTypeTraits<CharT>::SharedImmutableString uncompressed);
 
     MOZ_MUST_USE bool tryCompressOffThread(JSContext* cx);
 
-    MOZ_MUST_USE bool setCompressedSource(JSContext* cx,
-                                          UniqueChars&& raw,
-                                          size_t rawLength,
+    // The CharT parameter determines which type of compressed source is
+    // recorded, but raw compressed source is always single-byte.
+    template<typename CharT>
+    void setCompressedSource(SharedImmutableString compressed, size_t sourceLength);
+
+    template<typename CharT>
+    MOZ_MUST_USE bool setCompressedSource(JSContext* cx, UniqueChars&& raw, size_t rawLength,
                                           size_t sourceLength);
-    void setCompressedSource(SharedImmutableString&& raw, size_t sourceLength);
 
 #if defined(JS_BUILD_BINAST)
 
     /*
      * 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.
      */
@@ -645,20 +1003,71 @@ class ScriptSource
      * de-duplicated version.
      */
     MOZ_MUST_USE bool setBinASTSource(JSContext* cx, UniqueChars&& buf, size_t len);
 
     const uint8_t* binASTSource();
 
 #endif /* JS_BUILD_BINAST */
 
+  private:
+    void performTaskWork(SourceCompressionTask* task);
+
+    struct SetCompressedSourceFromTask
+    {
+        ScriptSource* const source_;
+        SharedImmutableString& compressed_;
+
+        SetCompressedSourceFromTask(ScriptSource* source, SharedImmutableString& compressed)
+          : source_(source),
+            compressed_(compressed)
+        {}
+
+        template<typename CharT>
+        void match(const Uncompressed<CharT>&) {
+            source_->setCompressedSource<CharT>(std::move(compressed_), source_->length());
+        }
+
+        template<typename CharT>
+        void match(const Compressed<CharT>&) {
+            MOZ_CRASH("can't set compressed source when source is already "
+                      "compressed -- ScriptSource::tryCompressOffThread "
+                      "shouldn't have queued up this task?");
+        }
+
+        void match(const BinAST&) {
+            MOZ_CRASH("doesn't make sense to set compressed source for BinAST "
+                      "data");
+        }
+
+        void match(const Missing&) {
+            MOZ_CRASH("doesn't make sense to set compressed source for "
+                      "missing source -- ScriptSource::tryCompressOffThread "
+                      "shouldn't have queued up this task?");
+        }
+    };
+
+    void setCompressedSourceFromTask(SharedImmutableString compressed);
+
+  public:
     // XDR handling
     template <XDRMode mode>
     MOZ_MUST_USE XDRResult performXDR(XDRState<mode>* xdr);
 
+  private:
+    // It'd be better to make this function take <XDRMode, CharT>, as both
+    // specializations of this function contain nested CharT-parametrized
+    // helper classes that do everything the function needs to do.  But then
+    // we'd need template function partial specialization to hold XDRMode
+    // constant while varying CharT, so that idea's no dice.
+    template <XDRMode mode>
+    MOZ_MUST_USE XDRResult xdrUncompressedSource(XDRState<mode>* xdr, uint8_t sourceCharSize,
+                                                 uint32_t uncompressedLength);
+
+  public:
     MOZ_MUST_USE bool setFilename(JSContext* cx, const char* filename);
     const char* introducerFilename() const {
         return introducerFilename_ ? introducerFilename_.get() : filename_.get();
     }
     bool hasIntroductionType() const {
         return introductionType_;
     }
     const char* introductionType() const {
--- a/js/src/vm/SharedImmutableStringsCache.h
+++ b/js/src/vm/SharedImmutableStringsCache.h
@@ -7,16 +7,17 @@
 #ifndef vm_SharedImmutableStringsCache_h
 #define vm_SharedImmutableStringsCache_h
 
 #include "mozilla/Maybe.h"
 #include "mozilla/UniquePtr.h"
 
 #include <cstring>
 #include <new> // for placement new
+#include <utility> // std::move
 
 #include "builtin/String.h"
 
 #include "js/HashTable.h"
 #include "js/UniquePtr.h"
 #include "js/Utility.h"
 
 #include "threading/ExclusiveData.h"
@@ -24,19 +25,19 @@
 #include "vm/MutexIDs.h"
 
 namespace js {
 
 class SharedImmutableString;
 class SharedImmutableTwoByteString;
 
 /**
- * The `SharedImmutableStringsCache` allows for safely sharing and deduplicating
- * immutable strings (either `const char*` or `const char16_t*`) between
- * threads.
+ * The `SharedImmutableStringsCache` allows safely sharing and deduplicating
+ * immutable strings (either `const char*` [any encoding, not restricted to
+ * only Latin-1 or only UTF-8] or `const char16_t*`) between threads.
  *
  * The locking mechanism is dead-simple and coarse grained: a single lock guards
  * all of the internal table itself, the table's entries, and the entries'
  * reference counts. It is only safe to perform any mutation on the cache or any
  * data stored within the cache when this lock is acquired.
  */
 class SharedImmutableStringsCache
 {
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -26,16 +26,17 @@
 #include "mozilla/Unused.h"
 
 #include <new>
 
 #include "jsmath.h"
 #include "jsutil.h"
 
 #include "builtin/String.h"
+#include "frontend/ParseNode.h"
 #include "frontend/Parser.h"
 #include "gc/Policy.h"
 #include "js/MemoryMetrics.h"
 #include "js/Printf.h"
 #include "js/SourceBufferHolder.h"
 #include "js/StableStringChars.h"
 #include "js/Wrapper.h"
 #include "util/StringBuffer.h"
@@ -47,17 +48,16 @@
 #include "wasm/WasmCompile.h"
 #include "wasm/WasmGenerator.h"
 #include "wasm/WasmInstance.h"
 #include "wasm/WasmIonCompile.h"
 #include "wasm/WasmJS.h"
 #include "wasm/WasmSerialize.h"
 #include "wasm/WasmValidate.h"
 
-#include "frontend/ParseNode-inl.h"
 #include "vm/ArrayBufferObject-inl.h"
 #include "vm/JSObject-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 using namespace js::jit;
 using namespace js::wasm;
 
@@ -698,17 +698,17 @@ static inline ParseNode*
 MaybeInitializer(ParseNode* pn)
 {
     return pn->as<NameNode>().initializer();
 }
 
 static inline bool
 IsUseOfName(ParseNode* pn, PropertyName* name)
 {
-    return pn->isKind(ParseNodeKind::Name) && pn->name() == name;
+    return pn->isName(name);
 }
 
 static inline bool
 IsIgnoredDirectiveName(JSContext* cx, JSAtom* atom)
 {
     return atom != cx->names().useStrict;
 }
 
@@ -2245,17 +2245,17 @@ IsCallToGlobal(ModuleValidator& m, Parse
         return false;
     }
 
     ParseNode* callee = CallCallee(pn);
     if (!callee->isKind(ParseNodeKind::Name)) {
         return false;
     }
 
-    *global = m.lookupGlobal(callee->name());
+    *global = m.lookupGlobal(callee->as<NameNode>().name());
     return !!*global;
 }
 
 static bool
 IsCoercionCall(ModuleValidator& m, ParseNode* pn, Type* coerceTo, ParseNode** coercedExpr)
 {
     const ModuleValidator::Global* global;
     if (!IsCallToGlobal(m, pn, &global)) {
@@ -2766,21 +2766,22 @@ static bool
 CheckArgument(ModuleValidator& m, ParseNode* arg, PropertyName** name)
 {
     *name = nullptr;
 
     if (!arg->isKind(ParseNodeKind::Name)) {
         return m.fail(arg, "argument is not a plain name");
     }
 
-    if (!CheckIdentifier(m, arg, arg->name())) {
-        return false;
-    }
-
-    *name = arg->name();
+    PropertyName* argName = arg->as<NameNode>().name();;
+    if (!CheckIdentifier(m, arg, argName)) {
+        return false;
+    }
+
+    *name = argName;
     return true;
 }
 
 static bool
 CheckModuleArgument(ModuleValidator& m, ParseNode* arg, PropertyName** name)
 {
     if (!CheckArgument(m, arg, name)) {
         return false;
@@ -3003,17 +3004,17 @@ CheckNewArrayView(ModuleValidator& m, Pr
         if (!IsArrayViewCtorName(m, field, &type)) {
             return m.fail(ctorExpr, "could not match typed array name");
         }
     } else {
         if (!ctorExpr->isKind(ParseNodeKind::Name)) {
             return m.fail(ctorExpr, "expecting name of imported array view constructor");
         }
 
-        PropertyName* globalName = ctorExpr->name();
+        PropertyName* globalName = ctorExpr->as<NameNode>().name();
         const ModuleValidator::Global* global = m.lookupGlobal(globalName);
         if (!global) {
             return m.failName(ctorExpr, "%s not found in module global scope", globalName);
         }
 
         if (global->which() != ModuleValidator::Global::ArrayViewCtor) {
             return m.failName(ctorExpr, "%s must be an imported array view constructor", globalName);
         }
@@ -3078,72 +3079,74 @@ CheckGlobalDotImport(ModuleValidator& m,
         }
         return m.failName(base, "expecting %s.Math", globalName);
     }
 
     if (!base->isKind(ParseNodeKind::Name)) {
         return m.fail(base, "expected name of variable or parameter");
     }
 
-    if (base->name() == m.globalArgumentName()) {
+    auto baseName = base->as<NameNode>().name();
+    if (baseName == m.globalArgumentName()) {
         if (field == m.cx()->names().NaN) {
             return m.addGlobalConstant(varName, GenericNaN(), field);
         }
         if (field == m.cx()->names().Infinity) {
             return m.addGlobalConstant(varName, PositiveInfinity<double>(), field);
         }
 
         Scalar::Type type;
         if (IsArrayViewCtorName(m, field, &type)) {
             return m.addArrayViewCtor(varName, type, field);
         }
 
         return m.failName(initNode, "'%s' is not a standard constant or typed array name", field);
     }
 
-    if (base->name() != m.importArgumentName()) {
+    if (baseName != m.importArgumentName()) {
         return m.fail(base, "expected global or import name");
     }
 
     return m.addFFI(varName, field);
 }
 
 static bool
 CheckModuleGlobal(ModuleValidator& m, ParseNode* var, bool isConst)
 {
     if (!var->isKind(ParseNodeKind::Name)) {
         return m.fail(var, "import variable is not a plain name");
     }
 
-    if (!CheckModuleLevelName(m, var, var->name())) {
+    PropertyName* varName = var->as<NameNode>().name();
+    if (!CheckModuleLevelName(m, var, varName)) {
         return false;
     }
 
     ParseNode* initNode = MaybeInitializer(var);
     if (!initNode) {
         return m.fail(var, "module import needs initializer");
     }
 
     if (IsNumericLiteral(m, initNode)) {
-        return CheckGlobalVariableInitConstant(m, var->name(), initNode, isConst);
+        return CheckGlobalVariableInitConstant(m, varName, initNode, isConst);
     }
 
     if (initNode->isKind(ParseNodeKind::BitOr) ||
         initNode->isKind(ParseNodeKind::Pos) ||
         initNode->isKind(ParseNodeKind::Call))
     {
-        return CheckGlobalVariableInitImport(m, var->name(), initNode, isConst);
+        return CheckGlobalVariableInitImport(m, varName, initNode, isConst);
     }
 
     if (initNode->isKind(ParseNodeKind::New)) {
-        return CheckNewArrayView(m, var->name(), initNode);
+        return CheckNewArrayView(m, varName, initNode);
     }
 
     if (initNode->isKind(ParseNodeKind::Dot)) {
-        return CheckGlobalDotImport(m, var->name(), initNode);
+        return CheckGlobalDotImport(m, varName, initNode);
     }
 
     return m.fail(initNode, "unsupported import expression");
 }
 
 static bool
 CheckModuleProcessingDirectives(ModuleValidator& m)
 {
@@ -3278,17 +3281,17 @@ CheckArguments(FunctionValidator& f, Par
     *stmtIter = stmt;
     return true;
 }
 
 static bool
 IsLiteralOrConst(FunctionValidator& f, ParseNode* pn, NumLit* lit)
 {
     if (pn->isKind(ParseNodeKind::Name)) {
-        const ModuleValidator::Global* global = f.lookupGlobal(pn->name());
+        const ModuleValidator::Global* global = f.lookupGlobal(pn->as<NameNode>().name());
         if (!global || global->which() != ModuleValidator::Global::ConstantLiteral) {
             return false;
         }
 
         *lit = global->constLiteralValue();
         return true;
     }
 
@@ -3321,17 +3324,17 @@ CheckFinalReturn(FunctionValidator& f, P
 
 static bool
 CheckVariable(FunctionValidator& f, ParseNode* var, ValTypeVector* types, Vector<NumLit>* inits)
 {
     if (!var->isKind(ParseNodeKind::Name)) {
         return f.fail(var, "local variable is not a plain name");
     }
 
-    PropertyName* name = var->name();
+    PropertyName* name = var->as<NameNode>().name();
 
     if (!CheckIdentifier(f.m(), var, name)) {
         return false;
     }
 
     ParseNode* initNode = MaybeInitializer(var);
     if (!initNode) {
         return f.failName(var, "var '%s' needs explicit type declaration via an initial value", name);
@@ -3409,17 +3412,17 @@ CheckNumericLiteral(FunctionValidator& f
     }
     *type = Type::lit(lit);
     return f.writeConstExpr(lit);
 }
 
 static bool
 CheckVarRef(FunctionValidator& f, ParseNode* varRef, Type* type)
 {
-    PropertyName* name = varRef->name();
+    PropertyName* name = varRef->as<NameNode>().name();
 
     if (const FunctionValidator::Local* local = f.lookupLocal(name)) {
         if (!f.encoder().writeOp(Op::GetLocal)) {
             return false;
         }
         if (!f.encoder().writeVarU32(local->slot)) {
             return false;
         }
@@ -3468,17 +3471,17 @@ static const int32_t NoMask = -1;
 static bool
 CheckArrayAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr,
                  Scalar::Type* viewType)
 {
     if (!viewName->isKind(ParseNodeKind::Name)) {
         return f.fail(viewName, "base of array access must be a typed array view name");
     }
 
-    const ModuleValidator::Global* global = f.lookupGlobal(viewName->name());
+    const ModuleValidator::Global* global = f.lookupGlobal(viewName->as<NameNode>().name());
     if (!global || !global->isAnyArrayView()) {
         return f.fail(viewName, "base of array access must be a typed array view name");
     }
 
     *viewType = global->viewType();
 
     uint32_t index;
     if (IsLiteralOrConstInt(f, indexExpr, &index)) {
@@ -3701,17 +3704,17 @@ CheckStoreArray(FunctionValidator& f, Pa
 
     *type = rhsType;
     return true;
 }
 
 static bool
 CheckAssignName(FunctionValidator& f, ParseNode* lhs, ParseNode* rhs, Type* type)
 {
-    RootedPropertyName name(f.cx(), lhs->name());
+    RootedPropertyName name(f.cx(), lhs->as<NameNode>().name());
 
     if (const FunctionValidator::Local* lhsVar = f.lookupLocal(name)) {
         Type rhsType;
         if (!CheckExpr(f, rhs, &rhsType)) {
             return false;
         }
 
         if (!f.encoder().writeOp(Op::TeeLocal)) {
@@ -4101,17 +4104,17 @@ CheckFuncPtrCall(FunctionValidator& f, P
     ParseNode* callee = CallCallee(callNode);
     ParseNode* tableNode = ElemBase(callee);
     ParseNode* indexExpr = ElemIndex(callee);
 
     if (!tableNode->isKind(ParseNodeKind::Name)) {
         return f.fail(tableNode, "expecting name of function-pointer array");
     }
 
-    PropertyName* name = tableNode->name();
+    PropertyName* name = tableNode->as<NameNode>().name();
     if (const ModuleValidator::Global* existing = f.lookupGlobal(name)) {
         if (existing->which() != ModuleValidator::Global::Table) {
             return f.failName(tableNode, "'%s' is not the name of a function-pointer array", name);
         }
     }
 
     if (!indexExpr->isKind(ParseNodeKind::BitAnd)) {
         return f.fail(indexExpr, "function-pointer table index expression needs & mask");
@@ -4168,17 +4171,17 @@ CheckIsExternType(FunctionValidator& f, 
     return true;
 }
 
 static bool
 CheckFFICall(FunctionValidator& f, ParseNode* callNode, unsigned ffiIndex, Type ret, Type* type)
 {
     MOZ_ASSERT(ret.isCanonical());
 
-    PropertyName* calleeName = CallCallee(callNode)->name();
+    PropertyName* calleeName = CallCallee(callNode)->as<NameNode>().name();
 
     if (ret.isFloat()) {
         return f.fail(callNode, "FFI calls can't return float");
     }
 
     ValTypeVector args;
     if (!CheckCallArgs<CheckIsExternType>(f, callNode, &args)) {
         return false;
@@ -4461,31 +4464,31 @@ CheckCoercedCall(FunctionValidator& f, P
     if (callee->isKind(ParseNodeKind::Elem)) {
         return CheckFuncPtrCall(f, call, ret, type);
     }
 
     if (!callee->isKind(ParseNodeKind::Name)) {
         return f.fail(callee, "unexpected callee expression type");
     }
 
-    PropertyName* calleeName = callee->name();
+    PropertyName* calleeName = callee->as<NameNode>().name();
 
     if (const ModuleValidator::Global* global = f.lookupGlobal(calleeName)) {
         switch (global->which()) {
           case ModuleValidator::Global::FFI:
             return CheckFFICall(f, call, global->ffiIndex(), ret, type);
           case ModuleValidator::Global::MathBuiltinFunction:
             return CheckCoercedMathBuiltinCall(f, call, global->mathBuiltinFunction(), ret, type);
           case ModuleValidator::Global::ConstantLiteral:
           case ModuleValidator::Global::ConstantImport:
           case ModuleValidator::Global::Variable:
           case ModuleValidator::Global::Table:
           case ModuleValidator::Global::ArrayView:
           case ModuleValidator::Global::ArrayViewCtor:
-            return f.failName(callee, "'%s' is not callable function", callee->name());
+            return f.failName(callee, "'%s' is not callable function", calleeName);
           case ModuleValidator::Global::Function:
             break;
         }
     }
 
     return CheckInternalCall(f, call, calleeName, ret, type);
 }
 
@@ -6009,17 +6012,17 @@ CheckFuncPtrTable(ModuleValidator& m, Pa
 
     Uint32Vector elemFuncDefIndices;
     const FuncType* sig = nullptr;
     for (ParseNode* elem = ListHead(arrayLiteral); elem; elem = NextNode(elem)) {
         if (!elem->isKind(ParseNodeKind::Name)) {
             return m.fail(elem, "function-pointer table's elements must be names of functions");
         }
 
-        PropertyName* funcName = elem->name();
+        PropertyName* funcName = elem->as<NameNode>().name();
         const ModuleValidator::Func* func = m.lookupFuncDef(funcName);
         if (!func) {
             return m.fail(elem, "function-pointer table's elements must be names of functions");
         }
 
         const FuncType& funcSig = m.env().types[func->sigIndex()].funcType();
         if (sig) {
             if (*sig != funcSig) {
@@ -6035,17 +6038,17 @@ CheckFuncPtrTable(ModuleValidator& m, Pa
     }
 
     FuncType copy;
     if (!copy.clone(*sig)) {
         return false;
     }
 
     uint32_t tableIndex;
-    if (!CheckFuncPtrTableAgainstExisting(m, var, var->name(), std::move(copy), mask, &tableIndex)) {
+    if (!CheckFuncPtrTableAgainstExisting(m, var, var->as<NameNode>().name(), std::move(copy), mask, &tableIndex)) {
         return false;
     }
 
     if (!m.defineFuncPtrTable(tableIndex, std::move(elemFuncDefIndices))) {
         return m.fail(var, "duplicate function-pointer definition");
     }
 
     return true;
@@ -6083,17 +6086,17 @@ CheckFuncPtrTables(ModuleValidator& m)
 
 static bool
 CheckModuleExportFunction(ModuleValidator& m, ParseNode* pn, PropertyName* maybeFieldName = nullptr)
 {
     if (!pn->isKind(ParseNodeKind::Name)) {
         return m.fail(pn, "expected name of exported function");
     }
 
-    PropertyName* funcName = pn->name();
+    PropertyName* funcName = pn->as<NameNode>().name();
     const ModuleValidator::Func* func = m.lookupFuncDef(funcName);
     if (!func) {
         return m.failName(pn, "function '%s' not found", funcName);
     }
 
     return m.addExportField(*func, maybeFieldName);
 }
 
@@ -6997,17 +7000,17 @@ class ModuleCharsForStore : ModuleChars
         // not present in the source so we must manually explicitly serialize
         // and match the formals as a Vector of PropertyName.
         isFunCtor_ = parser.pc->isStandaloneFunctionBody();
         if (isFunCtor_) {
             unsigned numArgs;
             CodeNode* functionNode = parser.pc->functionBox()->functionNode;
             ParseNode* arg = FunctionFormalParametersList(functionNode, &numArgs);
             for (unsigned i = 0; i < numArgs; i++, arg = arg->pn_next) {
-                UniqueChars name = StringToNewUTF8CharsZ(nullptr, *arg->name());
+                UniqueChars name = StringToNewUTF8CharsZ(nullptr, *arg->as<NameNode>().name());
                 if (!name || !funCtorArgs_.append(std::move(name))) {
                     return false;
                 }
             }
         }
 
         return true;
     }
@@ -7090,17 +7093,17 @@ class ModuleCharsForLookup : ModuleChars
             }
             unsigned numArgs;
             CodeNode* functionNode = parser.pc->functionBox()->functionNode;
             ParseNode* arg = FunctionFormalParametersList(functionNode, &numArgs);
             if (funCtorArgs_.length() != numArgs) {
                 return false;
             }
             for (unsigned i = 0; i < funCtorArgs_.length(); i++, arg = arg->pn_next) {
-                UniqueChars name = StringToNewUTF8CharsZ(nullptr, *arg->name());
+                UniqueChars name = StringToNewUTF8CharsZ(nullptr, *arg->as<NameNode>().name());
                 if (!name || strcmp(funCtorArgs_[i].get(), name.get())) {
                     return false;
                 }
             }
         }
         return true;
     }
 };
--- a/mfbt/RecordReplay.cpp
+++ b/mfbt/RecordReplay.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "RecordReplay.h"
 
 #include "js/GCAnnotations.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Casting.h"
+#include "mozilla/Utf8.h"
 
 #include <stdlib.h>
 
 // Recording and replaying is only enabled on Mac nightlies.
 #if defined(XP_MACOSX) && defined(NIGHTLY_BUILD)
 #define ENABLE_RECORD_REPLAY
 #endif
 
@@ -71,17 +72,20 @@ namespace recordreplay {
   Macro(InternalRecordReplayAssertBytes,                        \
         (const void* aData, size_t aSize), (aData, aSize))      \
   Macro(InternalRegisterThing, (void* aThing), (aThing))        \
   Macro(InternalUnregisterThing, (void* aThing), (aThing))      \
   Macro(InternalRecordReplayDirective, (long aDirective), (aDirective)) \
   Macro(BeginContentParse,                                      \
         (const void* aToken, const char* aURL, const char* aContentType), \
         (aToken, aURL, aContentType))                           \
-  Macro(AddContentParseData,                                    \
+  Macro(AddContentParseData8,                                   \
+        (const void* aToken, const mozilla::Utf8Unit* aUtf8Buffer, size_t aLength), \
+        (aToken, aUtf8Buffer, aLength))                         \
+  Macro(AddContentParseData16,                                  \
         (const void* aToken, const char16_t* aBuffer, size_t aLength), \
         (aToken, aBuffer, aLength))                             \
   Macro(EndContentParse, (const void* aToken), (aToken))
 
 #define DECLARE_SYMBOL(aName, aReturnType, aFormals, _) \
   static aReturnType (*gPtr ##aName) aFormals;
 #define DECLARE_SYMBOL_VOID(aName, aFormals, _)  DECLARE_SYMBOL(aName, void, aFormals, _)
 
--- a/mfbt/RecordReplay.h
+++ b/mfbt/RecordReplay.h
@@ -8,16 +8,17 @@
 
 #ifndef mozilla_RecordReplay_h
 #define mozilla_RecordReplay_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/GuardObjects.h"
 #include "mozilla/TemplateLib.h"
 #include "mozilla/Types.h"
+#include "mozilla/Utf8.h"
 
 #include <functional>
 #include <stdarg.h>
 
 struct PLDHashTableOps;
 struct JSContext;
 class JSObject;
 
@@ -348,31 +349,46 @@ MFBT_API bool DefineRecordReplayControlO
 // being parsed. This is used to provide the complete contents of the URL to
 // devtools code when it is inspecting the state of this process; that devtools
 // code can't simply fetch the URL itself since it may have been changed since
 // the recording was made or may no longer exist. The token for a parse may not
 // be used in other parses until after EndContentParse() is called.
 MFBT_API void BeginContentParse(const void* aToken,
                                 const char* aURL, const char* aContentType);
 
-// Add some parse data to an existing content parse.
-MFBT_API void AddContentParseData(const void* aToken,
-                                  const char16_t* aBuffer, size_t aLength);
+// Add some UTF-8 parse data to an existing content parse.
+MFBT_API void AddContentParseData8(const void* aToken,
+                                   const Utf8Unit* aUtf8Buffer, size_t aLength);
+
+// Add some UTF-16 parse data to an existing content parse.
+MFBT_API void AddContentParseData16(const void* aToken,
+                                    const char16_t* aBuffer, size_t aLength);
 
 // Mark a content parse as having completed.
 MFBT_API void EndContentParse(const void* aToken);
 
 // Perform an entire content parse, when the entire URL is available at once.
 static inline void
-NoteContentParse(const void* aToken,
-                 const char* aURL, const char* aContentType,
-                 const char16_t* aBuffer, size_t aLength)
+NoteContentParse8(const void* aToken,
+                  const char* aURL, const char* aContentType,
+                  const mozilla::Utf8Unit* aUtf8Buffer, size_t aLength)
 {
   BeginContentParse(aToken, aURL, aContentType);
-  AddContentParseData(aToken, aBuffer, aLength);
+  AddContentParseData8(aToken, aUtf8Buffer, aLength);
+  EndContentParse(aToken);
+}
+
+// Perform an entire content parse, when the entire URL is available at once.
+static inline void
+NoteContentParse16(const void* aToken,
+                   const char* aURL, const char* aContentType,
+                   const char16_t* aBuffer, size_t aLength)
+{
+  BeginContentParse(aToken, aURL, aContentType);
+  AddContentParseData16(aToken, aBuffer, aLength);
   EndContentParse(aToken);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // API inline function implementation
 ///////////////////////////////////////////////////////////////////////////////
 
 // Define inline wrappers on builds where recording/replaying is enabled.
--- a/mobile/android/config/mozconfigs/android-aarch64/nightly
+++ b/mobile/android/config/mozconfigs/android-aarch64/nightly
@@ -1,22 +1,13 @@
 . "$topsrcdir/mobile/android/config/mozconfigs/common"
 
 # Android
 ac_add_options --with-android-min-sdk=21
 ac_add_options --target=aarch64-linux-android
 
 ac_add_options --with-branding=mobile/android/branding/nightly
 
-export AR="$topsrcdir/clang/bin/llvm-ar"
-export NM="$topsrcdir/clang/bin/llvm-nm"
-export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
-
-# Enable LTO if the NDK is available.
-if [ -z "$NO_NDK" ]; then
-  ac_add_options --enable-lto
-fi
-
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_POCKET=1
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/mobile/android/config/mozconfigs/android-api-16/nightly
+++ b/mobile/android/config/mozconfigs/android-api-16/nightly
@@ -11,18 +11,9 @@ ac_add_options --target=arm-linux-androi
 
 ac_add_options --with-branding=mobile/android/branding/nightly
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_MMA=1
 export MOZ_ANDROID_POCKET=1
 
-export AR="$topsrcdir/clang/bin/llvm-ar"
-export NM="$topsrcdir/clang/bin/llvm-nm"
-export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
-
-# Enable LTO if the NDK is available.
-if [ -z "$NO_NDK" ]; then
-  ac_add_options --enable-lto
-fi
-
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/mobile/android/config/mozconfigs/android-x86/nightly
+++ b/mobile/android/config/mozconfigs/android-x86/nightly
@@ -9,18 +9,9 @@ ac_add_options --target=i686-linux-andro
 ac_add_options --with-android-min-sdk=16
 
 ac_add_options --with-branding=mobile/android/branding/nightly
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_POCKET=1
 
-export AR="$topsrcdir/clang/bin/llvm-ar"
-export NM="$topsrcdir/clang/bin/llvm-nm"
-export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
-
-# Enable LTO if the NDK is available.
-if [ -z "$NO_NDK" ]; then
-  ac_add_options --enable-lto
-fi
-
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1740,19 +1740,25 @@ VARCACHE_PREF(
    devtools_enabled,
   RelaxedAtomicBool, false
 )
 
 //---------------------------------------------------------------------------
 // Feature-Policy prefs
 //---------------------------------------------------------------------------
 
+#ifdef NIGHTLY_BUILD
+# define PREF_VALUE true
+#else
+# define PREF_VALUE false
+#endif
 VARCACHE_PREF(
   "dom.security.featurePolicy.enabled",
    dom_security_featurePolicy_enabled,
-  bool, false
+  bool, PREF_VALUE
 )
+#undef PREF_VALUE
 
 //---------------------------------------------------------------------------
 // End of prefs
 //---------------------------------------------------------------------------
 
 // clang-format on
--- a/parser/html/nsHtml5StreamParser.cpp
+++ b/parser/html/nsHtml5StreamParser.cpp
@@ -849,17 +849,17 @@ nsHtml5StreamParser::WriteStreamBytes(co
     auto dst = mLastBuffer->TailAsSpan(NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE);
     uint32_t result;
     size_t read;
     size_t written;
     bool hadErrors;
     Tie(result, read, written, hadErrors) =
       mUnicodeDecoder->DecodeToUTF16(src, dst, false);
     if (recordreplay::IsRecordingOrReplaying()) {
-      recordreplay::AddContentParseData(this, dst.data(), written);
+      recordreplay::AddContentParseData16(this, dst.data(), written);
     }
     if (hadErrors && !mHasHadErrors) {
       mHasHadErrors = true;
       if (mEncoding == UTF_8_ENCODING) {
         mTreeBuilder->TryToEnableEncodingMenu();
       }
     }
     src = src.From(read);
@@ -1108,17 +1108,17 @@ nsHtml5StreamParser::DoStopRequest()
     auto dst = mLastBuffer->TailAsSpan(NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE);
     uint32_t result;
     size_t read;
     size_t written;
     bool hadErrors;
     Tie(result, read, written, hadErrors) =
       mUnicodeDecoder->DecodeToUTF16(src, dst, true);
     if (recordreplay::IsRecordingOrReplaying()) {
-      recordreplay::AddContentParseData(this, dst.data(), written);
+      recordreplay::AddContentParseData16(this, dst.data(), written);
     }
     if (hadErrors && !mHasHadErrors) {
       mHasHadErrors = true;
       if (mEncoding == UTF_8_ENCODING) {
         mTreeBuilder->TryToEnableEncodingMenu();
       }
     }
     MOZ_ASSERT(read == 0, "How come an empty span was read form?");
--- a/taskcluster/ci/build/windows.yml
+++ b/taskcluster/ci/build/windows.yml
@@ -757,51 +757,16 @@ win64-devedition-nightly/opt:
     toolchains:
         - win64-clang-cl
         - win64-rust
         - win64-rust-size
         - win64-cbindgen
         - win64-sccache
         - win64-node
 
-win32-mingw32/opt:
-    description: "Win32 MinGW Opt"
-    index:
-        product: firefox
-        job-name: win32-mingw32-opt
-    treeherder:
-        platform: windows-mingw32/all
-        symbol: WM32(Bo)
-        tier: 2
-    worker-type: aws-provisioner-v1/gecko-{level}-b-linux
-    worker:
-        docker-image: {in-tree: mingw32-build}
-        max-run-time: 7200
-        env:
-            PERFHERDER_EXTRA_OPTIONS: "opt"
-    run:
-        using: mozharness
-        actions: [build]
-        script: mozharness/scripts/fx_desktop_build.py
-        config:
-            - builds/releng_base_firefox.py
-            - builds/releng_base_windows_32_mingw_builds.py
-        need-xvfb: false
-    run-on-projects: []
-    toolchains:
-        - mingw32-rust
-        - linux64-upx
-        - linux64-wine
-        - win64-cbindgen
-        - linux64-sccache
-        - linux64-mingw32-gcc
-        - linux64-mingw32-nsis
-        - linux64-mingw32-fxc2
-        - linux64-node
-
 win32-msvc/debug:
     description: "Win32 MSVC Debug"
     index:
         product: firefox
         job-name: win32-msvc-debug
     treeherder:
         platform: windows2012-32/debug
         symbol: Bmsvc
@@ -930,52 +895,16 @@ win64-msvc/opt:
             mozconfig_variant: 'opt-msvc'
     toolchains:
         - win64-clang-cl
         - win64-rust
         - win64-cbindgen
         - win64-sccache
         - win64-node
 
-win32-mingw32/debug:
-    description: "Win32 MinGW Debug"
-    index:
-        product: firefox
-        job-name: win32-mingw32-debug
-    treeherder:
-        platform: windows-mingw32/all
-        symbol: WM32(Bd)
-        tier: 2
-    worker-type: aws-provisioner-v1/gecko-{level}-b-linux
-    worker:
-        docker-image: {in-tree: mingw32-build}
-        max-run-time: 7200
-        env:
-            PERFHERDER_EXTRA_OPTIONS: "debug"
-    run:
-        using: mozharness
-        actions: [build]
-        script: mozharness/scripts/fx_desktop_build.py
-        config:
-            - builds/releng_base_firefox.py
-            - builds/releng_base_windows_32_mingw_builds.py
-            - builds/releng_sub_windows_configs/32_mingw_debug.py
-        need-xvfb: false
-    run-on-projects: []
-    toolchains:
-        - mingw32-rust
-        - linux64-upx
-        - linux64-wine
-        - linux64-cbindgen
-        - linux64-sccache
-        - linux64-mingw32-gcc
-        - linux64-mingw32-nsis
-        - linux64-mingw32-fxc2
-        - linux64-node
-
 win32-mingwclang/opt:
     description: "Win32 MinGW-Clang Opt"
     index:
         product: firefox
         job-name: win32-mingwclang-opt
     treeherder:
         platform: windows-mingw32/all
         symbol: WMC32(Bo)
--- a/taskcluster/ci/config.yml
+++ b/taskcluster/ci/config.yml
@@ -61,18 +61,16 @@ treeherder:
         'Deb9': 'Packages for Debian 9'
         'Nexus 5-L': 'Nexus 5-L Device Image'
         'I': 'Docker Image Builds'
         'TL': 'Toolchain builds for Linux 64-bits'
         'TM': 'Toolchain builds for OSX'
         'TMW': 'Toolchain builds for Windows MinGW'
         'TW32': 'Toolchain builds for Windows 32-bits'
         'TW64': 'Toolchain builds for Windows 64-bits'
-        'WM32': 'MinGW builds for Windows 32-bits'
-        'WM64': 'MinGW builds for Windows 64-bits'
         'WMC32': 'MinGW-Clang builds for Windows 32-bits'
         'WMC64': 'MinGW-Clang builds for Windows 64-bits'
         'Searchfox': 'Searchfox builds'
         'SM': 'Spidermonkey builds'
         'pub': 'APK publishing'
         'p': 'Partial generation'
         'ps': 'Partials signing'
         'Rel': 'Release promotion'
--- a/testing/mozbase/mozdevice/mozdevice/adb.py
+++ b/testing/mozbase/mozdevice/mozdevice/adb.py
@@ -708,18 +708,23 @@ class ADBDevice(ADBCommand):
                 raise ValueError("ADBDevice called with multiple devices "
                                  "attached and no device specified")
             elif len(devices) == 0:
                 # We could error here, but this way we'll wait-for-device before we next
                 # run a command, which seems more friendly
                 return
             device = devices[0]
 
+        # Allow : in device serial if it matches a tcpip device serial.
+        re_device_serial_tcpip = re.compile(r'[^:]+:[0-9]+$')
+
         def is_valid_serial(serial):
-            return ":" not in serial or serial.startswith("usb:")
+            return serial.startswith("usb:") or \
+                re_device_serial_tcpip.match(serial) is not None or \
+                ":" not in serial
 
         if isinstance(device, (str, unicode)):
             # Treat this as a device serial
             if not is_valid_serial(device):
                 raise ValueError("Device serials containing ':' characters are "
                                  "invalid. Pass the output from "
                                  "ADBHost.devices() for the device instead")
             return device
--- a/testing/mozbase/mozdevice/setup.py
+++ b/testing/mozbase/mozdevice/setup.py
@@ -3,17 +3,17 @@
 # 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/.
 
 from __future__ import absolute_import
 
 from setuptools import setup
 
 PACKAGE_NAME = 'mozdevice'
-PACKAGE_VERSION = '1.1.2'
+PACKAGE_VERSION = '1.1.3'
 
 deps = ['mozfile >= 1.0',
         'mozlog >= 3.0',
         'moznetwork >= 0.24',
         'mozprocess >= 0.19',
         ]
 
 setup(name=PACKAGE_NAME,
deleted file mode 100755
--- a/testing/mozharness/configs/builds/releng_sub_windows_configs/32_mingw_debug.py
+++ /dev/null
@@ -1,3 +0,0 @@
-config = {
-    'mozconfig_variant': 'mingw32-debug',
-}
--- a/testing/web-platform/meta/url/failure.html.ini
+++ b/testing/web-platform/meta/url/failure.html.ini
@@ -1,45 +1,33 @@
 [failure.html]
   [URL's href: file://example:1/ should throw]
     expected: FAIL
 
   [XHR: file://example:1/ should throw]
     expected: FAIL
 
-  [Location's href: file://example:1/ should throw]
-    expected: FAIL
-
   [URL's href: file://example:test/ should throw]
     expected: FAIL
 
   [XHR: file://example:test/ should throw]
     expected: FAIL
 
-  [Location's href: file://example:test/ should throw]
-    expected: FAIL
-
   [URL's href: file://example%/ should throw]
     expected: FAIL
 
   [XHR: file://example%/ should throw]
     expected: FAIL
 
-  [Location's href: file://example%/ should throw]
-    expected: FAIL
-
   [URL's href: file://[example\]/ should throw]
     expected: FAIL
 
   [XHR: file://[example\]/ should throw]
     expected: FAIL
 
-  [Location's href: file://[example\]/ should throw]
-    expected: FAIL
-
   [Location's href: http://user:pass@/ should throw]
     expected: FAIL
 
   [Location's href: http://foo:-80/ should throw]
     expected: FAIL
 
   [XHR: http:/:@/www.example.com should throw]
     expected: FAIL
--- a/testing/web-platform/meta/wake-lock/wakelock-enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html.ini
+++ b/testing/web-platform/meta/wake-lock/wakelock-enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html.ini
@@ -1,7 +1,4 @@
 [wakelock-enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html]
   [Feature-Policy allow="wake-lock" allows same-origin relocation]
     expected: FAIL
 
-  [Feature-Policy allow="wake-lock" disallows cross-origin relocation]
-    expected: FAIL
-
--- a/testing/web-platform/meta/webusb/usb-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html.ini
+++ b/testing/web-platform/meta/webusb/usb-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html.ini
@@ -1,13 +1,11 @@
 [usb-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html]
+  expected: TIMEOUT
   [Feature-Policy allow="usb" allows same-origin relocation.]
     expected: FAIL
 
-  [Feature-Policy allow="usb" disallows cross-origin relocation.]
-    expected: FAIL
-
   [Feature-Policy allow="usb" allows workers in same-origin relocation.]
-    expected: FAIL
+    expected: TIMEOUT
 
   [Feature-Policy allow="usb" disallows workers in cross-origin relocation.]
-    expected: FAIL
+    expected: TIMEOUT
 
--- a/testing/web-platform/meta/webusb/usb-allowed-by-feature-policy-attribute.https.sub.html.ini
+++ b/testing/web-platform/meta/webusb/usb-allowed-by-feature-policy-attribute.https.sub.html.ini
@@ -1,19 +1,20 @@
 [usb-allowed-by-feature-policy-attribute.https.sub.html]
+  expected: TIMEOUT
   [Feature policy "usb" can be enabled in cross-origin iframes using "allowed" attribute.]
     expected: FAIL
 
   [Feature policy "usb" can be enabled in same-origin iframe using allow="usb" attribute]
     expected: FAIL
 
   [Feature policy "usb" can be enabled in cross-origin iframe using allow="usb" attribute]
     expected: FAIL
 
   [Feature policy "usb" can be enabled in a worker in same-origin iframe using allow="usb" attribute]
-    expected: FAIL
+    expected: TIMEOUT
 
   [Feature policy "usb" can be enabled in a worker in cross-origin iframe using allow="usb" attribute]
-    expected: FAIL
+    expected: TIMEOUT
 
   [Inherited header feature policy allows dedicated workers.]
     expected: FAIL
 
--- a/toolkit/recordreplay/ipc/JSControl.cpp
+++ b/toolkit/recordreplay/ipc/JSControl.cpp
@@ -571,29 +571,31 @@ GetEntryPosition(const BreakpointPositio
 // Replaying process content
 ///////////////////////////////////////////////////////////////////////////////
 
 struct ContentInfo
 {
   const void* mToken;
   char* mURL;
   char* mContentType;
-  InfallibleVector<char16_t> mContent;
+  InfallibleVector<char> mContent8;
+  InfallibleVector<char16_t> mContent16;
 
   ContentInfo(const void* aToken, const char* aURL, const char* aContentType)
     : mToken(aToken),
       mURL(strdup(aURL)),
       mContentType(strdup(aContentType))
   {}
 
   ContentInfo(ContentInfo&& aOther)
     : mToken(aOther.mToken),
       mURL(aOther.mURL),
       mContentType(aOther.mContentType),
-      mContent(std::move(aOther.mContent))
+      mContent8(std::move(aOther.mContent8)),
+      mContent16(std::move(aOther.mContent16))
   {
     aOther.mURL = nullptr;
     aOther.mContentType = nullptr;
   }
 
   ~ContentInfo()
   {
     free(mURL);
@@ -618,28 +620,47 @@ RecordReplayInterface_BeginContentParse(
   MonitorAutoLock lock(*child::gMonitor);
   for (ContentInfo& info : gContent) {
     MOZ_RELEASE_ASSERT(info.mToken != aToken);
   }
   gContent.emplaceBack(aToken, aURL, aContentType);
 }
 
 MOZ_EXPORT void
-RecordReplayInterface_AddContentParseData(const void* aToken,
-                                          const char16_t* aBuffer, size_t aLength)
+RecordReplayInterface_AddContentParseData8(const void* aToken,
+                                           const Utf8Unit* aUtf8Buffer, size_t aLength)
 {
   MOZ_RELEASE_ASSERT(IsRecordingOrReplaying());
   MOZ_RELEASE_ASSERT(aToken);
 
-  RecordReplayAssert("AddContentParseDataForRecordReplay %d", (int) aLength);
+  RecordReplayAssert("AddContentParseData8ForRecordReplay %d", (int) aLength);
 
   MonitorAutoLock lock(*child::gMonitor);
   for (ContentInfo& info : gContent) {
     if (info.mToken == aToken) {
-      info.mContent.append(aBuffer, aLength);
+      info.mContent8.append(reinterpret_cast<const char*>(aUtf8Buffer), aLength);
+      return;
+    }
+  }
+  MOZ_CRASH("Unknown content parse token");
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_AddContentParseData16(const void* aToken,
+                                            const char16_t* aBuffer, size_t aLength)
+{
+  MOZ_RELEASE_ASSERT(IsRecordingOrReplaying());
+  MOZ_RELEASE_ASSERT(aToken);
+
+  RecordReplayAssert("AddContentParseData16ForRecordReplay %d", (int) aLength);
+
+  MonitorAutoLock lock(*child::gMonitor);
+  for (ContentInfo& info : gContent) {
+    if (info.mToken == aToken) {
+      info.mContent16.append(aBuffer, aLength);
       return;
     }
   }
   MOZ_CRASH("Unknown content parse token");
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_EndContentParse(const void* aToken)
@@ -662,19 +683,29 @@ RecordReplayInterface_EndContentParse(co
 static bool
 FetchContent(JSContext* aCx, HandleString aURL,
              MutableHandleString aContentType, MutableHandleString aContent)
 {
   MonitorAutoLock lock(*child::gMonitor);
   for (ContentInfo& info : gContent) {
     if (JS_FlatStringEqualsAscii(JS_ASSERT_STRING_IS_FLAT(aURL), info.mURL)) {
       aContentType.set(JS_NewStringCopyZ(aCx, info.mContentType));
-      aContent.set(JS_NewUCStringCopyN(aCx, (const char16_t*) info.mContent.begin(),
-                                       info.mContent.length()));
-      return aContentType && aContent;
+      if (!aContentType) {
+        return false;
+      }
+
+      MOZ_ASSERT(info.mContent8.length() == 0 ||
+                 info.mContent16.length() == 0,
+                 "should have content data of only one type");
+
+      aContent.set(info.mContent8.length() > 0
+                   ? JS_NewStringCopyUTF8N(aCx, JS::UTF8Chars(info.mContent8.begin(),
+                                                              info.mContent8.length()))
+                   : JS_NewUCStringCopyN(aCx, info.mContent16.begin(), info.mContent16.length()));
+      return aContent != nullptr;
     }
   }
   aContentType.set(JS_NewStringCopyZ(aCx, "text/plain"));
   aContent.set(JS_NewStringCopyZ(aCx, "Could not find record/replay content"));
   return aContentType && aContent;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
--- a/tools/profiler/core/ProfileBufferEntry.cpp
+++ b/tools/profiler/core/ProfileBufferEntry.cpp
@@ -1094,17 +1094,20 @@ ProfileBuffer::StreamSamplesToJSON(Splic
         e.Next();
 
       } else {
         break;
       }
     }
 
     if (numFrames == 0) {
-      ERROR_AND_CONTINUE("expected one or more frame entries");
+      // It is possible to have empty stacks if native stackwalking is
+      // disabled. Skip samples with empty stacks. (See Bug 1497985).
+      // Thus, don't use ERROR_AND_CONTINUE, but just continue.
+      continue;
     }
 
     sample.mStack = aUniqueStacks.GetOrAddStackIndex(stack);
 
     // Skip over the markers. We process them in StreamMarkersToJSON().
     while (e.Has()) {
       if (e.Get().IsMarker()) {
         e.Next();