Merge mozilla-central to autoland
authorarthur.iakab <aiakab@mozilla.com>
Thu, 15 Nov 2018 11:55:43 +0200
changeset 502989 9a65ee9355e8b014a8ff5fdb28e0f500f0aa652f
parent 502988 72b025a3da8164ed67ff92258a945acbd73bc4d4 (current diff)
parent 502971 dca9c72df68bb59692118595dea65b78cde5b371 (diff)
child 502990 40d19766439121cd6d231dbee225243d14939db0
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland
browser/components/extensions/test/browser/browser_ext_legacy_extension_context_contentscript.js
js/public/SourceBufferHolder.h
testing/web-platform/meta/feature-policy/idlharness.window.js.ini
testing/web-platform/meta/feature-policy/reporting/camera-reporting.https.html.ini
testing/web-platform/meta/feature-policy/reporting/fullscreen-reporting.html.ini
testing/web-platform/meta/feature-policy/reporting/geolocation-reporting.https.html.ini
testing/web-platform/meta/feature-policy/reporting/microphone-reporting.https.html.ini
testing/web-platform/meta/feature-policy/reporting/midi-reporting.html.ini
testing/web-platform/meta/feature-policy/reporting/vr-reporting.https.html.ini
toolkit/components/extensions/LegacyExtensionsUtils.jsm
toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_context.js
toolkit/components/extensions/test/xpcshell/test_ext_legacy_extension_embedding.js
toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -1,12 +1,11 @@
 [DEFAULT]
 tags = webextensions in-process-webextensions
 support-files =
   head.js
 
 [browser_ext_autocompletepopup.js]
-[browser_ext_legacy_extension_context_contentscript.js]
 [browser_ext_windows_allowScriptsToClose.js]
 
 [include:browser-common.ini]
 skip-if = os == 'win' # Windows WebExtensions always run OOP
 [parent:browser-common.ini]
deleted file mode 100644
--- a/browser/components/extensions/test/browser/browser_ext_legacy_extension_context_contentscript.js
+++ /dev/null
@@ -1,173 +0,0 @@
-"use strict";
-
-const {
-  LegacyExtensionContext,
-} = ChromeUtils.import("resource://gre/modules/LegacyExtensionsUtils.jsm", {});
-
-function promiseAddonStartup(extension) {
-  const {Management} = ChromeUtils.import("resource://gre/modules/Extension.jsm", {});
-
-  return new Promise((resolve) => {
-    let listener = (evt, extensionInstance) => {
-      Management.off("startup", listener);
-      resolve(extensionInstance);
-    };
-    Management.on("startup", listener);
-  });
-}
-
-/**
- * This test case ensures that the LegacyExtensionContext can receive a connection
- * from a content script and that the received port contains the expected sender
- * tab info.
- */
-add_task(async function test_legacy_extension_context_contentscript_connection() {
-  function backgroundScript() {
-    // Extract the assigned uuid from the background page url and send it
-    // in a test message.
-    let uuid = window.location.hostname;
-
-    browser.test.onMessage.addListener(async msg => {
-      if (msg == "open-test-tab") {
-        let tab = await browser.tabs.create({url: "http://example.com/"});
-        browser.test.sendMessage("get-expected-sender-info",
-                                 {uuid, tab});
-      } else if (msg == "close-current-tab") {
-        try {
-          let [tab] = await browser.tabs.query({active: true});
-          await browser.tabs.remove(tab.id);
-          browser.test.sendMessage("current-tab-closed", true);
-        } catch (e) {
-          browser.test.sendMessage("current-tab-closed", false);
-        }
-      }
-    });
-
-    browser.test.sendMessage("ready");
-  }
-
-  function contentScript() {
-    browser.runtime.sendMessage("webextension -> legacy_extension message", (reply) => {
-      browser.test.assertEq("legacy_extension -> webextension reply", reply,
-                            "Got the expected reply from the LegacyExtensionContext");
-      browser.test.sendMessage("got-reply-message");
-    });
-
-    let port = browser.runtime.connect();
-
-    port.onMessage.addListener(msg => {
-      browser.test.assertEq("legacy_extension -> webextension port message", msg,
-                            "Got the expected message from the LegacyExtensionContext");
-      port.postMessage("webextension -> legacy_extension port message");
-    });
-  }
-
-  let extensionData = {
-    background: `new ${backgroundScript}`,
-    manifest: {
-      content_scripts: [
-        {
-          matches: ["http://example.com/*"],
-          js: ["content-script.js"],
-        },
-      ],
-    },
-    files: {
-      "content-script.js": `new ${contentScript}`,
-    },
-  };
-
-  let extension = ExtensionTestUtils.loadExtension(extensionData);
-
-  let waitForExtensionReady = extension.awaitMessage("ready");
-
-  let waitForExtensionInstance = promiseAddonStartup(extension);
-
-  extension.startup();
-
-  let extensionInstance = await waitForExtensionInstance;
-
-  // Connect to the target extension.id as an external context
-  // using the given custom sender info.
-  let legacyContext = new LegacyExtensionContext(extensionInstance);
-
-  let waitConnectPort = new Promise(resolve => {
-    let {browser} = legacyContext.api;
-    browser.runtime.onConnect.addListener(port => {
-      resolve(port);
-    });
-  });
-
-  let waitMessage = new Promise(resolve => {
-    let {browser} = legacyContext.api;
-    browser.runtime.onMessage.addListener((singleMsg, msgSender, sendReply) => {
-      sendReply("legacy_extension -> webextension reply");
-      resolve({singleMsg, msgSender});
-    });
-  });
-
-  is(legacyContext.envType, "legacy_extension",
-     "LegacyExtensionContext instance has the expected type");
-
-  ok(legacyContext.api, "Got the API object");
-
-  await waitForExtensionReady;
-
-  extension.sendMessage("open-test-tab");
-
-  let {tab} = await extension.awaitMessage("get-expected-sender-info");
-
-  let {singleMsg, msgSender} = await waitMessage;
-  is(singleMsg, "webextension -> legacy_extension message",
-     "Got the expected message");
-  ok(msgSender, "Got a message sender object");
-
-  is(msgSender.id, extension.id, "The sender has the expected id property");
-  is(msgSender.url, "http://example.com/", "The sender has the expected url property");
-  ok(msgSender.tab, "The sender has a tab property");
-  is(msgSender.tab.id, tab.id, "The port sender has the expected tab.id");
-
-  // Wait confirmation that the reply has been received.
-  await extension.awaitMessage("got-reply-message");
-
-  let port = await waitConnectPort;
-
-  ok(port, "Got the Port API object");
-  ok(port.sender, "The port has a sender property");
-
-  is(port.sender.id, extension.id, "The port sender has an id property");
-  is(port.sender.url, "http://example.com/", "The port sender has the expected url property");
-  ok(port.sender.tab, "The port sender has a tab property");
-  is(port.sender.tab.id, tab.id, "The port sender has the expected tab.id");
-
-  let waitPortMessage = new Promise(resolve => {
-    port.onMessage.addListener((msg) => {
-      resolve(msg);
-    });
-  });
-
-  port.postMessage("legacy_extension -> webextension port message");
-
-  let msg = await waitPortMessage;
-
-  is(msg, "webextension -> legacy_extension port message",
-     "LegacyExtensionContext received the expected message from the webextension");
-
-  let waitForDisconnect = new Promise(resolve => {
-    port.onDisconnect.addListener(resolve);
-  });
-
-  let waitForTestDone = extension.awaitMessage("current-tab-closed");
-
-  extension.sendMessage("close-current-tab");
-
-  await waitForDisconnect;
-
-  info("Got the disconnect event on tab closed");
-
-  let success = await waitForTestDone;
-
-  ok(success, "Test completed successfully");
-
-  await extension.unload();
-});
--- a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
@@ -1,26 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-requestLongerTimeout(2);
-
-function add_tasks(task) {
-  add_task(task.bind(null, {embedded: false}));
-
-  add_task(task.bind(null, {embedded: true}));
-}
-
 async function loadExtension(options) {
   let extension = ExtensionTestUtils.loadExtension({
     useAddonManager: "temporary",
 
-    embedded: options.embedded,
-
     manifest: Object.assign({
       "permissions": ["tabs"],
     }, options.manifest),
 
     files: {
       "options.html": `<!DOCTYPE html>
         <html>
           <head>
@@ -64,22 +54,22 @@ async function loadExtension(options) {
     background: options.background,
   });
 
   await extension.startup();
 
   return extension;
 }
 
-add_tasks(async function test_inline_options(extraOptions) {
-  info(`Test options opened inline (${JSON.stringify(extraOptions)})`);
+add_task(async function test_inline_options() {
+  info(`Test options opened inline`);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
 
-  let extension = await loadExtension(Object.assign({}, extraOptions, {
+  let extension = await loadExtension({
     manifest: {
       applications: {gecko: {id: "inline_options@tests.mozilla.org"}},
       "options_ui": {
         "page": "options.html",
       },
     },
 
     background: async function() {
@@ -189,17 +179,17 @@ add_tasks(async function test_inline_opt
         await browser.tabs.remove(tab.id);
 
         browser.test.notifyPass("options-ui");
       } catch (error) {
         browser.test.fail(`Error: ${error} :: ${error.stack}`);
         browser.test.notifyFail("options-ui");
       }
     },
-  }));
+  });
 
   await Promise.all([
     extension.awaitMessage("options-html-inbound-pong"),
     extension.awaitMessage("options-html-outbound-pong"),
     extension.awaitMessage("bg-inbound-pong"),
     extension.awaitMessage("bg-outbound-pong"),
   ]);
 
@@ -207,22 +197,22 @@ add_tasks(async function test_inline_opt
 
   await extension.awaitFinish("options-ui");
 
   await extension.unload();
 
   BrowserTestUtils.removeTab(tab);
 });
 
-add_tasks(async function test_tab_options(extraOptions) {
-  info(`Test options opened in a tab (${JSON.stringify(extraOptions)})`);
+add_task(async function test_tab_options() {
+  info(`Test options opened in a tab`);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
 
-  let extension = await loadExtension(Object.assign({}, extraOptions, {
+  let extension = await loadExtension({
     manifest: {
       applications: {gecko: {id: "tab_options@tests.mozilla.org"}},
       "options_ui": {
         "page": "options.html",
         "open_in_tab": true,
       },
     },
 
@@ -302,39 +292,39 @@ add_tasks(async function test_tab_option
         await browser.tabs.remove(tab.id);
 
         browser.test.notifyPass("options-ui-tab");
       } catch (error) {
         browser.test.fail(`Error: ${error} :: ${error.stack}`);
         browser.test.notifyFail("options-ui-tab");
       }
     },
-  }));
+  });
 
   await extension.awaitFinish("options-ui-tab");
   await extension.unload();
 
   BrowserTestUtils.removeTab(tab);
 });
 
-add_tasks(async function test_options_no_manifest(extraOptions) {
-  info(`Test with no manifest key (${JSON.stringify(extraOptions)})`);
+add_task(async function test_options_no_manifest() {
+  info(`Test with no manifest key`);
 
-  let extension = await loadExtension(Object.assign({}, extraOptions, {
+  let extension = await loadExtension({
     manifest: {
       applications: {gecko: {id: "no_options@tests.mozilla.org"}},
     },
 
     async background() {
       browser.test.log("Try to open options page when not specified in the manifest.");
 
       await browser.test.assertRejects(
         browser.runtime.openOptionsPage(),
         /No `options_ui` declared/,
         "Expected error from openOptionsPage()");
 
       browser.test.notifyPass("options-no-manifest");
     },
-  }));
+  });
 
   await extension.awaitFinish("options-no-manifest");
   await extension.unload();
 });
--- a/dom/base/DOMPrefsInternal.h
+++ b/dom/base/DOMPrefsInternal.h
@@ -23,9 +23,12 @@ DOM_WEBIDL_PREF(dom_testing_structuredcl
 DOM_WEBIDL_PREF(dom_promise_rejection_events_enabled)
 DOM_WEBIDL_PREF(dom_push_enabled)
 DOM_WEBIDL_PREF(gfx_offscreencanvas_enabled)
 DOM_WEBIDL_PREF(dom_webkitBlink_dirPicker_enabled)
 DOM_WEBIDL_PREF(dom_netinfo_enabled)
 DOM_WEBIDL_PREF(dom_fetchObserver_enabled)
 DOM_WEBIDL_PREF(dom_enable_performance_observer)
 DOM_WEBIDL_PREF(dom_performance_enable_scheduler_timing)
+DOM_WEBIDL_PREF(dom_reporting_enabled)
+DOM_WEBIDL_PREF(dom_reporting_testing_enabled)
+DOM_WEBIDL_PREF(dom_reporting_featurePolicy_enabled)
 DOM_WEBIDL_PREF(javascript_options_streams)
--- a/dom/base/nsDOMNavigationTiming.cpp
+++ b/dom/base/nsDOMNavigationTiming.cpp
@@ -47,16 +47,18 @@ nsDOMNavigationTiming::Clear()
   mUnloadEnd = TimeStamp();
   mLoadEventStart = TimeStamp();
   mLoadEventEnd = TimeStamp();
   mDOMLoading = TimeStamp();
   mDOMInteractive = TimeStamp();
   mDOMContentLoadedEventStart = TimeStamp();
   mDOMContentLoadedEventEnd = TimeStamp();
   mDOMComplete = TimeStamp();
+  mContentfulPaint = TimeStamp();
+  mNonBlankPaint = TimeStamp();
 
   mDocShellHasBeenActiveSinceNavigationStart = false;
 }
 
 DOMTimeMilliSec
 nsDOMNavigationTiming::TimeStampToDOM(TimeStamp aStamp) const
 {
   if (aStamp.IsNull()) {
@@ -303,50 +305,49 @@ MaxWithinWindowBeginningAtMin(const Time
 
 #define TTI_WINDOW_SIZE_MS (5 * 1000)
 
 void
 nsDOMNavigationTiming::TTITimeout(nsITimer* aTimer)
 {
   // Check TTI: see if it's been 5 seconds since the last Long Task
   TimeStamp now = TimeStamp::Now();
-  MOZ_RELEASE_ASSERT(!mNonBlankPaint.IsNull(), "TTI timeout with no non-blank-paint?");
+  MOZ_RELEASE_ASSERT(!mContentfulPaint.IsNull(), "TTI timeout with no contentful-paint?");
 
   nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
   TimeStamp lastLongTaskEnded;
   mainThread->GetLastLongNonIdleTaskEnd(&lastLongTaskEnded);
   if (!lastLongTaskEnded.IsNull()) {
     TimeDuration delta = now - lastLongTaskEnded;
     if (delta.ToMilliseconds() < TTI_WINDOW_SIZE_MS) {
       // Less than 5 seconds since the last long task.  Schedule another check
       aTimer->InitWithNamedFuncCallback(TTITimeoutCallback, this, TTI_WINDOW_SIZE_MS,
                                         nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
                                          "nsDOMNavigationTiming::TTITimeout");
       return;
     }
   }
-  // To correctly implement TTI/TTFI as proposed, we'd need to use
-  // FirstContentfulPaint (FCP, which we have not yet implemented) instead
-  // of FirstNonBlankPaing (FNBP) to start at, and not fire it until there
-  // are no more than 2 network loads.  By the proposed definition, without
-  // that we're closer to TimeToFirstInteractive.
+  // To correctly implement TTI/TTFI as proposed, we'd need to not
+  // fire it until there are no more than 2 network loads.  By the
+  // proposed definition, without that we're closer to
+  // TimeToFirstInteractive.
 
   // XXX check number of network loads, and if > 2 mark to check if loads
   // decreases to 2 (or record that point and let the normal timer here
   // handle it)
 
   // TTI has occurred!  TTI is either FCP (if there are no longtasks and no
   // DCLEnd in the window that starts at FCP), or at the end of the last
   // Long Task or DOMContentLoadedEnd (whichever is later).
 
   if (mTTFI.IsNull()) {
     mTTFI = MaxWithinWindowBeginningAtMin(lastLongTaskEnded, mDOMContentLoadedEventEnd,
                                           TimeDuration::FromMilliseconds(TTI_WINDOW_SIZE_MS));
     if (mTTFI.IsNull()) {
-      mTTFI = mNonBlankPaint;
+      mTTFI = mContentfulPaint;
     }
   }
   // XXX Implement TTI via check number of network loads, and if > 2 mark
   // to check if loads decreases to 2 (or record that point and let the
   // normal timer here handle it)
 
   mTTITimer = nullptr;
 
@@ -394,25 +395,16 @@ nsDOMNavigationTiming::NotifyNonBlankPai
     }
     nsPrintfCString marker("Non-blank paint after %dms for URL %s, %s",
                            int(elapsed.ToMilliseconds()), spec.get(),
                            mDocShellHasBeenActiveSinceNavigationStart ? "foreground tab" : "this tab was inactive some of the time between navigation start and first non-blank paint");
     profiler_add_marker(marker.get());
   }
 #endif
 
-  if (!mTTITimer) {
-    mTTITimer = NS_NewTimer();
-  }
-
-  // TTI is first checked 5 seconds after the FCP (non-blank-paint is very close to FCP).
-  mTTITimer->InitWithNamedFuncCallback(TTITimeoutCallback, this, TTI_WINDOW_SIZE_MS,
-                                       nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
-                                       "nsDOMNavigationTiming::TTITimeout");
-
   if (mDocShellHasBeenActiveSinceNavigationStart) {
     if (net::nsHttp::IsBeforeLastActiveTabLoadOptimization(mNavigationStart)) {
       Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_NON_BLANK_PAINT_NETOPT_MS,
                                      mNavigationStart,
                                      mNonBlankPaint);
     } else {
       Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_NON_BLANK_PAINT_NO_NETOPT_MS,
                                      mNavigationStart,
@@ -421,16 +413,52 @@ nsDOMNavigationTiming::NotifyNonBlankPai
 
     Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_NON_BLANK_PAINT_MS,
                                    mNavigationStart,
                                    mNonBlankPaint);
   }
 }
 
 void
+nsDOMNavigationTiming::NotifyContentfulPaintForRootContentDocument()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mNavigationStart.IsNull());
+
+  if (!mContentfulPaint.IsNull()) {
+    return;
+  }
+
+  mContentfulPaint = TimeStamp::Now();
+
+#ifdef MOZ_GECKO_PROFILER
+  if (profiler_is_active()) {
+    TimeDuration elapsed = mContentfulPaint - mNavigationStart;
+    nsAutoCString spec;
+    if (mLoadedURI) {
+      mLoadedURI->GetSpec(spec);
+    }
+    nsPrintfCString marker("Contentful paint after %dms for URL %s, %s",
+                           int(elapsed.ToMilliseconds()), spec.get(),
+                           mDocShellHasBeenActiveSinceNavigationStart ? "foreground tab" : "this tab was inactive some of the time between navigation start and first non-blank paint");
+    profiler_add_marker(marker.get());
+  }
+#endif
+
+  if (!mTTITimer) {
+    mTTITimer = NS_NewTimer();
+  }
+
+  // TTI is first checked 5 seconds after the FCP (non-blank-paint is very close to FCP).
+  mTTITimer->InitWithNamedFuncCallback(TTITimeoutCallback, this, TTI_WINDOW_SIZE_MS,
+                                       nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
+                                       "nsDOMNavigationTiming::TTITimeout");
+}
+
+void
 nsDOMNavigationTiming::NotifyDOMContentFlushedForRootContentDocument()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mNavigationStart.IsNull());
 
   if (!mDOMContentFlushed.IsNull()) {
     return;
   }
--- a/dom/base/nsDOMNavigationTiming.h
+++ b/dom/base/nsDOMNavigationTiming.h
@@ -92,16 +92,20 @@ public:
   DOMTimeMilliSec GetLoadEventEnd() const
   {
     return TimeStampToDOM(mLoadEventEnd);
   }
   DOMTimeMilliSec GetTimeToNonBlankPaint() const
   {
     return TimeStampToDOM(mNonBlankPaint);
   }
+  DOMTimeMilliSec GetTimeToContentfulPaint() const
+  {
+    return TimeStampToDOM(mContentfulPaint);
+  }
   DOMTimeMilliSec GetTimeToTTFI() const
   {
     return TimeStampToDOM(mTTFI);
   }
   DOMTimeMilliSec GetTimeToDOMContentFlushed() const
   {
     return TimeStampToDOM(mDOMContentFlushed);
   }
@@ -169,16 +173,17 @@ public:
   void NotifyDOMContentLoadedStart(nsIURI* aURI);
   void NotifyDOMContentLoadedEnd(nsIURI* aURI);
 
   static void TTITimeoutCallback(nsITimer* aTimer, void *aClosure);
   void TTITimeout(nsITimer* aTimer);
 
   void NotifyLongTask(mozilla::TimeStamp aWhen);
   void NotifyNonBlankPaintForRootContentDocument();
+  void NotifyContentfulPaintForRootContentDocument();
   void NotifyDOMContentFlushedForRootContentDocument();
   void NotifyDocShellStateChanged(DocShellState aDocShellState);
 
   DOMTimeMilliSec TimeStampToDOM(mozilla::TimeStamp aStamp) const;
 
   inline DOMHighResTimeStamp TimeStampToDOMHighRes(mozilla::TimeStamp aStamp) const
   {
     if (aStamp.IsNull()) {
@@ -204,16 +209,17 @@ private:
   nsCOMPtr<nsIURI> mUnloadedURI;
   nsCOMPtr<nsIURI> mLoadedURI;
   nsCOMPtr<nsITimer> mTTITimer;
 
   Type mNavigationType;
   DOMHighResTimeStamp mNavigationStartHighRes;
   mozilla::TimeStamp mNavigationStart;
   mozilla::TimeStamp mNonBlankPaint;
+  mozilla::TimeStamp mContentfulPaint;
   mozilla::TimeStamp mDOMContentFlushed;
 
   mozilla::TimeStamp mBeforeUnloadStart;
   mozilla::TimeStamp mUnloadStart;
   mozilla::TimeStamp mUnloadEnd;
   mozilla::TimeStamp mLoadEventStart;
   mozilla::TimeStamp mLoadEventEnd;
 
--- a/dom/base/nsDeprecatedOperationList.h
+++ b/dom/base/nsDeprecatedOperationList.h
@@ -39,12 +39,15 @@ DEPRECATED_OPERATION(MixedDisplayObjectS
 DEPRECATED_OPERATION(MotionEvent)
 DEPRECATED_OPERATION(OrientationEvent)
 DEPRECATED_OPERATION(ProximityEvent)
 DEPRECATED_OPERATION(AmbientLightEvent)
 DEPRECATED_OPERATION(IDBOpenDBOptions_StorageType)
 DEPRECATED_OPERATION(DOMAttrModifiedEvent)
 DEPRECATED_OPERATION(MozBoxOrInlineBoxDisplay)
 DEPRECATED_OPERATION(DOMQuadBoundsAttr)
+DEPRECATED_OPERATION(DeprecatedTestingInterface)
+DEPRECATED_OPERATION(DeprecatedTestingMethod)
+DEPRECATED_OPERATION(DeprecatedTestingAttribute)
 DEPRECATED_OPERATION(CreateImageBitmapCanvasRenderingContext2D)
 DEPRECATED_OPERATION(MozRequestFullScreenDeprecatedPrefix)
 DEPRECATED_OPERATION(MozfullscreenchangeDeprecatedPrefix)
 DEPRECATED_OPERATION(MozfullscreenerrorDeprecatedPrefix)
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -3039,16 +3039,21 @@ nsIDocument::InitFeaturePolicy(nsIChanne
     }
   }
 
   if (parentPolicy) {
     // Let's inherit the policy from the parent HTMLIFrameElement if it exists.
     mFeaturePolicy->InheritPolicy(parentPolicy);
   }
 
+  // We don't want to parse the http Feature-Policy header if this pref is off.
+  if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) {
+    return NS_OK;
+  }
+
   nsCOMPtr<nsIHttpChannel> httpChannel;
   nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (!httpChannel) {
     return NS_OK;
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -26,17 +26,17 @@
 #include "nsIScriptError.h"
 #include "nsIConsoleService.h"
 #include "nsIMemoryReporter.h"
 #include "nsIProtocolHandler.h"
 #include "nsIScriptSecurityManager.h"
 #include "xpcpublic.h"
 #include "js/CompilationAndEvaluation.h"
 #include "js/JSON.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ScriptPreloader.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/dom/ChildProcessMessageManager.h"
 #include "mozilla/dom/ChromeMessageBroadcaster.h"
 #include "mozilla/dom/File.h"
@@ -1386,20 +1386,24 @@ nsMessageManagerScriptExecutor::TryCache
       }
 
       uint32_t size = (uint32_t)std::min(written, (uint64_t)UINT32_MAX);
       ScriptLoader::ConvertToUTF16(channel, (uint8_t*)buffer.get(), size,
                                    EmptyString(), nullptr,
                                    dataStringBuf, dataStringLength);
     }
 
-    JS::SourceBufferHolder srcBuf(dataStringBuf, dataStringLength,
-                                  JS::SourceBufferHolder::GiveOwnership);
+    if (!dataStringBuf || dataStringLength == 0) {
+      return;
+    }
 
-    if (!dataStringBuf || dataStringLength == 0) {
+    JS::UniqueTwoByteChars srcChars(dataStringBuf);
+
+    JS::SourceText<char16_t> srcBuf;
+    if (!srcBuf.init(cx, std::move(srcChars), dataStringLength)) {
       return;
     }
 
     JS::CompileOptions options(cx);
     options.setFileAndLine(url.get(), 1);
     options.setNoScriptRval(true);
 
     if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) {
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -232,16 +232,18 @@
 #include "mozilla/dom/WindowBinding.h"
 #include "nsITabChild.h"
 #include "mozilla/dom/MediaQueryList.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/NavigatorBinding.h"
 #include "mozilla/dom/ImageBitmap.h"
 #include "mozilla/dom/ImageBitmapBinding.h"
 #include "mozilla/dom/InstallTriggerBinding.h"
+#include "mozilla/dom/Report.h"
+#include "mozilla/dom/ReportingObserver.h"
 #include "mozilla/dom/ServiceWorker.h"
 #include "mozilla/dom/ServiceWorkerRegistration.h"
 #include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h"
 #include "mozilla/dom/U2F.h"
 #include "mozilla/dom/WebIDLGlobalNameHash.h"
 #include "mozilla/dom/Worklet.h"
 #ifdef HAVE_SIDEBAR
 #include "mozilla/dom/ExternalBinding.h"
@@ -332,16 +334,19 @@ using mozilla::dom::cache::CacheStorage;
 #define MAX_SUCCESSIVE_DIALOG_COUNT 5
 
 // Idle fuzz time upper limit
 #define MAX_IDLE_FUZZ_TIME_MS 90000
 
 // Min idle notification time in seconds.
 #define MIN_IDLE_NOTIFICATION_TIME_S 1
 
+// Max number of Report objects
+#define MAX_REPORT_RECORDS 100
+
 static LazyLogModule gDOMLeakPRLogInner("DOMLeakInner");
 
 static bool                 gIdleObserversAPIFuzzTimeDisabled = false;
 static FILE                *gDumpFile                         = nullptr;
 
 nsGlobalWindowInner::InnerWindowByIdTable *nsGlobalWindowInner::sInnerWindowsById = nullptr;
 
 bool nsGlobalWindowInner::sDragServiceDisabled = false;
@@ -1466,16 +1471,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollbars)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mU2F)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPaintWorklet)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInstallTrigger)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIntlUtils)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReportRecords)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReportingObservers)
 
   tmp->TraverseHostObjectURIs(cb);
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mMessageManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mGroupMessageManagers)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPromises)
 
@@ -1555,16 +1562,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollbars)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mU2F)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPaintWorklet)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mInstallTrigger)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntlUtils)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mReportRecords)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mReportingObservers)
 
   tmp->UnlinkHostObjectURIs();
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleRequestExecutor)
 
   // Here the IdleRequest list would've been unlinked, but we rely on
   // that IdleRequest objects have been traced and will remove
   // themselves while unlinking.
@@ -5664,16 +5673,17 @@ nsGlobalWindowInner::Observe(nsISupports
         FireOfflineStatusEventIfChanged();
     }
     return NS_OK;
   }
 
   if (!nsCRT::strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) {
     if (mPerformance) {
       mPerformance->MemoryPressure();
+      mReportRecords.Clear();
     }
     return NS_OK;
   }
 
   if (!nsCRT::strcmp(aTopic, "clear-site-data-reload-needed")) {
     // The reload is propagated from the top-level window only.
     NS_ConvertUTF16toUTF8 otherOrigin(aData);
     PropagateClearSiteDataReload(otherOrigin);
@@ -8081,13 +8091,69 @@ nsPIDOMWindowInner::nsPIDOMWindowInner(n
   mHasTriedToCacheTopInnerWindow(false),
   mNumOfIndexedDBDatabases(0),
   mNumOfOpenWebSockets(0),
   mEvent(nullptr)
 {
   MOZ_ASSERT(aOuterWindow);
 }
 
+void
+nsPIDOMWindowInner::RegisterReportingObserver(ReportingObserver* aObserver,
+                                              bool aBuffered)
+{
+  MOZ_ASSERT(aObserver);
+
+  if (mReportingObservers.Contains(aObserver)) {
+    return;
+  }
+
+  if (NS_WARN_IF(!mReportingObservers.AppendElement(aObserver, fallible))) {
+    return;
+  }
+
+  if (!aBuffered) {
+    return;
+  }
+
+  for (Report* report : mReportRecords) {
+    aObserver->MaybeReport(report);
+  }
+}
+
+void
+nsPIDOMWindowInner::UnregisterReportingObserver(ReportingObserver* aObserver)
+{
+  MOZ_ASSERT(aObserver);
+  mReportingObservers.RemoveElement(aObserver);
+}
+
+void
+nsPIDOMWindowInner::BroadcastReport(Report* aReport)
+{
+  MOZ_ASSERT(aReport);
+
+  for (ReportingObserver* observer : mReportingObservers) {
+    observer->MaybeReport(aReport);
+  }
+
+  if (NS_WARN_IF(!mReportRecords.AppendElement(aReport, fallible))) {
+    return;
+  }
+
+  while (mReportRecords.Length() > MAX_REPORT_RECORDS) {
+    mReportRecords.RemoveElementAt(0);
+  }
+}
+
+void
+nsPIDOMWindowInner::NotifyReportingObservers()
+{
+  for (ReportingObserver* observer : mReportingObservers) {
+    observer->MaybeNotify();
+  }
+}
+
 nsPIDOMWindowInner::~nsPIDOMWindowInner() {}
 
 #undef FORWARD_TO_OUTER
 #undef FORWARD_TO_OUTER_OR_THROW
 #undef FORWARD_TO_OUTER_VOID
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -11,17 +11,17 @@
  * the generated code itself.
  */
 
 #include "nsJSUtils.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/CompilationAndEvaluation.h"
 #include "js/OffThreadScriptCompilation.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "nsIScriptContext.h"
 #include "nsIScriptElement.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIXPConnect.h"
 #include "nsCOMPtr.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsPIDOMWindow.h"
 #include "GeckoProfiler.h"
@@ -91,19 +91,26 @@ nsJSUtils::CompileFunction(AutoJSAPI& js
                 js::IsObjectInContextCompartment(aScopeChain[0], cx));
 
   // Do the junk Gecko is supposed to do before calling into JSAPI.
   for (size_t i = 0; i < aScopeChain.length(); ++i) {
     JS::ExposeObjectToActiveJS(aScopeChain[i]);
   }
 
   // Compile.
+  const nsPromiseFlatString& flatBody = PromiseFlatString(aBody);
+
+  JS::SourceText<char16_t> source;
+  if (!source.init(cx, flatBody.get(), flatBody.Length(),
+                   JS::SourceOwnership::Borrowed))
+  {
+    return NS_ERROR_FAILURE;
+  }
+
   JS::Rooted<JSFunction*> fun(cx);
-  JS::SourceBufferHolder source(PromiseFlatString(aBody).get(), aBody.Length(),
-                                JS::SourceBufferHolder::NoOwnership);
   if (!JS::CompileFunction(cx, aScopeChain, aOptions,
                            PromiseFlatCString(aName).get(),
                            aArgCount, aArgArray,
                            source, &fun))
   {
     return NS_ERROR_FAILURE;
   }
 
@@ -212,17 +219,17 @@ nsJSUtils::ExecutionContext::JoinAndExec
     return mRv;
   }
 
   return NS_OK;
 }
 
 nsresult
 nsJSUtils::ExecutionContext::CompileAndExec(JS::CompileOptions& aCompileOptions,
-                                            JS::SourceBufferHolder& aSrcBuf,
+                                            JS::SourceText<char16_t>& aSrcBuf,
                                             JS::MutableHandle<JSScript*> aScript)
 {
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(aSrcBuf.get());
   MOZ_ASSERT(mRetValue.isUndefined());
@@ -265,18 +272,25 @@ nsJSUtils::ExecutionContext::CompileAndE
                                             const nsAString& aScript)
 {
   MOZ_ASSERT(!mEncodeBytecode, "A JSScript is needed for calling FinishIncrementalEncoding");
   if (mSkip) {
     return mRv;
   }
 
   const nsPromiseFlatString& flatScript = PromiseFlatString(aScript);
-  JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(),
-                                JS::SourceBufferHolder::NoOwnership);
+  JS::SourceText<char16_t> srcBuf;
+  if (!srcBuf.init(mCx, flatScript.get(), flatScript.Length(),
+                   JS::SourceOwnership::Borrowed))
+  {
+    mSkip = true;
+    mRv = EvaluationExceptionToNSResult(mCx);
+    return mRv;
+  }
+
   JS::Rooted<JSScript*> script(mCx);
   return CompileAndExec(aCompileOptions, srcBuf, &script);
 }
 
 nsresult
 nsJSUtils::ExecutionContext::DecodeAndExec(JS::CompileOptions& aCompileOptions,
                                            mozilla::Vector<uint8_t>& aBytecodeBuf,
                                            size_t aBytecodeIndex)
@@ -468,17 +482,17 @@ nsJSUtils::ExecutionContext::ExtractRetu
   }
 
   aRetValue.set(mRetValue);
   return NS_OK;
 }
 
 nsresult
 nsJSUtils::CompileModule(JSContext* aCx,
-                       JS::SourceBufferHolder& aSrcBuf,
+                       JS::SourceText<char16_t>& aSrcBuf,
                        JS::Handle<JSObject*> aEvaluationGlobal,
                        JS::CompileOptions &aCompileOptions,
                        JS::MutableHandle<JSObject*> aModule)
 {
   AUTO_PROFILER_LABEL("nsJSUtils::CompileModule", JS);
 
   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
   MOZ_ASSERT(aSrcBuf.get());
--- a/dom/base/nsJSUtils.h
+++ b/dom/base/nsJSUtils.h
@@ -15,16 +15,17 @@
  */
 
 #include "mozilla/Assertions.h"
 
 #include "GeckoProfiler.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/Conversions.h"
+#include "js/SourceText.h"
 #include "js/StableStringChars.h"
 #include "nsString.h"
 
 class nsIScriptContext;
 class nsIScriptElement;
 class nsIScriptGlobalObject;
 class nsXBLPrototypeBinding;
 
@@ -152,19 +153,19 @@ public:
     // After getting a notification that an off-thread compilation terminated,
     // this function will take the result of the parser by moving it to the main
     // thread before starting the execution of the script.
     //
     // The compiled script would be returned in the |aScript| out-param.
     MOZ_MUST_USE nsresult JoinAndExec(JS::OffThreadToken** aOffThreadToken,
                                       JS::MutableHandle<JSScript*> aScript);
 
-    // Compile a script contained in a SourceBuffer, and execute it.
+    // Compile a script contained in a SourceText, and execute it.
     nsresult CompileAndExec(JS::CompileOptions& aCompileOptions,
-                            JS::SourceBufferHolder& aSrcBuf,
+                            JS::SourceText<char16_t>& aSrcBuf,
                             JS::MutableHandle<JSScript*> aScript);
 
     // Compile a script contained in a string, and execute it.
     nsresult CompileAndExec(JS::CompileOptions& aCompileOptions,
                             const nsAString& aScript);
 
     // Decode a script contained in a buffer, and execute it.
     MOZ_MUST_USE nsresult DecodeAndExec(JS::CompileOptions& aCompileOptions,
@@ -181,17 +182,17 @@ public:
 
     // Decode a BinAST encoded script contained in a buffer, and execute it.
     nsresult DecodeBinASTAndExec(JS::CompileOptions& aCompileOptions,
                                  const uint8_t* aBuf, size_t aLength,
                                  JS::MutableHandle<JSScript*> aScript);
   };
 
   static nsresult CompileModule(JSContext* aCx,
-                                JS::SourceBufferHolder& aSrcBuf,
+                                JS::SourceText<char16_t>& aSrcBuf,
                                 JS::Handle<JSObject*> aEvaluationGlobal,
                                 JS::CompileOptions &aCompileOptions,
                                 JS::MutableHandle<JSObject*> aModule);
 
   static nsresult InitModuleSourceElement(JSContext* aCx,
                                           JS::Handle<JSObject*> aModule,
                                           nsIScriptElement* aElement);
 
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -53,16 +53,19 @@ class ClientInfo;
 class ClientState;
 class ContentFrameMessageManager;
 class DocGroup;
 class TabGroup;
 class Element;
 class MozIdleObserver;
 class Navigator;
 class Performance;
+class Report;
+class ReportBody;
+class ReportingObserver;
 class Selection;
 class ServiceWorker;
 class ServiceWorkerDescriptor;
 class Timeout;
 class TimeoutManager;
 class CustomElementRegistry;
 enum class CallerType : uint32_t;
 } // namespace dom
@@ -623,16 +626,29 @@ public:
   virtual nsISerialEventTarget*
   EventTargetFor(mozilla::TaskCategory aCategory) const = 0;
 
   // Returns the AutoplayPermissionManager that documents in this window should
   // use to request permission to autoplay.
   already_AddRefed<mozilla::AutoplayPermissionManager>
   GetAutoplayPermissionManager();
 
+  void
+  RegisterReportingObserver(mozilla::dom::ReportingObserver* aObserver,
+                            bool aBuffered);
+
+  void
+  UnregisterReportingObserver(mozilla::dom::ReportingObserver* aObserver);
+
+  void
+  BroadcastReport(mozilla::dom::Report* aReport);
+
+  void
+  NotifyReportingObservers();
+
 protected:
   void CreatePerformanceObjectIfNeeded();
 
   // Lazily instantiate an about:blank document if necessary, and if
   // we have what it takes to do so.
   void MaybeCreateDoc();
 
   void SetChromeEventHandlerInternal(mozilla::dom::EventTarget* aChromeEventHandler) {
@@ -717,16 +733,20 @@ protected:
   // If we're in the process of requesting permission for this window to
   // play audible media, or we've already been granted permission by the
   // user, this is non-null, and encapsulates the request.
   RefPtr<mozilla::AutoplayPermissionManager> mAutoplayPermissionManager;
 
   // The event dispatch code sets and unsets this while keeping
   // the event object alive.
   mozilla::dom::Event* mEvent;
+
+  // List of Report objects for ReportingObservers.
+  nsTArray<RefPtr<mozilla::dom::ReportingObserver>> mReportingObservers;
+  nsTArray<RefPtr<mozilla::dom::Report>> mReportRecords;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
 
 class nsPIDOMWindowOuter : public mozIDOMWindowProxy
 {
 protected:
   explicit nsPIDOMWindowOuter(uint64_t aWindowID);
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -8,53 +8,57 @@
 
 #include <algorithm>
 #include <stdarg.h>
 
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs.h"
 #include "mozilla/Unused.h"
 #include "mozilla/UseCounter.h"
 
 #include "AccessCheck.h"
 #include "js/JSON.h"
 #include "js/StableStringChars.h"
 #include "jsfriendapi.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsHTMLTags.h"
 #include "nsIDocShell.h"
 #include "nsIDOMGlobalPropertyInitializer.h"
 #include "nsINode.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
+#include "nsIURIFixup.h"
 #include "nsIXPConnect.h"
 #include "nsUTF8Utils.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WrapperFactory.h"
 #include "xpcprivate.h"
 #include "XrayWrapper.h"
 #include "nsPrintfCString.h"
 #include "mozilla/Sprintf.h"
 #include "nsGlobalWindow.h"
 #include "nsReadableUtils.h"
 
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/DeprecationReportBody.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/ElementBinding.h"
 #include "mozilla/dom/HTMLObjectElement.h"
 #include "mozilla/dom/HTMLObjectElementBinding.h"
 #include "mozilla/dom/HTMLEmbedElement.h"
 #include "mozilla/dom/HTMLElementBinding.h"
 #include "mozilla/dom/HTMLEmbedElementBinding.h"
+#include "mozilla/dom/ReportingUtils.h"
 #include "mozilla/dom/XULElementBinding.h"
 #include "mozilla/dom/XULFrameElementBinding.h"
 #include "mozilla/dom/XULMenuElementBinding.h"
 #include "mozilla/dom/XULPopupElementBinding.h"
 #include "mozilla/dom/XULTextElementBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/WebIDLGlobalNameHash.h"
 #include "mozilla/dom/WorkerPrivate.h"
@@ -2195,17 +2199,17 @@ GetCachedSlotStorageObjectSlow(JSContext
   if (!xpc::WrapperFactory::IsXrayWrapper(obj)) {
     JSObject* retval = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
     MOZ_ASSERT(IsDOMObject(retval));
     *isXray = false;
     return retval;
   }
 
   *isXray = true;
-  return xpc::EnsureXrayExpandoObject(cx, obj);;
+  return xpc::EnsureXrayExpandoObject(cx, obj);
 }
 
 DEFINE_XRAY_EXPANDO_CLASS(, DefaultXrayExpandoObjectClass, 0);
 
 NativePropertyHooks sEmptyNativePropertyHooks = {
   nullptr,
   nullptr,
   nullptr,
@@ -4098,21 +4102,105 @@ SetDocumentAndPageUseCounter(JSObject* a
   nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(js::UncheckedUnwrap(aObject));
   if (win && win->GetDocument()) {
     win->GetDocument()->SetDocumentAndPageUseCounter(aUseCounter);
   }
 }
 
 namespace {
 
+#define DEPRECATED_OPERATION(_op) #_op,
+static const char* kDeprecatedOperations[] = {
+#include "nsDeprecatedOperationList.h"
+  nullptr
+};
+#undef DEPRECATED_OPERATION
+
+void
+ReportDeprecation(nsPIDOMWindowInner* aWindow, nsIURI* aURI,
+                  nsIDocument::DeprecatedOperations aOperation,
+                  const nsAString& aFileName,
+                  const Nullable<uint32_t>& aLineNumber,
+                  const Nullable<uint32_t>& aColumnNumber)
+{
+  MOZ_ASSERT(aURI);
+
+  // Anonymize the URL.
+  // Strip the URL of any possible username/password and make it ready to be
+  // presented in the UI.
+  nsCOMPtr<nsIURIFixup> urifixup = services::GetURIFixup();
+  if (NS_WARN_IF(!urifixup)) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> exposableURI;
+  nsresult rv = urifixup->CreateExposableURI(aURI, getter_AddRefs(exposableURI));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsAutoCString spec;
+  rv = exposableURI->GetSpec(spec);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsAutoString type;
+  type.AssignASCII(kDeprecatedOperations[aOperation]);
+
+  nsAutoCString key;
+  key.AssignASCII(kDeprecatedOperations[aOperation]);
+  key.AppendASCII("Warning");
+
+  nsAutoString msg;
+  rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+                                          key.get(), msg);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  RefPtr<DeprecationReportBody> body =
+    new DeprecationReportBody(aWindow, type, Nullable<Date>(),
+                              msg, aFileName, aLineNumber, aColumnNumber);
+
+  ReportingUtils::Report(aWindow, nsGkAtoms::deprecation,
+                         NS_ConvertUTF8toUTF16(spec), body);
+}
+
+void
+MaybeReportDeprecation(nsPIDOMWindowInner* aWindow,
+                       nsIDocument::DeprecatedOperations aOperation,
+                       const nsAString& aFileName,
+                       const Nullable<uint32_t>& aLineNumber,
+                       const Nullable<uint32_t>& aColumnNumber)
+{
+  MOZ_ASSERT(aWindow);
+
+  if (!StaticPrefs::dom_reporting_enabled()) {
+    return;
+  }
+
+  if (NS_WARN_IF(!aWindow->GetExtantDoc())) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> uri = aWindow->GetExtantDoc()->GetDocumentURI();
+  if (NS_WARN_IF(!uri)) {
+    return;
+  }
+
+  ReportDeprecation(aWindow, uri, aOperation, aFileName, aLineNumber,
+                    aColumnNumber);
+}
+
 // This runnable is used to write a deprecation message from a worker to the
 // console running on the main-thread.
 class DeprecationWarningRunnable final : public WorkerProxyToMainThreadRunnable
 {
-  nsIDocument::DeprecatedOperations mOperation;
+  const nsIDocument::DeprecatedOperations mOperation;
 
 public:
   explicit DeprecationWarningRunnable(nsIDocument::DeprecatedOperations aOperation)
     : mOperation(aOperation)
   {}
 
 private:
   void
@@ -4156,16 +4244,31 @@ DeprecationWarning(JSContext* aCx, JSObj
 void
 DeprecationWarning(const GlobalObject& aGlobal,
                    nsIDocument::DeprecatedOperations aOperation)
 {
   if (NS_IsMainThread()) {
     nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
     if (window && window->GetExtantDoc()) {
       window->GetExtantDoc()->WarnOnceAbout(aOperation);
+
+      nsAutoCString fileName;
+      Nullable<uint32_t> lineNumber;
+      Nullable<uint32_t> columnNumber;
+      uint32_t line = 0;
+      uint32_t column = 0;
+      if (nsJSUtils::GetCallingLocation(aGlobal.Context(), fileName,
+                                        &line, &column)) {
+        lineNumber.SetValue(line);
+        columnNumber.SetValue(column);
+      }
+
+      MaybeReportDeprecation(window, aOperation,
+                             NS_ConvertUTF8toUTF16(fileName), lineNumber,
+                             columnNumber);
     }
 
     return;
   }
 
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aGlobal.Context());
   if (!workerPrivate) {
     return;
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -391,16 +391,17 @@ HTMLCanvasElementObserver::HandleEvent(E
 
 NS_IMPL_ISUPPORTS(HTMLCanvasElementObserver, nsIObserver)
 
 // ---------------------------------------------------------------------------
 
 HTMLCanvasElement::HTMLCanvasElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
   : nsGenericHTMLElement(std::move(aNodeInfo)),
     mResetLayer(true) ,
+    mMaybeModified(false) ,
     mWriteOnly(false)
 {}
 
 HTMLCanvasElement::~HTMLCanvasElement()
 {
   if (mContextObserver) {
     mContextObserver->Destroy();
     mContextObserver = nullptr;
@@ -1005,30 +1006,32 @@ HTMLCanvasElement::MozGetAsFileImpl(cons
   return NS_OK;
 }
 
 nsresult
 HTMLCanvasElement::GetContext(const nsAString& aContextId,
                               nsISupports** aContext)
 {
   ErrorResult rv;
+  mMaybeModified = true; // For FirstContentfulPaint
   *aContext = GetContext(nullptr, aContextId, JS::NullHandleValue, rv).take();
   return rv.StealNSResult();
 }
 
 already_AddRefed<nsISupports>
 HTMLCanvasElement::GetContext(JSContext* aCx,
                               const nsAString& aContextId,
                               JS::Handle<JS::Value> aContextOptions,
                               ErrorResult& aRv)
 {
   if (mOffscreenCanvas) {
     return nullptr;
   }
 
+  mMaybeModified = true; // For FirstContentfulPaint
   return CanvasRenderingContextHelper::GetContext(aCx, aContextId,
     aContextOptions.isObject() ? aContextOptions : JS::NullHandleValue,
     aRv);
 }
 
 already_AddRefed<nsISupports>
 HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId,
                                     ErrorResult& aRv)
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -344,16 +344,18 @@ public:
 
   void OnMemoryPressure();
 
   static void SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer);
   static void InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer);
 
   already_AddRefed<layers::SharedSurfaceTextureClient> GetVRFrame();
 
+  bool MaybeModified() const { return mMaybeModified; };
+
 protected:
   virtual ~HTMLCanvasElement();
 
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   virtual nsIntSize GetWidthHeight() override;
 
   virtual already_AddRefed<nsICanvasRenderingContextInternal>
@@ -382,16 +384,17 @@ protected:
                                 bool aNotify) override;
   virtual nsresult OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName,
                                           const nsAttrValueOrString& aValue,
                                           bool aNotify) override;
 
   AsyncCanvasRenderer* GetAsyncCanvasRenderer();
 
   bool mResetLayer;
+  bool mMaybeModified; // we fetched the context, so we may have written to the canvas
   RefPtr<HTMLCanvasElement> mOriginalCanvas;
   RefPtr<PrintCallback> mPrintCallback;
   RefPtr<HTMLCanvasPrintState> mPrintState;
   nsTArray<WeakPtr<FrameCaptureListener>> mRequestedFrameListeners;
   RefPtr<RequestedFrameRefreshObserver> mRequestedFrameRefreshObserver;
   RefPtr<AsyncCanvasRenderer> mAsyncCanvasRenderer;
   RefPtr<OffscreenCanvas> mOffscreenCanvas;
   RefPtr<HTMLCanvasElementObserver> mContextObserver;
--- a/dom/html/test/test_fullscreen-api.html
+++ b/dom/html/test/test_fullscreen-api.html
@@ -43,17 +43,20 @@ var gTestWindows = [
   { test: "file_fullscreen-top-layer.html" },
   { test: "file_fullscreen-backdrop.html" },
   { test: "file_fullscreen-nested.html" },
   { test: "file_fullscreen-prefixed.html" },
   { test: "file_fullscreen-unprefix-disabled.html" },
   { test: "file_fullscreen-lenient-setters.html" },
   { test: "file_fullscreen-table.html" },
   { test: "file_fullscreen-event-order.html" },
-  { test: "file_fullscreen-featurePolicy.html", prefs: [["dom.security.featurePolicy.enabled", true]] },
+  { test: "file_fullscreen-featurePolicy.html",
+    prefs: [["dom.security.featurePolicy.enabled", true],
+            ["dom.security.featurePolicy.header.enabled", true],
+            ["dom.security.featurePolicy.webidl.enabled", true]] },
 ];
 
 var testWindow = null;
 var gTestIndex = 0;
 
 function finish() {
   SimpleTest.finish();
 }
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -348,16 +348,23 @@ MixedDisplayObjectSubrequestWarning=Load
 MotionEventWarning=Use of the motion sensor is deprecated.
 OrientationEventWarning=Use of the orientation sensor is deprecated.
 ProximityEventWarning=Use of the proximity sensor is deprecated.
 AmbientLightEventWarning=Use of the ambient light sensor is deprecated.
 # LOCALIZATION NOTE: Do not translate "storage", "indexedDB.open" and "navigator.storage.persist()".
 IDBOpenDBOptions_StorageTypeWarning=The ‘storage’ attribute in options passed to indexedDB.open is deprecated and will soon be removed. To get persistent storage, please use navigator.storage.persist() instead.
 DOMQuadBoundsAttrWarning=DOMQuad.bounds is deprecated in favor of DOMQuad.getBounds()
 UnsupportedEntryTypesIgnored=Ignoring unsupported entryTypes: %S.
+
+#LOCALIZATION NOTE(DeprecatedTestingInterfaceWarning): Do not translate this message. It's just testing only.
+DeprecatedTestingInterfaceWarning=TestingDeprecatedInterface is a testing-only interface and this is its testing deprecation message.
+#LOCALIZATION NOTE(DeprecatedTestingMethodWarning): Do not translate this message. It's just testing only.
+DeprecatedTestingMethodWarning=TestingDeprecatedInterface.deprecatedMethod() is a testing-only method and this is its testing deprecation message.
+#LOCALIZATION NOTE(DeprecatedTestingAttributeWarning): Do not translate this message. It's just testing only.
+DeprecatedTestingAttributeWarning=TestingDeprecatedInterface.deprecatedAttribute is a testing-only attribute and this is its testing deprecation message.
 # LOCALIZATION NOTE (CreateImageBitmapCanvasRenderingContext2DWarning): Do not translate CanvasRenderingContext2D and createImageBitmap.
 CreateImageBitmapCanvasRenderingContext2DWarning=Use of CanvasRenderingContext2D in createImageBitmap is deprecated.
 # LOCALIZATION NOTE (MozRequestFullScreenDeprecatedPrefixWarning): Do not translate mozRequestFullScreen.
 MozRequestFullScreenDeprecatedPrefixWarning=mozRequestFullScreen() is deprecated.
 # LOCALIZATION NOTE (MozfullscreenchangeDeprecatedPrefixWarning): Do not translate onmozfullscreenchange.
 MozfullscreenchangeDeprecatedPrefixWarning=onmozfullscreenchange is deprecated.
 # LOCALIZATION NOTE (MozfullscreenerrorDeprecatedPrefixWarning): Do not translate onmozfullscreenerror.
 MozfullscreenerrorDeprecatedPrefixWarning=onmozfullscreenerror is deprecated.
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -97,16 +97,17 @@ DIRS += [
     'webbrowserpersist',
     'xhr',
     'worklet',
     'script',
     'payments',
     'websocket',
     'serviceworkers',
     'simpledb',
+    'reporting',
 ]
 
 if CONFIG['MOZ_LIBPRIO']:
     DIRS += ['prio']
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['plugins/ipc/hangui']
 
--- a/dom/performance/PerformanceTiming.h
+++ b/dom/performance/PerformanceTiming.h
@@ -435,16 +435,30 @@ public:
     if (mPerformance->IsSystemPrincipal()) {
       return GetDOMTiming()->GetTimeToNonBlankPaint();
     }
     return nsRFPService::ReduceTimePrecisionAsMSecs(
       GetDOMTiming()->GetTimeToNonBlankPaint(),
       mPerformance->GetRandomTimelineSeed());
   }
 
+  DOMTimeMilliSec TimeToContentfulPaint() const
+  {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
+      return 0;
+    }
+    if (mPerformance->IsSystemPrincipal()) {
+      return GetDOMTiming()->GetTimeToContentfulPaint();
+    }
+    return nsRFPService::ReduceTimePrecisionAsMSecs(
+      GetDOMTiming()->GetTimeToContentfulPaint(),
+      mPerformance->GetRandomTimelineSeed());
+  }
+
   DOMTimeMilliSec TimeToDOMContentFlushed() const
   {
     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
         nsContentUtils::ShouldResistFingerprinting()) {
       return 0;
     }
     if (mPerformance->IsSystemPrincipal()) {
       return GetDOMTiming()->GetTimeToDOMContentFlushed();
new file mode 100644
--- /dev/null
+++ b/dom/reporting/DeprecationReportBody.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DeprecationReportBody.h"
+#include "mozilla/dom/ReportingBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+DeprecationReportBody::DeprecationReportBody(nsPIDOMWindowInner* aWindow,
+                                             const nsAString& aId,
+                                             const Nullable<Date>& aDate,
+                                             const nsAString& aMessage,
+                                             const nsAString& aSourceFile,
+                                             const Nullable<uint32_t>& aLineNumber,
+                                             const Nullable<uint32_t>& aColumnNumber)
+  : ReportBody(aWindow)
+  , mId(aId)
+  , mDate(aDate)
+  , mMessage(aMessage)
+  , mSourceFile(aSourceFile)
+  , mLineNumber(aLineNumber)
+  , mColumnNumber(aColumnNumber)
+{
+  MOZ_ASSERT(aWindow);
+}
+
+DeprecationReportBody::~DeprecationReportBody() = default;
+
+JSObject*
+DeprecationReportBody::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return DeprecationReportBody_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+DeprecationReportBody::GetId(nsAString& aId) const
+{
+  aId = mId;
+}
+
+Nullable<Date>
+DeprecationReportBody::GetAnticipatedRemoval() const
+{
+  return mDate;
+}
+
+void
+DeprecationReportBody::GetMessage(nsAString& aMessage) const
+{
+  aMessage = mMessage;
+}
+
+void
+DeprecationReportBody::GetSourceFile(nsAString& aSourceFile) const
+{
+  aSourceFile = mSourceFile;
+}
+
+Nullable<uint32_t>
+DeprecationReportBody::GetLineNumber() const
+{
+  return mLineNumber;
+}
+
+Nullable<uint32_t>
+DeprecationReportBody::GetColumnNumber() const
+{
+  return mColumnNumber;
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/reporting/DeprecationReportBody.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DeprecationReportBody_h
+#define mozilla_dom_DeprecationReportBody_h
+
+#include "mozilla/dom/ReportBody.h"
+#include "mozilla/dom/Date.h"
+
+namespace mozilla {
+namespace dom {
+
+class DeprecationReportBody final : public ReportBody
+{
+public:
+  DeprecationReportBody(nsPIDOMWindowInner* aWindow,
+                        const nsAString& aId,
+                        const Nullable<Date>& aDate,
+                        const nsAString& aMessage,
+                        const nsAString& aSourceFile,
+                        const Nullable<uint32_t>& aLineNumber,
+                        const Nullable<uint32_t>& aColumnNumber);
+
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  void
+  GetId(nsAString& aId) const;
+
+  Nullable<Date>
+  GetAnticipatedRemoval() const;
+
+  void
+  GetMessage(nsAString& aMessage) const;
+
+  void
+  GetSourceFile(nsAString& aSourceFile) const;
+
+  Nullable<uint32_t>
+  GetLineNumber() const;
+
+  Nullable<uint32_t>
+  GetColumnNumber() const;
+
+private:
+  ~DeprecationReportBody();
+
+  const nsString mId;
+  const Nullable<Date> mDate;
+  const nsString mMessage;
+  const nsString mSourceFile;
+  const Nullable<uint32_t> mLineNumber;
+  const Nullable<uint32_t> mColumnNumber;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_DeprecationReportBody_h
new file mode 100644
--- /dev/null
+++ b/dom/reporting/FeaturePolicyViolationReportBody.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/FeaturePolicyViolationReportBody.h"
+#include "mozilla/dom/FeaturePolicyBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+FeaturePolicyViolationReportBody::FeaturePolicyViolationReportBody(nsPIDOMWindowInner* aWindow,
+                                                                   const nsAString& aFeatureId,
+                                                                   const nsAString& aSourceFile,
+                                                                   const Nullable<int32_t>& aLineNumber,
+                                                                   const Nullable<int32_t>& aColumnNumber,
+                                                                   const nsAString& aDisposition)
+  : ReportBody(aWindow)
+  , mFeatureId(aFeatureId)
+  , mSourceFile(aSourceFile)
+  , mLineNumber(aLineNumber)
+  , mColumnNumber(aColumnNumber)
+  , mDisposition(aDisposition)
+{}
+
+FeaturePolicyViolationReportBody::~FeaturePolicyViolationReportBody() = default;
+
+JSObject*
+FeaturePolicyViolationReportBody::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return FeaturePolicyViolationReportBody_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+FeaturePolicyViolationReportBody::GetFeatureId(nsAString& aFeatureId) const
+{
+  aFeatureId = mFeatureId;
+}
+
+void
+FeaturePolicyViolationReportBody::GetSourceFile(nsAString& aSourceFile) const
+{
+  aSourceFile = mSourceFile;
+}
+
+Nullable<int32_t>
+FeaturePolicyViolationReportBody::GetLineNumber() const
+{
+  return mLineNumber;
+}
+
+Nullable<int32_t>
+FeaturePolicyViolationReportBody::GetColumnNumber() const
+{
+  return mColumnNumber;
+}
+
+void
+FeaturePolicyViolationReportBody::GetDisposition(nsAString& aDisposition) const
+{
+  aDisposition = mDisposition;
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/reporting/FeaturePolicyViolationReportBody.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FeaturePolicyViolationReportBody_h
+#define mozilla_dom_FeaturePolicyViolationReportBody_h
+
+#include "mozilla/dom/ReportBody.h"
+#include "mozilla/dom/Date.h"
+
+namespace mozilla {
+namespace dom {
+
+class FeaturePolicyViolationReportBody final : public ReportBody
+{
+public:
+  FeaturePolicyViolationReportBody(nsPIDOMWindowInner* aWindow,
+                                   const nsAString& aFeatureId,
+                                   const nsAString& aSourceFile,
+                                   const Nullable<int32_t>& aLineNumber,
+                                   const Nullable<int32_t>& aColumnNumber,
+                                   const nsAString& aDisposition);
+
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  void
+  GetFeatureId(nsAString& aFeatureId) const;
+
+  void
+  GetSourceFile(nsAString& aSourceFile) const;
+
+  Nullable<int32_t>
+  GetLineNumber() const;
+
+  Nullable<int32_t>
+  GetColumnNumber() const;
+
+  void
+  GetDisposition(nsAString& aDisposition) const;
+
+private:
+  ~FeaturePolicyViolationReportBody();
+
+  const nsString mFeatureId;
+  const nsString mSourceFile;
+  const Nullable<int32_t> mLineNumber;
+  const Nullable<int32_t> mColumnNumber;
+  const nsString mDisposition;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_FeaturePolicyViolationReportBody_h
new file mode 100644
--- /dev/null
+++ b/dom/reporting/Report.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Report.h"
+#include "mozilla/dom/ReportBody.h"
+#include "mozilla/dom/ReportingBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Report, mWindow, mBody)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Report)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Report)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Report)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+Report::Report(nsPIDOMWindowInner* aWindow,
+               const nsAString& aType,
+               const nsAString& aURL,
+               ReportBody* aBody)
+  : mWindow(aWindow)
+  , mType(aType)
+  , mURL(aURL)
+  , mBody(aBody)
+{
+  MOZ_ASSERT(aWindow);
+}
+
+Report::~Report() = default;
+
+already_AddRefed<Report>
+Report::Clone()
+{
+  RefPtr<Report> report =
+    new Report(mWindow, mType, mURL, mBody);
+  return report.forget();
+}
+
+JSObject*
+Report::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return Report_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+Report::GetType(nsAString& aType) const
+{
+  aType = mType;
+}
+
+void
+Report::GetUrl(nsAString& aURL) const
+{
+  aURL = mURL;
+}
+
+ReportBody*
+Report::GetBody() const
+{
+  return mBody;
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/reporting/Report.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Report_h
+#define mozilla_dom_Report_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class ReportBody;
+
+class Report final : public nsISupports
+                   , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Report)
+
+  Report(nsPIDOMWindowInner* aWindow,
+         const nsAString& aType,
+         const nsAString& aURL,
+         ReportBody* aBody);
+
+  already_AddRefed<Report>
+  Clone();
+
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  nsPIDOMWindowInner*
+  GetParentObject() const
+  {
+    return mWindow;
+  }
+
+  void
+  GetType(nsAString& aType) const;
+
+  void
+  GetUrl(nsAString& aURL) const;
+
+  ReportBody*
+  GetBody() const;
+
+private:
+  ~Report();
+
+  nsCOMPtr<nsPIDOMWindowInner> mWindow;
+
+  const nsString mType;
+  const nsString mURL;
+  RefPtr<ReportBody> mBody;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_Report_h
new file mode 100644
--- /dev/null
+++ b/dom/reporting/ReportBody.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ReportBody.h"
+#include "mozilla/dom/ReportingBinding.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ReportBody, mWindow)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ReportBody)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ReportBody)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReportBody)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ReportBody::ReportBody(nsPIDOMWindowInner* aWindow)
+  : mWindow(aWindow)
+{
+  MOZ_ASSERT(aWindow);
+}
+
+ReportBody::~ReportBody() = default;
+
+JSObject*
+ReportBody::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return ReportBody_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/reporting/ReportBody.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ReportBody_h
+#define mozilla_dom_ReportBody_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class ReportBody : public nsISupports
+                 , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ReportBody)
+
+  explicit ReportBody(nsPIDOMWindowInner* aWindow);
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  nsPIDOMWindowInner*
+  GetParentObject() const
+  {
+    return mWindow;
+  }
+
+protected:
+  virtual ~ReportBody();
+
+  nsCOMPtr<nsPIDOMWindowInner> mWindow;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_ReportBody_h
new file mode 100644
--- /dev/null
+++ b/dom/reporting/ReportingObserver.cpp
@@ -0,0 +1,197 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ReportingObserver.h"
+#include "mozilla/dom/ReportingBinding.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindowInner.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ReportingObserver)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ReportingObserver)
+  tmp->Shutdown();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mReports)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ReportingObserver)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReports)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(ReportingObserver)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ReportingObserver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ReportingObserver)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReportingObserver)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END
+
+/* static */ already_AddRefed<ReportingObserver>
+ReportingObserver::Constructor(const GlobalObject& aGlobal,
+                               ReportingObserverCallback& aCallback,
+                               const ReportingObserverOptions& aOptions,
+                               ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
+  MOZ_ASSERT(window);
+
+  nsTArray<nsString> types;
+  if (aOptions.mTypes.WasPassed()) {
+    types = aOptions.mTypes.Value();
+  }
+
+  RefPtr<ReportingObserver> ro =
+    new ReportingObserver(window, aCallback, types, aOptions.mBuffered);
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (NS_WARN_IF(!obs)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  aRv = obs->AddObserver(ro, "memory-pressure", true);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return ro.forget();
+}
+
+ReportingObserver::ReportingObserver(nsPIDOMWindowInner* aWindow,
+                                     ReportingObserverCallback& aCallback,
+                                     const nsTArray<nsString>& aTypes,
+                                     bool aBuffered)
+  : mWindow(aWindow)
+  , mCallback(&aCallback)
+  , mTypes(aTypes)
+  , mBuffered(aBuffered)
+{
+  MOZ_ASSERT(aWindow);
+}
+
+ReportingObserver::~ReportingObserver()
+{
+  Shutdown();
+}
+
+void
+ReportingObserver::Shutdown()
+{
+  Disconnect();
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(this, "memory-pressure");
+  }
+}
+
+JSObject*
+ReportingObserver::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return ReportingObserver_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+ReportingObserver::Observe()
+{
+  mWindow->RegisterReportingObserver(this, mBuffered);
+}
+
+void
+ReportingObserver::Disconnect()
+{
+  if (mWindow) {
+    mWindow->UnregisterReportingObserver(this);
+  }
+}
+
+void
+ReportingObserver::TakeRecords(nsTArray<RefPtr<Report>>& aRecords)
+{
+  mReports.SwapElements(aRecords);
+}
+
+void
+ReportingObserver::MaybeReport(Report* aReport)
+{
+  MOZ_ASSERT(aReport);
+
+  if (!mTypes.IsEmpty()) {
+    nsAutoString type;
+    aReport->GetType(type);
+
+    if (!mTypes.Contains(type)) {
+      return;
+    }
+  }
+
+  bool wasEmpty = mReports.IsEmpty();
+
+  RefPtr<Report> report = aReport->Clone();
+  MOZ_ASSERT(report);
+
+  if (NS_WARN_IF(!mReports.AppendElement(report, fallible))) {
+    return;
+  }
+
+  if (!wasEmpty) {
+    return;
+  }
+
+  nsCOMPtr<nsPIDOMWindowInner> window = mWindow;
+
+  nsCOMPtr<nsIRunnable> r =
+    NS_NewRunnableFunction(
+      "ReportingObserver::MaybeReport",
+      [window]() {
+        window->NotifyReportingObservers();
+      });
+
+  NS_DispatchToCurrentThread(r);
+}
+
+void
+ReportingObserver::MaybeNotify()
+{
+  if (mReports.IsEmpty()) {
+    return;
+  }
+
+  // Let's take the ownership of the reports.
+  nsTArray<RefPtr<Report>> list;
+  list.SwapElements(mReports);
+
+  Sequence<OwningNonNull<Report>> reports;
+  for (Report* report : list) {
+    if (NS_WARN_IF(!reports.AppendElement(*report, fallible))) {
+      return;
+    }
+  }
+
+  // We should report if this throws exception. But where?
+  mCallback->Call(reports, *this);
+}
+
+NS_IMETHODIMP
+ReportingObserver::Observe(nsISupports* aSubject, const char* aTopic,
+                           const char16_t* aData)
+{
+  MOZ_ASSERT(!strcmp(aTopic, "memory-pressure"));
+  mReports.Clear();
+  return NS_OK;
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/reporting/ReportingObserver.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ReportingObserver_h
+#define mozilla_dom_ReportingObserver_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsWrapperCache.h"
+#include "nsTArray.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class Report;
+class ReportingObserverCallback;
+struct ReportingObserverOptions;
+
+class ReportingObserver final : public nsIObserver
+                              , public nsWrapperCache
+                              , public nsSupportsWeakReference
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(ReportingObserver,
+                                                         nsIObserver)
+  NS_DECL_NSIOBSERVER
+
+  static already_AddRefed<ReportingObserver>
+  Constructor(const GlobalObject& aGlobal,
+              ReportingObserverCallback& aCallback,
+              const ReportingObserverOptions& aOptions,
+              ErrorResult& aRv);
+
+  ReportingObserver(nsPIDOMWindowInner* aWindow,
+                    ReportingObserverCallback& aCallback,
+                    const nsTArray<nsString>& aTypes,
+                    bool aBuffered);
+
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  nsPIDOMWindowInner*
+  GetParentObject() const
+  {
+    return mWindow;
+  }
+
+  void
+  Observe();
+
+  void
+  Disconnect();
+
+  void
+  TakeRecords(nsTArray<RefPtr<Report>>& aRecords);
+
+  void
+  MaybeReport(Report* aReport);
+
+  void
+  MaybeNotify();
+
+private:
+  ~ReportingObserver();
+
+  void
+  Shutdown();
+
+  nsTArray<RefPtr<Report>> mReports;
+
+  nsCOMPtr<nsPIDOMWindowInner> mWindow;
+  RefPtr<ReportingObserverCallback> mCallback;
+  nsTArray<nsString> mTypes;
+  bool mBuffered;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_ReportingObserver_h
new file mode 100644
--- /dev/null
+++ b/dom/reporting/ReportingUtils.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ReportingUtils.h"
+#include "mozilla/dom/ReportBody.h"
+#include "mozilla/dom/Report.h"
+#include "nsAtom.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+/* static */ void
+ReportingUtils::Report(nsPIDOMWindowInner* aWindow,
+                       nsAtom* aType,
+                       const nsAString& aURL,
+                       ReportBody* aBody)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aBody);
+
+  RefPtr<mozilla::dom::Report> report =
+    new mozilla::dom::Report(aWindow, nsDependentAtomString(aType), aURL,
+                             aBody);
+  aWindow->BroadcastReport(report);
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/reporting/ReportingUtils.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ReportingUtils_h
+#define mozilla_dom_ReportingUtils_h
+
+#include "nsString.h"
+
+class nsIPDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class ReportBody;
+
+class ReportingUtils final
+{
+public:
+  static void
+  Report(nsPIDOMWindowInner* aWindow,
+         nsAtom* aType,
+         const nsAString& aURL,
+         ReportBody* aBody);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ReportingUtils_h
new file mode 100644
--- /dev/null
+++ b/dom/reporting/TestingDeprecatedInterface.cpp
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TestingDeprecatedInterface.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestingDeprecatedInterface, mGlobal)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TestingDeprecatedInterface)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TestingDeprecatedInterface)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestingDeprecatedInterface)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/* static */ already_AddRefed<TestingDeprecatedInterface>
+TestingDeprecatedInterface::Constructor(const GlobalObject& aGlobal,
+                                        ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  MOZ_ASSERT(global);
+
+  RefPtr<TestingDeprecatedInterface> obj =
+    new TestingDeprecatedInterface(global);
+  return obj.forget();
+}
+
+TestingDeprecatedInterface::TestingDeprecatedInterface(nsIGlobalObject* aGlobal)
+  : mGlobal(aGlobal)
+{}
+
+
+TestingDeprecatedInterface::~TestingDeprecatedInterface() = default;
+
+JSObject*
+TestingDeprecatedInterface::WrapObject(JSContext* aCx,
+                                       JS::Handle<JSObject*> aGivenProto)
+{
+  return TestingDeprecatedInterface_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+TestingDeprecatedInterface::DeprecatedMethod() const
+{}
+
+bool
+TestingDeprecatedInterface::DeprecatedAttribute() const
+{
+  return true;
+}
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/reporting/TestingDeprecatedInterface.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TestingDeprecatedInterface_h
+#define mozilla_dom_TestingDeprecatedInterface_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class TestingDeprecatedInterface final : public nsISupports
+                                       , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestingDeprecatedInterface)
+
+  static already_AddRefed<TestingDeprecatedInterface>
+  Constructor(const GlobalObject& aGlobal,
+              ErrorResult& aRv);
+
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  nsIGlobalObject*
+  GetParentObject() const
+  {
+    return mGlobal;
+  }
+
+  void
+  DeprecatedMethod() const;
+
+  bool
+  DeprecatedAttribute() const;
+
+private:
+  explicit TestingDeprecatedInterface(nsIGlobalObject* aGlobal);
+  ~TestingDeprecatedInterface();
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_TestingDeprecatedInterface_h
+
new file mode 100644
--- /dev/null
+++ b/dom/reporting/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom = [
+    'DeprecationReportBody.h',
+    'FeaturePolicyViolationReportBody.h',
+    'Report.h',
+    'ReportBody.h',
+    'ReportingObserver.h',
+    'ReportingUtils.h',
+    'TestingDeprecatedInterface.h',
+]
+
+UNIFIED_SOURCES += [
+    'DeprecationReportBody.cpp',
+    'FeaturePolicyViolationReportBody.cpp',
+    'Report.cpp',
+    'ReportBody.cpp',
+    'ReportingObserver.cpp',
+    'ReportingUtils.cpp',
+    'TestingDeprecatedInterface.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+with Files('**'):
+    BUG_COMPONENT = ('DOM', 'Security')
+
+FINAL_LIBRARY = 'xul'
+
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
new file mode 100644
--- /dev/null
+++ b/dom/reporting/tests/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/mochitest-test",
+  ],
+};
new file mode 100644
--- /dev/null
+++ b/dom/reporting/tests/mochitest.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+prefs =
+  dom.reporting.enabled=true
+  dom.reporting.testing.enabled=true
+
+[test_deprecated.html]
+[test_memoryPressure.html]
new file mode 100644
--- /dev/null
+++ b/dom/reporting/tests/test_deprecated.html
@@ -0,0 +1,143 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Deprecated reports</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript">
+
+let testingInterface;
+
+(new Promise(resolve => {
+  info("Testing DeprecatedTestingInterface report");
+  let obs = new ReportingObserver((reports, o) => {
+    is(obs, o, "Same observer!");
+    ok(reports.length == 1, "We have 1 report");
+
+    let report = reports[0];
+    is(report.type, "deprecation", "Deprecation report received");
+    is(report.url, location.href, "URL is window.location");
+    ok(!!report.body, "The report has a body");
+    ok(report.body instanceof DeprecationReportBody, "Correct type for the body");
+    is(report.body.id, "DeprecatedTestingInterface", "report.body.id matches DeprecatedTestingMethod");
+    ok(!report.body.anticipatedRemoval, "We don't have a anticipatedRemoval");
+    ok(report.body.message.includes("TestingDeprecatedInterface"), "We have a message");
+    is(report.body.sourceFile, location.href, "We have a sourceFile");
+    is(report.body.lineNumber, 40, "We have a lineNumber");
+    is(report.body.columnNumber, 21, "We have a columnNumber");
+
+    obs.disconnect();
+    resolve();
+  });
+  ok(!!obs, "ReportingObserver is a thing");
+
+  obs.observe();
+  ok(true, "ReportingObserver.observe() is callable");
+
+  testingInterface = new TestingDeprecatedInterface();
+  ok(true, "Created a deprecated interface");
+}))
+
+.then(() => {
+  info("Testing DeprecatedTestingMethod report");
+  return new Promise(resolve => {
+    let obs = new ReportingObserver((reports, o) => {
+      is(obs, o, "Same observer!");
+      ok(reports.length == 1, "We have 1 report");
+
+      let report = reports[0];
+      is(report.type, "deprecation", "Deprecation report received");
+      is(report.url, location.href, "URL is window.location");
+      ok(!!report.body, "The report has a body");
+      ok(report.body instanceof DeprecationReportBody, "Correct type for the body");
+      is(report.body.id, "DeprecatedTestingMethod", "report.body.id matches DeprecatedTestingMethod");
+      ok(!report.body.anticipatedRemoval, "We don't have a anticipatedRemoval");
+      ok(report.body.message.includes("TestingDeprecatedInterface.deprecatedMethod"), "We have a message");
+      is(report.body.sourceFile, location.href, "We have a sourceFile");
+      is(report.body.lineNumber, 71, "We have a lineNumber");
+      is(report.body.columnNumber, 4, "We have a columnNumber");
+
+      obs.disconnect();
+      resolve();
+    });
+    ok(!!obs, "ReportingObserver is a thing");
+
+    obs.observe();
+    ok(true, "ReportingObserver.observe() is callable");
+
+    testingInterface.deprecatedMethod();
+    ok(true, "Run a deprecated method.");
+  });
+})
+
+.then(() => {
+  info("Testing DeprecatedTestingAttribute report");
+  return new Promise(resolve => {
+    let obs = new ReportingObserver((reports, o) => {
+      is(obs, o, "Same observer!");
+      ok(reports.length == 1, "We have 1 report");
+
+      let report = reports[0];
+      is(report.type, "deprecation", "Deprecation report received");
+      is(report.url, location.href, "URL is window.location");
+      ok(!!report.body, "The report has a body");
+      ok(report.body instanceof DeprecationReportBody, "Correct type for the body");
+      is(report.body.id, "DeprecatedTestingAttribute", "report.body.id matches DeprecatedTestingAttribute");
+      ok(!report.body.anticipatedRemoval, "We don't have a anticipatedRemoval");
+      ok(report.body.message.includes("TestingDeprecatedInterface.deprecatedAttribute"), "We have a message");
+      is(report.body.sourceFile, location.href, "We have a sourceFile");
+      is(report.body.lineNumber, 103, "We have a lineNumber");
+      is(report.body.columnNumber, 4, "We have a columnNumber");
+
+      obs.disconnect();
+      resolve();
+    });
+    ok(!!obs, "ReportingObserver is a thing");
+
+    obs.observe();
+    ok(true, "ReportingObserver.observe() is callable");
+
+    ok(testingInterface.deprecatedAttribute, "Attributed called");
+  });
+})
+
+.then(() => {
+  info("Testing ReportingObserver.takeRecords()");
+  let p = new Promise(resolve => {
+    let obs = new ReportingObserver((reports, o) => {
+      is(obs, o, "Same observer!");
+      resolve(obs);
+    });
+    ok(!!obs, "ReportingObserver is a thing");
+
+    obs.observe();
+    ok(true, "ReportingObserver.observe() is callable");
+
+    testingInterface.deprecatedMethod();
+    ok(true, "Run a deprecated method.");
+  });
+
+  return p.then(obs => {
+    let reports = obs.takeRecords();
+    is(reports.length, 0, "No reports after an callback");
+
+    testingInterface.deprecatedAttribute + 1;
+
+    reports = obs.takeRecords();
+    ok(reports.length >= 1, "We have at least 1 report");
+
+    reports = obs.takeRecords();
+    is(reports.length, 0, "No more reports");
+  });
+})
+
+.then(() => { SimpleTest.finish(); });
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/reporting/tests/test_memoryPressure.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for ReportingObserver + memory-pressure</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript">
+
+info("Testing TakeRecords() without memory-pressure");
+let o = new ReportingObserver(() => {});
+o.observe();
+
+new TestingDeprecatedInterface();
+let r = o.takeRecords();
+is(r.length, 1, "We have 1 report");
+
+r = o.takeRecords();
+is(r.length, 0, "We have 0 reports after a takeRecords()");
+
+info("Testing DeprecatedTestingMethod report");
+
+new TestingDeprecatedInterface();
+SpecialPowers.Services.obs.notifyObservers(null, "memory-pressure", "heap-minimize");
+
+r = o.takeRecords();
+is(r.length, 0, "We have 0 reports after a memory-pressure");
+
+</script>
+</body>
+</html>
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -11,17 +11,17 @@
 #include "ModuleLoadRequest.h"
 #include "ModuleScript.h"
 
 #include "prsystem.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/CompilationAndEvaluation.h"
 #include "js/OffThreadScriptCompilation.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "js/Utility.h"
 #include "xpcpublic.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIContent.h"
 #include "nsJSUtils.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ScriptSettings.h"
@@ -63,17 +63,17 @@
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "nsIScriptError.h"
 #include "nsIOutputStream.h"
 
-using JS::SourceBufferHolder;
+using JS::SourceText;
 
 using mozilla::Telemetry::LABELS_DOM_SCRIPT_PRELOAD_RESULT;
 
 namespace mozilla {
 namespace dom {
 
 LazyLogModule ScriptLoader::gCspPRLog("CSP");
 LazyLogModule ScriptLoader::gScriptLoaderLog("ScriptLoader");
@@ -1928,40 +1928,56 @@ ScriptLoader::CompileOffThreadOrProcessR
 
   if (couldCompile) {
     return NS_OK;
   }
 
   return ProcessRequest(aRequest);
 }
 
-mozilla::Maybe<SourceBufferHolder>
+mozilla::Maybe<SourceText<char16_t>>
 ScriptLoader::GetScriptSource(JSContext* aCx, ScriptLoadRequest* aRequest)
 {
-  // Return a SourceBufferHolder object holding the script's source text.
-  // Ownership of the buffer is transferred to the resulting SourceBufferHolder.
+  // Return a SourceText<char16_t> object holding the script's source text.
+  // Ownership of the buffer is transferred to the resulting holder.
 
   // If there's no script text, we try to get it from the element
   if (aRequest->mIsInline) {
     nsAutoString inlineData;
     aRequest->Element()->GetScriptText(inlineData);
 
     size_t nbytes = inlineData.Length() * sizeof(char16_t);
     JS::UniqueTwoByteChars chars(static_cast<char16_t*>(JS_malloc(aCx, nbytes)));
     if (!chars) {
       return Nothing();
     }
 
     memcpy(chars.get(), inlineData.get(), nbytes);
-    return Some(SourceBufferHolder(std::move(chars), inlineData.Length()));
+
+    SourceText<char16_t> srcBuf;
+    if (!srcBuf.init(aCx, std::move(chars), inlineData.Length())) {
+      return Nothing();
+    }
+
+    return Some(SourceText<char16_t>(std::move(srcBuf)));
   }
 
   size_t length = aRequest->ScriptText().length();
   JS::UniqueTwoByteChars chars(aRequest->ScriptText().extractOrCopyRawBuffer());
-  return Some(SourceBufferHolder(std::move(chars), length));
+  if (!chars) {
+    JS_ReportOutOfMemory(aCx);
+    return Nothing();
+  }
+
+  SourceText<char16_t> srcBuf;
+  if (!srcBuf.init(aCx, std::move(chars), length)) {
+    return Nothing();
+  }
+
+  return Some(SourceText<char16_t>(std::move(srcBuf)));
 }
 
 nsresult
 ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest)
 {
   LOG(("ScriptLoadRequest (%p): Process request", aRequest));
 
   NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
--- a/dom/script/ScriptLoader.h
+++ b/dom/script/ScriptLoader.h
@@ -27,17 +27,19 @@
 #include "mozilla/MozPromise.h"
 #include "mozilla/net/ReferrerPolicy.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/Vector.h"
 
 class nsIURI;
 
 namespace JS {
-  class SourceBufferHolder;
+
+template<typename UnitT> class SourceText;
+
 } // namespace JS
 
 namespace mozilla {
 namespace dom {
 
 class AutoJSAPI;
 class ModuleLoadRequest;
 class ModuleScript;
@@ -502,18 +504,18 @@ private:
                                 nsresult aStatus);
 
   void AddDeferRequest(ScriptLoadRequest* aRequest);
   void AddAsyncRequest(ScriptLoadRequest* aRequest);
   bool MaybeRemovedDeferRequests();
 
   void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest);
 
-  mozilla::Maybe<JS::SourceBufferHolder> GetScriptSource(JSContext* aCx,
-                                                         ScriptLoadRequest* aRequest);
+  mozilla::Maybe<JS::SourceText<char16_t>>
+  GetScriptSource(JSContext* aCx, ScriptLoadRequest* aRequest);
 
   void SetModuleFetchStarted(ModuleLoadRequest *aRequest);
   void SetModuleFetchFinishedAndResumeWaitingRequests(ModuleLoadRequest* aRequest,
                                                       nsresult aResult);
 
   bool IsFetchingModule(ModuleLoadRequest* aRequest) const;
 
   bool ModuleMapContainsURL(nsIURI* aURL) const;
--- a/dom/security/featurepolicy/FeaturePolicy.h
+++ b/dom/security/featurepolicy/FeaturePolicy.h
@@ -45,16 +45,19 @@
  *
  * When FeaturePolicy must decide if feature X is allowed or denied for the
  * current origin, it checks if the parent context denied that feature.
  * If not, it checks if there is a Feature object for that
  * feature named X and if the origin is allowed or not.
  *
  * From a C++ point of view, use FeaturePolicyUtils to obtain the list of
  * features and to check if they are allowed in the current context.
+ *
+ * dom.security.featurePolicy.header.enabled pref can be used to disable the
+ * HTTP header support.
  **/
 
 class nsIDocument;
 class nsIHttpChannel;
 class nsINode;
 
 namespace mozilla {
 namespace dom {
--- a/dom/security/featurepolicy/FeaturePolicyUtils.cpp
+++ b/dom/security/featurepolicy/FeaturePolicyUtils.cpp
@@ -1,18 +1,21 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "FeaturePolicyUtils.h"
 #include "mozilla/dom/FeaturePolicy.h"
+#include "mozilla/dom/FeaturePolicyViolationReportBody.h"
+#include "mozilla/dom/ReportingUtils.h"
 #include "mozilla/StaticPrefs.h"
 #include "nsIDocument.h"
+#include "nsIURIFixup.h"
 
 namespace mozilla {
 namespace dom {
 
 struct FeatureMap {
   const char* mFeatureName;
   FeaturePolicyUtils::FeaturePolicyValue mDefaultAllowList;
 };
@@ -82,13 +85,78 @@ FeaturePolicyUtils::IsFeatureAllowed(nsI
 
   if (!aDocument->IsHTMLDocument()) {
     return true;
   }
 
   FeaturePolicy* policy = aDocument->Policy();
   MOZ_ASSERT(policy);
 
-  return policy->AllowsFeatureInternal(aFeatureName, policy->DefaultOrigin());
+  if (policy->AllowsFeatureInternal(aFeatureName, policy->DefaultOrigin())) {
+    return true;
+  }
+
+  ReportViolation(aDocument, aFeatureName);
+  return false;
+}
+
+/* static */ void
+FeaturePolicyUtils::ReportViolation(nsIDocument* aDocument,
+                                    const nsAString& aFeatureName)
+{
+  MOZ_ASSERT(aDocument);
+
+  nsCOMPtr<nsIURI> uri = aDocument->GetDocumentURI();
+  if (NS_WARN_IF(!uri)) {
+    return;
+  }
+
+  // Strip the URL of any possible username/password and make it ready to be
+  // presented in the UI.
+  nsCOMPtr<nsIURIFixup> urifixup = services::GetURIFixup();
+  if (NS_WARN_IF(!urifixup)) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> exposableURI;
+  nsresult rv = urifixup->CreateExposableURI(uri, getter_AddRefs(exposableURI));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsAutoCString spec;
+  rv = exposableURI->GetSpec(spec);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+  JSContext* cx = nsContentUtils::GetCurrentJSContext();
+  if (NS_WARN_IF(!cx)) {
+    return;
+  }
+
+  nsAutoCString fileName;
+  Nullable<int32_t> lineNumber;
+  Nullable<int32_t> columnNumber;
+  uint32_t line = 0;
+  uint32_t column = 0;
+  if (nsJSUtils::GetCallingLocation(cx, fileName, &line, &column)) {
+    lineNumber.SetValue(static_cast<int32_t>(line));
+    columnNumber.SetValue(static_cast<int32_t>(column));
+  }
+
+  nsPIDOMWindowInner* window = aDocument->GetInnerWindow();
+  if (NS_WARN_IF(!window)) {
+    return;
+  }
+
+  RefPtr<FeaturePolicyViolationReportBody> body =
+    new FeaturePolicyViolationReportBody(window, aFeatureName,
+                                         NS_ConvertUTF8toUTF16(fileName),
+                                         lineNumber, columnNumber,
+                                         NS_LITERAL_STRING("enforce"));
+
+  ReportingUtils::Report(window,
+                         nsGkAtoms::featurePolicyViolation,
+                         NS_ConvertUTF8toUTF16(spec), body);
 }
 
 } // dom namespace
 } // mozilla namespace
--- a/dom/security/featurepolicy/FeaturePolicyUtils.h
+++ b/dom/security/featurepolicy/FeaturePolicyUtils.h
@@ -43,14 +43,19 @@ public:
   // Runs aCallback for each known feature policy, with the feature name as
   // argument.
   static void
   ForEachFeature(const std::function<void(const char*)>& aCallback);
 
   // Returns the default policy value for aFeatureName.
   static FeaturePolicyValue
   DefaultAllowListFeature(const nsAString& aFeatureName);
+
+private:
+  static void
+  ReportViolation(nsIDocument* aDocument,
+                  const nsAString& aFeatureName);
 };
 
 } // dom namespace
 } // mozilla namespace
 
 #endif // mozilla_dom_FeaturePolicyUtils_h
--- a/dom/security/featurepolicy/test/mochitest/mochitest.ini
+++ b/dom/security/featurepolicy/test/mochitest/mochitest.ini
@@ -1,8 +1,10 @@
 [DEFAULT]
 prefs =
   dom.security.featurePolicy.enabled=true
+  dom.security.featurePolicy.header.enabled=true
+  dom.security.featurePolicy.webidl.enabled=true
 support-files =
   empty.html
   test_parser.html^headers^
 
 [test_parser.html]
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -804,22 +804,24 @@ nsCSPContext::logToConsole(const char* a
 void
 StripURIForReporting(nsIURI* aURI,
                      nsIURI* aSelfURI,
                      nsACString& outStrippedURI)
 {
   // 1) If the origin of uri is a globally unique identifier (for example,
   // aURI has a scheme of data, blob, or filesystem), then return the
   // ASCII serialization of uri’s scheme.
-  bool isHttpOrFtp =
-    (NS_SUCCEEDED(aURI->SchemeIs("http", &isHttpOrFtp)) && isHttpOrFtp) ||
-    (NS_SUCCEEDED(aURI->SchemeIs("https", &isHttpOrFtp)) && isHttpOrFtp) ||
-    (NS_SUCCEEDED(aURI->SchemeIs("ftp", &isHttpOrFtp)) && isHttpOrFtp);
+  bool isHttpFtpOrWs =
+    (NS_SUCCEEDED(aURI->SchemeIs("http", &isHttpFtpOrWs)) && isHttpFtpOrWs) ||
+    (NS_SUCCEEDED(aURI->SchemeIs("https", &isHttpFtpOrWs)) && isHttpFtpOrWs) ||
+    (NS_SUCCEEDED(aURI->SchemeIs("ftp", &isHttpFtpOrWs)) && isHttpFtpOrWs) ||
+    (NS_SUCCEEDED(aURI->SchemeIs("ws", &isHttpFtpOrWs)) && isHttpFtpOrWs) ||
+    (NS_SUCCEEDED(aURI->SchemeIs("wss", &isHttpFtpOrWs)) && isHttpFtpOrWs);
 
-  if (!isHttpOrFtp) {
+  if (!isHttpFtpOrWs) {
     // not strictly spec compliant, but what we really care about is
     // http/https and also ftp. If it's not http/https or ftp, then treat aURI
     // as if it's a globally unique identifier and just return the scheme.
     aURI->GetScheme(outStrippedURI);
     return;
   }
 
   // Return uri, with any fragment component removed.
--- a/dom/tests/mochitest/geolocation/test_featurePolicy.html
+++ b/dom/tests/mochitest/geolocation/test_featurePolicy.html
@@ -40,12 +40,16 @@ function nextTest() {
     document.body.removeChild(iframe);
     SimpleTest.executeSoon(nextTest);
   };
 
   iframe.src = "file_featurePolicy.html";
   document.body.appendChild(iframe);
 }
 
-SpecialPowers.pushPrefEnv({"set": [["dom.security.featurePolicy.enabled", true]]}).then(nextTest);
+SpecialPowers.pushPrefEnv({"set": [
+  ["dom.security.featurePolicy.enabled", true],
+  ["dom.security.featurePolicy.header.enabled", true],
+  ["dom.security.featurePolicy.webidl.enabled", true],
+]}).then(nextTest);
 </script>
 </body>
 </html>
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -545,11 +545,11 @@ Document implements TouchEventHandlers;
 Document implements ParentNode;
 Document implements OnErrorEventHandlerForNodes;
 Document implements GeometryUtils;
 Document implements FontFaceSource;
 Document implements DocumentOrShadowRoot;
 
 // https://wicg.github.io/feature-policy/#policy
 partial interface Document {
-    [SameObject, Pref="dom.security.featurePolicy.enabled"]
+    [SameObject, Pref="dom.security.featurePolicy.webidl.enabled"]
     readonly attribute Policy policy;
 };
--- a/dom/webidl/FeaturePolicy.webidl
+++ b/dom/webidl/FeaturePolicy.webidl
@@ -8,8 +8,17 @@
  */
 
 [NoInterfaceObject]
 interface Policy {
   boolean allowsFeature(DOMString feature, optional DOMString origin);
   sequence<DOMString> allowedFeatures();
   sequence<DOMString> getAllowlistForFeature(DOMString feature);
 };
+
+[Func="mozilla::dom::DOMPrefs::dom_reporting_featurePolicy_enabled"]
+interface FeaturePolicyViolationReportBody : ReportBody {
+  readonly attribute DOMString featureId;
+  readonly attribute DOMString? sourceFile;
+  readonly attribute long? lineNumber;
+  readonly attribute long? columnNumber;
+  readonly attribute DOMString disposition;
+};
--- a/dom/webidl/HTMLIFrameElement.webidl
+++ b/dom/webidl/HTMLIFrameElement.webidl
@@ -67,14 +67,14 @@ partial interface HTMLIFrameElement {
            attribute boolean mozbrowser;
 };
 
 HTMLIFrameElement implements MozFrameLoaderOwner;
 HTMLIFrameElement implements BrowserElement;
 
 // https://wicg.github.io/feature-policy/#policy
 partial interface HTMLIFrameElement {
-  [SameObject, Pref="dom.security.featurePolicy.enabled"]
+  [SameObject, Pref="dom.security.featurePolicy.webidl.enabled"]
   readonly attribute Policy policy;
 
   [CEReactions, SetterThrows, Pure, Pref="dom.security.featurePolicy.enabled"]
            attribute DOMString allow;
 };
--- a/dom/webidl/PerformanceTiming.webidl
+++ b/dom/webidl/PerformanceTiming.webidl
@@ -34,16 +34,20 @@ interface PerformanceTiming {
   readonly attribute unsigned long long loadEventEnd;
 
   // This is a Chrome proprietary extension and not part of the
   // performance/navigation timing specification.
   // Returns 0 if a non-blank paint has not happened.
   [Pref="dom.performance.time_to_non_blank_paint.enabled"]
   readonly attribute unsigned long long timeToNonBlankPaint;
 
+  // Returns 0 if a contentful paint has not happened.
+  [Pref="dom.performance.time_to_contentful_paint.enabled"]
+  readonly attribute unsigned long long timeToContentfulPaint;
+
   // This is a Mozilla proprietary extension and not part of the
   // performance/navigation timing specification. It marks the
   // completion of the first presentation flush after DOMContentLoaded.
   [Pref="dom.performance.time_to_dom_content_flushed.enabled"]
   readonly attribute unsigned long long timeToDOMContentFlushed;
 
   // This is a Chrome proprietary extension and not part of the
   // performance/navigation timing specification.
new file mode 100644
--- /dev/null
+++ b/dom/webidl/Reporting.webidl
@@ -0,0 +1,57 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is
+ * https://w3c.github.io/reporting/#interface-reporting-observer
+ */
+
+[Func="mozilla::dom::DOMPrefs::dom_reporting_enabled"]
+interface ReportBody {
+};
+
+[Func="mozilla::dom::DOMPrefs::dom_reporting_enabled"]
+interface Report {
+  readonly attribute DOMString type;
+  readonly attribute DOMString url;
+  readonly attribute ReportBody? body;
+};
+
+[Constructor(ReportingObserverCallback callback, optional ReportingObserverOptions options),
+ Func="mozilla::dom::DOMPrefs::dom_reporting_enabled"]
+interface ReportingObserver {
+  void observe();
+  void disconnect();
+  ReportList takeRecords();
+};
+
+callback ReportingObserverCallback = void (sequence<Report> reports, ReportingObserver observer);
+
+dictionary ReportingObserverOptions {
+  sequence<DOMString> types;
+  boolean buffered = false;
+};
+
+typedef sequence<Report> ReportList;
+
+[Func="mozilla::dom::DOMPrefs::dom_reporting_enabled"]
+interface DeprecationReportBody : ReportBody {
+  readonly attribute DOMString id;
+  readonly attribute Date? anticipatedRemoval;
+  readonly attribute DOMString message;
+  readonly attribute DOMString? sourceFile;
+  readonly attribute unsigned long? lineNumber;
+  readonly attribute unsigned long? columnNumber;
+};
+
+[Constructor(), Deprecated="DeprecatedTestingInterface",
+ Func="mozilla::dom::DOMPrefs::dom_reporting_testing_enabled",
+ Exposed=(Window,DedicatedWorker)]
+interface TestingDeprecatedInterface {
+  [Deprecated="DeprecatedTestingMethod"]
+  void deprecatedMethod();
+
+  [Deprecated="DeprecatedTestingAttribute"]
+  readonly attribute boolean deprecatedAttribute;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -735,16 +735,17 @@ WEBIDL_FILES = [
     'PushEvent.webidl',
     'PushManager.webidl',
     'PushManager.webidl',
     'PushMessageData.webidl',
     'PushSubscription.webidl',
     'PushSubscriptionOptions.webidl',
     'RadioNodeList.webidl',
     'Range.webidl',
+    'Reporting.webidl',
     'Request.webidl',
     'Response.webidl',
     'RTCStatsReport.webidl',
     'Screen.webidl',
     'ScreenOrientation.webidl',
     'ScriptProcessorNode.webidl',
     'ScrollAreaEvent.webidl',
     'Selection.webidl',
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ScriptLoader.h"
 
+#include <algorithm>
+
 #include "nsIChannel.h"
 #include "nsIContentPolicy.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIDocShell.h"
 #include "nsIHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIInputStreamPump.h"
 #include "nsIIOService.h"
@@ -21,17 +23,17 @@
 #include "nsIStreamLoader.h"
 #include "nsIStreamListenerTee.h"
 #include "nsIThreadRetargetableRequest.h"
 #include "nsIURI.h"
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/CompilationAndEvaluation.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "nsError.h"
 #include "nsContentPolicyUtils.h"
 #include "nsContentUtils.h"
 #include "nsDocShellCID.h"
 #include "nsISupportsPrimitives.h"
 #include "nsNetUtil.h"
 #include "nsIPipe.h"
 #include "nsIOutputStream.h"
@@ -2119,21 +2121,29 @@ ScriptExecutorRunnable::WorkerRun(JSCont
 
     JS::CompileOptions options(aCx);
     options.setFileAndLine(filename.get(), 1)
            .setNoScriptRval(true);
 
     MOZ_ASSERT(loadInfo.mMutedErrorFlag.isSome());
     options.setMutedErrors(loadInfo.mMutedErrorFlag.valueOr(true));
 
-    JS::SourceBufferHolder srcBuf(loadInfo.mScriptTextBuf,
-                                  loadInfo.mScriptTextLength,
-                                  JS::SourceBufferHolder::GiveOwnership);
-    loadInfo.mScriptTextBuf = nullptr;
-    loadInfo.mScriptTextLength = 0;
+    // Pass ownership of the data, first to local variables, then to the
+    // UniqueTwoByteChars moved into the |init| function.
+    size_t dataLength = 0;
+    char16_t* data = nullptr;
+
+    std::swap(dataLength, loadInfo.mScriptTextLength);
+    std::swap(data, loadInfo.mScriptTextBuf);
+
+    JS::SourceText<char16_t> srcBuf;
+    if (!srcBuf.init(aCx, JS::UniqueTwoByteChars(data), dataLength)) {
+      mScriptLoader.mRv.StealExceptionFromJSContext(aCx);
+      return true;
+    }
 
     // Our ErrorResult still shouldn't be a failure.
     MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?");
     JS::Rooted<JS::Value> unused(aCx);
     if (!JS::Evaluate(aCx, options, srcBuf, &unused)) {
       mScriptLoader.mRv.StealExceptionFromJSContext(aCx);
       return true;
     }
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4,17 +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 "WorkerPrivate.h"
 
 #include "js/CompilationAndEvaluation.h"
 #include "js/LocaleSensitive.h"
 #include "js/MemoryMetrics.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "MessageEventRunnable.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/dom/BlobURLProtocolHandler.h"
 #include "mozilla/dom/ClientManager.h"
 #include "mozilla/dom/ClientSource.h"
 #include "mozilla/dom/ClientState.h"
 #include "mozilla/dom/Console.h"
@@ -4897,22 +4897,25 @@ WorkerPrivate::RunExpiredTimeouts(JSCont
       uint32_t lineNo = 0, dummyColumn = 0;
       info->mHandler->GetLocation(&filename, &lineNo, &dummyColumn);
 
       JS::CompileOptions options(aes.cx());
       options.setFileAndLine(filename, lineNo).setNoScriptRval(true);
 
       JS::Rooted<JS::Value> unused(aes.cx());
 
-      JS::SourceBufferHolder srcBuf(script.BeginReading(), script.Length(),
-                                    JS::SourceBufferHolder::NoOwnership);
-      if (!JS::Evaluate(aes.cx(), options, srcBuf, &unused) &&
-          !JS_IsExceptionPending(aCx)) {
-        retval = false;
-        break;
+      JS::SourceText<char16_t> srcBuf;
+      if (!srcBuf.init(aes.cx(), script.BeginReading(), script.Length(),
+                       JS::SourceOwnership::Borrowed) ||
+          !JS::Evaluate(aes.cx(), options, srcBuf, &unused))
+      {
+        if (!JS_IsExceptionPending(aCx)) {
+          retval = false;
+          break;
+        }
       }
     } else {
       ErrorResult rv;
       JS::Rooted<JS::Value> ignoredVal(aCx);
       callback->Call(GlobalScope(), info->mHandler->GetArgs(), &ignoredVal, rv,
                      reason);
       if (rv.IsUncatchableException()) {
         rv.SuppressException();
--- a/dom/worklet/Worklet.cpp
+++ b/dom/worklet/Worklet.cpp
@@ -12,17 +12,17 @@
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/Response.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/ScriptLoader.h"
 #include "mozilla/dom/WorkletImpl.h"
 #include "js/CompilationAndEvaluation.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "nsIInputStreamPump.h"
 #include "nsIThreadRetargetableRequest.h"
 #include "nsNetUtil.h"
 #include "xpcprivate.h"
 
 namespace mozilla {
 namespace dom {
 
@@ -396,20 +396,20 @@ ExecutionRunnable::RunOnWorkletThread()
   JS::CompileOptions compileOptions(cx);
   compileOptions.setIntroductionType("Worklet");
   compileOptions.setFileAndLine(url.get(), 0);
   compileOptions.setIsRunOnce(true);
   compileOptions.setNoScriptRval(true);
 
   JSAutoRealm ar(cx, globalObj);
 
-  JS::SourceBufferHolder buffer(mScriptBuffer.release(), mScriptLength,
-                                JS::SourceBufferHolder::GiveOwnership);
   JS::Rooted<JS::Value> unused(cx);
-  if (!JS::Evaluate(cx, compileOptions, buffer, &unused)) {
+  JS::SourceText<char16_t> buffer;
+  if (!buffer.init(cx, std::move(mScriptBuffer), mScriptLength) ||
+      !JS::Evaluate(cx, compileOptions, buffer, &unused)) {
     ErrorResult error;
     error.MightThrowJSException();
     error.StealExceptionFromJSContext(cx);
     mResult = error.StealNSResult();
     return;
   }
 
   // All done.
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -18,16 +18,18 @@
      which may be lazily created during frame construction, the
      document observer methods will never be called because we'll be
      adding the XUL nodes into the content model "quietly".
 
 */
 
 #include "mozilla/ArrayUtils.h"
 
+#include <algorithm>
+
 #include "XULDocument.h"
 
 #include "nsError.h"
 #include "nsIBoxObject.h"
 #include "nsIChromeRegistry.h"
 #include "nsView.h"
 #include "nsViewManager.h"
 #include "nsIContentViewer.h"
@@ -84,17 +86,17 @@
 #include "mozilla/dom/XULDocumentBinding.h"
 #include "mozilla/dom/XULPersist.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/Preferences.h"
 #include "nsTextNode.h"
 #include "nsJSUtils.h"
 #include "js/CompilationAndEvaluation.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "mozilla/dom/URL.h"
 #include "nsIContentPolicy.h"
 #include "mozAutoDocUpdate.h"
 #include "xpcpublic.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "nsIConsoleService.h"
 
@@ -1278,32 +1280,29 @@ XULDocument::OnStreamComplete(nsIStreamL
                                             !mOffThreadCompileStringBuf),
                    "XULDocument can't load multiple scripts at once");
 
         rv = ScriptLoader::ConvertToUTF16(channel, string, stringLen,
                                           EmptyString(), this,
                                           mOffThreadCompileStringBuf,
                                           mOffThreadCompileStringLength);
         if (NS_SUCCEEDED(rv)) {
-            // Attempt to give ownership of the buffer to the JS engine.  If
-            // we hit offthread compilation, however, we will have to take it
-            // back below in order to keep the memory alive until compilation
-            // completes.
-            JS::SourceBufferHolder srcBuf(mOffThreadCompileStringBuf,
-                                          mOffThreadCompileStringLength,
-                                          JS::SourceBufferHolder::GiveOwnership);
-            mOffThreadCompileStringBuf = nullptr;
-            mOffThreadCompileStringLength = 0;
+            // Pass ownership of the buffer, carefully emptying the existing
+            // fields in the process.  Note that the |Compile| function called
+            // below always takes ownership of the buffer.
+            char16_t* units = nullptr;
+            size_t unitsLength = 0;
 
-            rv = mCurrentScriptProto->Compile(srcBuf, uri, 1, this, this);
+            std::swap(units, mOffThreadCompileStringBuf);
+            std::swap(unitsLength, mOffThreadCompileStringLength);
+
+            rv = mCurrentScriptProto->Compile(units, unitsLength,
+                                              JS::SourceOwnership::TakeOwnership,
+                                              uri, 1, this, this);
             if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->HasScriptObject()) {
-                // We will be notified via OnOffThreadCompileComplete when the
-                // compile finishes. The JS engine has taken ownership of the
-                // source buffer.
-                MOZ_RELEASE_ASSERT(!srcBuf.ownsChars());
                 mOffThreadCompiling = true;
                 BlockOnload();
                 return NS_OK;
             }
         }
     }
 
     return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv);
--- a/dom/xul/nsXULContentSink.cpp
+++ b/dom/xul/nsXULContentSink.cpp
@@ -500,19 +500,21 @@ XULContentSinkImpl::HandleEndElement(con
         nsXULPrototypeScript* script =
             static_cast<nsXULPrototypeScript*>(node.get());
 
         // If given a src= attribute, we must ignore script tag content.
         if (!script->mSrcURI && !script->HasScriptObject()) {
             nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument);
 
             script->mOutOfLine = false;
-            if (doc)
-                script->Compile(mText, mTextLength, mDocumentURL,
+            if (doc) {
+                script->Compile(mText, mTextLength,
+                                JS::SourceOwnership::Borrowed, mDocumentURL,
                                 script->mLineNo, doc);
+            }
         }
 
         FlushText(false);
     }
     break;
 
     default:
         NS_ERROR("didn't expect that");
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -14,17 +14,17 @@
 #include "nsIDOMXULSelectCntrlItemEl.h"
 #include "nsIDocument.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/DeclarationBlock.h"
 #include "js/CompilationAndEvaluation.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "nsFocusManager.h"
 #include "nsHTMLStyleSheet.h"
 #include "nsNameSpaceManager.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIPresShell.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptContext.h"
@@ -2273,28 +2273,41 @@ OffThreadScriptReceiverCallback(JS::OffT
     // may be invoked off the main thread.
     nsIOffThreadScriptReceiver* aReceiver = static_cast<nsIOffThreadScriptReceiver*>(aCallbackData);
     RefPtr<NotifyOffThreadScriptCompletedRunnable> notify =
         new NotifyOffThreadScriptCompletedRunnable(aReceiver, aToken);
     NS_DispatchToMainThread(notify);
 }
 
 nsresult
-nsXULPrototypeScript::Compile(JS::SourceBufferHolder& aSrcBuf,
+nsXULPrototypeScript::Compile(const char16_t* aText,
+                              size_t aTextLength,
+                              JS::SourceOwnership aOwnership,
                               nsIURI* aURI, uint32_t aLineNo,
                               nsIDocument* aDocument,
                               nsIOffThreadScriptReceiver *aOffThreadReceiver /* = nullptr */)
 {
     // We'll compile the script in the compilation scope.
     AutoJSAPI jsapi;
     if (!jsapi.Init(xpc::CompilationScope())) {
+        if (aOwnership == JS::SourceOwnership::TakeOwnership) {
+            // In this early-exit case -- before the |srcBuf.init| call will
+            // own |aText| -- we must relinquish ownership manually.
+            js_free(const_cast<char16_t*>(aText));
+        }
+
         return NS_ERROR_UNEXPECTED;
     }
     JSContext* cx = jsapi.cx();
 
+    JS::SourceText<char16_t> srcBuf;
+    if (NS_WARN_IF(!srcBuf.init(cx, aText, aTextLength, aOwnership))) {
+        return NS_ERROR_FAILURE;
+    }
+
     nsAutoCString urlspec;
     nsresult rv = aURI->GetSpec(urlspec);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     // Ok, compile it to create a prototype script object!
     JS::CompileOptions options(cx);
@@ -2304,46 +2317,32 @@ nsXULPrototypeScript::Compile(JS::Source
     // Function.prototype.toSource(). If it's out of line, we retrieve the
     // source from the files on demand.
     options.setSourceIsLazy(mOutOfLine);
     JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
     if (scope) {
       JS::ExposeObjectToActiveJS(scope);
     }
 
-    if (aOffThreadReceiver && JS::CanCompileOffThread(cx, options, aSrcBuf.length())) {
-        if (!JS::CompileOffThread(cx, options,
-                                  aSrcBuf,
+    if (aOffThreadReceiver && JS::CanCompileOffThread(cx, options, aTextLength)) {
+        if (!JS::CompileOffThread(cx, options, srcBuf,
                                   OffThreadScriptReceiverCallback,
                                   static_cast<void*>(aOffThreadReceiver))) {
             return NS_ERROR_OUT_OF_MEMORY;
         }
         NotifyOffThreadScriptCompletedRunnable::NoteReceiver(aOffThreadReceiver);
     } else {
         JS::Rooted<JSScript*> script(cx);
-        if (!JS::Compile(cx, options, aSrcBuf, &script))
+        if (!JS::Compile(cx, options, srcBuf, &script))
             return NS_ERROR_OUT_OF_MEMORY;
         Set(script);
     }
     return NS_OK;
 }
 
-nsresult
-nsXULPrototypeScript::Compile(const char16_t* aText,
-                              int32_t aTextLength,
-                              nsIURI* aURI,
-                              uint32_t aLineNo,
-                              nsIDocument* aDocument,
-                              nsIOffThreadScriptReceiver *aOffThreadReceiver /* = nullptr */)
-{
-  JS::SourceBufferHolder srcBuf(aText, aTextLength,
-                                JS::SourceBufferHolder::NoOwnership);
-  return Compile(srcBuf, aURI, aLineNo, aDocument, aOffThreadReceiver);
-}
-
 void
 nsXULPrototypeScript::UnlinkJSObjects()
 {
     if (mScriptObject) {
         mScriptObject = nullptr;
         mozilla::DropJSObjects(this);
     }
 }
--- a/dom/xul/nsXULElement.h
+++ b/dom/xul/nsXULElement.h
@@ -7,16 +7,17 @@
 
   The base XUL element class and associates.
 
 */
 
 #ifndef nsXULElement_h__
 #define nsXULElement_h__
 
+#include "js/SourceText.h"
 #include "js/TracingAPI.h"
 #include "mozilla/Attributes.h"
 #include "nsIServiceManager.h"
 #include "nsAtom.h"
 #include "mozilla/dom/NodeInfo.h"
 #include "nsIControllers.h"
 #include "nsIDOMXULMultSelectCntrlEl.h"
 #include "nsIURI.h"
@@ -47,20 +48,16 @@ class StyleRule;
 } // namespace css
 namespace dom {
 class BoxObject;
 class HTMLIFrameElement;
 enum class CallerType : uint32_t;
 } // namespace dom
 } // namespace mozilla
 
-namespace JS {
-class SourceBufferHolder;
-} // namespace JS
-
 ////////////////////////////////////////////////////////////////////////
 
 #ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
 #define XUL_PROTOTYPE_ATTRIBUTE_METER(counter) (nsXULPrototypeAttribute::counter++)
 #else
 #define XUL_PROTOTYPE_ATTRIBUTE_METER(counter) ((void) 0)
 #endif
 
@@ -213,22 +210,18 @@ public:
                                 nsXULPrototypeDocument* aProtoDoc);
     virtual nsresult Deserialize(nsIObjectInputStream* aStream,
                                  nsXULPrototypeDocument* aProtoDoc,
                                  nsIURI* aDocumentURI,
                                  const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) override;
     nsresult DeserializeOutOfLine(nsIObjectInputStream* aInput,
                                   nsXULPrototypeDocument* aProtoDoc);
 
-    nsresult Compile(JS::SourceBufferHolder& aSrcBuf,
-                     nsIURI* aURI, uint32_t aLineNo,
-                     nsIDocument* aDocument,
-                     nsIOffThreadScriptReceiver *aOffThreadReceiver = nullptr);
-
-    nsresult Compile(const char16_t* aText, int32_t aTextLength,
+    nsresult Compile(const char16_t* aText, size_t aTextLength,
+                     JS::SourceOwnership aOwnership,
                      nsIURI* aURI, uint32_t aLineNo,
                      nsIDocument* aDocument,
                      nsIOffThreadScriptReceiver *aOffThreadReceiver = nullptr);
 
     void UnlinkJSObjects();
 
     void Set(JSScript* aObject);
 
--- a/ipc/testshell/XPCShellEnvironment.cpp
+++ b/ipc/testshell/XPCShellEnvironment.cpp
@@ -13,17 +13,17 @@
 #include <unistd.h>     /* for isatty() */
 #endif
 
 #include "base/basictypes.h"
 
 #include "jsapi.h"
 #include "js/CharacterEncoding.h"
 #include "js/CompilationAndEvaluation.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 
 #include "xpcpublic.h"
 
 #include "XPCShellEnvironment.h"
 
 #include "mozilla/XPCOM.h"
 
 #include "nsIChannel.h"
@@ -487,19 +487,24 @@ XPCShellEnvironment::EvaluateString(cons
 {
   AutoEntryScript aes(GetGlobalObject(),
                       "ipc XPCShellEnvironment::EvaluateString");
   JSContext* cx = aes.cx();
 
   JS::CompileOptions options(cx);
   options.setFileAndLine("typein", 0);
 
+  JS::SourceText<char16_t> srcBuf;
+  if (!srcBuf.init(cx, aString.get(), aString.Length(),
+                   JS::SourceOwnership::Borrowed))
+  {
+    return false;
+  }
+
   JS::Rooted<JSScript*> script(cx);
-  JS::SourceBufferHolder srcBuf(aString.get(), aString.Length(),
-                                JS::SourceBufferHolder::NoOwnership);
   if (!JS::Compile(cx, options, srcBuf, &script))
   {
      return false;
   }
 
   if (aResult) {
       aResult->Truncate();
   }
--- a/js/public/CharacterEncoding.h
+++ b/js/public/CharacterEncoding.h
@@ -77,16 +77,22 @@ class UTF8Chars : public mozilla::Range<
 
     UTF8Chars() : Base() {}
     UTF8Chars(char* aBytes, size_t aLength)
       : Base(reinterpret_cast<unsigned char*>(aBytes), aLength)
     {}
     UTF8Chars(const char* aBytes, size_t aLength)
       : Base(reinterpret_cast<unsigned char*>(const_cast<char*>(aBytes)), aLength)
     {}
+    UTF8Chars(mozilla::Utf8Unit* aUnits, size_t aLength)
+      : UTF8Chars(reinterpret_cast<char*>(aUnits), aLength)
+    {}
+    UTF8Chars(const mozilla::Utf8Unit* aUnits, size_t aLength)
+      : UTF8Chars(reinterpret_cast<const char*>(aUnits), aLength)
+    {}
 };
 
 /*
  * SpiderMonkey also deals directly with UTF-8 encoded text in some places.
  */
 class UTF8CharsZ : public mozilla::RangedPtr<unsigned char>
 {
     typedef mozilla::RangedPtr<unsigned char> Base;
@@ -103,16 +109,20 @@ class UTF8CharsZ : public mozilla::Range
     }
 
     UTF8CharsZ(unsigned char* aBytes, size_t aLength)
       : Base(aBytes, aLength)
     {
         MOZ_ASSERT(aBytes[aLength] == '\0');
     }
 
+    UTF8CharsZ(mozilla::Utf8Unit* aUnits, size_t aLength)
+      : UTF8CharsZ(reinterpret_cast<char*>(aUnits), aLength)
+    {}
+
     using Base::operator=;
 
     char* c_str() { return reinterpret_cast<char*>(get()); }
 };
 
 /*
  * A wrapper for a "const char*" that is encoded using UTF-8.
  * This class does not manage ownership of the data; that is left
--- a/js/public/CompilationAndEvaluation.h
+++ b/js/public/CompilationAndEvaluation.h
@@ -21,17 +21,17 @@ struct JSContext;
 class JSFunction;
 class JSObject;
 class JSScript;
 
 namespace JS {
 
 template<typename T> class AutoVector;
 
-class SourceBufferHolder;
+template<typename UnitT> class SourceText;
 
 } // namespace JS
 
 /**
  * Given a buffer, return false if the buffer might become a valid JavaScript
  * script with the addition of more lines, or true if the validity of such a
  * script is conclusively known (because it's the prefix of a valid script --
  * and possibly the entirety of such a script).
@@ -102,26 +102,26 @@ extern JS_PUBLIC_API(bool)
 CloneAndExecuteScript(JSContext* cx, AutoVector<JSObject*>& envChain, Handle<JSScript*> script,
                       MutableHandle<Value> rval);
 
 /**
  * Evaluate the given source buffer in the scope of the current global of cx.
  */
 extern JS_PUBLIC_API(bool)
 Evaluate(JSContext* cx, const ReadOnlyCompileOptions& options,
-         SourceBufferHolder& srcBuf, MutableHandle<Value> rval);
+         SourceText<char16_t>& srcBuf, MutableHandle<Value> rval);
 
 /**
  * As above, but providing an explicit scope chain.  envChain must not include
  * the global object on it; that's implicit.  It needs to contain the other
  * objects that should end up on the script's scope chain.
  */
 extern JS_PUBLIC_API(bool)
 Evaluate(JSContext* cx, AutoVector<JSObject*>& envChain, const ReadOnlyCompileOptions& options,
-         SourceBufferHolder& srcBuf, MutableHandle<Value> rval);
+         SourceText<char16_t>& srcBuf, MutableHandle<Value> rval);
 
 /**
  * Evaluate the provided UTF-8 data in the scope of the current global of |cx|,
  * and return the completion value in |rval|.  If the data contains invalid
  * UTF-8, an error is reported.
  */
 extern JS_PUBLIC_API(bool)
 EvaluateUtf8(JSContext* cx, const ReadOnlyCompileOptions& options,
@@ -149,29 +149,43 @@ extern JS_PUBLIC_API(bool)
 EvaluateUtf8Path(JSContext* cx, const ReadOnlyCompileOptions& options,
                  const char* filename, MutableHandle<Value> rval);
 
 /**
  * |script| will always be set. On failure, it will be set to nullptr.
  */
 extern JS_PUBLIC_API(bool)
 Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
-        SourceBufferHolder& srcBuf, MutableHandle<JSScript*> script);
+        SourceText<char16_t>& srcBuf, MutableHandle<JSScript*> script);
 
 /**
  * Compile the provided UTF-8 data into a script.  If the data contains invalid
  * UTF-8, an error is reported.
  *
  * |script| is always set to the compiled script or to null in case of error.
  */
 extern JS_PUBLIC_API(bool)
 CompileUtf8(JSContext* cx, const ReadOnlyCompileOptions& options,
             const char* bytes, size_t length, MutableHandle<JSScript*> script);
 
 /**
+ * Compile the provided UTF-8 data into a script.  If the data contains invalid
+ * UTF-8, an error is reported.
+ *
+ * |script| is always set to the compiled script or to null in case of error.
+ *
+ * NOTE: This function DOES NOT INFLATE the UTF-8 bytes to UTF-16 before
+ *       compiling them.  UTF-8 compilation is currently experimental and has
+ *       known bugs.  Use only if you're willing to tolerate unspecified bugs!
+ */
+extern JS_PUBLIC_API(bool)
+CompileUtf8DontInflate(JSContext* cx, const ReadOnlyCompileOptions& options,
+                       const char* bytes, size_t length, MutableHandle<JSScript*> script);
+
+/**
  * Compile the provided Latin-1 data (i.e. each byte directly corresponds to
  * the same Unicode code point) into a script.
  *
  * This function may eventually be removed, such that *only* bytes containing
  * UTF-8 source text may be directly compiled.  Avoid using it if you can.
  *
  * |script| is always set to the compiled script or to null in case of error.
  */
@@ -185,29 +199,43 @@ CompileLatin1(JSContext* cx, const ReadO
  *
  * |script| is always set to the compiled script or to null in case of error.
  */
 extern JS_PUBLIC_API(bool)
 CompileUtf8File(JSContext* cx, const ReadOnlyCompileOptions& options,
                 FILE* file, MutableHandle<JSScript*> script);
 
 /**
+ * Compile the UTF-8 contents of the given file into a script.  If the contents
+ * contain any malformed UTF-8, an error is reported.
+ *
+ * |script| is always set to the compiled script or to null in case of error.
+ *
+ * NOTE: This function DOES NOT INFLATE the UTF-8 bytes to UTF-16 before
+ *       compiling them.  UTF-8 compilation is currently experimental and has
+ *       known bugs.  Use only if you're willing to tolerate unspecified bugs!
+ */
+extern JS_PUBLIC_API(bool)
+CompileUtf8FileDontInflate(JSContext* cx, const ReadOnlyCompileOptions& options,
+                           FILE* file, MutableHandle<JSScript*> script);
+
+/**
  * Compile the UTF-8 contents of the file at the given path into a script.
  * (The path itself is in the system encoding, not [necessarily] UTF-8.)  If
  * the contents contain any malformed UTF-8, an error is reported.
  *
  * |script| is always set to the compiled script or to null in case of error.
  */
 extern JS_PUBLIC_API(bool)
 CompileUtf8Path(JSContext* cx, const ReadOnlyCompileOptions& options,
                 const char* filename, MutableHandle<JSScript*> script);
 
 extern JS_PUBLIC_API(bool)
 CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& options,
-                            SourceBufferHolder& srcBuf, MutableHandle<JSScript*> script);
+                            SourceText<char16_t>& srcBuf, MutableHandle<JSScript*> script);
 
 /**
  * Compile the given Latin-1 data for non-syntactic scope.
  *
  * There is no way to compile UTF-8 data for non-syntactic scope because no
  * user currently needs it.  Such way could be added in the future if it's ever
  * needed.
  */
@@ -222,17 +250,17 @@ CompileLatin1ForNonSyntacticScope(JSCont
  * scope chain used for the function will consist of With wrappers for those
  * objects, followed by the current global of the compartment cx is in.  This
  * global must not be explicitly included in the scope chain.
  */
 extern JS_PUBLIC_API(bool)
 CompileFunction(JSContext* cx, AutoVector<JSObject*>& envChain,
                 const ReadOnlyCompileOptions& options,
                 const char* name, unsigned nargs, const char* const* argnames,
-                SourceBufferHolder& srcBuf, MutableHandle<JSFunction*> fun);
+                SourceText<char16_t>& srcBuf, MutableHandle<JSFunction*> fun);
 
 /**
  * Same as above, but taking UTF-8 encoded const char* for the function body.
  */
 extern JS_PUBLIC_API(bool)
 CompileFunctionUtf8(JSContext* cx, AutoVector<JSObject*>& envChain,
                     const ReadOnlyCompileOptions& options,
                     const char* name, unsigned nargs, const char* const* argnames,
--- a/js/public/OffThreadScriptCompilation.h
+++ b/js/public/OffThreadScriptCompilation.h
@@ -22,17 +22,17 @@
 #include "js/GCVector.h" // JS::GCVector
 #include "js/Transcoding.h" // JS::TranscodeSource
 
 struct JSContext;
 class JSScript;
 
 namespace JS {
 
-class SourceBufferHolder;
+template<typename UnitT> class SourceText;
 
 } // namespace JS
 
 namespace JS {
 
 class OffThreadToken;
 
 using OffThreadCompileCallback = void (*)(OffThreadToken* token, void* callbackData);
@@ -56,28 +56,29 @@ CanDecodeOffThread(JSContext* cx, const 
  * - CancelOffThreadScript, to free the resources without creating a script.
  *
  * The characters passed in to CompileOffThread must remain live until the
  * callback is invoked, and the resulting script will be rooted until the call
  * to FinishOffThreadScript.
  */
 
 extern JS_PUBLIC_API(bool)
-CompileOffThread(JSContext* cx, const ReadOnlyCompileOptions& options, SourceBufferHolder& srcBuf,
-                 OffThreadCompileCallback callback, void* callbackData);
+CompileOffThread(JSContext* cx, const ReadOnlyCompileOptions& options,
+                 SourceText<char16_t>& srcBuf, OffThreadCompileCallback callback,
+                 void* callbackData);
 
 extern JS_PUBLIC_API(JSScript*)
 FinishOffThreadScript(JSContext* cx, OffThreadToken* token);
 
 extern JS_PUBLIC_API(void)
 CancelOffThreadScript(JSContext* cx, OffThreadToken* token);
 
 extern JS_PUBLIC_API(bool)
 CompileOffThreadModule(JSContext* cx, const ReadOnlyCompileOptions& options,
-                       SourceBufferHolder& srcBuf, OffThreadCompileCallback callback,
+                       SourceText<char16_t>& srcBuf, OffThreadCompileCallback callback,
                        void* callbackData);
 
 extern JS_PUBLIC_API(JSObject*)
 FinishOffThreadModule(JSContext* cx, OffThreadToken* token);
 
 extern JS_PUBLIC_API(void)
 CancelOffThreadModule(JSContext* cx, OffThreadToken* token);
 
rename from js/public/SourceBufferHolder.h
rename to js/public/SourceText.h
--- a/js/public/SourceBufferHolder.h
+++ b/js/public/SourceText.h
@@ -1,141 +1,282 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 /*
- * SourceBufferHolder groups buffer and length values and provides a way to
- * optionally pass ownership of the buffer to the JS engine without copying.
+ * SourceText encapsulates a count of char16_t (UTF-16) or Utf8Unit (UTF-8)
+ * code units (note: code *units*, not bytes or code points) and those code
+ * units ("source units").  (Latin-1 is not supported: all places where Latin-1
+ * must be compiled first convert to a supported encoding.)
+ *
+ * A SourceText either observes without owning, or takes ownership of, source
+ * units passed to |SourceText::init|.  Thus SourceText can be used to
+ * efficiently avoid copying.
  *
  * Rules for use:
  *
- *  1) The data array must be allocated with js_malloc() or js_realloc() if
- *     ownership is being granted to the SourceBufferHolder.
- *  2) If ownership is not given to the SourceBufferHolder, then the memory
- *     must be kept alive until the JS compilation is complete.
- *  3) Any code calling SourceBufferHolder::take() must guarantee to keep the
- *     memory alive until JS compilation completes.  Normally only the JS
- *     engine should be calling take().
+ *  1) The passed-in source units must be allocated with js_malloc(),
+ *     js_calloc(), or js_realloc() if |SourceText::init| is instructed to take
+ *     ownership of the source units.
+ *  2) If |SourceText::init| merely borrows the source units, the user must
+ *     keep them alive until associated JS compilation is complete.
+ *  3) Code that calls |SourceText::take{Chars,Units}()| must keep the source
+ *     units alive until JS compilation completes.  Normally only the JS engine
+ *     should call |SourceText::take{Chars,Units}()|.
+ *  4) Use the appropriate SourceText parameterization depending on the source
+ *     units encoding.
  *
  * Example use:
  *
  *    size_t length = 512;
  *    char16_t* chars = js_pod_malloc<char16_t>(length);
- *    JS::SourceBufferHolder srcBuf(chars, length, JS::SourceBufferHolder::GiveOwnership);
- *    JS::Compile(cx, options, srcBuf);
+ *    if (!chars) {
+ *        JS_ReportOutOfMemory(cx);
+ *        return false;
+ *    }
+ *    JS::SourceText<char16_t> srcBuf;
+ *    if (!srcBuf.init(cx, chars, length, JS::SourceOwnership::TakeOwnership)) {
+ *        return false;
+ *    }
+ *    JS::Rooted<JSScript*> script(cx);
+ *    if (!JS::Compile(cx, options, srcBuf, &script)) {
+ *        return false;
+ *    }
  */
 
-#ifndef js_SourceBufferHolder_h
-#define js_SourceBufferHolder_h
+#ifndef js_SourceText_h
+#define js_SourceText_h
 
 #include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_COLD, MOZ_IS_CLASS_INIT, MOZ_MUST_USE
+#include "mozilla/Likely.h" // MOZ_UNLIKELY
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
 
 #include <stddef.h> // size_t
+#include <stdint.h> // UINT32_MAX
+#include <type_traits> // std::conditional, std::is_same
 
-#include "js/Utility.h" // JS::UniqueTwoByteChars
+#include "js/UniquePtr.h" // js::UniquePtr
+#include "js/Utility.h" // JS::FreePolicy
 
 namespace JS {
 
-class SourceBufferHolder final
+namespace detail {
+
+MOZ_COLD extern JS_PUBLIC_API(void)
+ReportSourceTooLong(JSContext* cx);
+
+} // namespace detail
+
+enum class SourceOwnership
+{
+    Borrowed,
+    TakeOwnership,
+};
+
+template<typename Unit>
+class SourceText final
 {
   private:
-    const char16_t* data_;
-    size_t length_;
-    bool ownsChars_;
+    static_assert(std::is_same<Unit, mozilla::Utf8Unit>::value ||
+                  std::is_same<Unit, char16_t>::value,
+                  "Unit must be either char16_t or Utf8Unit for "
+                  "SourceText<Unit>");
+
+    /** |char16_t| or |Utf8Unit| source units of uncertain validity. */
+    const Unit* units_ = nullptr;
+
+    /** The length in code units of |units_|. */
+    uint32_t length_ = 0;
+
+    /**
+     * Whether this owns |units_| or merely observes source units owned by some
+     * other object.
+     */
+    bool ownsUnits_ = false;
+
+  public:
+    // A C++ character type that can represent the source units -- suitable for
+    // passing to C++ string functions.
+    using CharT =
+        typename std::conditional<std::is_same<Unit, char16_t>::value,
+                                  char16_t,
+                                  char>::type;
 
-  private:
-    void fixEmptyBuffer() {
-        // Ensure that null buffers properly return an unowned, empty,
-        // null-terminated string.
-        static const char16_t NullChar_ = 0;
-        if (!data_) {
-            data_ = &NullChar_;
-            length_ = 0;
-            ownsChars_ = false;
+  public:
+    /**
+     * Construct a SourceText.  It must be initialized using |init()| before it
+     * can be used as compilation source text.
+     */
+    SourceText() = default;
+
+    /**
+     * Construct a SourceText from contents extracted from |other|.  This
+     * SourceText will then act exactly as |other| would have acted, had it
+     * not been passed to this function.  |other| will return to its default-
+     * constructed state and must have |init()| called on it to use it.
+     */
+    SourceText(SourceText&& other)
+      : units_(other.units_),
+        length_(other.length_),
+        ownsUnits_(other.ownsUnits_)
+    {
+        other.units_ = nullptr;
+        other.length_ = 0;
+        other.ownsUnits_ = false;
+    }
+
+    ~SourceText() {
+        if (ownsUnits_) {
+            js_free(const_cast<Unit*>(units_));
         }
     }
 
-  public:
-    enum Ownership {
-      NoOwnership,
-      GiveOwnership
-    };
+    /**
+     * Initialize this with source unit data: |char16_t| for UTF-16 source
+     * units, or |Utf8Unit| for UTF-8 source units.
+     *
+     * If |ownership == TakeOwnership|, *this function* takes ownership of
+     * |units|, *even if* this function fails, and you MUST NOT free |units|
+     * yourself.  This single-owner-friendly approach reduces risk of leaks on
+     * failure.
+     *
+     * |units| may be null if |unitsLength == 0|; if so, this will silently be
+     * initialized using non-null, unowned units.
+     */
+    MOZ_IS_CLASS_INIT MOZ_MUST_USE bool
+    init(JSContext* cx, const Unit* units, size_t unitsLength, SourceOwnership ownership) {
+        MOZ_ASSERT_IF(units == nullptr, unitsLength == 0);
+
+        // Ideally we'd use |Unit| and not cast below, but the risk of a static
+        // initializer is too great.
+        static const CharT emptyString[] = { '\0' };
 
-    SourceBufferHolder(const char16_t* data, size_t dataLength, Ownership ownership)
-      : data_(data),
-        length_(dataLength),
-        ownsChars_(ownership == GiveOwnership)
-    {
-        fixEmptyBuffer();
-    }
+        // Initialize all fields *before* checking length.  This ensures that
+        // if |ownership == SourceOwnership::TakeOwnership|, |units| will be
+        // freed when |this|'s destructor is called.
+        if (units) {
+            units_ = units;
+            length_ = static_cast<uint32_t>(unitsLength);
+            ownsUnits_ = ownership == SourceOwnership::TakeOwnership;
+        } else {
+            units_ = reinterpret_cast<const Unit*>(emptyString);
+            length_ = 0;
+            ownsUnits_ = false;
+        }
 
-    SourceBufferHolder(UniqueTwoByteChars&& data, size_t dataLength)
-      : data_(data.release()),
-        length_(dataLength),
-        ownsChars_(true)
-    {
-        fixEmptyBuffer();
+        // IMPLEMENTATION DETAIL, DO NOT RELY ON: This limit is used so we can
+        // store offsets in |JSScript|s as |uint32_t|.  It could be lifted
+        // fairly easily if desired, as the compiler uses |size_t| internally.
+        if (MOZ_UNLIKELY(unitsLength > UINT32_MAX)) {
+            detail::ReportSourceTooLong(cx);
+            return false;
+        }
+
+        return true;
     }
 
-    SourceBufferHolder(SourceBufferHolder&& other)
-      : data_(other.data_),
-        length_(other.length_),
-        ownsChars_(other.ownsChars_)
+    /**
+     * Exactly identical to the |init()| overload above that accepts
+     * |const Unit*|, but instead takes character data: |const CharT*|.
+     *
+     * (We can't just write this to accept |const CharT*|, because then in the
+     * UTF-16 case this overload and the one above would be identical.  So we
+     * use SFINAE to expose the |CharT| overload only if it's different.)
+     */
+    template<typename Char,
+             typename =
+                typename std::enable_if<std::is_same<Char, CharT>::value &&
+                                        !std::is_same<Char, Unit>::value>::type>
+    MOZ_IS_CLASS_INIT MOZ_MUST_USE bool
+    init(JSContext* cx, const Char* chars, size_t charsLength, SourceOwnership ownership) {
+        return init(cx, reinterpret_cast<const Unit*>(chars), charsLength, ownership);
+    }
+
+    /**
+     * Initialize this using source units transferred out of |data|.
+     */
+    MOZ_MUST_USE bool init(JSContext* cx,
+                           js::UniquePtr<CharT[], JS::FreePolicy> data,
+                           size_t dataLength)
     {
-        other.data_ = nullptr;
-        other.length_ = 0;
-        other.ownsChars_ = false;
+        return init(cx, data.release(), dataLength, SourceOwnership::TakeOwnership);
     }
 
-    ~SourceBufferHolder() {
-        if (ownsChars_) {
-            js_free(const_cast<char16_t*>(data_));
-        }
+    /**
+     * Access the encapsulated data using a code unit type.
+     *
+     * This function is useful for code that wants to interact with source text
+     * as *code units*, not as string data.  This doesn't matter for UTF-16,
+     * but it's a crucial distinction for UTF-8.  When UTF-8 source text is
+     * encapsulated, |Unit| being |mozilla::Utf8Unit| unambiguously indicates
+     * that the code units are UTF-8.  In contrast |const char*| returned by
+     * |get()| below could hold UTF-8 (or its ASCII subset) or Latin-1 or (in
+     * particularly cursed embeddings) EBCDIC or some other legacy character
+     * set.  Prefer this function to |get()| wherever possible.
+     */
+    const Unit* units() const { return units_; }
+
+    /**
+     * Access the encapsulated data using a character type.
+     *
+     * This function is useful for interactions with character-centric actions
+     * like interacting with UniqueChars/UniqueTwoByteChars or printing out
+     * text in a debugger, that only work with |CharT|.  But as |CharT| loses
+     * encoding specificity when UTF-8 source text is encapsulated, prefer
+     * |units()| to this function.
+     */
+    const CharT* get() const { return reinterpret_cast<const CharT*>(units_); }
+
+    /**
+     * Returns true if this owns the source units and will free them on
+     * destruction.  If true, it is legal to call |take{Chars,Units}()|.
+     */
+    bool ownsUnits() const {
+        return ownsUnits_;
     }
 
-    /** Access the underlying source buffer without affecting ownership. */
-    const char16_t* get() const {
-        return data_;
-    }
-
-    /** Length of the source buffer in char16_t code units (not bytes). */
-    size_t length() const {
+    /**
+     * Count of the underlying source units -- code units, not bytes or code
+     * points -- in this.
+     */
+    uint32_t length() const {
         return length_;
     }
 
     /**
-     * Returns true if the SourceBufferHolder owns the buffer and will free it
-     * upon destruction.  If true, it is legal to call take().
-     */
-    bool ownsChars() const {
-        return ownsChars_;
-    }
-
-    /**
-     * Retrieve and take ownership of the underlying data buffer.  The caller
+     * Retrieve and take ownership of the underlying source units.  The caller
      * is now responsible for calling js_free() on the returned value, *but
      * only after JS script compilation has completed*.
      *
-     * After the buffer has been taken the SourceBufferHolder functions as if
-     * it had been constructed on an unowned buffer;  get() and length() still
-     * work.  In order for this to be safe the taken buffer must be kept alive
-     * until after JS script compilation completes as noted above.
+     * After underlying source units have been taken, this will continue to
+     * refer to the same data -- it just won't own the data.  get() and
+     * length() will return the same values, but ownsUnits() will be false.
+     * The taken source units must be kept alive until after JS script
+     * compilation completes, as noted above, for this to be safe.
      *
-     * It's the caller's responsibility to check ownsChars() before taking the
-     * buffer.  Taking and then free'ing an unowned buffer will have dire
-     * consequences.
+     * The caller must check ownsUnits() before calling takeUnits().  Taking
+     * and then free'ing an unowned buffer will have dire consequences.
      */
-    char16_t* take() {
-        MOZ_ASSERT(ownsChars_);
-        ownsChars_ = false;
-        return const_cast<char16_t*>(data_);
+    Unit* takeUnits() {
+        MOZ_ASSERT(ownsUnits_);
+        ownsUnits_ = false;
+        return const_cast<Unit*>(units_);
+    }
+
+    /**
+     * Akin to |takeUnits()| in all respects, but returns characters rather
+     * than units.
+     */
+    CharT* takeChars() {
+        return reinterpret_cast<CharT*>(takeUnits());
     }
 
   private:
-    SourceBufferHolder(SourceBufferHolder&) = delete;
-    SourceBufferHolder& operator=(SourceBufferHolder&) = delete;
+    SourceText(const SourceText&) = delete;
+    void operator=(const SourceText&) = delete;
 };
 
 } // namespace JS
 
-#endif /* js_SourceBufferHolder_h */
+#endif /* js_SourceText_h */
--- a/js/rust/build.rs
+++ b/js/rust/build.rs
@@ -222,17 +222,17 @@ const WHITELIST_TYPES: &'static [&'stati
     "JS::Rooted",
     "JS::RootedObject",
     "JS::RootingContext",
     "JS::RootKind",
     "js::Scalar::Type",
     "JS::ServoSizes",
     "js::shadow::Object",
     "js::shadow::ObjectGroup",
-    "JS::SourceBufferHolder",
+    "JS::SourceText",
     "js::StackFormat",
     "JSStructuredCloneCallbacks",
     "JS::Symbol",
     "JS::SymbolCode",
     "JS::TraceKind",
     "JS::TransferableOwnership",
     "JS::Value",
     "JS::WarningReporter",
--- a/js/rust/etc/wrapper.hpp
+++ b/js/rust/etc/wrapper.hpp
@@ -13,16 +13,16 @@ typedef uint32_t HashNumber;
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/CompilationAndEvaluation.h"
 #include "js/CompileOptions.h"
 #include "js/Conversions.h"
 #include "js/Initialization.h"
 #include "js/MemoryMetrics.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "js/StructuredClone.h"
 
 // Replacements for types that are too difficult for rust-bindgen.
 
 /// <div rustbindgen replaces="JS::detail::MaybeWrapped" />
 template <typename T>
 using replaces_MaybeWrapped = T;
--- a/js/rust/src/rust.rs
+++ b/js/rust/src/rust.rs
@@ -227,20 +227,21 @@ impl Runtime {
         } else {
             (script_utf16.as_ptr(), script_utf16.len() as c_uint)
         };
         assert!(!ptr.is_null());
         unsafe {
             let _ar = AutoRealm::with_obj(self.cx(), glob.get());
             let options = CompileOptionsWrapper::new(self.cx(), filename_cstr.as_ptr(), line_num);
 
-            let mut srcBuf = JS::SourceBufferHolder {
-                data_: ptr,
+            let mut srcBuf = JS::SourceText {
+                units_: ptr,
                 length_: len as _,
-                ownsChars_: false
+                ownsUnits_: false,
+                _phantom_0: marker::PhantomData
             };
             if !JS::Evaluate(self.cx(), options.ptr, &mut srcBuf, rval) {
                 debug!("...err!");
                 panic::maybe_resume_unwind();
                 Err(())
             } else {
                 // we could return the script result but then we'd have
                 // to root it and so forth and, really, who cares?
--- a/js/src/NamespaceImports.h
+++ b/js/src/NamespaceImports.h
@@ -33,17 +33,17 @@ class UTF8CharsZ;
 using AutoValueVector = AutoVector<Value>;
 using AutoIdVector = AutoVector<jsid>;
 using AutoObjectVector = AutoVector<JSObject*>;
 
 using ValueVector = JS::GCVector<JS::Value>;
 using IdVector = JS::GCVector<jsid>;
 using ScriptVector = JS::GCVector<JSScript*>;
 
-class SourceBufferHolder;
+template<typename UnitT> class SourceText;
 
 class HandleValueArray;
 
 class ObjectOpResult;
 class PropertyResult;
 
 enum class SymbolCode: uint32_t;
 
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -4,19 +4,19 @@
  * 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 "builtin/Eval.h"
 
 #include "mozilla/HashFunctions.h"
 #include "mozilla/Range.h"
 
-#include "frontend/BytecodeCompiler.h"
+#include "frontend/BytecodeCompilation.h"
 #include "gc/HashUtil.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "js/StableStringChars.h"
 #include "vm/Debugger.h"
 #include "vm/GlobalObject.h"
 #include "vm/JSContext.h"
 #include "vm/JSONParser.h"
 
 #include "vm/Interpreter-inl.h"
 
@@ -24,17 +24,18 @@ using namespace js;
 
 using mozilla::AddToHash;
 using mozilla::HashString;
 using mozilla::RangedPtr;
 
 using JS::AutoCheckCannotGC;
 using JS::AutoStableStringChars;
 using JS::CompileOptions;
-using JS::SourceBufferHolder;
+using JS::SourceOwnership;
+using JS::SourceText;
 
 // We should be able to assert this for *any* fp->environmentChain().
 static void
 AssertInnerizedEnvironmentChain(JSContext* cx, JSObject& env)
 {
 #ifdef DEBUG
     RootedObject obj(cx);
     for (obj = &env; obj; obj = obj->enclosingEnvironment()) {
@@ -312,22 +313,28 @@ EvalKernel(JSContext* cx, HandleValue v,
             options.setIntroductionType("eval");
         }
 
         AutoStableStringChars linearChars(cx);
         if (!linearChars.initTwoByte(cx, linearStr)) {
             return false;
         }
 
+        SourceText<char16_t> srcBuf;
+
         const char16_t* chars = linearChars.twoByteRange().begin().get();
-        SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
-                                                  ? SourceBufferHolder::GiveOwnership
-                                                  : SourceBufferHolder::NoOwnership;
-        SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
-        JSScript* compiled = frontend::CompileEvalScript(cx, env, enclosing, options, srcBuf);
+        SourceOwnership ownership = linearChars.maybeGiveOwnershipToCaller()
+                                    ? SourceOwnership::TakeOwnership
+                                    : SourceOwnership::Borrowed;
+        if (!srcBuf.init(cx, chars, linearStr->length(), ownership)) {
+            return false;
+        }
+
+        frontend::EvalScriptInfo info(cx, options, env, enclosing);
+        JSScript* compiled = frontend::CompileEvalScript(info, srcBuf);
         if (!compiled) {
             return false;
         }
 
         esg.setNewScript(compiled);
     }
 
     // Look up the newTarget from the frame iterator.
@@ -396,22 +403,28 @@ js::DirectEvalStringFromIon(JSContext* c
             options.setIntroductionType("eval");
         }
 
         AutoStableStringChars linearChars(cx);
         if (!linearChars.initTwoByte(cx, linearStr)) {
             return false;
         }
 
+        SourceText<char16_t> srcBuf;
+
         const char16_t* chars = linearChars.twoByteRange().begin().get();
-        SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
-                                                  ? SourceBufferHolder::GiveOwnership
-                                                  : SourceBufferHolder::NoOwnership;
-        SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
-        JSScript* compiled = frontend::CompileEvalScript(cx, env, enclosing, options, srcBuf);
+        SourceOwnership ownership = linearChars.maybeGiveOwnershipToCaller()
+                                    ? SourceOwnership::TakeOwnership
+                                    : SourceOwnership::Borrowed;
+        if (!srcBuf.init(cx, chars, linearStr->length(), ownership)) {
+            return false;
+        }
+
+        frontend::EvalScriptInfo info(cx, options, env, enclosing);
+        JSScript* compiled = frontend::CompileEvalScript(info, srcBuf);
         if (!compiled) {
             return false;
         }
 
         esg.setNewScript(compiled);
     }
 
     return ExecuteKernel(cx, esg.script(), *env, newTargetValue,
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -1282,20 +1282,20 @@ GlobalObject::initModuleProto(JSContext*
 #undef DEFINE_GETTER_FUNCTIONS
 #undef DEFINE_STRING_ACCESSOR_METHOD
 #undef DEFINE_ARRAY_SLOT_ACCESSOR
 
 ///////////////////////////////////////////////////////////////////////////
 // ModuleBuilder
 
 ModuleBuilder::ModuleBuilder(JSContext* cx, HandleModuleObject module,
-                             const frontend::TokenStreamAnyChars& tokenStream)
+                             const frontend::EitherParser& eitherParser)
   : cx_(cx),
     module_(cx, module),
-    tokenStream_(tokenStream),
+    eitherParser_(eitherParser),
     requestedModuleSpecifiers_(cx, AtomSet(cx)),
     requestedModules_(cx, RequestedModuleVector(cx)),
     importEntries_(cx, ImportEntryMap(cx)),
     exportEntries_(cx, ExportEntryVector(cx)),
     exportNames_(cx, AtomSet(cx)),
     localExportEntries_(cx, ExportEntryVector(cx)),
     indirectExportEntries_(cx, ExportEntryVector(cx)),
     starExportEntries_(cx, ExportEntryVector(cx))
@@ -1413,17 +1413,17 @@ ModuleBuilder::processImport(frontend::B
 
         NameNode* localNameNode = &spec->right()->as<NameNode>();
 
         importName = importNameNode->atom();
         localName = localNameNode->atom();
 
         uint32_t line;
         uint32_t column;
-        tokenStream_.lineAndColumnAt(importNameNode->pn_pos.begin, &line, &column);
+        eitherParser_.computeLineAndColumn(importNameNode->pn_pos.begin, &line, &column);
 
         RootedImportEntryObject importEntry(cx_);
         importEntry = ImportEntryObject::create(cx_, module, importName, localName, line, column);
         if (!importEntry || !appendImportEntryObject(importEntry)) {
             return false;
         }
     }
 
@@ -1681,32 +1681,32 @@ ModuleBuilder::hasExportedName(JSAtom* n
 
 bool
 ModuleBuilder::appendExportEntry(HandleAtom exportName, HandleAtom localName,
                                  frontend::ParseNode* node)
 {
     uint32_t line = 0;
     uint32_t column = 0;
     if (node) {
-        tokenStream_.lineAndColumnAt(node->pn_pos.begin, &line, &column);
+        eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column);
     }
 
     Rooted<ExportEntryObject*> exportEntry(cx_);
     exportEntry = ExportEntryObject::create(cx_, exportName, nullptr, nullptr, localName,
                                             line, column);
     return exportEntry && appendExportEntryObject(exportEntry);
 }
 
 bool
 ModuleBuilder::appendExportFromEntry(HandleAtom exportName, HandleAtom moduleRequest,
                                      HandleAtom importName, frontend::ParseNode* node)
 {
     uint32_t line;
     uint32_t column;
-    tokenStream_.lineAndColumnAt(node->pn_pos.begin, &line, &column);
+    eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column);
 
     Rooted<ExportEntryObject*> exportEntry(cx_);
     exportEntry = ExportEntryObject::create(cx_, exportName, moduleRequest, importName, nullptr,
                                             line, column);
     return exportEntry && appendExportEntryObject(exportEntry);
 }
 
 bool
@@ -1724,17 +1724,17 @@ bool
 ModuleBuilder::maybeAppendRequestedModule(HandleAtom specifier, frontend::ParseNode* node)
 {
     if (requestedModuleSpecifiers_.has(specifier)) {
         return true;
     }
 
     uint32_t line;
     uint32_t column;
-    tokenStream_.lineAndColumnAt(node->pn_pos.begin, &line, &column);
+    eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column);
 
     JSContext* cx = cx_;
     RootedRequestedModuleObject req(cx, RequestedModuleObject::create(cx, specifier, line, column));
     if (!req) {
         return false;
     }
 
     return FreezeObject(cx, req) &&
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -7,16 +7,17 @@
 #ifndef builtin_ModuleObject_h
 #define builtin_ModuleObject_h
 
 #include "mozilla/Maybe.h"
 
 #include "jsapi.h"
 
 #include "builtin/SelfHostingDefines.h"
+#include "frontend/EitherParser.h"
 #include "gc/Zone.h"
 #include "js/GCVector.h"
 #include "js/Id.h"
 #include "js/UniquePtr.h"
 #include "vm/JSAtom.h"
 #include "vm/NativeObject.h"
 #include "vm/ProxyObject.h"
 
@@ -24,17 +25,16 @@ namespace js {
 
 class ModuleEnvironmentObject;
 class ModuleObject;
 
 namespace frontend {
 class BinaryNode;
 class ListNode;
 class ParseNode;
-class TokenStreamAnyChars;
 } /* namespace frontend */
 
 typedef Rooted<ModuleObject*> RootedModuleObject;
 typedef Handle<ModuleObject*> HandleModuleObject;
 typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject;
 typedef Handle<ModuleEnvironmentObject*> HandleModuleEnvironmentObject;
 
 class ImportEntryObject : public NativeObject
@@ -350,19 +350,24 @@ class ModuleObject : public NativeObject
     bool hasImportBindings() const;
     FunctionDeclarationVector* functionDeclarations();
 };
 
 // Process a module's parse tree to collate the import and export data used when
 // creating a ModuleObject.
 class MOZ_STACK_CLASS ModuleBuilder
 {
+    explicit ModuleBuilder(JSContext* cx, HandleModuleObject module,
+                           const frontend::EitherParser& eitherParser);
+
   public:
-    explicit ModuleBuilder(JSContext* cx, HandleModuleObject module,
-                           const frontend::TokenStreamAnyChars& tokenStream);
+    template<class Parser>
+    explicit ModuleBuilder(JSContext* cx, HandleModuleObject module, Parser* parser)
+      : ModuleBuilder(cx, module, frontend::EitherParser(parser))
+    {}
 
     bool processImport(frontend::BinaryNode* importNode);
     bool processExport(frontend::ParseNode* exportNode);
     bool processExportFrom(frontend::BinaryNode* exportNode);
 
     bool hasExportedName(JSAtom* name) const;
 
     using ExportEntryVector = GCVector<ExportEntryObject*>;
@@ -379,17 +384,17 @@ class MOZ_STACK_CLASS ModuleBuilder
     using ImportEntryMap = GCHashMap<JSAtom*, ImportEntryObject*>;
     using RootedExportEntryVector = JS::Rooted<ExportEntryVector>;
     using RootedRequestedModuleVector = JS::Rooted<RequestedModuleVector>;
     using RootedAtomSet = JS::Rooted<AtomSet>;
     using RootedImportEntryMap = JS::Rooted<ImportEntryMap>;
 
     JSContext* cx_;
     RootedModuleObject module_;
-    const frontend::TokenStreamAnyChars& tokenStream_;
+    frontend::EitherParser eitherParser_;
     RootedAtomSet requestedModuleSpecifiers_;
     RootedRequestedModuleVector requestedModules_;
     RootedImportEntryMap importEntries_;
     RootedExportEntryVector exportEntries_;
     RootedAtomSet exportNames_;
     RootedExportEntryVector localExportEntries_;
     RootedExportEntryVector indirectExportEntries_;
     RootedExportEntryVector starExportEntries_;
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -11,19 +11,19 @@
 #include "mozilla/Move.h"
 
 #include <stdlib.h>
 
 #include "jspubtd.h"
 
 #include "builtin/Array.h"
 #include "builtin/Reflect.h"
+#include "frontend/ModuleSharedContext.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 "vm/JSObject-inl.h"
 
@@ -233,27 +233,32 @@ enum class GeneratorStyle
  *
  * Bug 569487: generalize builder interface
  */
 class NodeBuilder
 {
     typedef AutoValueArray<AST_LIMIT> CallbackArray;
 
     JSContext*  cx;
-    TokenStreamAnyChars* tokenStream;
+    frontend::Parser<frontend::FullParseHandler, char16_t>* parser;
     bool        saveLoc;               /* save source location information?     */
     char const* src;                  /* source filename or null               */
     RootedValue srcval;                /* source filename JS value or null      */
     CallbackArray callbacks;           /* user-specified callbacks              */
     RootedValue userv;                 /* user-specified builder object or null */
 
   public:
     NodeBuilder(JSContext* c, bool l, char const* s)
-      : cx(c), tokenStream(nullptr), saveLoc(l), src(s), srcval(c), callbacks(cx),
-          userv(c)
+      : cx(c),
+        parser(nullptr),
+        saveLoc(l),
+        src(s),
+        srcval(c),
+        callbacks(cx),
+        userv(c)
     {}
 
     MOZ_MUST_USE bool init(HandleObject userobj = nullptr) {
         if (src) {
             if (!atomValue(src, &srcval)) {
                 return false;
             }
         } else {
@@ -294,18 +299,18 @@ class NodeBuilder
             }
 
             callbacks[i].set(funv);
         }
 
         return true;
     }
 
-    void setTokenStream(TokenStreamAnyChars* ts) {
-        tokenStream = ts;
+    void setParser(frontend::Parser<frontend::FullParseHandler, char16_t>* p) {
+        parser = p;
     }
 
   private:
     MOZ_MUST_USE bool callbackHelper(HandleValue fun, const InvokeArgs& args, size_t i,
                                      TokenPos* pos, MutableHandleValue dst)
     {
         // The end of the implementation of callback(). All arguments except
         // loc have already been stored in range [0, i).
@@ -701,18 +706,18 @@ NodeBuilder::newNodeLoc(TokenPos* pos, M
     if (!newObject(&loc)) {
         return false;
     }
 
     dst.setObject(*loc);
 
     uint32_t startLineNum, startColumnIndex;
     uint32_t endLineNum, endColumnIndex;
-    tokenStream->srcCoords.lineNumAndColumnIndex(pos->begin, &startLineNum, &startColumnIndex);
-    tokenStream->srcCoords.lineNumAndColumnIndex(pos->end, &endLineNum, &endColumnIndex);
+    parser->anyChars.srcCoords.lineNumAndColumnIndex(pos->begin, &startLineNum, &startColumnIndex);
+    parser->anyChars.srcCoords.lineNumAndColumnIndex(pos->end, &endLineNum, &endColumnIndex);
 
     if (!newObject(&to)) {
         return false;
     }
     val.setObject(*to);
     if (!defineProperty(loc, "start", val)) {
         return false;
     }
@@ -1828,19 +1833,19 @@ class ASTSerializer
         , lineno(ln)
 #endif
     {}
 
     bool init(HandleObject userobj) {
         return builder.init(userobj);
     }
 
-    void setParser(Parser<FullParseHandler, char16_t>* p) {
+    void setParser(frontend::Parser<frontend::FullParseHandler, char16_t>* p) {
         parser = p;
-        builder.setTokenStream(&p->anyChars);
+        builder.setParser(p);
     }
 
     bool program(ListNode* node, MutableHandleValue dst);
 };
 
 } /* anonymous namespace */
 
 AssignmentOperator
@@ -2012,17 +2017,22 @@ ASTSerializer::blockStatement(ListNode* 
     NodeVector stmts(cx);
     return statements(node, stmts) &&
            builder.blockStatement(stmts, &node->pn_pos, dst);
 }
 
 bool
 ASTSerializer::program(ListNode* node, MutableHandleValue dst)
 {
-    MOZ_ASSERT(parser->anyChars.srcCoords.lineNum(node->pn_pos.begin) == lineno);
+#ifdef DEBUG
+    {
+        const auto& srcCoords = parser->anyChars.srcCoords;
+        MOZ_ASSERT(srcCoords.lineNum(node->pn_pos.begin) == lineno);
+    }
+#endif
 
     NodeVector stmts(cx);
     return statements(node, stmts) &&
            builder.program(stmts, &node->pn_pos, dst);
 }
 
 bool
 ASTSerializer::sourceElement(ParseNode* pn, MutableHandleValue dst)
@@ -3813,17 +3823,17 @@ reflect_parse(JSContext* cx, uint32_t ar
             return false;
         }
 
         Rooted<ModuleObject*> module(cx, ModuleObject::create(cx));
         if (!module) {
             return false;
         }
 
-        ModuleBuilder builder(cx, module, parser.anyChars);
+        ModuleBuilder builder(cx, module, &parser);
 
         ModuleSharedContext modulesc(cx, module, &cx->global()->emptyGlobalScope(), builder);
         pn = parser.moduleBody(&modulesc);
         if (!pn) {
             return false;
         }
 
         MOZ_ASSERT(pn->getKind() == ParseNodeKind::Module);
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -42,17 +42,17 @@
 #include "jit/InlinableNatives.h"
 #include "jit/JitRealm.h"
 #include "js/CharacterEncoding.h"
 #include "js/CompilationAndEvaluation.h"
 #include "js/CompileOptions.h"
 #include "js/Debug.h"
 #include "js/HashTable.h"
 #include "js/LocaleSensitive.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "js/StableStringChars.h"
 #include "js/StructuredClone.h"
 #include "js/UbiNode.h"
 #include "js/UbiNodeBreadthFirst.h"
 #include "js/UbiNodeShortestPaths.h"
 #include "js/UniquePtr.h"
 #include "js/Vector.h"
 #include "js/Wrapper.h"
@@ -88,17 +88,18 @@
 
 using namespace js;
 
 using mozilla::ArrayLength;
 using mozilla::Maybe;
 
 using JS::AutoStableStringChars;
 using JS::CompileOptions;
-using JS::SourceBufferHolder;
+using JS::SourceOwnership;
+using JS::SourceText;
 
 // If fuzzingSafe is set, remove functionality that could cause problems with
 // fuzzers. Set this via the environment variable MOZ_FUZZING_SAFE.
 mozilla::Atomic<bool> fuzzingSafe(false);
 
 // If disableOOMFunctions is set, disable functionality that causes artificial
 // OOM conditions.
 static mozilla::Atomic<bool> disableOOMFunctions(false);
@@ -4028,17 +4029,21 @@ EvalReturningScope(JSContext* cx, unsign
     unsigned lineno;
 
     JS::DescribeScriptedCaller(cx, &filename, &lineno);
 
     JS::CompileOptions options(cx);
     options.setFileAndLine(filename.get(), lineno);
     options.setNoScriptRval(true);
 
-    JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership);
+    JS::SourceText<char16_t> srcBuf;
+    if (!srcBuf.init(cx, src, srclen, SourceOwnership::Borrowed)) {
+        return false;
+    }
+
     RootedScript script(cx);
     if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) {
         return false;
     }
 
     if (global) {
         global = CheckedUnwrap(global);
         if (!global) {
@@ -4129,17 +4134,21 @@ ShellCloneAndExecuteScript(JSContext* cx
     unsigned lineno;
 
     JS::DescribeScriptedCaller(cx, &filename, &lineno);
 
     JS::CompileOptions options(cx);
     options.setFileAndLine(filename.get(), lineno);
     options.setNoScriptRval(true);
 
-    JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership);
+    JS::SourceText<char16_t> srcBuf;
+    if (!srcBuf.init(cx, src, srclen, SourceOwnership::Borrowed)) {
+        return false;
+    }
+
     RootedScript script(cx);
     if (!JS::Compile(cx, options, srcBuf, &script)) {
         return false;
     }
 
     global = CheckedUnwrap(global);
     if (!global) {
         JS_ReportErrorASCII(cx, "Permission denied to access global");
@@ -5553,19 +5562,23 @@ js::TestingFunctionArgumentToScript(JSCo
         }
         size_t len = GetLinearStringLength(linearStr);
         AutoStableStringChars linearChars(cx);
         if (!linearChars.initTwoByte(cx, linearStr)) {
             return nullptr;
         }
         const char16_t* chars = linearChars.twoByteRange().begin().get();
 
+        SourceText<char16_t> source;
+        if (!source.init(cx, chars, len, SourceOwnership::Borrowed)) {
+            return nullptr;
+        }
+
         RootedScript script(cx);
         CompileOptions options(cx);
-        SourceBufferHolder source(chars, len, SourceBufferHolder::NoOwnership);
         if (!JS::Compile(cx, options, source, &script)) {
             return nullptr;
         }
         return script;
     }
 
     RootedFunction fun(cx, JS_ValueToFunction(cx, v));
     if (!fun) {
copy from js/src/frontend/BytecodeCompiler.cpp
copy to js/src/frontend/BytecodeCompilation.h
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompilation.h
@@ -1,1133 +1,189 @@
 /* -*- 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/BytecodeCompiler.h"
+#ifndef frontend_BytecodeCompilation_h
+#define frontend_BytecodeCompilation_h
 
-#include "mozilla/IntegerPrintfMacros.h"
-#include "mozilla/Maybe.h"
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS
+#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Nothing
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include <stddef.h> // size_t
+#include <stdint.h> // uint32_t
 
-#include "builtin/ModuleObject.h"
-#if defined(JS_BUILD_BINAST)
-# include "frontend/BinSource.h"
-#endif // JS_BUILD_BINAST
-#include "frontend/BytecodeEmitter.h"
-#include "frontend/ErrorReporter.h"
-#include "frontend/FoldConstants.h"
-#include "frontend/Parser.h"
-#include "js/SourceBufferHolder.h"
-#include "vm/GlobalObject.h"
-#include "vm/JSContext.h"
-#include "vm/JSScript.h"
-#include "vm/TraceLogging.h"
-#include "wasm/AsmJS.h"
+#include "frontend/EitherParser.h" // js::frontend::EitherParser
+#include "frontend/ParseContext.h" // js::frontend::UsedNameTracker
+#include "frontend/SharedContext.h" // js::frontend::Directives, js::frontend::{,Eval,Global}SharedContext
+#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions
+#include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted
+#include "js/SourceText.h" // JS::SourceText
+#include "vm/JSContext.h" // js::AutoKeepAtoms
+#include "vm/JSScript.h" // js::{FunctionAsync,Generator}Kind, js::LazyScript, JSScript, js::ScriptSource, js::ScriptSourceObject
+#include "vm/Scope.h" // js::ScopeKind
 
-#include "vm/EnvironmentObject-inl.h"
-#include "vm/GeckoProfiler-inl.h"
-#include "vm/JSContext-inl.h"
+class JSFunction;
+class JSObject;
 
-using namespace js;
-using namespace js::frontend;
+namespace js {
 
-using mozilla::Maybe;
-using mozilla::Nothing;
+namespace frontend {
 
-using JS::CompileOptions;
-using JS::ReadOnlyCompileOptions;
-using JS::SourceBufferHolder;
+template<typename Unit> class SourceAwareCompiler;
+template<typename Unit> class ScriptCompiler;
+template<typename Unit> class ModuleCompiler;
+template<typename Unit> class StandaloneFunctionCompiler;
 
 // The BytecodeCompiler class contains resources common to compiling scripts and
 // function bodies.
 class MOZ_STACK_CLASS BytecodeCompiler
 {
   protected:
     AutoKeepAtoms keepAtoms;
 
     JSContext* cx;
-    const ReadOnlyCompileOptions& options;
-    SourceBufferHolder& sourceBuffer;
+    const JS::ReadOnlyCompileOptions& options;
 
-    RootedScriptSourceObject sourceObject;
-    ScriptSource* scriptSource;
+    JS::Rooted<ScriptSourceObject*> sourceObject;
+    ScriptSource* scriptSource = nullptr;
 
-    Maybe<UsedNameTracker> usedNames;
-    Maybe<Parser<SyntaxParseHandler, char16_t>> syntaxParser;
-    Maybe<Parser<FullParseHandler, char16_t>> parser;
+    mozilla::Maybe<UsedNameTracker> usedNames;
 
     Directives directives;
 
-    RootedScript script;
+    JS::Rooted<JSScript*> script;
+
+  protected:
+    BytecodeCompiler(JSContext* cx, const JS::ReadOnlyCompileOptions& options);
+
+    template<typename Unit> friend class SourceAwareCompiler;
+    template<typename Unit> friend class ScriptCompiler;
+    template<typename Unit> friend class ModuleCompiler;
+    template<typename Unit> friend class StandaloneFunctionCompiler;
 
   public:
-    // Construct an object passing mandatory arguments.
-    BytecodeCompiler(JSContext* cx,
-                     const ReadOnlyCompileOptions& options,
-                     SourceBufferHolder& sourceBuffer);
+    JSContext* context() const {
+        return cx;
+    }
 
     ScriptSourceObject* sourceObjectPtr() const {
         return sourceObject.get();
     }
 
-    // Call this before calling compile{Global,Eval}Script.
-    MOZ_MUST_USE bool prepareScriptParse() {
-        return createSourceAndParser(ParseGoal::Script) && createCompleteScript();
-    }
-
-    JSScript* compileGlobalScript(ScopeKind scopeKind);
-    JSScript* compileEvalScript(HandleObject environment, HandleScope enclosingScope);
-
-    // Call this before calling compileModule.
-    MOZ_MUST_USE bool prepareModuleParse() {
-        return createSourceAndParser(ParseGoal::Module) && createCompleteScript();
-    }
-
-    ModuleObject* compileModule(HandleScope enclosingScope);
-
-    // Call this before calling parseStandaloneFunction.
-    MOZ_MUST_USE bool prepareStandaloneFunctionParse(const Maybe<uint32_t>& parameterListEnd) {
-        return createSourceAndParser(ParseGoal::Script, parameterListEnd);
-    }
-
-    // Call this before calling compileStandaloneFunction.
-    CodeNode* parseStandaloneFunction(MutableHandleFunction fun, GeneratorKind generatorKind,
-                                      FunctionAsyncKind asyncKind,
-                                      const Maybe<uint32_t>& parameterListEnd,
-                                      HandleScope enclosingScope);
-
-    bool compileStandaloneFunction(CodeNode* parsedFunction, MutableHandleFunction fun);
-
-  private:
-    void assertSourceAndParserCreated() const {
+  protected:
+    void assertSourceCreated() const {
         MOZ_ASSERT(sourceObject != nullptr);
         MOZ_ASSERT(scriptSource != nullptr);
-        MOZ_ASSERT(usedNames.isSome());
-        MOZ_ASSERT(parser.isSome());
-    }
-
-    void assertSourceParserAndScriptCreated() const {
-        assertSourceAndParserCreated();
-        MOZ_ASSERT(script != nullptr);
     }
 
-    JSScript* compileScript(HandleObject environment, SharedContext* sc);
-    bool checkLength();
-    bool createScriptSource(const Maybe<uint32_t>& parameterListEnd);
-    bool canLazilyParse();
-    bool createParser(ParseGoal goal);
-    bool createSourceAndParser(ParseGoal goal,
-                               const Maybe<uint32_t>& parameterListEnd = Nothing());
-
-    // This assumes the created script's offsets in the source used to parse it
-    // are the same as are used to compute its Function.prototype.toString()
-    // value.
-    bool createCompleteScript();
-
-    // This uses explicitly-provided toString offsets as the created script's
-    // offsets in the source.
-    bool createScript(uint32_t toStringStart, uint32_t toStringEnd);
-
-    using TokenStreamPosition = frontend::TokenStreamPosition<char16_t>;
-
-    bool emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext);
-    bool handleParseFailure(const Directives& newDirectives, TokenStreamPosition& startPosition);
-    bool deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment);
-};
-
-AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id,
-                                           const ErrorReporter& errorReporter)
-#ifdef JS_TRACE_LOGGING
-  : logger_(TraceLoggerForCurrentThread(cx))
-{
-    // If the tokenizer hasn't yet gotten any tokens, use the line and column
-    // numbers from CompileOptions.
-    uint32_t line, column;
-    if (errorReporter.hasTokenizationStarted()) {
-        line = errorReporter.options().lineno;
-        column = errorReporter.options().column;
-    } else {
-        errorReporter.currentLineAndColumn(&line, &column);
-    }
-    frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), line, column);
-    frontendLog_.emplace(logger_, *frontendEvent_);
-    typeLog_.emplace(logger_, id);
-}
-#else
-{ }
-#endif
-
-AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id,
-                                           const ErrorReporter& errorReporter,
-                                           FunctionBox* funbox)
-#ifdef JS_TRACE_LOGGING
-  : logger_(TraceLoggerForCurrentThread(cx))
-{
-    frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(),
-                           funbox->startLine, funbox->startColumn);
-    frontendLog_.emplace(logger_, *frontendEvent_);
-    typeLog_.emplace(logger_, id);
-}
-#else
-{ }
-#endif
-
-AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id,
-                                           const ErrorReporter& errorReporter, ParseNode* pn)
-#ifdef JS_TRACE_LOGGING
-  : logger_(TraceLoggerForCurrentThread(cx))
-{
-    uint32_t line, column;
-    errorReporter.lineAndColumnAt(pn->pn_pos.begin, &line, &column);
-    frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), line, column);
-    frontendLog_.emplace(logger_, *frontendEvent_);
-    typeLog_.emplace(logger_, id);
-}
-#else
-{ }
-#endif
-
-BytecodeCompiler::BytecodeCompiler(JSContext* cx,
-                                   const ReadOnlyCompileOptions& options,
-                                   SourceBufferHolder& sourceBuffer)
-  : keepAtoms(cx),
-    cx(cx),
-    options(options),
-    sourceBuffer(sourceBuffer),
-    sourceObject(cx),
-    scriptSource(nullptr),
-    directives(options.strictOption),
-    script(cx)
-{
-    MOZ_ASSERT(sourceBuffer.get());
-}
+    MOZ_MUST_USE bool createScriptSource(const mozilla::Maybe<uint32_t>& parameterListEnd);
 
-bool
-BytecodeCompiler::checkLength()
-{
-    // Note this limit is simply so we can store sourceStart and sourceEnd in
-    // JSScript as 32-bits. It could be lifted fairly easily, since the compiler
-    // is using size_t internally already.
-    if (sourceBuffer.length() > UINT32_MAX) {
-        if (!cx->helperThread()) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                                      JSMSG_SOURCE_TOO_LONG);
-        }
-        return false;
-    }
-    return true;
-}
-
-bool
-BytecodeCompiler::createScriptSource(const Maybe<uint32_t>& parameterListEnd)
-{
-    if (!checkLength()) {
-        return false;
-    }
-
-    sourceObject = CreateScriptSourceObject(cx, options, parameterListEnd);
-    if (!sourceObject) {
-        return false;
-    }
-
-    scriptSource = sourceObject->source();
-
-    if (!cx->realm()->behaviors().discardSource()) {
-        if (options.sourceIsLazy) {
-            scriptSource->setSourceRetrievable();
-        } else if (!scriptSource->setSourceCopy(cx, sourceBuffer)) {
-            return false;
-        }
-    }
-
-    return true;
-}
-
-bool
-BytecodeCompiler::canLazilyParse()
-{
-    return options.canLazilyParse &&
-           !cx->realm()->behaviors().disableLazyParsing() &&
-           !cx->realm()->behaviors().discardSource() &&
-           !options.sourceIsLazy &&
-           !cx->lcovEnabled() &&
-           // Disabled during record/replay. The replay debugger requires
-           // scripts to be constructed in a consistent order, which might not
-           // happen with lazy parsing.
-           !mozilla::recordreplay::IsRecordingOrReplaying();
-}
-
-bool
-BytecodeCompiler::createParser(ParseGoal goal)
-{
-    usedNames.emplace(cx);
-
-    if (canLazilyParse()) {
-        syntaxParser.emplace(cx, cx->tempLifoAlloc(), options,
-                             sourceBuffer.get(), sourceBuffer.length(),
-                             /* foldConstants = */ false, *usedNames, nullptr, nullptr,
-                             sourceObject, goal);
-        if (!syntaxParser->checkOptions()) {
-            return false;
-        }
-    }
-
-    parser.emplace(cx, cx->tempLifoAlloc(), options, sourceBuffer.get(), sourceBuffer.length(),
-                   /* foldConstants = */ true, *usedNames, syntaxParser.ptrOr(nullptr), nullptr,
-                   sourceObject, goal);
-    parser->ss = scriptSource;
-    return parser->checkOptions();
-}
-
-bool
-BytecodeCompiler::createSourceAndParser(ParseGoal goal,
-                                        const Maybe<uint32_t>& parameterListEnd /* = Nothing() */)
-{
-    return createScriptSource(parameterListEnd) &&
-           createParser(goal);
-}
-
-bool
-BytecodeCompiler::createCompleteScript()
-{
-    return createScript(0, sourceBuffer.length());
-}
-
-bool
-BytecodeCompiler::createScript(uint32_t toStringStart, uint32_t toStringEnd)
-{
-    script = JSScript::Create(cx, options,
-                              sourceObject, /* sourceStart = */ 0, sourceBuffer.length(),
-                              toStringStart, toStringEnd);
-    return script != nullptr;
-}
-
-bool
-BytecodeCompiler::emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext)
-{
-    BytecodeEmitter::EmitterMode emitterMode =
-        options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
-    emitter.emplace(/* parent = */ nullptr, parser.ptr(), sharedContext, script,
-                    /* lazyScript = */ nullptr, options.lineno, emitterMode);
-    return emitter->init();
-}
-
-bool
-BytecodeCompiler::handleParseFailure(const Directives& newDirectives,
-                                     TokenStreamPosition& startPosition)
-{
-    if (parser->hadAbortedSyntaxParse()) {
-        // Hit some unrecoverable ambiguity during an inner syntax parse.
-        // Syntax parsing has now been disabled in the parser, so retry
-        // the parse.
-        parser->clearAbortedSyntaxParse();
-    } else if (parser->anyChars.hadError() || directives == newDirectives) {
-        return false;
+    void createUsedNames() {
+        usedNames.emplace(cx);
     }
 
-    parser->tokenStream.seek(startPosition);
-
-    // Assignment must be monotonic to prevent reparsing iloops
-    MOZ_ASSERT_IF(directives.strict(), newDirectives.strict());
-    MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS());
-    directives = newDirectives;
-    return true;
-}
-
-bool
-BytecodeCompiler::deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment)
-{
-    RootedObject env(cx, environment);
-    while (env->is<EnvironmentObject>() || env->is<DebugEnvironmentProxy>()) {
-        if (env->is<CallObject>()) {
-            RootedFunction fun(cx, &env->as<CallObject>().callee());
-            RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
-            if (!script) {
-                return false;
-            }
-            if (script->argumentsHasVarBinding()) {
-                if (!JSScript::argumentsOptimizationFailed(cx, script)) {
-                    return false;
-                }
-            }
-        }
-        env = env->enclosingEnvironment();
-    }
-
-    return true;
-}
+    // Create a script for source of the given length, using the explicitly-
+    // provided toString offsets as the created script's offsets in the source.
+    MOZ_MUST_USE bool internalCreateScript(uint32_t toStringStart, uint32_t toStringEnd,
+                                           uint32_t sourceBufferLength);
 
-JSScript*
-BytecodeCompiler::compileScript(HandleObject environment, SharedContext* sc)
-{
-    assertSourceParserAndScriptCreated();
-
-    TokenStreamPosition startPosition(keepAtoms, parser->tokenStream);
-
-    Maybe<BytecodeEmitter> emitter;
-    if (!emplaceEmitter(emitter, sc)) {
-        return nullptr;
-    }
+    MOZ_MUST_USE bool emplaceEmitter(mozilla::Maybe<BytecodeEmitter>& emitter,
+                                     const EitherParser& parser, SharedContext* sharedContext);
 
-    for (;;) {
-        ParseNode* pn;
-        {
-            AutoGeckoProfilerEntry pseudoFrame(cx, "script parsing");
-            if (sc->isEvalContext()) {
-                pn = parser->evalBody(sc->asEvalContext());
-            } else {
-                pn = parser->globalBody(sc->asGlobalContext());
-            }
-        }
+    // This function lives here, not in SourceAwareCompiler, because it mostly
+    // uses fields in *this* class.
+    template<typename Unit>
+    MOZ_MUST_USE bool assignSource(JS::SourceText<Unit>& sourceBuffer);
 
-        // Successfully parsed. Emit the script.
-        AutoGeckoProfilerEntry pseudoFrame(cx, "script emit");
-        if (pn) {
-            if (sc->isEvalContext() && sc->hasDebuggerStatement() && !cx->helperThread()) {
-                // If the eval'ed script contains any debugger statement, force construction
-                // of arguments objects for the caller script and any other scripts it is
-                // transitively nested inside. The debugger can access any variable on the
-                // scope chain.
-                if (!deoptimizeArgumentsInEnclosingScripts(cx, environment)) {
-                    return nullptr;
-                }
-            }
-            if (!emitter->emitScript(pn)) {
-                return nullptr;
-            }
-            break;
-        }
+    bool canLazilyParse() const;
 
-        // Maybe we aborted a syntax parse. See if we can try again.
-        if (!handleParseFailure(directives, startPosition)) {
-            return nullptr;
-        }
-
-        // Reset UsedNameTracker state before trying again.
-        usedNames->reset();
-    }
-
-    // We have just finished parsing the source. Inform the source so that we
-    // can compute statistics (e.g. how much time our functions remain lazy).
-    script->scriptSource()->recordParseEnded();
-
-    // Enqueue an off-thread source compression task after finishing parsing.
-    if (!scriptSource->tryCompressOffThread(cx)) {
-        return nullptr;
-    }
-
-    MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending());
-
-    return script;
-}
-
-JSScript*
-BytecodeCompiler::compileGlobalScript(ScopeKind scopeKind)
-{
-    GlobalSharedContext globalsc(cx, scopeKind, directives, options.extraWarningsOption);
-
-    if (!prepareScriptParse()) {
-        return nullptr;
-    }
-
-    return compileScript(nullptr, &globalsc);
-}
+    MOZ_MUST_USE bool
+    deoptimizeArgumentsInEnclosingScripts(JSContext* cx, JS::Handle<JSObject*> environment);
+};
 
-JSScript*
-BytecodeCompiler::compileEvalScript(HandleObject environment, HandleScope enclosingScope)
+class MOZ_STACK_CLASS GlobalScriptInfo final
+  : public BytecodeCompiler
 {
-    EvalSharedContext evalsc(cx, environment, enclosingScope,
-                             directives, options.extraWarningsOption);
-
-    if (!prepareScriptParse()) {
-        return nullptr;
-    }
-
-    return compileScript(environment, &evalsc);
-}
-
-ModuleObject*
-BytecodeCompiler::compileModule(HandleScope enclosingScope)
-{
-    assertSourceParserAndScriptCreated();
+    GlobalSharedContext globalsc_;
 
-    Rooted<ModuleObject*> module(cx, ModuleObject::create(cx));
-    if (!module) {
-        return nullptr;
-    }
-
-    module->init(script);
-
-    ModuleBuilder builder(cx, module, parser->anyChars);
-
-    ModuleSharedContext modulesc(cx, module, enclosingScope, builder);
-    ParseNode* pn = parser->moduleBody(&modulesc);
-    if (!pn) {
-        return nullptr;
-    }
-
-    Maybe<BytecodeEmitter> emitter;
-    if (!emplaceEmitter(emitter, &modulesc)) {
-        return nullptr;
-    }
-    if (!emitter->emitScript(pn->as<CodeNode>().body())) {
-        return nullptr;
+  public:
+    GlobalScriptInfo(JSContext* cx, const JS::ReadOnlyCompileOptions& options, ScopeKind scopeKind)
+      : BytecodeCompiler(cx, options),
+        globalsc_(cx, scopeKind, directives, options.extraWarningsOption)
+    {
+        MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
     }
 
-    if (!builder.initModule()) {
-        return nullptr;
-    }
-
-    RootedModuleEnvironmentObject env(cx, ModuleEnvironmentObject::create(cx, module));
-    if (!env) {
-        return nullptr;
-    }
-
-    module->setInitialEnvironment(env);
-
-    // Enqueue an off-thread source compression task after finishing parsing.
-    if (!scriptSource->tryCompressOffThread(cx)) {
-        return nullptr;
-    }
-
-    MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending());
-    return module;
-}
-
-// Parse a standalone JS function, which might appear as the value of an
-// event handler attribute in an HTML <INPUT> tag, or in a Function()
-// constructor.
-CodeNode*
-BytecodeCompiler::parseStandaloneFunction(MutableHandleFunction fun,
-                                          GeneratorKind generatorKind,
-                                          FunctionAsyncKind asyncKind,
-                                          const Maybe<uint32_t>& parameterListEnd,
-                                          HandleScope enclosingScope)
-{
-    MOZ_ASSERT(fun);
-    MOZ_ASSERT(fun->isTenured());
-
-    assertSourceAndParserCreated();
-
-    TokenStreamPosition startPosition(keepAtoms, parser->tokenStream);
-
-    // Speculatively parse using the default directives implied by the context.
-    // If a directive is encountered (e.g., "use strict") that changes how the
-    // function should have been parsed, we backup and reparse with the new set
-    // of directives.
-
-    ParseNode* fn;
-    do {
-        Directives newDirectives = directives;
-        fn = parser->standaloneFunction(fun, enclosingScope, parameterListEnd, generatorKind,
-                                        asyncKind, directives, &newDirectives);
-        if (!fn && !handleParseFailure(newDirectives, startPosition)) {
-            return nullptr;
-        }
-    } while (!fn);
-
-    return &fn->as<CodeNode>();
-}
-
-// Compile a standalone JS function.
-bool
-BytecodeCompiler::compileStandaloneFunction(CodeNode* parsedFunction, MutableHandleFunction fun)
-{
-    FunctionBox* funbox = parsedFunction->funbox();
-    if (funbox->function()->isInterpreted()) {
-        MOZ_ASSERT(fun == funbox->function());
-
-        if (!createScript(funbox->toStringStart, funbox->toStringEnd)) {
-            return false;
-        }
-
-        Maybe<BytecodeEmitter> emitter;
-        if (!emplaceEmitter(emitter, funbox)) {
-            return false;
-        }
-        if (!emitter->emitFunctionScript(parsedFunction, BytecodeEmitter::TopLevelFunction::Yes)) {
-            return false;
-        }
-    } else {
-        fun.set(funbox->function());
-        MOZ_ASSERT(IsAsmJSModule(fun));
-    }
-
-    // Enqueue an off-thread source compression task after finishing parsing.
-    return scriptSource->tryCompressOffThread(cx);
-}
-
-ScriptSourceObject*
-frontend::CreateScriptSourceObject(JSContext* cx, const ReadOnlyCompileOptions& options,
-                                   const Maybe<uint32_t>& parameterListEnd /* = Nothing() */)
-{
-    ScriptSource* ss = cx->new_<ScriptSource>();
-    if (!ss) {
-        return nullptr;
-    }
-    ScriptSourceHolder ssHolder(ss);
-
-    if (!ss->initFromOptions(cx, options, parameterListEnd)) {
-        return nullptr;
-    }
-
-    RootedScriptSourceObject sso(cx, ScriptSourceObject::create(cx, ss));
-    if (!sso) {
-        return nullptr;
-    }
-
-    // Off-thread compilations do all their GC heap allocation, including the
-    // SSO, in a temporary compartment. Hence, for the SSO to refer to the
-    // gc-heap-allocated values in |options|, it would need cross-compartment
-    // wrappers from the temporary compartment to the real compartment --- which
-    // would then be inappropriate once we merged the temporary and real
-    // compartments.
-    //
-    // Instead, we put off populating those SSO slots in off-thread compilations
-    // until after we've merged compartments.
-    if (!cx->helperThread()) {
-        if (!ScriptSourceObject::initFromOptions(cx, sso, options)) {
-            return nullptr;
-        }
-    }
-
-    return sso;
-}
-
-// CompileScript independently returns the ScriptSourceObject (SSO) for the
-// compile.  This is used by off-thread script compilation (OT-SC).
-//
-// OT-SC cannot initialize the SSO when it is first constructed because the
-// SSO is allocated initially in a separate compartment.
-//
-// After OT-SC, the separate compartment is merged with the main compartment,
-// at which point the JSScripts created become observable by the debugger via
-// memory-space scanning.
-//
-// Whatever happens to the top-level script compilation (even if it fails and
-// returns null), we must finish initializing the SSO.  This is because there
-// may be valid inner scripts observable by the debugger which reference the
-// partially-initialized SSO.
-class MOZ_STACK_CLASS AutoInitializeSourceObject
-{
-    BytecodeCompiler& compiler_;
-    ScriptSourceObject** sourceObjectOut_;
-
-  public:
-    AutoInitializeSourceObject(BytecodeCompiler& compiler,
-                               ScriptSourceObject** sourceObjectOut)
-      : compiler_(compiler),
-        sourceObjectOut_(sourceObjectOut)
-    { }
-
-    ~AutoInitializeSourceObject() {
-        if (sourceObjectOut_) {
-            *sourceObjectOut_ = compiler_.sourceObjectPtr();
-        }
+    GlobalSharedContext* sharedContext() {
+        return &globalsc_;
     }
 };
 
-// RAII class to check the frontend reports an exception when it fails to
-// compile a script.
-class MOZ_RAII AutoAssertReportedException
+extern JSScript*
+CompileGlobalScript(GlobalScriptInfo& info, JS::SourceText<char16_t>& srcBuf,
+                    ScriptSourceObject** sourceObjectOut = nullptr);
+
+extern JSScript*
+CompileGlobalScript(GlobalScriptInfo& info, JS::SourceText<mozilla::Utf8Unit>& srcBuf,
+                    ScriptSourceObject** sourceObjectOut = nullptr);
+
+class MOZ_STACK_CLASS EvalScriptInfo final
+  : public BytecodeCompiler
 {
-#ifdef DEBUG
-    JSContext* cx_;
-    bool check_;
+    JS::Handle<JSObject*> environment_;
+    EvalSharedContext evalsc_;
 
   public:
-    explicit AutoAssertReportedException(JSContext* cx)
-      : cx_(cx),
-        check_(true)
+    EvalScriptInfo(JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+                   JS::Handle<JSObject*> environment, JS::Handle<Scope*> enclosingScope)
+      : BytecodeCompiler(cx, options),
+        environment_(environment),
+        evalsc_(cx, environment_, enclosingScope, directives, options.extraWarningsOption)
     {}
-    void reset() {
-        check_ = false;
-    }
-    ~AutoAssertReportedException() {
-        if (!check_) {
-            return;
-        }
-
-        if (!cx_->helperThread()) {
-            MOZ_ASSERT(cx_->isExceptionPending());
-            return;
-        }
-
-        ParseTask* task = cx_->helperThread()->parseTask();
-        MOZ_ASSERT(task->outOfMemory ||
-                   task->overRecursed ||
-                   !task->errors.empty());
-    }
-#else
-  public:
-    explicit AutoAssertReportedException(JSContext*) {}
-    void reset() {}
-#endif
-};
-
-JSScript*
-frontend::CompileGlobalScript(JSContext* cx, ScopeKind scopeKind,
-                              const ReadOnlyCompileOptions& options,
-                              SourceBufferHolder& srcBuf,
-                              ScriptSourceObject** sourceObjectOut)
-{
-    MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
-    AutoAssertReportedException assertException(cx);
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
-    JSScript* script = compiler.compileGlobalScript(scopeKind);
-    if (!script) {
-        return nullptr;
-    }
-    assertException.reset();
-    return script;
-}
-
-#if defined(JS_BUILD_BINAST)
 
-JSScript*
-frontend::CompileGlobalBinASTScript(JSContext* cx, LifoAlloc& alloc, const ReadOnlyCompileOptions& options,
-                                    const uint8_t* src, size_t len, ScriptSourceObject** sourceObjectOut)
-{
-    AutoAssertReportedException assertException(cx);
-
-    frontend::UsedNameTracker usedNames(cx);
-
-    RootedScriptSourceObject sourceObj(cx, CreateScriptSourceObject(cx, options));
-
-    if (!sourceObj) {
-        return nullptr;
-    }
-
-    if (!sourceObj->source()->setBinASTSourceCopy(cx, src, len)) {
-        return nullptr;
-    }
-
-    RootedScript script(cx, JSScript::Create(cx, options, sourceObj, 0, len, 0, len));
-
-    if (!script) {
-        return nullptr;
-    }
-
-    Directives directives(options.strictOption);
-    GlobalSharedContext globalsc(cx, ScopeKind::Global, directives, options.extraWarningsOption);
-
-    frontend::BinASTParser<BinTokenReaderMultipart> parser(cx, alloc, usedNames, options, sourceObj);
-
-    // Metadata stores internal pointers, so we must use the same buffer every time, including for lazy parses
-    ScriptSource* ss = sourceObj->source();
-    BinASTSourceMetadata* metadata = nullptr;
-    auto parsed = parser.parse(&globalsc, ss->binASTSource(), ss->length(), &metadata);
-
-    if (parsed.isErr()) {
-        return nullptr;
-    }
-
-    sourceObj->source()->setBinASTSourceMetadata(metadata);
-
-    BytecodeEmitter bce(nullptr, &parser, &globalsc, script, nullptr, 0);
-
-    if (!bce.init()) {
-        return nullptr;
-    }
-
-    ParseNode *pn = parsed.unwrap();
-    if (!bce.emitScript(pn)) {
-        return nullptr;
-    }
-
-    if (sourceObjectOut) {
-        *sourceObjectOut = sourceObj;
+    HandleObject environment() {
+        return environment_;
     }
 
-    assertException.reset();
-    return script;
-}
-
-#endif // JS_BUILD_BINAST
-
-JSScript*
-frontend::CompileEvalScript(JSContext* cx, HandleObject environment,
-                            HandleScope enclosingScope,
-                            const ReadOnlyCompileOptions& options,
-                            SourceBufferHolder& srcBuf,
-                            ScriptSourceObject** sourceObjectOut)
-{
-    AutoAssertReportedException assertException(cx);
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
-    JSScript* script = compiler.compileEvalScript(environment, enclosingScope);
-    if (!script) {
-        return nullptr;
-    }
-    assertException.reset();
-    return script;
-
-}
-
-ModuleObject*
-frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput,
-                        SourceBufferHolder& srcBuf,
-                        ScriptSourceObject** sourceObjectOut)
-{
-    MOZ_ASSERT(srcBuf.get());
-    MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr);
-
-    AutoAssertReportedException assertException(cx);
-
-    CompileOptions options(cx, optionsInput);
-    options.maybeMakeStrictMode(true); // ES6 10.2.1 Module code is always strict mode code.
-    options.setIsRunOnce(true);
-    options.allowHTMLComments = false;
-
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
-
-    if (!compiler.prepareModuleParse()) {
-        return nullptr;
-    }
-
-    RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
-    ModuleObject* module = compiler.compileModule(emptyGlobalScope);
-    if (!module) {
-        return nullptr;
-    }
-
-    assertException.reset();
-    return module;
-}
-
-ModuleObject*
-frontend::CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options,
-                        SourceBufferHolder& srcBuf)
-{
-    AutoAssertReportedException assertException(cx);
-
-    if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global())) {
-        return nullptr;
-    }
-
-    RootedModuleObject module(cx, CompileModule(cx, options, srcBuf, nullptr));
-    if (!module) {
-        return nullptr;
-    }
-
-    // This happens in GlobalHelperThreadState::finishModuleParseTask() when a
-    // module is compiled off thread.
-    if (!ModuleObject::Freeze(cx, module)) {
-        return nullptr;
-    }
-
-    assertException.reset();
-    return module;
-}
-
-// When leaving this scope, the given function should either:
-//   * be linked to a fully compiled script
-//   * remain linking to a lazy script
-class MOZ_STACK_CLASS AutoAssertFunctionDelazificationCompletion
-{
-#ifdef DEBUG
-    RootedFunction fun_;
-#endif
-
-  public:
-    AutoAssertFunctionDelazificationCompletion(JSContext* cx, HandleFunction fun)
-#ifdef DEBUG
-      : fun_(cx, fun)
-#endif
-    {
-        MOZ_ASSERT(fun_->isInterpretedLazy());
-        MOZ_ASSERT(!fun_->lazyScript()->hasScript());
-    }
-
-    ~AutoAssertFunctionDelazificationCompletion() {
-#ifdef DEBUG
-        if (!fun_) {
-            return;
-        }
-#endif
-
-        // If fun_ is not nullptr, it means delazification doesn't complete.
-        // Assert that the function keeps linking to lazy script
-        MOZ_ASSERT(fun_->isInterpretedLazy());
-        MOZ_ASSERT(!fun_->lazyScript()->hasScript());
-    }
-
-    void complete() {
-        // Assert the completion of delazification and forget the function.
-        MOZ_ASSERT(fun_->hasScript());
-        MOZ_ASSERT(!fun_->hasUncompletedScript());
-
-#ifdef DEBUG
-        fun_ = nullptr;
-#endif
+    EvalSharedContext* sharedContext() {
+        return &evalsc_;
     }
 };
 
-bool
-frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length)
-{
-    MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment());
-
-    // We can only compile functions whose parents have previously been
-    // compiled, because compilation requires full information about the
-    // function's immediately enclosing scope.
-    MOZ_ASSERT(lazy->enclosingScriptHasEverBeenCompiled());
-
-    MOZ_ASSERT(!lazy->isBinAST());
-
-    AutoAssertReportedException assertException(cx);
-    Rooted<JSFunction*> fun(cx, lazy->functionNonDelazifying());
-    AutoAssertFunctionDelazificationCompletion delazificationCompletion(cx, fun);
-
-    JS::CompileOptions options(cx);
-    options.setMutedErrors(lazy->mutedErrors())
-           .setFileAndLine(lazy->filename(), lazy->lineno())
-           .setColumn(lazy->column())
-           .setScriptSourceOffset(lazy->sourceStart())
-           .setNoScriptRval(false)
-           .setSelfHostingMode(false);
-
-    // Update statistics to find out if we are delazifying just after having
-    // lazified. Note that we are interested in the delta between end of
-    // syntax parsing and start of full parsing, so we do this now rather than
-    // after parsing below.
-    if (!lazy->scriptSource()->parseEnded().IsNull()) {
-        const mozilla::TimeDuration delta = ReallyNow() -
-            lazy->scriptSource()->parseEnded();
-
-        // Differentiate between web-facing and privileged code, to aid
-        // with optimization. Due to the number of calls to this function,
-        // we use `cx->runningWithTrustedPrincipals`, which is fast but
-        // will classify addons alongside with web-facing code.
-        const int HISTOGRAM = cx->runningWithTrustedPrincipals()
-            ? JS_TELEMETRY_PRIVILEGED_PARSER_COMPILE_LAZY_AFTER_MS
-            : JS_TELEMETRY_WEB_PARSER_COMPILE_LAZY_AFTER_MS;
-        cx->runtime()->addTelemetry(HISTOGRAM, delta.ToMilliseconds());
-    }
-
-    UsedNameTracker usedNames(cx);
-
-    RootedScriptSourceObject sourceObject(cx, &lazy->sourceObject());
-    Parser<FullParseHandler, char16_t> parser(cx, cx->tempLifoAlloc(), options, chars, length,
-                                              /* foldConstants = */ true, usedNames, nullptr,
-                                              lazy, sourceObject, lazy->parseGoal());
-    if (!parser.checkOptions()) {
-        return false;
-    }
-
-    ParseNode* pn = parser.standaloneLazyFunction(fun, lazy->toStringStart(),
-                                                  lazy->strict(), lazy->generatorKind(),
-                                                  lazy->asyncKind());
-    if (!pn) {
-        return false;
-    }
-
-    Rooted<JSScript*> script(cx, JSScript::Create(cx, options, sourceObject,
-                                                  lazy->sourceStart(), lazy->sourceEnd(),
-                                                  lazy->toStringStart(), lazy->toStringEnd()));
-    if (!script) {
-        return false;
-    }
+extern JSScript*
+CompileEvalScript(EvalScriptInfo& info, JS::SourceText<char16_t>& srcBuf);
 
-    if (lazy->isLikelyConstructorWrapper()) {
-        script->setLikelyConstructorWrapper();
-    }
-    if (lazy->hasBeenCloned()) {
-        script->setHasBeenCloned();
-    }
-
-    BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->as<CodeNode>().funbox(), script, lazy,
-                        pn->pn_pos, BytecodeEmitter::LazyFunction);
-    if (!bce.init()) {
-        return false;
-    }
-
-    if (!bce.emitFunctionScript(&pn->as<CodeNode>(), BytecodeEmitter::TopLevelFunction::Yes)) {
-        return false;
-    }
-
-    delazificationCompletion.complete();
-    assertException.reset();
-    return true;
-}
-
-#ifdef JS_BUILD_BINAST
-
-bool
-frontend::CompileLazyBinASTFunction(JSContext* cx, Handle<LazyScript*> lazy, const uint8_t* buf, size_t length)
+class MOZ_STACK_CLASS ModuleInfo final
+  : public BytecodeCompiler
 {
-    MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment());
-
-    // We can only compile functions whose parents have previously been
-    // compiled, because compilation requires full information about the
-    // function's immediately enclosing scope.
-    MOZ_ASSERT(lazy->enclosingScriptHasEverBeenCompiled());
-    MOZ_ASSERT(lazy->isBinAST());
-
-    AutoAssertReportedException assertException(cx);
-    Rooted<JSFunction*> fun(cx, lazy->functionNonDelazifying());
-    AutoAssertFunctionDelazificationCompletion delazificationCompletion(cx, fun);
-
-    CompileOptions options(cx);
-    options.setMutedErrors(lazy->mutedErrors())
-           .setFileAndLine(lazy->filename(), lazy->lineno())
-           .setColumn(lazy->column())
-           .setScriptSourceOffset(lazy->sourceStart())
-           .setNoScriptRval(false)
-           .setSelfHostingMode(false);
-
-    UsedNameTracker usedNames(cx);
-
-    RootedScriptSourceObject sourceObj(cx, &lazy->sourceObject());
-    MOZ_ASSERT(sourceObj);
-
-    RootedScript script(cx, JSScript::Create(cx, options, sourceObj, lazy->sourceStart(), lazy->sourceEnd(),
-                                             lazy->sourceStart(), lazy->sourceEnd()));
-
-    if (!script) {
-        return false;
-    }
-
-    if (lazy->hasBeenCloned()) {
-        script->setHasBeenCloned();
-    }
-
-    frontend::BinASTParser<BinTokenReaderMultipart> parser(cx, cx->tempLifoAlloc(),
-                                                           usedNames, options, sourceObj,
-                                                           lazy);
+  public:
+    ModuleInfo(JSContext* cx, const JS::ReadOnlyCompileOptions& options)
+      : BytecodeCompiler(cx, options)
+    {}
+};
 
-    auto parsed = parser.parseLazyFunction(lazy->scriptSource(), lazy->sourceStart());
-
-    if (parsed.isErr()) {
-        return false;
-    }
-
-    ParseNode *pn = parsed.unwrap();
-
-    BytecodeEmitter bce(nullptr, &parser, pn->as<CodeNode>().funbox(), script,
-                        lazy, pn->pn_pos, BytecodeEmitter::LazyFunction);
-
-    if (!bce.init()) {
-        return false;
-    }
-
-    if (!bce.emitFunctionScript(&pn->as<CodeNode>(), BytecodeEmitter::TopLevelFunction::Yes)) {
-        return false;
-    }
-
-    delazificationCompletion.complete();
-    assertException.reset();
-    return script;
-}
-
-#endif // JS_BUILD_BINAST
-
-bool
-frontend::CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun,
-                                    const JS::ReadOnlyCompileOptions& options,
-                                    JS::SourceBufferHolder& srcBuf,
-                                    const Maybe<uint32_t>& parameterListEnd,
-                                    HandleScope enclosingScope /* = nullptr */)
+class MOZ_STACK_CLASS StandaloneFunctionInfo final
+  : public BytecodeCompiler
 {
-    AutoAssertReportedException assertException(cx);
-
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) {
-        return false;
-    }
-
-    RootedScope scope(cx, enclosingScope);
-    if (!scope) {
-        scope = &cx->global()->emptyGlobalScope();
-    }
-
-    CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::NotGenerator,
-                                                                FunctionAsyncKind::SyncFunction,
-                                                                parameterListEnd, scope);
-    if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) {
-        return false;
-    }
-
-    assertException.reset();
-    return true;
-}
-
-bool
-frontend::CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun,
-                                     const JS::ReadOnlyCompileOptions& options,
-                                     JS::SourceBufferHolder& srcBuf,
-                                     const Maybe<uint32_t>& parameterListEnd)
-{
-    AutoAssertReportedException assertException(cx);
+  public:
+    StandaloneFunctionInfo(JSContext* cx, const JS::ReadOnlyCompileOptions& options)
+      : BytecodeCompiler(cx, options)
+    {}
+};
 
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) {
-        return false;
-    }
-
-    RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
-    CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::Generator,
-                                                                FunctionAsyncKind::SyncFunction,
-                                                                parameterListEnd,
-                                                                emptyGlobalScope);
-    if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) {
-        return false;
-    }
-
-    assertException.reset();
-    return true;
-}
-
-bool
-frontend::CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun,
-                                         const ReadOnlyCompileOptions& options,
-                                         JS::SourceBufferHolder& srcBuf,
-                                         const Maybe<uint32_t>& parameterListEnd)
-{
-    AutoAssertReportedException assertException(cx);
-
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) {
-        return false;
-    }
+extern MOZ_MUST_USE bool
+CompileLazyFunction(JSContext* cx, JS::Handle<LazyScript*> lazy,
+                    const char16_t* units, size_t length);
 
-    RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
-    CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::NotGenerator,
-                                                                FunctionAsyncKind::AsyncFunction,
-                                                                parameterListEnd,
-                                                                emptyGlobalScope);
-    if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) {
-        return false;
-    }
-
-    assertException.reset();
-    return true;
-}
+extern MOZ_MUST_USE bool
+CompileLazyFunction(JSContext* cx, JS::Handle<LazyScript*> lazy,
+                    const mozilla::Utf8Unit* units, size_t length);
 
-bool
-frontend::CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun,
-                                          const ReadOnlyCompileOptions& options,
-                                          JS::SourceBufferHolder& srcBuf,
-                                          const Maybe<uint32_t>& parameterListEnd)
-{
-    AutoAssertReportedException assertException(cx);
-
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) {
-        return false;
-    }
+} // namespace frontend
 
-    RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
-    CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::Generator,
-                                                                FunctionAsyncKind::AsyncFunction,
-                                                                parameterListEnd,
-                                                                emptyGlobalScope);
-    if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) {
-        return false;
-    }
+} // namespace js
 
-    assertException.reset();
-    return true;
-}
+#endif // frontend_BytecodeCompilation_h
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -1,143 +1,336 @@
 /* -*- 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/BytecodeCompiler.h"
 
+#include "mozilla/Attributes.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/Utf8.h"
 
 #include "builtin/ModuleObject.h"
 #if defined(JS_BUILD_BINAST)
 # include "frontend/BinSource.h"
 #endif // JS_BUILD_BINAST
+#include "frontend/BytecodeCompilation.h"
 #include "frontend/BytecodeEmitter.h"
+#include "frontend/EitherParser.h"
 #include "frontend/ErrorReporter.h"
 #include "frontend/FoldConstants.h"
+#include "frontend/ModuleSharedContext.h"
 #include "frontend/Parser.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "vm/GlobalObject.h"
 #include "vm/JSContext.h"
 #include "vm/JSScript.h"
 #include "vm/TraceLogging.h"
 #include "wasm/AsmJS.h"
 
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/GeckoProfiler-inl.h"
 #include "vm/JSContext-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 
 using mozilla::Maybe;
 using mozilla::Nothing;
+using mozilla::Utf8Unit;
 
 using JS::CompileOptions;
 using JS::ReadOnlyCompileOptions;
-using JS::SourceBufferHolder;
+using JS::SourceText;
+
+// CompileScript independently returns the ScriptSourceObject (SSO) for the
+// compile.  This is used by off-thread script compilation (OT-SC).
+//
+// OT-SC cannot initialize the SSO when it is first constructed because the
+// SSO is allocated initially in a separate compartment.
+//
+// After OT-SC, the separate compartment is merged with the main compartment,
+// at which point the JSScripts created become observable by the debugger via
+// memory-space scanning.
+//
+// Whatever happens to the top-level script compilation (even if it fails and
+// returns null), we must finish initializing the SSO.  This is because there
+// may be valid inner scripts observable by the debugger which reference the
+// partially-initialized SSO.
+class MOZ_STACK_CLASS AutoInitializeSourceObject
+{
+    BytecodeCompiler& compiler_;
+    ScriptSourceObject** sourceObjectOut_;
+
+  public:
+    AutoInitializeSourceObject(BytecodeCompiler& compiler,
+                               ScriptSourceObject** sourceObjectOut)
+      : compiler_(compiler),
+        sourceObjectOut_(sourceObjectOut)
+    { }
+
+    inline ~AutoInitializeSourceObject() {
+        if (sourceObjectOut_) {
+            *sourceObjectOut_ = compiler_.sourceObjectPtr();
+        }
+    }
+};
 
-// The BytecodeCompiler class contains resources common to compiling scripts and
-// function bodies.
-class MOZ_STACK_CLASS BytecodeCompiler
+// RAII class to check the frontend reports an exception when it fails to
+// compile a script.
+class MOZ_RAII AutoAssertReportedException
+{
+#ifdef DEBUG
+    JSContext* cx_;
+    bool check_;
+
+  public:
+    explicit AutoAssertReportedException(JSContext* cx)
+      : cx_(cx),
+        check_(true)
+    {}
+    void reset() {
+        check_ = false;
+    }
+    ~AutoAssertReportedException() {
+        if (!check_) {
+            return;
+        }
+
+        if (!cx_->helperThread()) {
+            MOZ_ASSERT(cx_->isExceptionPending());
+            return;
+        }
+
+        ParseTask* task = cx_->helperThread()->parseTask();
+        MOZ_ASSERT(task->outOfMemory ||
+                   task->overRecursed ||
+                   !task->errors.empty());
+    }
+#else
+  public:
+    explicit AutoAssertReportedException(JSContext*) {}
+    void reset() {}
+#endif
+};
+
+template<typename Unit>
+class MOZ_STACK_CLASS frontend::SourceAwareCompiler
 {
   protected:
-    AutoKeepAtoms keepAtoms;
-
-    JSContext* cx;
-    const ReadOnlyCompileOptions& options;
-    SourceBufferHolder& sourceBuffer;
+    SourceText<Unit>& sourceBuffer_;
 
-    RootedScriptSourceObject sourceObject;
-    ScriptSource* scriptSource;
-
-    Maybe<UsedNameTracker> usedNames;
-    Maybe<Parser<SyntaxParseHandler, char16_t>> syntaxParser;
-    Maybe<Parser<FullParseHandler, char16_t>> parser;
+    Maybe<Parser<SyntaxParseHandler, Unit>> syntaxParser;
+    Maybe<Parser<FullParseHandler, Unit>> parser;
 
-    Directives directives;
-
-    RootedScript script;
+    using TokenStreamPosition = frontend::TokenStreamPosition<Unit>;
 
-  public:
-    // Construct an object passing mandatory arguments.
-    BytecodeCompiler(JSContext* cx,
-                     const ReadOnlyCompileOptions& options,
-                     SourceBufferHolder& sourceBuffer);
-
-    ScriptSourceObject* sourceObjectPtr() const {
-        return sourceObject.get();
+  protected:
+    explicit SourceAwareCompiler(SourceText<Unit>& sourceBuffer)
+      : sourceBuffer_(sourceBuffer)
+    {
+        MOZ_ASSERT(sourceBuffer_.get() != nullptr);
     }
 
     // Call this before calling compile{Global,Eval}Script.
-    MOZ_MUST_USE bool prepareScriptParse() {
-        return createSourceAndParser(ParseGoal::Script) && createCompleteScript();
-    }
-
-    JSScript* compileGlobalScript(ScopeKind scopeKind);
-    JSScript* compileEvalScript(HandleObject environment, HandleScope enclosingScope);
-
-    // Call this before calling compileModule.
-    MOZ_MUST_USE bool prepareModuleParse() {
-        return createSourceAndParser(ParseGoal::Module) && createCompleteScript();
+    MOZ_MUST_USE bool prepareScriptParse(BytecodeCompiler& info) {
+        return createSourceAndParser(info, ParseGoal::Script) && createCompleteScript(info);
     }
 
-    ModuleObject* compileModule(HandleScope enclosingScope);
-
-    // Call this before calling parseStandaloneFunction.
-    MOZ_MUST_USE bool prepareStandaloneFunctionParse(const Maybe<uint32_t>& parameterListEnd) {
-        return createSourceAndParser(ParseGoal::Script, parameterListEnd);
-    }
-
-    // Call this before calling compileStandaloneFunction.
-    CodeNode* parseStandaloneFunction(MutableHandleFunction fun, GeneratorKind generatorKind,
-                                      FunctionAsyncKind asyncKind,
-                                      const Maybe<uint32_t>& parameterListEnd,
-                                      HandleScope enclosingScope);
-
-    bool compileStandaloneFunction(CodeNode* parsedFunction, MutableHandleFunction fun);
-
-  private:
-    void assertSourceAndParserCreated() const {
-        MOZ_ASSERT(sourceObject != nullptr);
-        MOZ_ASSERT(scriptSource != nullptr);
-        MOZ_ASSERT(usedNames.isSome());
+    void assertSourceAndParserCreated(BytecodeCompiler& info) const {
+        info.assertSourceCreated();
+        MOZ_ASSERT(info.usedNames.isSome());
         MOZ_ASSERT(parser.isSome());
     }
 
-    void assertSourceParserAndScriptCreated() const {
-        assertSourceAndParserCreated();
-        MOZ_ASSERT(script != nullptr);
+    void assertSourceParserAndScriptCreated(BytecodeCompiler& info) {
+        assertSourceAndParserCreated(info);
+        MOZ_ASSERT(info.script != nullptr);
     }
 
-    JSScript* compileScript(HandleObject environment, SharedContext* sc);
-    bool checkLength();
-    bool createScriptSource(const Maybe<uint32_t>& parameterListEnd);
-    bool canLazilyParse();
-    bool createParser(ParseGoal goal);
-    bool createSourceAndParser(ParseGoal goal,
-                               const Maybe<uint32_t>& parameterListEnd = Nothing());
+    MOZ_MUST_USE bool emplaceEmitter(BytecodeCompiler& info, Maybe<BytecodeEmitter>& emitter,
+                                     SharedContext* sharedContext)
+    {
+        return info.emplaceEmitter(emitter, EitherParser(parser.ptr()), sharedContext);
+    }
+
+    MOZ_MUST_USE bool createSourceAndParser(BytecodeCompiler& compiler, ParseGoal goal,
+                                            const Maybe<uint32_t>& parameterListEnd = Nothing());
 
     // This assumes the created script's offsets in the source used to parse it
     // are the same as are used to compute its Function.prototype.toString()
     // value.
-    bool createCompleteScript();
+    MOZ_MUST_USE bool createCompleteScript(BytecodeCompiler& info) {
+        uint32_t toStringStart = 0;
+        uint32_t len = sourceBuffer_.length();
+        uint32_t toStringEnd = len;
+        return info.internalCreateScript(toStringStart, toStringEnd, len);
+    }
+
+    MOZ_MUST_USE bool
+    handleParseFailure(BytecodeCompiler& compiler, const Directives& newDirectives,
+                       TokenStreamPosition& startPosition);
+};
+
+template<typename Unit>
+class MOZ_STACK_CLASS frontend::ScriptCompiler
+  : public SourceAwareCompiler<Unit>
+{
+    using Base = SourceAwareCompiler<Unit>;
+
+  protected:
+    using Base::parser;
+    using Base::sourceBuffer_;
+
+    using Base::assertSourceParserAndScriptCreated;
+    using Base::emplaceEmitter;
+    using Base::handleParseFailure;
+
+    using typename Base::TokenStreamPosition;
+
+  public:
+    explicit ScriptCompiler(SourceText<Unit>& srcBuf)
+      : Base(srcBuf)
+    {}
+
+    MOZ_MUST_USE bool prepareScriptParse(BytecodeCompiler& compiler) {
+        return Base::prepareScriptParse(compiler);
+    }
 
-    // This uses explicitly-provided toString offsets as the created script's
-    // offsets in the source.
-    bool createScript(uint32_t toStringStart, uint32_t toStringEnd);
+    JSScript* compileScript(BytecodeCompiler& compiler, HandleObject environment,
+                            SharedContext* sc);
+};
+
+template<typename Unit>
+static JSScript*
+CreateGlobalScript(GlobalScriptInfo& info, JS::SourceText<Unit>& srcBuf,
+                   ScriptSourceObject** sourceObjectOut = nullptr)
+{
+    AutoAssertReportedException assertException(info.context());
+
+    frontend::ScriptCompiler<Unit> compiler(srcBuf);
+    AutoInitializeSourceObject autoSSO(info, sourceObjectOut);
+
+    if (!compiler.prepareScriptParse(info)) {
+        return nullptr;
+    }
+
+    JSScript* script = compiler.compileScript(info, nullptr, info.sharedContext());
+    if (!script) {
+        return nullptr;
+    }
+
+    assertException.reset();
+    return script;
+}
+
+JSScript*
+frontend::CompileGlobalScript(GlobalScriptInfo& info, JS::SourceText<char16_t>& srcBuf,
+                              ScriptSourceObject** sourceObjectOut /* = nullptr */)
+{
+    return CreateGlobalScript(info, srcBuf, sourceObjectOut);
+}
+
+JSScript*
+frontend::CompileGlobalScript(GlobalScriptInfo& info, JS::SourceText<Utf8Unit>& srcBuf,
+                              ScriptSourceObject** sourceObjectOut /* = nullptr */)
+{
+    return CreateGlobalScript(info, srcBuf, sourceObjectOut);
+}
 
-    using TokenStreamPosition = frontend::TokenStreamPosition<char16_t>;
+template<typename Unit>
+static JSScript*
+CreateEvalScript(frontend::EvalScriptInfo& info, SourceText<Unit>& srcBuf)
+{
+    AutoAssertReportedException assertException(info.context());
+
+    frontend::ScriptCompiler<Unit> compiler(srcBuf);
+    if (!compiler.prepareScriptParse(info)) {
+        return nullptr;
+    }
+
+    JSScript* script = compiler.compileScript(info, info.environment(), info.sharedContext());
+    if (!script) {
+        return nullptr;
+    }
+
+    assertException.reset();
+    return script;
+}
+
+JSScript*
+frontend::CompileEvalScript(EvalScriptInfo& info, JS::SourceText<char16_t>& srcBuf)
+{
+    return CreateEvalScript(info, srcBuf);
+}
+
+template<typename Unit>
+class MOZ_STACK_CLASS frontend::ModuleCompiler final
+  : public SourceAwareCompiler<Unit>
+{
+    using Base = SourceAwareCompiler<Unit>;
+
+    using Base::assertSourceParserAndScriptCreated;
+    using Base::createCompleteScript;
+    using Base::createSourceAndParser;
+    using Base::emplaceEmitter;
+    using Base::parser;
+
+  public:
+    explicit ModuleCompiler(SourceText<Unit>& srcBuf)
+      : Base(srcBuf)
+    {}
 
-    bool emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext);
-    bool handleParseFailure(const Directives& newDirectives, TokenStreamPosition& startPosition);
-    bool deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment);
+    ModuleObject* compile(ModuleInfo& info);
+};
+
+template<typename Unit>
+class MOZ_STACK_CLASS frontend::StandaloneFunctionCompiler final
+  : public SourceAwareCompiler<Unit>
+{
+    using Base = SourceAwareCompiler<Unit>;
+
+    using Base::assertSourceAndParserCreated;
+    using Base::createSourceAndParser;
+    using Base::emplaceEmitter;
+    using Base::handleParseFailure;
+    using Base::parser;
+    using Base::sourceBuffer_;
+
+    using typename Base::TokenStreamPosition;
+
+  public:
+    explicit StandaloneFunctionCompiler(SourceText<Unit>& srcBuf)
+      : Base(srcBuf)
+    {}
+
+    MOZ_MUST_USE bool prepare(StandaloneFunctionInfo& info,
+                              const Maybe<uint32_t>& parameterListEnd)
+    {
+        return createSourceAndParser(info, ParseGoal::Script, parameterListEnd);
+    }
+
+    CodeNode* parse(StandaloneFunctionInfo& info, HandleFunction fun,
+                    HandleScope enclosingScope, GeneratorKind generatorKind,
+                    FunctionAsyncKind asyncKind, const Maybe<uint32_t>& parameterListEnd);
+
+    MOZ_MUST_USE bool compile(MutableHandleFunction fun, StandaloneFunctionInfo& info,
+                              CodeNode* parsedFunction);
+
+  private:
+    // Create a script for a function with the given toString offsets in source
+    // text.
+    MOZ_MUST_USE bool createFunctionScript(StandaloneFunctionInfo& info,
+                                           uint32_t toStringStart, uint32_t toStringEnd)
+    {
+        return info.internalCreateScript(toStringStart, toStringEnd, sourceBuffer_.length());
+    }
 };
 
 AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id,
                                            const ErrorReporter& errorReporter)
 #ifdef JS_TRACE_LOGGING
   : logger_(TraceLoggerForCurrentThread(cx))
 {
     // If the tokenizer hasn't yet gotten any tokens, use the line and column
@@ -182,160 +375,139 @@ AutoFrontendTraceLog::AutoFrontendTraceL
     frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), line, column);
     frontendLog_.emplace(logger_, *frontendEvent_);
     typeLog_.emplace(logger_, id);
 }
 #else
 { }
 #endif
 
-BytecodeCompiler::BytecodeCompiler(JSContext* cx,
-                                   const ReadOnlyCompileOptions& options,
-                                   SourceBufferHolder& sourceBuffer)
+BytecodeCompiler::BytecodeCompiler(JSContext* cx, const ReadOnlyCompileOptions& options)
   : keepAtoms(cx),
     cx(cx),
     options(options),
-    sourceBuffer(sourceBuffer),
     sourceObject(cx),
-    scriptSource(nullptr),
     directives(options.strictOption),
     script(cx)
-{
-    MOZ_ASSERT(sourceBuffer.get());
-}
-
-bool
-BytecodeCompiler::checkLength()
-{
-    // Note this limit is simply so we can store sourceStart and sourceEnd in
-    // JSScript as 32-bits. It could be lifted fairly easily, since the compiler
-    // is using size_t internally already.
-    if (sourceBuffer.length() > UINT32_MAX) {
-        if (!cx->helperThread()) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                                      JSMSG_SOURCE_TOO_LONG);
-        }
-        return false;
-    }
-    return true;
-}
+{}
 
 bool
 BytecodeCompiler::createScriptSource(const Maybe<uint32_t>& parameterListEnd)
 {
-    if (!checkLength()) {
-        return false;
-    }
-
     sourceObject = CreateScriptSourceObject(cx, options, parameterListEnd);
     if (!sourceObject) {
         return false;
     }
 
     scriptSource = sourceObject->source();
+    return true;
+}
 
+template<typename Unit>
+bool
+BytecodeCompiler::assignSource(SourceText<Unit>& sourceBuffer)
+{
     if (!cx->realm()->behaviors().discardSource()) {
         if (options.sourceIsLazy) {
             scriptSource->setSourceRetrievable();
         } else if (!scriptSource->setSourceCopy(cx, sourceBuffer)) {
             return false;
         }
     }
 
     return true;
 }
 
 bool
-BytecodeCompiler::canLazilyParse()
+BytecodeCompiler::canLazilyParse() const
 {
     return options.canLazilyParse &&
            !cx->realm()->behaviors().disableLazyParsing() &&
            !cx->realm()->behaviors().discardSource() &&
            !options.sourceIsLazy &&
            !cx->lcovEnabled() &&
            // Disabled during record/replay. The replay debugger requires
            // scripts to be constructed in a consistent order, which might not
            // happen with lazy parsing.
            !mozilla::recordreplay::IsRecordingOrReplaying();
 }
 
+template<typename Unit>
 bool
-BytecodeCompiler::createParser(ParseGoal goal)
+frontend::SourceAwareCompiler<Unit>::createSourceAndParser(BytecodeCompiler& info, ParseGoal goal,
+                                                           const Maybe<uint32_t>& parameterListEnd /* = Nothing() */)
 {
-    usedNames.emplace(cx);
+    if (!info.createScriptSource(parameterListEnd)) {
+        return false;
+    }
 
-    if (canLazilyParse()) {
-        syntaxParser.emplace(cx, cx->tempLifoAlloc(), options,
-                             sourceBuffer.get(), sourceBuffer.length(),
-                             /* foldConstants = */ false, *usedNames, nullptr, nullptr,
-                             sourceObject, goal);
+    if (!info.assignSource(sourceBuffer_)) {
+        return false;
+    }
+
+    info.createUsedNames();
+
+    if (info.canLazilyParse()) {
+        syntaxParser.emplace(info.cx, info.cx->tempLifoAlloc(), info.options,
+                             sourceBuffer_.units(), sourceBuffer_.length(),
+                             /* foldConstants = */ false, *info.usedNames, nullptr, nullptr,
+                             info.sourceObject, goal);
         if (!syntaxParser->checkOptions()) {
             return false;
         }
     }
 
-    parser.emplace(cx, cx->tempLifoAlloc(), options, sourceBuffer.get(), sourceBuffer.length(),
-                   /* foldConstants = */ true, *usedNames, syntaxParser.ptrOr(nullptr), nullptr,
-                   sourceObject, goal);
-    parser->ss = scriptSource;
+    parser.emplace(info.cx, info.cx->tempLifoAlloc(), info.options,
+                   sourceBuffer_.units(), sourceBuffer_.length(), /* foldConstants = */ true,
+                   *info.usedNames, syntaxParser.ptrOr(nullptr), nullptr, info.sourceObject, goal);
+    parser->ss = info.scriptSource;
     return parser->checkOptions();
 }
 
 bool
-BytecodeCompiler::createSourceAndParser(ParseGoal goal,
-                                        const Maybe<uint32_t>& parameterListEnd /* = Nothing() */)
+BytecodeCompiler::internalCreateScript(uint32_t toStringStart, uint32_t toStringEnd,
+                                       uint32_t sourceBufferLength)
 {
-    return createScriptSource(parameterListEnd) &&
-           createParser(goal);
-}
-
-bool
-BytecodeCompiler::createCompleteScript()
-{
-    return createScript(0, sourceBuffer.length());
-}
-
-bool
-BytecodeCompiler::createScript(uint32_t toStringStart, uint32_t toStringEnd)
-{
-    script = JSScript::Create(cx, options,
-                              sourceObject, /* sourceStart = */ 0, sourceBuffer.length(),
+    script = JSScript::Create(cx, options, sourceObject, /* sourceStart = */ 0, sourceBufferLength,
                               toStringStart, toStringEnd);
     return script != nullptr;
 }
 
 bool
-BytecodeCompiler::emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext)
+BytecodeCompiler::emplaceEmitter(Maybe<BytecodeEmitter>& emitter,
+                                 const EitherParser& parser, SharedContext* sharedContext)
 {
     BytecodeEmitter::EmitterMode emitterMode =
         options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
-    emitter.emplace(/* parent = */ nullptr, parser.ptr(), sharedContext, script,
+    emitter.emplace(/* parent = */ nullptr, parser, sharedContext, script,
                     /* lazyScript = */ nullptr, options.lineno, emitterMode);
     return emitter->init();
 }
 
+template<typename Unit>
 bool
-BytecodeCompiler::handleParseFailure(const Directives& newDirectives,
-                                     TokenStreamPosition& startPosition)
+frontend::SourceAwareCompiler<Unit>::handleParseFailure(BytecodeCompiler& info,
+                                                        const Directives& newDirectives,
+                                                        TokenStreamPosition& startPosition)
 {
     if (parser->hadAbortedSyntaxParse()) {
         // Hit some unrecoverable ambiguity during an inner syntax parse.
         // Syntax parsing has now been disabled in the parser, so retry
         // the parse.
         parser->clearAbortedSyntaxParse();
-    } else if (parser->anyChars.hadError() || directives == newDirectives) {
+    } else if (parser->anyChars.hadError() || info.directives == newDirectives) {
         return false;
     }
 
     parser->tokenStream.seek(startPosition);
 
     // Assignment must be monotonic to prevent reparsing iloops
-    MOZ_ASSERT_IF(directives.strict(), newDirectives.strict());
-    MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS());
-    directives = newDirectives;
+    MOZ_ASSERT_IF(info.directives.strict(), newDirectives.strict());
+    MOZ_ASSERT_IF(info.directives.asmJS(), newDirectives.asmJS());
+    info.directives = newDirectives;
     return true;
 }
 
 bool
 BytecodeCompiler::deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment)
 {
     RootedObject env(cx, environment);
     while (env->is<EnvironmentObject>() || env->is<DebugEnvironmentProxy>()) {
@@ -352,28 +524,32 @@ BytecodeCompiler::deoptimizeArgumentsInE
             }
         }
         env = env->enclosingEnvironment();
     }
 
     return true;
 }
 
+template<typename Unit>
 JSScript*
-BytecodeCompiler::compileScript(HandleObject environment, SharedContext* sc)
+frontend::ScriptCompiler<Unit>::compileScript(BytecodeCompiler& info, HandleObject environment,
+                                              SharedContext* sc)
 {
-    assertSourceParserAndScriptCreated();
+    assertSourceParserAndScriptCreated(info);
 
-    TokenStreamPosition startPosition(keepAtoms, parser->tokenStream);
+    TokenStreamPosition startPosition(info.keepAtoms, parser->tokenStream);
 
     Maybe<BytecodeEmitter> emitter;
-    if (!emplaceEmitter(emitter, sc)) {
+    if (!emplaceEmitter(info, emitter, sc)) {
         return nullptr;
     }
 
+    JSContext* cx = info.cx;
+
     for (;;) {
         ParseNode* pn;
         {
             AutoGeckoProfilerEntry pseudoFrame(cx, "script parsing");
             if (sc->isEvalContext()) {
                 pn = parser->evalBody(sc->asEvalContext());
             } else {
                 pn = parser->globalBody(sc->asGlobalContext());
@@ -383,96 +559,77 @@ BytecodeCompiler::compileScript(HandleOb
         // Successfully parsed. Emit the script.
         AutoGeckoProfilerEntry pseudoFrame(cx, "script emit");
         if (pn) {
             if (sc->isEvalContext() && sc->hasDebuggerStatement() && !cx->helperThread()) {
                 // If the eval'ed script contains any debugger statement, force construction
                 // of arguments objects for the caller script and any other scripts it is
                 // transitively nested inside. The debugger can access any variable on the
                 // scope chain.
-                if (!deoptimizeArgumentsInEnclosingScripts(cx, environment)) {
+                if (!info.deoptimizeArgumentsInEnclosingScripts(cx, environment)) {
                     return nullptr;
                 }
             }
             if (!emitter->emitScript(pn)) {
                 return nullptr;
             }
             break;
         }
 
         // Maybe we aborted a syntax parse. See if we can try again.
-        if (!handleParseFailure(directives, startPosition)) {
+        if (!handleParseFailure(info, info.directives, startPosition)) {
             return nullptr;
         }
 
         // Reset UsedNameTracker state before trying again.
-        usedNames->reset();
+        info.usedNames->reset();
     }
 
     // We have just finished parsing the source. Inform the source so that we
     // can compute statistics (e.g. how much time our functions remain lazy).
-    script->scriptSource()->recordParseEnded();
+    info.script->scriptSource()->recordParseEnded();
 
     // Enqueue an off-thread source compression task after finishing parsing.
-    if (!scriptSource->tryCompressOffThread(cx)) {
+    if (!info.scriptSource->tryCompressOffThread(cx)) {
         return nullptr;
     }
 
     MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending());
 
-    return script;
+    return info.script;
 }
 
-JSScript*
-BytecodeCompiler::compileGlobalScript(ScopeKind scopeKind)
+template<typename Unit>
+ModuleObject*
+frontend::ModuleCompiler<Unit>::compile(ModuleInfo& info)
 {
-    GlobalSharedContext globalsc(cx, scopeKind, directives, options.extraWarningsOption);
-
-    if (!prepareScriptParse()) {
+    if (!createSourceAndParser(info, ParseGoal::Module) || !createCompleteScript(info)) {
         return nullptr;
     }
 
-    return compileScript(nullptr, &globalsc);
-}
-
-JSScript*
-BytecodeCompiler::compileEvalScript(HandleObject environment, HandleScope enclosingScope)
-{
-    EvalSharedContext evalsc(cx, environment, enclosingScope,
-                             directives, options.extraWarningsOption);
-
-    if (!prepareScriptParse()) {
-        return nullptr;
-    }
-
-    return compileScript(environment, &evalsc);
-}
-
-ModuleObject*
-BytecodeCompiler::compileModule(HandleScope enclosingScope)
-{
-    assertSourceParserAndScriptCreated();
+    JSContext* cx = info.cx;
 
     Rooted<ModuleObject*> module(cx, ModuleObject::create(cx));
     if (!module) {
         return nullptr;
     }
 
-    module->init(script);
+    module->init(info.script);
 
-    ModuleBuilder builder(cx, module, parser->anyChars);
+    ModuleBuilder builder(cx, module, parser.ptr());
 
+    RootedScope enclosingScope(cx, &cx->global()->emptyGlobalScope());
     ModuleSharedContext modulesc(cx, module, enclosingScope, builder);
     ParseNode* pn = parser->moduleBody(&modulesc);
     if (!pn) {
         return nullptr;
     }
 
     Maybe<BytecodeEmitter> emitter;
-    if (!emplaceEmitter(emitter, &modulesc)) {
+    if (!emplaceEmitter(info, emitter, &modulesc)) {
         return nullptr;
     }
     if (!emitter->emitScript(pn->as<CodeNode>().body())) {
         return nullptr;
     }
 
     if (!builder.initModule()) {
         return nullptr;
@@ -481,85 +638,90 @@ BytecodeCompiler::compileModule(HandleSc
     RootedModuleEnvironmentObject env(cx, ModuleEnvironmentObject::create(cx, module));
     if (!env) {
         return nullptr;
     }
 
     module->setInitialEnvironment(env);
 
     // Enqueue an off-thread source compression task after finishing parsing.
-    if (!scriptSource->tryCompressOffThread(cx)) {
+    if (!info.scriptSource->tryCompressOffThread(cx)) {
         return nullptr;
     }
 
     MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending());
     return module;
 }
 
 // Parse a standalone JS function, which might appear as the value of an
 // event handler attribute in an HTML <INPUT> tag, or in a Function()
 // constructor.
+template<typename Unit>
 CodeNode*
-BytecodeCompiler::parseStandaloneFunction(MutableHandleFunction fun,
-                                          GeneratorKind generatorKind,
-                                          FunctionAsyncKind asyncKind,
-                                          const Maybe<uint32_t>& parameterListEnd,
-                                          HandleScope enclosingScope)
+frontend::StandaloneFunctionCompiler<Unit>::parse(StandaloneFunctionInfo& info,
+                                                  HandleFunction fun,
+                                                  HandleScope enclosingScope,
+                                                  GeneratorKind generatorKind,
+                                                  FunctionAsyncKind asyncKind,
+                                                  const Maybe<uint32_t>& parameterListEnd)
 {
     MOZ_ASSERT(fun);
     MOZ_ASSERT(fun->isTenured());
 
-    assertSourceAndParserCreated();
+    assertSourceAndParserCreated(info);
 
-    TokenStreamPosition startPosition(keepAtoms, parser->tokenStream);
+    TokenStreamPosition startPosition(info.keepAtoms, parser->tokenStream);
 
     // Speculatively parse using the default directives implied by the context.
     // If a directive is encountered (e.g., "use strict") that changes how the
     // function should have been parsed, we backup and reparse with the new set
     // of directives.
 
     ParseNode* fn;
     do {
-        Directives newDirectives = directives;
+        Directives newDirectives = info.directives;
         fn = parser->standaloneFunction(fun, enclosingScope, parameterListEnd, generatorKind,
-                                        asyncKind, directives, &newDirectives);
-        if (!fn && !handleParseFailure(newDirectives, startPosition)) {
+                                        asyncKind, info.directives, &newDirectives);
+        if (!fn && !handleParseFailure(info, newDirectives, startPosition)) {
             return nullptr;
         }
     } while (!fn);
 
     return &fn->as<CodeNode>();
 }
 
 // Compile a standalone JS function.
+template<typename Unit>
 bool
-BytecodeCompiler::compileStandaloneFunction(CodeNode* parsedFunction, MutableHandleFunction fun)
+frontend::StandaloneFunctionCompiler<Unit>::compile(MutableHandleFunction fun,
+                                                    StandaloneFunctionInfo& info,
+                                                    CodeNode* parsedFunction)
 {
     FunctionBox* funbox = parsedFunction->funbox();
     if (funbox->function()->isInterpreted()) {
         MOZ_ASSERT(fun == funbox->function());
 
-        if (!createScript(funbox->toStringStart, funbox->toStringEnd)) {
+        if (!createFunctionScript(info, funbox->toStringStart, funbox->toStringEnd)) {
             return false;
         }
 
         Maybe<BytecodeEmitter> emitter;
-        if (!emplaceEmitter(emitter, funbox)) {
+        if (!emplaceEmitter(info, emitter, funbox)) {
             return false;
         }
         if (!emitter->emitFunctionScript(parsedFunction, BytecodeEmitter::TopLevelFunction::Yes)) {
             return false;
         }
     } else {
         fun.set(funbox->function());
         MOZ_ASSERT(IsAsmJSModule(fun));
     }
 
     // Enqueue an off-thread source compression task after finishing parsing.
-    return scriptSource->tryCompressOffThread(cx);
+    return info.scriptSource->tryCompressOffThread(info.cx);
 }
 
 ScriptSourceObject*
 frontend::CreateScriptSourceObject(JSContext* cx, const ReadOnlyCompileOptions& options,
                                    const Maybe<uint32_t>& parameterListEnd /* = Nothing() */)
 {
     ScriptSource* ss = cx->new_<ScriptSource>();
     if (!ss) {
@@ -589,105 +751,16 @@ frontend::CreateScriptSourceObject(JSCon
         if (!ScriptSourceObject::initFromOptions(cx, sso, options)) {
             return nullptr;
         }
     }
 
     return sso;
 }
 
-// CompileScript independently returns the ScriptSourceObject (SSO) for the
-// compile.  This is used by off-thread script compilation (OT-SC).
-//
-// OT-SC cannot initialize the SSO when it is first constructed because the
-// SSO is allocated initially in a separate compartment.
-//
-// After OT-SC, the separate compartment is merged with the main compartment,
-// at which point the JSScripts created become observable by the debugger via
-// memory-space scanning.
-//
-// Whatever happens to the top-level script compilation (even if it fails and
-// returns null), we must finish initializing the SSO.  This is because there
-// may be valid inner scripts observable by the debugger which reference the
-// partially-initialized SSO.
-class MOZ_STACK_CLASS AutoInitializeSourceObject
-{
-    BytecodeCompiler& compiler_;
-    ScriptSourceObject** sourceObjectOut_;
-
-  public:
-    AutoInitializeSourceObject(BytecodeCompiler& compiler,
-                               ScriptSourceObject** sourceObjectOut)
-      : compiler_(compiler),
-        sourceObjectOut_(sourceObjectOut)
-    { }
-
-    ~AutoInitializeSourceObject() {
-        if (sourceObjectOut_) {
-            *sourceObjectOut_ = compiler_.sourceObjectPtr();
-        }
-    }
-};
-
-// RAII class to check the frontend reports an exception when it fails to
-// compile a script.
-class MOZ_RAII AutoAssertReportedException
-{
-#ifdef DEBUG
-    JSContext* cx_;
-    bool check_;
-
-  public:
-    explicit AutoAssertReportedException(JSContext* cx)
-      : cx_(cx),
-        check_(true)
-    {}
-    void reset() {
-        check_ = false;
-    }
-    ~AutoAssertReportedException() {
-        if (!check_) {
-            return;
-        }
-
-        if (!cx_->helperThread()) {
-            MOZ_ASSERT(cx_->isExceptionPending());
-            return;
-        }
-
-        ParseTask* task = cx_->helperThread()->parseTask();
-        MOZ_ASSERT(task->outOfMemory ||
-                   task->overRecursed ||
-                   !task->errors.empty());
-    }
-#else
-  public:
-    explicit AutoAssertReportedException(JSContext*) {}
-    void reset() {}
-#endif
-};
-
-JSScript*
-frontend::CompileGlobalScript(JSContext* cx, ScopeKind scopeKind,
-                              const ReadOnlyCompileOptions& options,
-                              SourceBufferHolder& srcBuf,
-                              ScriptSourceObject** sourceObjectOut)
-{
-    MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
-    AutoAssertReportedException assertException(cx);
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
-    JSScript* script = compiler.compileGlobalScript(scopeKind);
-    if (!script) {
-        return nullptr;
-    }
-    assertException.reset();
-    return script;
-}
-
 #if defined(JS_BUILD_BINAST)
 
 JSScript*
 frontend::CompileGlobalBinASTScript(JSContext* cx, LifoAlloc& alloc, const ReadOnlyCompileOptions& options,
                                     const uint8_t* src, size_t len, ScriptSourceObject** sourceObjectOut)
 {
     AutoAssertReportedException assertException(cx);
 
@@ -741,70 +814,54 @@ frontend::CompileGlobalBinASTScript(JSCo
     }
 
     assertException.reset();
     return script;
 }
 
 #endif // JS_BUILD_BINAST
 
-JSScript*
-frontend::CompileEvalScript(JSContext* cx, HandleObject environment,
-                            HandleScope enclosingScope,
-                            const ReadOnlyCompileOptions& options,
-                            SourceBufferHolder& srcBuf,
-                            ScriptSourceObject** sourceObjectOut)
-{
-    AutoAssertReportedException assertException(cx);
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
-    JSScript* script = compiler.compileEvalScript(environment, enclosingScope);
-    if (!script) {
-        return nullptr;
-    }
-    assertException.reset();
-    return script;
-
-}
-
-ModuleObject*
-frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput,
-                        SourceBufferHolder& srcBuf,
-                        ScriptSourceObject** sourceObjectOut)
+template<typename Unit>
+static ModuleObject*
+CreateModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput,
+             SourceText<Unit>& srcBuf, ScriptSourceObject** sourceObjectOut)
 {
     MOZ_ASSERT(srcBuf.get());
     MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr);
 
     AutoAssertReportedException assertException(cx);
 
     CompileOptions options(cx, optionsInput);
     options.maybeMakeStrictMode(true); // ES6 10.2.1 Module code is always strict mode code.
     options.setIsRunOnce(true);
     options.allowHTMLComments = false;
 
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
+    ModuleInfo info(cx, options);
+    AutoInitializeSourceObject autoSSO(info, sourceObjectOut);
 
-    if (!compiler.prepareModuleParse()) {
-        return nullptr;
-    }
-
-    RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
-    ModuleObject* module = compiler.compileModule(emptyGlobalScope);
+    ModuleCompiler<char16_t> compiler(srcBuf);
+    ModuleObject* module = compiler.compile(info);
     if (!module) {
         return nullptr;
     }
 
     assertException.reset();
     return module;
 }
 
 ModuleObject*
-frontend::CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options,
-                        SourceBufferHolder& srcBuf)
+frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput,
+                        SourceText<char16_t>& srcBuf, ScriptSourceObject** sourceObjectOut)
+{
+    return CreateModule(cx, optionsInput, srcBuf, sourceObjectOut);
+}
+
+template<typename Unit>
+static ModuleObject*
+CreateModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options, SourceText<Unit>& srcBuf)
 {
     AutoAssertReportedException assertException(cx);
 
     if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global())) {
         return nullptr;
     }
 
     RootedModuleObject module(cx, CompileModule(cx, options, srcBuf, nullptr));
@@ -817,16 +874,23 @@ frontend::CompileModule(JSContext* cx, c
     if (!ModuleObject::Freeze(cx, module)) {
         return nullptr;
     }
 
     assertException.reset();
     return module;
 }
 
+ModuleObject*
+frontend::CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+                        SourceText<char16_t>& srcBuf)
+{
+    return CreateModule(cx, options, srcBuf);
+}
+
 // When leaving this scope, the given function should either:
 //   * be linked to a fully compiled script
 //   * remain linking to a lazy script
 class MOZ_STACK_CLASS AutoAssertFunctionDelazificationCompletion
 {
 #ifdef DEBUG
     RootedFunction fun_;
 #endif
@@ -860,18 +924,19 @@ class MOZ_STACK_CLASS AutoAssertFunction
         MOZ_ASSERT(!fun_->hasUncompletedScript());
 
 #ifdef DEBUG
         fun_ = nullptr;
 #endif
     }
 };
 
-bool
-frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length)
+template<typename Unit>
+static bool
+CompileLazyFunctionImpl(JSContext* cx, Handle<LazyScript*> lazy, const Unit* units, size_t length)
 {
     MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment());
 
     // We can only compile functions whose parents have previously been
     // compiled, because compilation requires full information about the
     // function's immediately enclosing scope.
     MOZ_ASSERT(lazy->enclosingScriptHasEverBeenCompiled());
 
@@ -905,19 +970,19 @@ frontend::CompileLazyFunction(JSContext*
             ? JS_TELEMETRY_PRIVILEGED_PARSER_COMPILE_LAZY_AFTER_MS
             : JS_TELEMETRY_WEB_PARSER_COMPILE_LAZY_AFTER_MS;
         cx->runtime()->addTelemetry(HISTOGRAM, delta.ToMilliseconds());
     }
 
     UsedNameTracker usedNames(cx);
 
     RootedScriptSourceObject sourceObject(cx, &lazy->sourceObject());
-    Parser<FullParseHandler, char16_t> parser(cx, cx->tempLifoAlloc(), options, chars, length,
-                                              /* foldConstants = */ true, usedNames, nullptr,
-                                              lazy, sourceObject, lazy->parseGoal());
+    Parser<FullParseHandler, Unit> parser(cx, cx->tempLifoAlloc(), options, units, length,
+                                          /* foldConstants = */ true, usedNames, nullptr,
+                                          lazy, sourceObject, lazy->parseGoal());
     if (!parser.checkOptions()) {
         return false;
     }
 
     ParseNode* pn = parser.standaloneLazyFunction(fun, lazy->toStringStart(),
                                                   lazy->strict(), lazy->generatorKind(),
                                                   lazy->asyncKind());
     if (!pn) {
@@ -948,16 +1013,30 @@ frontend::CompileLazyFunction(JSContext*
         return false;
     }
 
     delazificationCompletion.complete();
     assertException.reset();
     return true;
 }
 
+bool
+frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy,
+                              const char16_t* units, size_t length)
+{
+    return CompileLazyFunctionImpl(cx, lazy, units, length);
+}
+
+bool
+frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy,
+                              const Utf8Unit* units, size_t length)
+{
+    return CompileLazyFunctionImpl(cx, lazy, units, length);
+}
+
 #ifdef JS_BUILD_BINAST
 
 bool
 frontend::CompileLazyBinASTFunction(JSContext* cx, Handle<LazyScript*> lazy, const uint8_t* buf, size_t length)
 {
     MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment());
 
     // We can only compile functions whose parents have previously been
@@ -1022,112 +1101,116 @@ frontend::CompileLazyBinASTFunction(JSCo
     return script;
 }
 
 #endif // JS_BUILD_BINAST
 
 bool
 frontend::CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun,
                                     const JS::ReadOnlyCompileOptions& options,
-                                    JS::SourceBufferHolder& srcBuf,
+                                    JS::SourceText<char16_t>& srcBuf,
                                     const Maybe<uint32_t>& parameterListEnd,
                                     HandleScope enclosingScope /* = nullptr */)
 {
     AutoAssertReportedException assertException(cx);
 
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) {
+    StandaloneFunctionInfo info(cx, options);
+
+    StandaloneFunctionCompiler<char16_t> compiler(srcBuf);
+    if (!compiler.prepare(info, parameterListEnd)) {
         return false;
     }
 
     RootedScope scope(cx, enclosingScope);
     if (!scope) {
         scope = &cx->global()->emptyGlobalScope();
     }
 
-    CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::NotGenerator,
-                                                                FunctionAsyncKind::SyncFunction,
-                                                                parameterListEnd, scope);
-    if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) {
+    CodeNode* parsedFunction = compiler.parse(info ,fun, scope, GeneratorKind::NotGenerator,
+                                              FunctionAsyncKind::SyncFunction, parameterListEnd);
+    if (!parsedFunction || !compiler.compile(fun, info, parsedFunction)) {
         return false;
     }
 
     assertException.reset();
     return true;
 }
 
 bool
 frontend::CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun,
                                      const JS::ReadOnlyCompileOptions& options,
-                                     JS::SourceBufferHolder& srcBuf,
+                                     JS::SourceText<char16_t>& srcBuf,
                                      const Maybe<uint32_t>& parameterListEnd)
 {
     AutoAssertReportedException assertException(cx);
 
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) {
+    StandaloneFunctionInfo info(cx, options);
+
+    StandaloneFunctionCompiler<char16_t> compiler(srcBuf);
+    if (!compiler.prepare(info, parameterListEnd)) {
         return false;
     }
 
     RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
-    CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::Generator,
-                                                                FunctionAsyncKind::SyncFunction,
-                                                                parameterListEnd,
-                                                                emptyGlobalScope);
-    if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) {
+    CodeNode* parsedFunction =
+        compiler.parse(info, fun, emptyGlobalScope, GeneratorKind::Generator,
+                       FunctionAsyncKind::SyncFunction, parameterListEnd);
+    if (!parsedFunction || !compiler.compile(fun, info, parsedFunction)) {
         return false;
     }
 
     assertException.reset();
     return true;
 }
 
 bool
 frontend::CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun,
                                          const ReadOnlyCompileOptions& options,
-                                         JS::SourceBufferHolder& srcBuf,
+                                         JS::SourceText<char16_t>& srcBuf,
                                          const Maybe<uint32_t>& parameterListEnd)
 {
     AutoAssertReportedException assertException(cx);
 
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) {
+    StandaloneFunctionInfo info(cx, options);
+
+    StandaloneFunctionCompiler<char16_t> compiler(srcBuf);
+    if (!compiler.prepare(info, parameterListEnd)) {
         return false;
     }
 
     RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
-    CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::NotGenerator,
-                                                                FunctionAsyncKind::AsyncFunction,
-                                                                parameterListEnd,
-                                                                emptyGlobalScope);
-    if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) {
+    CodeNode* parsedFunction =
+        compiler.parse(info, fun, emptyGlobalScope, GeneratorKind::NotGenerator,
+                       FunctionAsyncKind::AsyncFunction, parameterListEnd);
+    if (!parsedFunction || !compiler.compile(fun, info, parsedFunction)) {
         return false;
     }
 
     assertException.reset();
     return true;
 }
 
 bool
 frontend::CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun,
                                           const ReadOnlyCompileOptions& options,
-                                          JS::SourceBufferHolder& srcBuf,
+                                          JS::SourceText<char16_t>& srcBuf,
                                           const Maybe<uint32_t>& parameterListEnd)
 {
     AutoAssertReportedException assertException(cx);
 
-    BytecodeCompiler compiler(cx, options, srcBuf);
-    if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) {
+    StandaloneFunctionInfo info(cx, options);
+
+    StandaloneFunctionCompiler<char16_t> compiler(srcBuf);
+    if (!compiler.prepare(info, parameterListEnd)) {
         return false;
     }
 
     RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
-    CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::Generator,
-                                                                FunctionAsyncKind::AsyncFunction,
-                                                                parameterListEnd,
-                                                                emptyGlobalScope);
-    if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) {
+    CodeNode* parsedFunction =
+        compiler.parse(info, fun, emptyGlobalScope, GeneratorKind::Generator,
+                       FunctionAsyncKind::AsyncFunction, parameterListEnd);
+    if (!parsedFunction || !compiler.compile(fun, info, parsedFunction)) {
         return false;
     }
 
     assertException.reset();
     return true;
 }
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -7,16 +7,17 @@
 #ifndef frontend_BytecodeCompiler_h
 #define frontend_BytecodeCompiler_h
 
 #include "mozilla/Maybe.h"
 
 #include "NamespaceImports.h"
 
 #include "js/CompileOptions.h"
+#include "js/SourceText.h"
 #include "vm/Scope.h"
 #include "vm/TraceLogging.h"
 
 class JSLinearString;
 
 namespace js {
 
 class LazyScript;
@@ -24,89 +25,73 @@ class ModuleObject;
 class ScriptSourceObject;
 
 namespace frontend {
 
 class ErrorReporter;
 class FunctionBox;
 class ParseNode;
 
-JSScript*
-CompileGlobalScript(JSContext* cx, ScopeKind scopeKind,
-                    const JS::ReadOnlyCompileOptions& options,
-                    JS::SourceBufferHolder& srcBuf,
-                    ScriptSourceObject** sourceObjectOut = nullptr);
-
 #if defined(JS_BUILD_BINAST)
 
 JSScript*
 CompileGlobalBinASTScript(JSContext *cx, LifoAlloc& alloc,
                           const JS::ReadOnlyCompileOptions& options,
                           const uint8_t* src, size_t len,
                           ScriptSourceObject** sourceObjectOut = nullptr);
 
 MOZ_MUST_USE bool
 CompileLazyBinASTFunction(JSContext* cx, Handle<LazyScript*> lazy, const uint8_t* buf, size_t length);
 
 #endif // JS_BUILD_BINAST
 
-JSScript*
-CompileEvalScript(JSContext* cx, HandleObject environment,
-                  HandleScope enclosingScope,
-                  const JS::ReadOnlyCompileOptions& options,
-                  JS::SourceBufferHolder& srcBuf,
-                  ScriptSourceObject** sourceObjectOut = nullptr);
+ModuleObject*
+CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+              JS::SourceText<char16_t>& srcBuf);
 
 ModuleObject*
 CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options,
-              JS::SourceBufferHolder& srcBuf);
-
-ModuleObject*
-CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options,
-              JS::SourceBufferHolder& srcBuf,
+              JS::SourceText<char16_t>& srcBuf,
               ScriptSourceObject** sourceObjectOut);
 
-MOZ_MUST_USE bool
-CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length);
-
 //
 // Compile a single function. The source in srcBuf must match the ECMA-262
 // FunctionExpression production.
 //
 // If nonzero, parameterListEnd is the offset within srcBuf where the parameter
 // list is expected to end. During parsing, if we find that it ends anywhere
 // else, it's a SyntaxError. This is used to implement the Function constructor;
 // it's how we detect that these weird cases are SyntaxErrors:
 //
 //     Function("/*", "*/x) {")
 //     Function("x){ if (3", "return x;}")
 //
 MOZ_MUST_USE bool
 CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun,
                           const JS::ReadOnlyCompileOptions& options,
-                          JS::SourceBufferHolder& srcBuf,
+                          JS::SourceText<char16_t>& srcBuf,
                           const mozilla::Maybe<uint32_t>& parameterListEnd,
                           HandleScope enclosingScope = nullptr);
 
 MOZ_MUST_USE bool
 CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun,
                            const JS::ReadOnlyCompileOptions& options,
-                           JS::SourceBufferHolder& srcBuf,
+                           JS::SourceText<char16_t>& srcBuf,
                            const mozilla::Maybe<uint32_t>& parameterListEnd);
 
 MOZ_MUST_USE bool
 CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun,
                                const JS::ReadOnlyCompileOptions& options,
-                               JS::SourceBufferHolder& srcBuf,
+                               JS::SourceText<char16_t>& srcBuf,
                                const mozilla::Maybe<uint32_t>& parameterListEnd);
 
 MOZ_MUST_USE bool
 CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun,
                                 const JS::ReadOnlyCompileOptions& options,
-                                JS::SourceBufferHolder& srcBuf,
+                                JS::SourceText<char16_t>& srcBuf,
                                 const mozilla::Maybe<uint32_t>& parameterListEnd);
 
 ScriptSourceObject*
 CreateScriptSourceObject(JSContext* cx, const JS::ReadOnlyCompileOptions& options,
                          const mozilla::Maybe<uint32_t>& parameterListEnd = mozilla::Nothing());
 
 /*
  * True if str consists of an IdentifierStart character, followed by one or
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -30,16 +30,17 @@
 #include "frontend/DoWhileEmitter.h"
 #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/ModuleSharedContext.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"
--- a/js/src/frontend/EitherParser.h
+++ b/js/src/frontend/EitherParser.h
@@ -11,16 +11,17 @@
 
 #ifndef frontend_EitherParser_h
 #define frontend_EitherParser_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Move.h"
 #include "mozilla/Tuple.h"
 #include "mozilla/TypeTraits.h"
+#include "mozilla/Utf8.h"
 #include "mozilla/Variant.h"
 
 #include <utility>
 
 #include "frontend/BCEParserHandle.h"
 #include "frontend/Parser.h"
 #include "frontend/TokenStream.h"
 
@@ -94,21 +95,29 @@ struct ParserNewObjectBox
     }
 };
 
 // Generic matchers.
 
 struct ParseHandlerMatcher
 {
     template<class Parser>
-    frontend::FullParseHandler& match(Parser *parser) {
+    frontend::FullParseHandler& match(Parser* parser) {
         return parser->handler;
     }
 };
 
+struct AnyCharsMatcher
+{
+    template<class Parser>
+    frontend::TokenStreamAnyChars& match(Parser* parser) {
+        return parser->anyChars;
+    }
+};
+
 struct ParserBaseMatcher
 {
     template<class Parser>
     frontend::ParserBase& match(Parser* parser) {
         return *static_cast<frontend::ParserBase*>(parser);
     }
 };
 
@@ -122,17 +131,18 @@ struct ErrorReporterMatcher
 
 } // namespace detail
 
 namespace frontend {
 
 class EitherParser : public BCEParserHandle
 {
     // Leave this as a variant, to promote good form until 8-bit parser integration.
-    mozilla::Variant<Parser<FullParseHandler, char16_t>* const> parser;
+    mozilla::Variant<Parser<FullParseHandler, char16_t>* const,
+                     Parser<FullParseHandler, mozilla::Utf8Unit>* const> parser;
 
     using Node = typename FullParseHandler::Node;
 
     template<template <class Parser> class GetThis,
              template <class This> class GetMemberFunction,
              typename... StoredArgs>
     using InvokeMemberFunction =
         detail::InvokeMemberFunction<GetThis, GetMemberFunction, StoredArgs...>;
@@ -159,14 +169,21 @@ class EitherParser : public BCEParserHan
 
     ObjectBox* newObjectBox(JSObject* obj) final {
         InvokeMemberFunction<detail::GetParser, detail::ParserNewObjectBox,
                              JSObject*>
             matcher { obj };
         return parser.match(std::move(matcher));
     }
 
+    const TokenStreamAnyChars& anyChars() const {
+        return parser.match(detail::AnyCharsMatcher());
+    }
+
+    void computeLineAndColumn(uint32_t offset, uint32_t* line, uint32_t* column) const {
+        return anyChars().lineAndColumnAt(offset, line, column);
+    }
 };
 
 } /* namespace frontend */
 } /* namespace js */
 
 #endif /* frontend_EitherParser_h */
--- a/js/src/frontend/EmitterScope.cpp
+++ b/js/src/frontend/EmitterScope.cpp
@@ -2,16 +2,17 @@
  * 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/EmitterScope.h"
 
 #include "frontend/BytecodeEmitter.h"
+#include "frontend/ModuleSharedContext.h"
 #include "frontend/TDZCheckCache.h"
 
 #include "vm/GlobalObject.h"
 
 using namespace js;
 using namespace js::frontend;
 
 using mozilla::DebugOnly;
copy from js/src/frontend/SharedContext.h
copy to js/src/frontend/ModuleSharedContext.h
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/ModuleSharedContext.h
@@ -1,628 +1,48 @@
 /* -*- 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_SharedContext_h
-#define frontend_SharedContext_h
-
-#include "jspubtd.h"
-#include "jstypes.h"
+#ifndef frontend_ModuleSharedContext_h
+#define frontend_ModuleSharedContext_h
 
-#include "builtin/ModuleObject.h"
-#include "ds/InlineTable.h"
-#include "frontend/ParseNode.h"
-#include "frontend/TokenStream.h"
-#include "vm/BytecodeUtil.h"
-#include "vm/JSFunction.h"
-#include "vm/JSScript.h"
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+
+#include "builtin/ModuleObject.h" // js::Module{Builder,Object}
+#include "frontend/SharedContext.h" // js::frontend::SharedContext
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "vm/Scope.h" // js::{Module,}Scope
 
 namespace js {
 namespace frontend {
 
-class ParseContext;
-class ParseNode;
-
-enum class StatementKind : uint8_t
-{
-    Label,
-    Block,
-    If,
-    Switch,
-    With,
-    Catch,
-    Try,
-    Finally,
-    ForLoopLexicalHead,
-    ForLoop,
-    ForInLoop,
-    ForOfLoop,
-    DoLoop,
-    WhileLoop,
-    Class,
-
-    // Used only by BytecodeEmitter.
-    Spread
-};
-
-static inline bool
-StatementKindIsLoop(StatementKind kind)
-{
-    return kind == StatementKind::ForLoop ||
-           kind == StatementKind::ForInLoop ||
-           kind == StatementKind::ForOfLoop ||
-           kind == StatementKind::DoLoop ||
-           kind == StatementKind::WhileLoop ||
-           kind == StatementKind::Spread;
-}
-
-static inline bool
-StatementKindIsUnlabeledBreakTarget(StatementKind kind)
-{
-    return StatementKindIsLoop(kind) || kind == StatementKind::Switch;
-}
-
-// List of directives that may be encountered in a Directive Prologue (ES5 15.1).
-class Directives
-{
-    bool strict_;
-    bool asmJS_;
-
-  public:
-    explicit Directives(bool strict) : strict_(strict), asmJS_(false) {}
-    explicit Directives(ParseContext* parent);
-
-    void setStrict() { strict_ = true; }
-    bool strict() const { return strict_; }
-
-    void setAsmJS() { asmJS_ = true; }
-    bool asmJS() const { return asmJS_; }
-
-    Directives& operator=(Directives rhs) {
-        strict_ = rhs.strict_;
-        asmJS_ = rhs.asmJS_;
-        return *this;
-    }
-    bool operator==(const Directives& rhs) const {
-        return strict_ == rhs.strict_ && asmJS_ == rhs.asmJS_;
-    }
-    bool operator!=(const Directives& rhs) const {
-        return !(*this == rhs);
-    }
-};
-
-// The kind of this-binding for the current scope. Note that arrow functions
-// have a lexical this-binding so their ThisBinding is the same as the
-// ThisBinding of their enclosing scope and can be any value.
-enum class ThisBinding : uint8_t { Global, Function, Module };
-
-class GlobalSharedContext;
-class EvalSharedContext;
-class ModuleSharedContext;
-
-/*
- * The struct SharedContext is part of the current parser context (see
- * ParseContext). It stores information that is reused between the parser and
- * the bytecode emitter.
- */
-class SharedContext
+class MOZ_STACK_CLASS ModuleSharedContext
+  : public SharedContext
 {
-  public:
-    JSContext* const context;
-
-  protected:
-    enum class Kind : uint8_t {
-        FunctionBox,
-        Global,
-        Eval,
-        Module
-    };
-
-    Kind kind_;
-
-    ThisBinding thisBinding_;
-
-  public:
-    bool strictScript:1;
-    bool localStrict:1;
-    bool extraWarnings:1;
-
-  protected:
-    bool allowNewTarget_:1;
-    bool allowSuperProperty_:1;
-    bool allowSuperCall_:1;
-    bool inWith_:1;
-    bool needsThisTDZChecks_:1;
-
-    // True if "use strict"; appears in the body instead of being inherited.
-    bool hasExplicitUseStrict_:1;
-
-    // The (static) bindings of this script need to support dynamic name
-    // read/write access. Here, 'dynamic' means dynamic dictionary lookup on
-    // the scope chain for a dynamic set of keys. The primary examples are:
-    //  - direct eval
-    //  - function::
-    //  - with
-    // since both effectively allow any name to be accessed. Non-examples are:
-    //  - upvars of nested functions
-    //  - function statement
-    // since the set of assigned name is known dynamically.
-    //
-    // Note: access through the arguments object is not considered dynamic
-    // binding access since it does not go through the normal name lookup
-    // mechanism. This is debatable and could be changed (although care must be
-    // taken not to turn off the whole 'arguments' optimization). To answer the
-    // more general "is this argument aliased" question, script->needsArgsObj
-    // should be tested (see JSScript::argIsAliased).
-    bool bindingsAccessedDynamically_:1;
-
-    // Whether this script, or any of its inner scripts contains a debugger
-    // statement which could potentially read or write anywhere along the
-    // scope chain.
-    bool hasDebuggerStatement_:1;
-
-    // A direct eval occurs in the body of the script.
-    bool hasDirectEval_:1;
-
-    void computeAllowSyntax(Scope* scope);
-    void computeInWith(Scope* scope);
-    void computeThisBinding(Scope* scope);
-
-  public:
-    SharedContext(JSContext* cx, Kind kind, Directives directives, bool extraWarnings)
-      : context(cx),
-        kind_(kind),
-        thisBinding_(ThisBinding::Global),
-        strictScript(directives.strict()),
-        localStrict(false),
-        extraWarnings(extraWarnings),
-        allowNewTarget_(false),
-        allowSuperProperty_(false),
-        allowSuperCall_(false),
-        inWith_(false),
-        needsThisTDZChecks_(false),
-        hasExplicitUseStrict_(false),
-        bindingsAccessedDynamically_(false),
-        hasDebuggerStatement_(false),
-        hasDirectEval_(false)
-    { }
-
-    // If this is the outermost SharedContext, the Scope that encloses
-    // it. Otherwise nullptr.
-    virtual Scope* compilationEnclosingScope() const = 0;
-
-    bool isFunctionBox() const { return kind_ == Kind::FunctionBox; }
-    inline FunctionBox* asFunctionBox();
-    bool isModuleContext() const { return kind_ == Kind::Module; }
-    inline ModuleSharedContext* asModuleContext();
-    bool isGlobalContext() const { return kind_ == Kind::Global; }
-    inline GlobalSharedContext* asGlobalContext();
-    bool isEvalContext() const { return kind_ == Kind::Eval; }
-    inline EvalSharedContext* asEvalContext();
-
-    ThisBinding thisBinding()          const { return thisBinding_; }
-
-    bool allowNewTarget()              const { return allowNewTarget_; }
-    bool allowSuperProperty()          const { return allowSuperProperty_; }
-    bool allowSuperCall()              const { return allowSuperCall_; }
-    bool inWith()                      const { return inWith_; }
-    bool needsThisTDZChecks()          const { return needsThisTDZChecks_; }
-
-    bool hasExplicitUseStrict()        const { return hasExplicitUseStrict_; }
-    bool bindingsAccessedDynamically() const { return bindingsAccessedDynamically_; }
-    bool hasDebuggerStatement()        const { return hasDebuggerStatement_; }
-    bool hasDirectEval()               const { return hasDirectEval_; }
-
-    void setExplicitUseStrict()           { hasExplicitUseStrict_        = true; }
-    void setBindingsAccessedDynamically() { bindingsAccessedDynamically_ = true; }
-    void setHasDebuggerStatement()        { hasDebuggerStatement_        = true; }
-    void setHasDirectEval()               { hasDirectEval_               = true; }
-
-    inline bool allBindingsClosedOver();
-
-    bool strict() const {
-        return strictScript || localStrict;
-    }
-    bool setLocalStrictMode(bool strict) {
-        bool retVal = localStrict;
-        localStrict = strict;
-        return retVal;
-    }
-
-    // JSOPTION_EXTRA_WARNINGS warnings or strict mode errors.
-    bool needStrictChecks() const {
-        return strict() || extraWarnings;
-    }
-};
-
-class MOZ_STACK_CLASS GlobalSharedContext : public SharedContext
-{
-    ScopeKind scopeKind_;
-
-  public:
-    Rooted<GlobalScope::Data*> bindings;
-
-    GlobalSharedContext(JSContext* cx, ScopeKind scopeKind, Directives directives,
-                        bool extraWarnings)
-      : SharedContext(cx, Kind::Global, directives, extraWarnings),
-        scopeKind_(scopeKind),
-        bindings(cx)
-    {
-        MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
-        thisBinding_ = ThisBinding::Global;
-    }
-
-    Scope* compilationEnclosingScope() const override {
-        return nullptr;
-    }
-
-    ScopeKind scopeKind() const {
-        return scopeKind_;
-    }
-};
-
-inline GlobalSharedContext*
-SharedContext::asGlobalContext()
-{
-    MOZ_ASSERT(isGlobalContext());
-    return static_cast<GlobalSharedContext*>(this);
-}
-
-class MOZ_STACK_CLASS EvalSharedContext : public SharedContext
-{
-    RootedScope enclosingScope_;
+    JS::Rooted<ModuleObject*> module_;
+    JS::Rooted<Scope*> enclosingScope_;
 
   public:
-    Rooted<EvalScope::Data*> bindings;
-
-    EvalSharedContext(JSContext* cx, JSObject* enclosingEnv, Scope* enclosingScope,
-                      Directives directives, bool extraWarnings);
-
-    Scope* compilationEnclosingScope() const override {
-        return enclosingScope_;
-    }
-};
-
-inline EvalSharedContext*
-SharedContext::asEvalContext()
-{
-    MOZ_ASSERT(isEvalContext());
-    return static_cast<EvalSharedContext*>(this);
-}
-
-class FunctionBox : public ObjectBox, public SharedContext
-{
-    // The parser handles tracing the fields below via the ObjectBox linked
-    // list.
-
-    // This field is used for two purposes:
-    //   * If this FunctionBox refers to the function being compiled, this field
-    //     holds its enclosing scope, used for compilation.
-    //   * If this FunctionBox refers to a lazy child of the function being
-    //     compiled, this field holds the child's immediately enclosing scope.
-    //     Once compilation succeeds, we will store it in the child's
-    //     LazyScript.  (Debugger may become confused if LazyScripts refer to
-    //     partially initialized enclosing scopes, so we must avoid storing the
-    //     scope in the LazyScript until compilation has completed
-    //     successfully.)
-    Scope* enclosingScope_;
-
-    // Names from the named lambda scope, if a named lambda.
-    LexicalScope::Data* namedLambdaBindings_;
-
-    // Names from the function scope.
-    FunctionScope::Data* functionScopeBindings_;
-
-    // Names from the extra 'var' scope of the function, if the parameter list
-    // has expressions.
-    VarScope::Data* extraVarScopeBindings_;
-
-    void initWithEnclosingScope(Scope* enclosingScope);
-
-  public:
-    CodeNode*      functionNode;            /* back pointer used by asm.js for error messages */
-    uint32_t        bufStart;
-    uint32_t        bufEnd;
-    uint32_t        startLine;
-    uint32_t        startColumn;
-    uint32_t        toStringStart;
-    uint32_t        toStringEnd;
-    uint16_t        length;
-
-    bool            isGenerator_:1;         /* generator function or async generator */
-    bool            isAsync_:1;             /* async function or async generator */
-    bool            hasDestructuringArgs:1; /* parameter list contains destructuring expression */
-    bool            hasParameterExprs:1;    /* parameter list contains expressions */
-    bool            hasDirectEvalInParameterExpr:1; /* parameter list contains direct eval */
-    bool            hasDuplicateParameters:1; /* parameter list contains duplicate names */
-    bool            useAsm:1;               /* see useAsmOrInsideUseAsm */
-    bool            isAnnexB:1;             /* need to emit a synthesized Annex B assignment */
-    bool            wasEmitted:1;           /* Bytecode has been emitted for this function. */
-
-    // Fields for use in heuristics.
-    bool            declaredArguments:1;    /* the Parser declared 'arguments' */
-    bool            usesArguments:1;        /* contains a free use of 'arguments' */
-    bool            usesApply:1;            /* contains an f.apply() call */
-    bool            usesThis:1;             /* contains 'this' */
-    bool            usesReturn:1;           /* contains a 'return' statement */
-    bool            hasRest_:1;             /* has rest parameter */
-    bool            hasExprBody_:1;         /* arrow function with expression
-                                             * body like: () => 1
-                                             * Only used by Reflect.parse */
-
-    // This function does something that can extend the set of bindings in its
-    // call objects --- it does a direct eval in non-strict code, or includes a
-    // function statement (as opposed to a function definition).
-    //
-    // This flag is *not* inherited by enclosed or enclosing functions; it
-    // applies only to the function in whose flags it appears.
-    //
-    bool hasExtensibleScope_:1;
-
-    // Technically, every function has a binding named 'arguments'. Internally,
-    // this binding is only added when 'arguments' is mentioned by the function
-    // body. This flag indicates whether 'arguments' has been bound either
-    // through implicit use:
-    //   function f() { return arguments }
-    // or explicit redeclaration:
-    //   function f() { var arguments; return arguments }
-    //
-    // Note 1: overwritten arguments (function() { arguments = 3 }) will cause
-    // this flag to be set but otherwise require no special handling:
-    // 'arguments' is just a local variable and uses of 'arguments' will just
-    // read the local's current slot which may have been assigned. The only
-    // special semantics is that the initial value of 'arguments' is the
-    // arguments object (not undefined, like normal locals).
-    //
-    // Note 2: if 'arguments' is bound as a formal parameter, there will be an
-    // 'arguments' in Bindings, but, as the "LOCAL" in the name indicates, this
-    // flag will not be set. This is because, as a formal, 'arguments' will
-    // have no special semantics: the initial value is unconditionally the
-    // actual argument (or undefined if nactual < nformal).
-    //
-    bool argumentsHasLocalBinding_:1;
-
-    // In many cases where 'arguments' has a local binding (as described above)
-    // we do not need to actually create an arguments object in the function
-    // prologue: instead we can analyze how 'arguments' is used (using the
-    // simple dataflow analysis in analyzeSSA) to determine that uses of
-    // 'arguments' can just read from the stack frame directly. However, the
-    // dataflow analysis only looks at how JSOP_ARGUMENTS is used, so it will
-    // be unsound in several cases. The frontend filters out such cases by
-    // setting this flag which eagerly sets script->needsArgsObj to true.
-    //
-    bool definitelyNeedsArgsObj_:1;
-
-    bool needsHomeObject_:1;
-    bool isDerivedClassConstructor_:1;
-
-    // Whether this function has a .this binding. If true, we need to emit
-    // JSOP_FUNCTIONTHIS in the prologue to initialize it.
-    bool hasThisBinding_:1;
-
-    // Whether this function has nested functions.
-    bool hasInnerFunctions_:1;
-
-    FunctionBox(JSContext* cx, ObjectBox* traceListHead, JSFunction* fun,
-                uint32_t toStringStart, Directives directives, bool extraWarnings,
-                GeneratorKind generatorKind, FunctionAsyncKind asyncKind);
-
-#ifdef DEBUG
-    bool atomsAreKept();
-#endif
-
-    MutableHandle<LexicalScope::Data*> namedLambdaBindings() {
-        MOZ_ASSERT(atomsAreKept());
-        return MutableHandle<LexicalScope::Data*>::fromMarkedLocation(&namedLambdaBindings_);
-    }
-
-    MutableHandle<FunctionScope::Data*> functionScopeBindings() {
-        MOZ_ASSERT(atomsAreKept());
-        return MutableHandle<FunctionScope::Data*>::fromMarkedLocation(&functionScopeBindings_);
-    }
-
-    MutableHandle<VarScope::Data*> extraVarScopeBindings() {
-        MOZ_ASSERT(atomsAreKept());
-        return MutableHandle<VarScope::Data*>::fromMarkedLocation(&extraVarScopeBindings_);
-    }
-
-    void initFromLazyFunction();
-    void initStandaloneFunction(Scope* enclosingScope);
-    void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind);
-
-    inline bool isLazyFunctionWithoutEnclosingScope() const {
-        return function()->isInterpretedLazy() &&
-               !function()->lazyScript()->hasEnclosingScope();
-    }
-    void setEnclosingScopeForInnerLazyFunction(Scope* enclosingScope);
-    void finish();
-
-    JSFunction* function() const { return &object->as<JSFunction>(); }
-
-    Scope* compilationEnclosingScope() const override {
-        // This method is used to distinguish the outermost SharedContext. If
-        // a FunctionBox is the outermost SharedContext, it must be a lazy
-        // function.
-
-        // If the function is lazy and it has enclosing scope, the function is
-        // being delazified.  In that case the enclosingScope_ field is copied
-        // from the lazy function at the beginning of delazification and should
-        // keep pointing the same scope.
-        MOZ_ASSERT_IF(function()->isInterpretedLazy() &&
-                      function()->lazyScript()->hasEnclosingScope(),
-                      enclosingScope_ == function()->lazyScript()->enclosingScope());
-
-        // If this FunctionBox is a lazy child of the function we're actually
-        // compiling, then it is not the outermost SharedContext, so this
-        // method should return nullptr."
-        if (isLazyFunctionWithoutEnclosingScope()) {
-            return nullptr;
-        }
-
-        return enclosingScope_;
-    }
-
-    bool needsCallObjectRegardlessOfBindings() const {
-        return hasExtensibleScope() ||
-               needsHomeObject() ||
-               isDerivedClassConstructor() ||
-               isGenerator() ||
-               isAsync();
-    }
-
-    bool hasExtraBodyVarScope() const {
-        return hasParameterExprs &&
-               (extraVarScopeBindings_ ||
-                needsExtraBodyVarEnvironmentRegardlessOfBindings());
-    }
-
-    bool needsExtraBodyVarEnvironmentRegardlessOfBindings() const {
-        MOZ_ASSERT(hasParameterExprs);
-        return hasExtensibleScope() || needsDotGeneratorName();
-    }
-
-    bool isLikelyConstructorWrapper() const {
-        return usesArguments && usesApply && usesThis && !usesReturn;
-    }
-
-    bool isGenerator() const { return isGenerator_; }
-    GeneratorKind generatorKind() const {
-        return isGenerator() ? GeneratorKind::Generator : GeneratorKind::NotGenerator;
-    }
-
-    bool isAsync() const { return isAsync_; }
-    FunctionAsyncKind asyncKind() const {
-        return isAsync() ? FunctionAsyncKind::AsyncFunction : FunctionAsyncKind::SyncFunction;
-    }
-
-    bool needsFinalYield() const {
-        return isGenerator() || isAsync();
-    }
-    bool needsDotGeneratorName() const {
-        return isGenerator() || isAsync();
-    }
-    bool needsIteratorResult() const {
-        return isGenerator();
-    }
-
-    bool isArrow() const { return function()->isArrow(); }
-
-    bool hasRest() const { return hasRest_; }
-    void setHasRest() {
-        hasRest_ = true;
-    }
-
-    bool hasExprBody() const { return hasExprBody_; }
-    void setHasExprBody() {
-        MOZ_ASSERT(isArrow());
-        hasExprBody_ = true;
-    }
-
-    bool hasExtensibleScope()        const { return hasExtensibleScope_; }
-    bool hasThisBinding()            const { return hasThisBinding_; }
-    bool argumentsHasLocalBinding()  const { return argumentsHasLocalBinding_; }
-    bool definitelyNeedsArgsObj()    const { return definitelyNeedsArgsObj_; }
-    bool needsHomeObject()           const { return needsHomeObject_; }
-    bool isDerivedClassConstructor() const { return isDerivedClassConstructor_; }
-    bool hasInnerFunctions()         const { return hasInnerFunctions_; }
-
-    void setHasExtensibleScope()           { hasExtensibleScope_       = true; }
-    void setHasThisBinding()               { hasThisBinding_           = true; }
-    void setArgumentsHasLocalBinding()     { argumentsHasLocalBinding_ = true; }
-    void setDefinitelyNeedsArgsObj()       { MOZ_ASSERT(argumentsHasLocalBinding_);
-                                             definitelyNeedsArgsObj_   = true; }
-    void setNeedsHomeObject()              { MOZ_ASSERT(function()->allowSuperProperty());
-                                             needsHomeObject_          = true; }
-    void setDerivedClassConstructor()      { MOZ_ASSERT(function()->isClassConstructor());
-                                             isDerivedClassConstructor_ = true; }
-    void setHasInnerFunctions()            { hasInnerFunctions_         = true; }
-
-    bool hasSimpleParameterList() const {
-        return !hasRest() && !hasParameterExprs && !hasDestructuringArgs;
-    }
-
-    bool hasMappedArgsObj() const {
-        return !strict() && hasSimpleParameterList();
-    }
-
-    // Return whether this or an enclosing function is being parsed and
-    // validated as asm.js. Note: if asm.js validation fails, this will be false
-    // while the function is being reparsed. This flag can be used to disable
-    // certain parsing features that are necessary in general, but unnecessary
-    // for validated asm.js.
-    bool useAsmOrInsideUseAsm() const {
-        return useAsm;
-    }
-
-    void setStart(const TokenStreamAnyChars& anyChars) {
-        uint32_t offset = anyChars.currentToken().pos.begin;
-        setStart(anyChars, offset);
-    }
-
-    void setStart(const TokenStreamAnyChars& anyChars, uint32_t offset) {
-        bufStart = offset;
-        anyChars.srcCoords.lineNumAndColumnIndex(offset, &startLine, &startColumn);
-    }
-
-    void setEnd(const TokenStreamAnyChars& anyChars) {
-        // For all functions except class constructors, the buffer and
-        // toString ending positions are the same. Class constructors override
-        // the toString ending position with the end of the class definition.
-        uint32_t offset = anyChars.currentToken().pos.end;
-        bufEnd = offset;
-        toStringEnd = offset;
-    }
-
-    void trace(JSTracer* trc) override;
-};
-
-inline FunctionBox*
-SharedContext::asFunctionBox()
-{
-    MOZ_ASSERT(isFunctionBox());
-    return static_cast<FunctionBox*>(this);
-}
-
-class MOZ_STACK_CLASS ModuleSharedContext : public SharedContext
-{
-    RootedModuleObject module_;
-    RootedScope enclosingScope_;
-
-  public:
-    Rooted<ModuleScope::Data*> bindings;
+    JS::Rooted<ModuleScope::Data*> bindings;
     ModuleBuilder& builder;
 
     ModuleSharedContext(JSContext* cx, ModuleObject* module, Scope* enclosingScope,
                         ModuleBuilder& builder);
 
-    HandleModuleObject module() const { return module_; }
+    JS::Handle<ModuleObject*> module() const { return module_; }
     Scope* compilationEnclosingScope() const override { return enclosingScope_; }
 };
 
 inline ModuleSharedContext*
 SharedContext::asModuleContext()
 {
     MOZ_ASSERT(isModuleContext());
     return static_cast<ModuleSharedContext*>(this);
 }
 
-// In generators, we treat all bindings as closed so that they get stored on
-// the heap.  This way there is less information to copy off the stack when
-// suspending, and back on when resuming.  It also avoids the need to create
-// and invalidate DebugScope proxies for unaliased locals in a generator
-// frame, as the generator frame will be copied out to the heap and released
-// only by GC.
-inline bool
-SharedContext::allBindingsClosedOver()
-{
-    return bindingsAccessedDynamically() ||
-           (isFunctionBox() &&
-            (asFunctionBox()->isGenerator() ||
-             asFunctionBox()->isAsync()));
-}
-
 } // namespace frontend
 } // namespace js
 
-#endif /* frontend_SharedContext_h */
+#endif /* frontend_ModuleSharedContext_h */
--- a/js/src/frontend/ParseContext.h
+++ b/js/src/frontend/ParseContext.h
@@ -6,16 +6,17 @@
 
 #ifndef frontend_ParseContext_h
 #define frontend_ParseContext_h
 
 #include "ds/Nestable.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/ErrorReporter.h"
+#include "frontend/NameCollections.h"
 #include "frontend/SharedContext.h"
 
 namespace js {
 
 namespace frontend {
 
 class ParserBase;
 
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -32,16 +32,17 @@
 
 #include "jsnum.h"
 #include "jstypes.h"
 
 #include "builtin/ModuleObject.h"
 #include "builtin/SelfHostingDefines.h"
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/FoldConstants.h"
+#include "frontend/ModuleSharedContext.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"
--- a/js/src/frontend/SharedContext.cpp
+++ b/js/src/frontend/SharedContext.cpp
@@ -1,15 +1,18 @@
 /* -*- 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/SharedContext.h"
+
+#include "frontend/ModuleSharedContext.h"
+
 #include "frontend/ParseContext-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 
 namespace js {
 namespace frontend {
 
 void
 SharedContext::computeAllowSyntax(Scope* scope)
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef frontend_SharedContext_h
 #define frontend_SharedContext_h
 
 #include "jspubtd.h"
 #include "jstypes.h"
 
-#include "builtin/ModuleObject.h"
 #include "ds/InlineTable.h"
 #include "frontend/ParseNode.h"
 #include "frontend/TokenStream.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/JSFunction.h"
 #include "vm/JSScript.h"
 
 namespace js {
@@ -579,39 +578,16 @@ class FunctionBox : public ObjectBox, pu
 
 inline FunctionBox*
 SharedContext::asFunctionBox()
 {
     MOZ_ASSERT(isFunctionBox());
     return static_cast<FunctionBox*>(this);
 }
 
-class MOZ_STACK_CLASS ModuleSharedContext : public SharedContext
-{
-    RootedModuleObject module_;
-    RootedScope enclosingScope_;
-
-  public:
-    Rooted<ModuleScope::Data*> bindings;
-    ModuleBuilder& builder;
-
-    ModuleSharedContext(JSContext* cx, ModuleObject* module, Scope* enclosingScope,
-                        ModuleBuilder& builder);
-
-    HandleModuleObject module() const { return module_; }
-    Scope* compilationEnclosingScope() const override { return enclosingScope_; }
-};
-
-inline ModuleSharedContext*
-SharedContext::asModuleContext()
-{
-    MOZ_ASSERT(isModuleContext());
-    return static_cast<ModuleSharedContext*>(this);
-}
-
 // In generators, we treat all bindings as closed so that they get stored on
 // the heap.  This way there is less information to copy off the stack when
 // suspending, and back on when resuming.  It also avoids the need to create
 // and invalidate DebugScope proxies for unaliased locals in a generator
 // frame, as the generator frame will be copied out to the heap and released
 // only by GC.
 inline bool
 SharedContext::allBindingsClosedOver()
--- a/js/src/js-config.mozbuild
+++ b/js/src/js-config.mozbuild
@@ -3,33 +3,33 @@
 # 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/.
 
 # Nightly-only features
 if CONFIG['NIGHTLY_BUILD']:
     DEFINES['ENABLE_BINARYDATA'] = True
     DEFINES['ENABLE_WASM_BULKMEM_OPS'] = True
-    DEFINES['ENABLE_WASM_THREAD_OPS'] = True
     DEFINES['ENABLE_WASM_GC'] = True
     DEFINES['ENABLE_WASM_GENERALIZED_TABLES'] = True
     DEFINES['WASM_PRIVATE_REFTYPES'] = True
 
 # Some huge-mapping optimization instead of bounds checks on supported
 # platforms.
 if CONFIG['JS_CODEGEN_X64'] or CONFIG['JS_CODEGEN_ARM64']:
     DEFINES['WASM_HUGE_MEMORY'] = True
 
 # Enables CACHEIR_LOGS to diagnose IC coverage.
 if CONFIG['MOZ_DEBUG'] or CONFIG['NIGHTLY_BUILD']:
     DEFINES['JS_CACHEIR_SPEW'] = True
 
-# Build with SharedArrayBuffer code.
+# Build with SharedArrayBuffer/wasm-thread-ops code.
 # NOTE: This Realm creation options decide if this is exposed to script.
 DEFINES['ENABLE_SHARED_ARRAY_BUFFER'] = True
+DEFINES['ENABLE_WASM_THREAD_OPS'] = True
 
 # CTypes
 if CONFIG['JS_HAS_CTYPES']:
     DEFINES['JS_HAS_CTYPES'] = True
     if not CONFIG['MOZ_SYSTEM_FFI']:
         DEFINES['FFI_BUILDING'] = True
 
 # Forward MOZ_LINKER config
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -17,16 +17,17 @@ UNIFIED_SOURCES += [
     'testAtomicOperations.cpp',
     'testBoundFunction.cpp',
     'testBug604087.cpp',
     'testCallArgs.cpp',
     'testCallNonGenericMethodOnProxy.cpp',
     'testChromeBuffer.cpp',
     'testCloneScript.cpp',
     'testCompileNonSyntactic.cpp',
+    'testCompileUtf8.cpp',
     'testDateToLocaleString.cpp',
     'testDebugger.cpp',
     'testDeepFreeze.cpp',
     'testDefineGetterSetterNonEnumerable.cpp',
     'testDefineProperty.cpp',
     'testDefinePropertyIgnoredAttributes.cpp',
     'testDeflateStringToUTF8Buffer.cpp',
     'testDifferentNewTargetInvokeConstructor.cpp',
--- a/js/src/jsapi-tests/testCompileNonSyntactic.cpp
+++ b/js/src/jsapi-tests/testCompileNonSyntactic.cpp
@@ -1,15 +1,15 @@
 /* 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 "gc/GCInternals.h"
 #include "js/CompilationAndEvaluation.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "jsapi-tests/tests.h"
 #include "vm/Monitor.h"
 #include "vm/MutexIDs.h"
 
 using namespace JS;
 using js::AutoLockMonitor;
 
 struct OffThreadTask
@@ -69,53 +69,60 @@ testCompile(bool nonSyntactic)
 
     constexpr size_t length = sizeof(src) - 1;
     static_assert(sizeof(src_16) / sizeof(*src_16) - 1 == length,
                   "Source buffers must be same length");
 
     JS::CompileOptions options(cx);
     options.setNonSyntacticScope(nonSyntactic);
 
-    JS::RootedScript script(cx);
+    JS::SourceText<char16_t> buf;
+    CHECK(buf.init(cx, src_16, length, JS::SourceOwnership::Borrowed));
 
-    JS::SourceBufferHolder buf(src_16, length, JS::SourceBufferHolder::NoOwnership);
+    JS::RootedScript script(cx);
 
     // Check explicit non-syntactic compilation first to make sure it doesn't
     // modify our options object.
     CHECK(CompileForNonSyntacticScope(cx, options, buf, &script));
     CHECK_EQUAL(script->hasNonSyntacticScope(), true);
 
     CHECK(CompileLatin1ForNonSyntacticScope(cx, options, src, length, &script));
     CHECK_EQUAL(script->hasNonSyntacticScope(), true);
 
     {
-        JS::SourceBufferHolder srcBuf(src_16, length, JS::SourceBufferHolder::NoOwnership);
+        JS::SourceText<char16_t> srcBuf;
+        CHECK(srcBuf.init(cx, src_16, length, JS::SourceOwnership::Borrowed));
+
         CHECK(CompileForNonSyntacticScope(cx, options, srcBuf, &script));
         CHECK_EQUAL(script->hasNonSyntacticScope(), true);
     }
 
 
     CHECK(Compile(cx, options, buf, &script));
     CHECK_EQUAL(script->hasNonSyntacticScope(), nonSyntactic);
 
     CHECK(CompileLatin1(cx, options, src, length, &script));
     CHECK_EQUAL(script->hasNonSyntacticScope(), nonSyntactic);
 
     {
-        JS::SourceBufferHolder srcBuf(src_16, length, JS::SourceBufferHolder::NoOwnership);
+        JS::SourceText<char16_t> srcBuf;
+        CHECK(srcBuf.init(cx, src_16, length, JS::SourceOwnership::Borrowed));
+
         CHECK(Compile(cx, options, srcBuf, &script));
         CHECK_EQUAL(script->hasNonSyntacticScope(), nonSyntactic);
     }
 
 
     options.forceAsync = true;
     OffThreadTask task;
     OffThreadToken* token;
 
-    JS::SourceBufferHolder srcBuf(src_16, length, JS::SourceBufferHolder::NoOwnership);
+    JS::SourceText<char16_t> srcBuf;
+    CHECK(srcBuf.init(cx, src_16, length, JS::SourceOwnership::Borrowed));
+
     CHECK(CompileOffThread(cx, options, srcBuf, task.OffThreadCallback, &task));
     CHECK(token = task.waitUntilDone(cx));
     CHECK(script = FinishOffThreadScript(cx, token));
     CHECK_EQUAL(script->hasNonSyntacticScope(), nonSyntactic);
 
     return true;
 }
 END_TEST(testCompileScript);
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testCompileUtf8.cpp
@@ -0,0 +1,279 @@
+/* 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 "mozilla/ArrayUtils.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Utf8.h"
+
+#include <cstring>
+
+#include "jsfriendapi.h"
+
+#include "js/CharacterEncoding.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/SourceText.h"
+#include "jsapi-tests/tests.h"
+#include "vm/ErrorReporting.h"
+
+using mozilla::ArrayLength;
+using mozilla::IsAsciiHexDigit;
+using mozilla::Utf8Unit;
+
+BEGIN_TEST(testUtf8BadBytes)
+{
+    static const char badLeadingUnit[] = "var x = \x80";
+    CHECK(testBadUtf8(badLeadingUnit,
+                      JSMSG_BAD_LEADING_UTF8_UNIT,
+                      [this](JS::ConstUTF8CharsZ message) {
+                          const char* chars = message.c_str();
+                          CHECK(startsWith(chars, "0x80"));
+                          CHECK(isBadLeadUnitMessage(chars));
+                          return true;
+                      },
+                      "0x80"));
+
+    static const char badSecondInTwoByte[] = "var x = \xDF\x20";
+    CHECK(testBadUtf8(badSecondInTwoByte,
+                      JSMSG_BAD_TRAILING_UTF8_UNIT,
+                      [this](JS::ConstUTF8CharsZ message) {
+                          const char* chars = message.c_str();
+                          CHECK(isBadTrailingBytesMessage(chars));
+                          CHECK(contains(chars, "0x20"));
+                          return true;
+                      },
+                      "0xDF 0x20"));
+
+    static const char badSecondInThreeByte[] = "var x = \xEF\x17\xA7";
+    CHECK(testBadUtf8(badSecondInThreeByte,
+                      JSMSG_BAD_TRAILING_UTF8_UNIT,
+                      [this](JS::ConstUTF8CharsZ message) {
+                          const char* chars = message.c_str();
+                          CHECK(isBadTrailingBytesMessage(chars));
+                          CHECK(contains(chars, "0x17"));
+                          return true;
+                      },
+                      // Validating stops with the first invalid code unit and
+                      // shouldn't go beyond that.
+                      "0xEF 0x17"));
+
+    static const char lengthTwoTooShort[] = "var x = \xDF";
+    CHECK(testBadUtf8(lengthTwoTooShort,
+                      JSMSG_NOT_ENOUGH_CODE_UNITS,
+                      [this](JS::ConstUTF8CharsZ message) {
+                          const char* chars = message.c_str();
+                          CHECK(isNotEnoughUnitsMessage(chars));
+                          CHECK(contains(chars, "0xDF"));
+                          CHECK(contains(chars, " 1 byte, but 0 bytes were present"));
+                          return true;
+                      },
+                      "0xDF"));
+
+    static const char forbiddenHighSurrogate[] = "var x = \xED\xA2\x87";
+    CHECK(testBadUtf8(forbiddenHighSurrogate,
+                      JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+                      [this](JS::ConstUTF8CharsZ message) {
+                          const char* chars = message.c_str();
+                          CHECK(isSurrogateMessage(chars));
+                          CHECK(contains(chars, "0xD887"));
+                          return true;
+                      },
+                      "0xED 0xA2 0x87"));
+
+    static const char forbiddenLowSurrogate[] = "var x = \xED\xB7\xAF";
+    CHECK(testBadUtf8(forbiddenLowSurrogate,
+                      JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+                      [this](JS::ConstUTF8CharsZ message) {
+                          const char* chars = message.c_str();
+                          CHECK(isSurrogateMessage(chars));
+                          CHECK(contains(chars, "0xDDEF"));
+                          return true;
+                      },
+                      "0xED 0xB7 0xAF"));
+
+    static const char oneTooBig[] = "var x = \xF4\x90\x80\x80";
+    CHECK(testBadUtf8(oneTooBig,
+                      JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+                      [this](JS::ConstUTF8CharsZ message) {
+                          const char* chars = message.c_str();
+                          CHECK(isTooBigMessage(chars));
+                          CHECK(contains(chars, "0x110000"));
+                          return true;
+                      },
+                      "0xF4 0x90 0x80 0x80"));
+
+    static const char notShortestFormZero[] = "var x = \xC0\x80";
+    CHECK(testBadUtf8(notShortestFormZero,
+                      JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+                      [this](JS::ConstUTF8CharsZ message) {
+                          const char* chars = message.c_str();
+                          CHECK(isNotShortestFormMessage(chars));
+                          CHECK(startsWith(chars, "0x0 isn't "));
+                          return true;
+                      },
+                      "0xC0 0x80"));
+
+    static const char notShortestFormNonzero[] = "var x = \xE0\x87\x80";
+    CHECK(testBadUtf8(notShortestFormNonzero,
+                      JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+                      [this](JS::ConstUTF8CharsZ message) {
+                          const char* chars = message.c_str();
+                          CHECK(isNotShortestFormMessage(chars));
+                          CHECK(startsWith(chars, "0x1C0 isn't "));
+                          return true;
+                      },
+                      "0xE0 0x87 0x80"));
+
+    return true;
+}
+
+static constexpr size_t LengthOfByte = ArrayLength("0xFF") - 1;
+
+static bool
+startsWithByte(const char* str)
+{
+    return str[0] == '0' &&
+           str[1] == 'x' &&
+           IsAsciiHexDigit(str[2]) &&
+           IsAsciiHexDigit(str[3]);
+}
+
+static bool
+startsWith(const char* str, const char* prefix)
+{
+    return std::strncmp(prefix, str, strlen(prefix)) == 0;
+}
+
+static bool
+contains(const char* str, const char* substr)
+{
+    return std::strstr(str, substr) != nullptr;
+}
+
+static bool
+equals(const char* str, const char* expected)
+{
+    return std::strcmp(str, expected) == 0;
+}
+
+static bool
+isBadLeadUnitMessage(const char* str)
+{
+    return startsWithByte(str) &&
+           equals(str + LengthOfByte,
+                  " byte doesn't begin a valid UTF-8 code point");
+}
+
+static bool
+isBadTrailingBytesMessage(const char* str)
+{
+    return startsWith(str, "bad trailing UTF-8 byte ");
+}
+
+static bool
+isNotEnoughUnitsMessage(const char* str)
+{
+    return startsWithByte(str) &&
+           startsWith(str + LengthOfByte,
+                      " byte in UTF-8 must be followed by ");
+}
+
+static bool
+isForbiddenCodePointMessage(const char* str)
+{
+    return contains(str, "isn't a valid code point because");
+}
+
+static bool
+isSurrogateMessage(const char* str)
+{
+    return isForbiddenCodePointMessage(str) &&
+           contains(str, " it's a UTF-16 surrogate");
+}
+
+static bool
+isTooBigMessage(const char* str)
+{
+    return isForbiddenCodePointMessage(str) &&
+           contains(str, "the maximum code point is U+10FFFF");
+}
+
+static bool
+isNotShortestFormMessage(const char* str)
+{
+    return isForbiddenCodePointMessage(str) &&
+           contains(str, "it wasn't encoded in shortest possible form");
+}
+
+bool
+compileUtf8(const char* chars, size_t len, JS::MutableHandleScript script)
+{
+    JS::RealmOptions globalOptions;
+    JS::RootedObject global(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+						   JS::FireOnNewGlobalHook, globalOptions));
+    CHECK(global);
+
+    JSAutoRealm ar(cx, global);
+
+    JS::CompileOptions options(cx);
+    return JS::CompileUtf8DontInflate(cx, options, chars, len, script);
+}
+
+template<size_t N, typename TestMessage>
+bool
+testBadUtf8(const char (&chars)[N], unsigned errorNumber,
+            TestMessage testMessage, const char* badBytes)
+{
+    JS::Rooted<JSScript*> script(cx);
+    CHECK(!compileUtf8(chars, N - 1, &script));
+
+    JS::RootedValue exn(cx);
+    CHECK(JS_GetPendingException(cx, &exn));
+    JS_ClearPendingException(cx);
+
+    js::ErrorReport report(cx);
+    CHECK(report.init(cx, exn, js::ErrorReport::WithSideEffects));
+
+    const auto* errorReport = report.report();
+
+    CHECK(errorReport->errorNumber == errorNumber);
+
+    CHECK(testMessage(errorReport->message()));
+
+    {
+        const auto& notes = errorReport->notes;
+        CHECK(notes != nullptr);
+
+        auto iter = notes->begin();
+        CHECK(iter != notes->end());
+
+        const char* noteMessage = (*iter)->message().c_str();
+
+        // The prefix ought always be the same.
+        static const char expectedPrefix[] =
+            "the code units comprising this invalid code point were: ";
+        constexpr size_t expectedPrefixLen = ArrayLength(expectedPrefix) - 1;
+
+        CHECK(startsWith(noteMessage, expectedPrefix));
+
+        // The end of the prefix is the bad bytes.
+        CHECK(equals(noteMessage + expectedPrefixLen, badBytes));
+
+        ++iter;
+        CHECK(iter == notes->end());
+    }
+
+    static const char16_t expectedContext[] = u"var x = ";
+    constexpr size_t expectedContextLen = ArrayLength(expectedContext) - 1;
+
+    const char16_t* lineOfContext = errorReport->linebuf();
+    size_t lineOfContextLength = errorReport->linebufLength();
+
+    CHECK(lineOfContext[lineOfContextLength] == '\0');
+    CHECK(lineOfContextLength == expectedContextLen);
+
+    CHECK(std::memcmp(lineOfContext, expectedContext, expectedContextLen * sizeof(char16_t)) == 0);
+
+    return true;
+}
+END_TEST(testUtf8BadBytes)
--- a/js/src/jsapi-tests/testErrorLineOfContext.cpp
+++ b/js/src/jsapi-tests/testErrorLineOfContext.cpp
@@ -1,16 +1,16 @@
 /* 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 "jsfriendapi.h"
 
 #include "js/CompilationAndEvaluation.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "jsapi-tests/tests.h"
 #include "vm/ErrorReporting.h"
 
 BEGIN_TEST(testErrorLineOfContext)
 {
     static const char16_t fullLineR[] = u"\n  var x = @;  \r  ";
     CHECK(testLineOfContextHasNoLineTerminator(fullLineR, ' '));
 
@@ -37,18 +37,21 @@ bool
 eval(const char16_t* chars, size_t len, JS::MutableHandleValue rval)
 {
     JS::RealmOptions globalOptions;
     JS::RootedObject global(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
 						   JS::FireOnNewGlobalHook, globalOptions));
     CHECK(global);
 
     JSAutoRealm ar(cx, global);
+
+    JS::SourceText<char16_t> srcBuf;
+    CHECK(srcBuf.init(cx, chars, len, JS::SourceOwnership::Borrowed));
+
     JS::CompileOptions options(cx);
-    JS::SourceBufferHolder srcBuf(chars, len, JS::SourceBufferHolder::NoOwnership);
     return JS::Evaluate(cx, options, srcBuf, rval);
 }
 
 template<size_t N>
 bool
 testLineOfContextHasNoLineTerminator(const char16_t (&chars)[N], char16_t expectedLast)
 {
     JS::RootedValue rval(cx);
--- a/js/src/jsapi-tests/testJSEvaluateScript.cpp
+++ b/js/src/jsapi-tests/testJSEvaluateScript.cpp
@@ -1,30 +1,33 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  */
 
 #include "js/CompilationAndEvaluation.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "jsapi-tests/tests.h"
 
 using mozilla::ArrayLength;
 
 BEGIN_TEST(testJSEvaluateScript)
 {
     JS::RootedObject obj(cx, JS_NewPlainObject(cx));
     CHECK(obj);
 
     static const char16_t src[] = u"var x = 5;";
 
     JS::RootedValue retval(cx);
     JS::CompileOptions opts(cx);
     JS::AutoObjectVector scopeChain(cx);
     CHECK(scopeChain.append(obj));
-    JS::SourceBufferHolder srcBuf(src, ArrayLength(src) - 1, JS::SourceBufferHolder::NoOwnership);
+
+    JS::SourceText<char16_t> srcBuf;
+    CHECK(srcBuf.init(cx, src, ArrayLength(src) - 1, JS::SourceOwnership::Borrowed));
+
     CHECK(JS::Evaluate(cx, scopeChain, opts.setFileAndLine(__FILE__, __LINE__),
                        srcBuf, &retval));
 
     bool hasProp = true;
     CHECK(JS_AlreadyHasOwnProperty(cx, obj, "x", &hasProp));
     CHECK(hasProp);
 
     hasProp = false;
--- a/js/src/jsapi-tests/testMutedErrors.cpp
+++ b/js/src/jsapi-tests/testMutedErrors.cpp
@@ -1,15 +1,15 @@
 /* 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 "jsfriendapi.h"
 #include "js/CompilationAndEvaluation.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "jsapi-tests/tests.h"
 
 BEGIN_TEST(testMutedErrors)
 {
     CHECK(testOuter("function f() {return 1}; f;"));
     CHECK(testOuter("function outer() { return (function () {return 2}); }; outer();"));
     CHECK(testOuter("eval('(function() {return 3})');"));
     CHECK(testOuter("(function (){ return eval('(function() {return 4})'); })()"));
@@ -52,17 +52,19 @@ eval(const char* asciiChars, bool mutedE
     JSAutoRealm ar(cx, global);
     CHECK(JS::InitRealmStandardClasses(cx));
 
 
     JS::CompileOptions options(cx);
     options.setMutedErrors(mutedErrors)
            .setFileAndLine("", 0);
 
-    JS::SourceBufferHolder srcBuf(chars.get(), len, JS::SourceBufferHolder::NoOwnership);
+    JS::SourceText<char16_t> srcBuf;
+    CHECK(srcBuf.init(cx, chars.get(), len, JS::SourceOwnership::Borrowed));
+
     return JS::Evaluate(cx, options, srcBuf, rval);
 }
 
 bool
 testOuter(const char* asciiChars)
 {
     CHECK(testInner(asciiChars, false));
     CHECK(testInner(asciiChars, true));
--- a/js/src/jsapi-tests/testScriptObject.cpp
+++ b/js/src/jsapi-tests/testScriptObject.cpp
@@ -1,17 +1,17 @@
 /* -*- 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 "js/CompilationAndEvaluation.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "jsapi-tests/tests.h"
 
 struct ScriptObjectFixture : public JSAPITest {
     static const int code_size;
     static const char code[];
     static char16_t uc_code[];
 
     ScriptObjectFixture()
@@ -76,44 +76,50 @@ BEGIN_FIXTURE_TEST(ScriptObjectFixture, 
 }
 END_FIXTURE_TEST(ScriptObjectFixture, bug438633_CompileScriptForPrincipals)
 
 BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScript)
 {
     JS::CompileOptions options(cx);
     options.setFileAndLine(__FILE__, __LINE__);
 
+    JS::SourceText<char16_t> srcBuf;
+    CHECK(srcBuf.init(cx, uc_code, code_size, JS::SourceOwnership::Borrowed));
+
     JS::RootedScript script(cx);
-    JS::SourceBufferHolder srcBuf(uc_code, code_size, JS::SourceBufferHolder::NoOwnership);
     CHECK(JS::Compile(cx, options, srcBuf, &script));
 
     return tryScript(script);
 }
 END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScript)
 
 BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScript_empty)
 {
     JS::CompileOptions options(cx);
     options.setFileAndLine(__FILE__, __LINE__);
 
+    JS::SourceText<char16_t> srcBuf;
+    CHECK(srcBuf.init(cx, uc_code, 0, JS::SourceOwnership::Borrowed));
+
     JS::RootedScript script(cx);
-    JS::SourceBufferHolder srcBuf(uc_code, 0, JS::SourceBufferHolder::NoOwnership);
     CHECK(JS::Compile(cx, options, srcBuf, &script));
 
     return tryScript(script);
 }
 END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScript_empty)
 
 BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScriptForPrincipals)
 {
     JS::CompileOptions options(cx);
     options.setFileAndLine(__FILE__, __LINE__);
 
+    JS::SourceText<char16_t> srcBuf;
+    CHECK(srcBuf.init(cx, uc_code, code_size, JS::SourceOwnership::Borrowed));
+
     JS::RootedScript script(cx);
-    JS::SourceBufferHolder srcBuf(uc_code, code_size, JS::SourceBufferHolder::NoOwnership);
     CHECK(JS::Compile(cx, options, srcBuf, &script));
 
     return tryScript(script);
 }
 END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScriptForPrincipals)
 
 BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFile)
 {
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -57,17 +57,17 @@
 #include "js/CompileOptions.h"
 #include "js/Conversions.h"
 #include "js/Date.h"
 #include "js/Initialization.h"
 #include "js/JSON.h"
 #include "js/LocaleSensitive.h"
 #include "js/Proxy.h"
 #include "js/SliceBudget.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "js/StableStringChars.h"
 #include "js/StructuredClone.h"
 #include "js/Utility.h"
 #include "js/Wrapper.h"
 #include "util/CompleteFile.h"
 #include "util/StringBuffer.h"
 #include "util/Text.h"
 #include "vm/AsyncFunction.h"
@@ -109,17 +109,17 @@ using namespace js;
 
 using mozilla::Maybe;
 using mozilla::PodCopy;
 using mozilla::Some;
 
 using JS::AutoStableStringChars;
 using JS::CompileOptions;
 using JS::ReadOnlyCompileOptions;
-using JS::SourceBufferHolder;
+using JS::SourceText;
 
 #ifdef HAVE_VA_LIST_AS_ARRAY
 #define JS_ADDRESSOF_VA_LIST(ap) ((va_list*)(ap))
 #else
 #define JS_ADDRESSOF_VA_LIST(ap) (&(ap))
 #endif
 
 JS_PUBLIC_API(bool)
@@ -4157,17 +4157,17 @@ JS::FinishDynamicModuleImport(JSContext*
     CHECK_THREAD(cx);
     cx->check(referencingPrivate, promise);
 
     return js::FinishDynamicModuleImport(cx, referencingPrivate, specifier, promise);
 }
 
 JS_PUBLIC_API(bool)
 JS::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
-                  SourceBufferHolder& srcBuf, JS::MutableHandleObject module)
+                  SourceText<char16_t>& srcBuf, JS::MutableHandleObject module)
 {
     MOZ_ASSERT(!cx->zone()->isAtomsZone());
     AssertHeapIsIdle();
     CHECK_THREAD(cx);
 
     module.set(frontend::CompileModule(cx, options, srcBuf));
     return !!module;
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -10,16 +10,17 @@
 #define jsapi_h
 
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Range.h"
 #include "mozilla/RangedPtr.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/Utf8.h"
 #include "mozilla/Variant.h"
 
 #include <stdarg.h>
 #include <stddef.h>
 #include <stdint.h>
 #include <stdio.h>
 
 #include "jspubtd.h"
@@ -46,17 +47,18 @@
 #include "js/Utility.h"
 #include "js/Value.h"
 #include "js/Vector.h"
 
 /************************************************************************/
 
 namespace JS {
 
-class SourceBufferHolder;
+template<typename UnitT> class SourceText;
+
 class TwoByteChars;
 
 /** AutoValueArray roots an internal fixed-size array of Values. */
 template <size_t N>
 class MOZ_RAII AutoValueArray : public AutoGCRooter
 {
     const size_t length_;
     Value elements_[N];
@@ -3120,17 +3122,17 @@ FinishDynamicModuleImport(JSContext* cx,
                           HandleObject promise);
 
 /**
  * Parse the given source buffer as a module in the scope of the current global
  * of cx and return a source text module record.
  */
 extern JS_PUBLIC_API(bool)
 CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
-              SourceBufferHolder& srcBuf, JS::MutableHandleObject moduleRecord);
+              SourceText<char16_t>& srcBuf, JS::MutableHandleObject moduleRecord);
 
 /**
  * Set a private value associated with a source text module record.
  */
 extern JS_PUBLIC_API(void)
 SetModulePrivate(JSObject* module, const JS::Value& value);
 
 /**
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -146,17 +146,17 @@ EXPORTS.js += [
     '../public/Proxy.h',
     '../public/Realm.h',
     '../public/RefCounted.h',
     '../public/RequiredDefines.h',
     '../public/Result.h',
     '../public/RootingAPI.h',
     '../public/SavedFrameAPI.h',
     '../public/SliceBudget.h',
-    '../public/SourceBufferHolder.h',
+    '../public/SourceText.h',
     '../public/StableStringChars.h',
     '../public/Stream.h',
     '../public/StructuredClone.h',
     '../public/SweepingAPI.h',
     '../public/TraceKind.h',
     '../public/TraceLoggerAPI.h',
     '../public/TracingAPI.h',
     '../public/TrackedOptimizationInfo.h',
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -65,33 +65,34 @@
 
 #include "builtin/Array.h"
 #include "builtin/ModuleObject.h"
 #include "builtin/RegExp.h"
 #include "builtin/TestingFunctions.h"
 #if defined(JS_BUILD_BINAST)
 # include "frontend/BinSource.h"
 #endif // defined(JS_BUILD_BINAST)
+#include "frontend/ModuleSharedContext.h"
 #include "frontend/Parser.h"
 #include "gc/PublicIterators.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/InlinableNatives.h"
 #include "jit/Ion.h"
 #include "jit/JitcodeMap.h"
 #include "jit/JitRealm.h"
 #include "jit/shared/CodeGenerator-shared.h"
 #include "js/CharacterEncoding.h"
 #include "js/CompilationAndEvaluation.h"
 #include "js/CompileOptions.h"
 #include "js/Debug.h"
 #include "js/GCVector.h"
 #include "js/Initialization.h"
 #include "js/JSON.h"
 #include "js/Printf.h"
-#include "js/SourceBufferHolder.h"
+#include "js/SourceText.h"
 #include "js/StableStringChars.h"
 #include "js/StructuredClone.h"
 #include "js/SweepingAPI.h"
 #include "js/Wrapper.h"
 #include "perf/jsperf.h"
 #include "shell/jsoptparse.h"
 #include "shell/jsshell.h"
 #include "shell/OSObject.h"
@@ -874,18 +875,25 @@ RegisterScriptPathWithModuleLoader(JSCon
     if (!JS_DefineProperty(cx, infoObject, "path", pathValue, 0)) {
         return false;
     }
 
     JS::SetScriptPrivate(script, ObjectValue(*infoObject));
     return true;
 }
 
+enum class CompileUtf8
+{
+    InflateToUtf16,
+    DontInflate,
+};
+
 static MOZ_MUST_USE bool
-RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
+RunFile(JSContext* cx, const char* filename, FILE* file, CompileUtf8 compileMethod,
+        bool compileOnly)
 {
     SkipUTF8BOM(file);
 
     // To support the UNIX #! shell hack, gobble the first line if it starts
     // with '#'.
     int ch = fgetc(file);
     if (ch == '#') {
         while ((ch = fgetc(file)) != EOF) {
@@ -901,19 +909,28 @@ RunFile(JSContext* cx, const char* filen
 
     {
         CompileOptions options(cx);
         options.setIntroductionType("js shell file")
                .setFileAndLine(filename, 1)
                .setIsRunOnce(true)
                .setNoScriptRval(true);
 
-        if (!JS::CompileUtf8File(cx, options, file, &script)) {
-            return false;
-        }
+        if (compileMethod == CompileUtf8::DontInflate) {
+            fprintf(stderr, "(compiling '%s' as UTF-8 without inflating)\n", filename);
+
+            if (!JS::CompileUtf8FileDontInflate(cx, options, file, &script)) {
+                return false;
+            }
+        } else {
+            if (!JS::CompileUtf8File(cx, options, file, &script)) {
+                return false;
+            }
+        }
+
         MOZ_ASSERT(script);
     }
 
     if (!RegisterScriptPathWithModuleLoader(cx, script, filename)) {
         return false;
     }
 
     #ifdef DEBUG
@@ -1358,16 +1375,17 @@ ReadEvalPrintLoop(JSContext* cx, FILE* i
     }
 
     return true;
 }
 
 enum FileKind
 {
     FileScript,
+    FileScriptUtf8, // FileScript, but don't inflate to UTF-16 before parsing
     FileModule,
     FileBinAST
 };
 
 static void
 ReportCantOpenErrorUnknownEncoding(JSContext* cx, const char* filename)
 {
     /*
@@ -1380,17 +1398,17 @@ ReportCantOpenErrorUnknownEncoding(JSCon
      * wrong it'll produce mojibake *safely*.  Run with Latin-1 til someone
      * complains.
      */
     JS_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr, JSSMSG_CANT_OPEN,
                                filename, strerror(errno));
 }
 
 static MOZ_MUST_USE bool
-Process(JSContext* cx, const char* filename, bool forceTTY, FileKind kind = FileScript)
+Process(JSContext* cx, const char* filename, bool forceTTY, FileKind kind)
 {
     FILE* file;
     if (forceTTY || !filename || strcmp(filename, "-") == 0) {
         file = stdin;
     } else {
         file = fopen(filename, "rb");
         if (!file) {
             ReportCantOpenErrorUnknownEncoding(cx, filename);
@@ -1398,17 +1416,22 @@ Process(JSContext* cx, const char* filen
         }
     }
     AutoCloseFile autoClose(file);
 
     if (!forceTTY && !isatty(fileno(file))) {
         // It's not interactive - just execute it.
         switch (kind) {
           case FileScript:
-            if (!RunFile(cx, filename, file, compileOnly)) {
+            if (!RunFile(cx, filename, file, CompileUtf8::InflateToUtf16, compileOnly)) {
+                return false;
+            }
+            break;
+          case FileScriptUtf8:
+            if (!RunFile(cx, filename, file, CompileUtf8::DontInflate, compileOnly)) {
                 return false;
             }
             break;
           case FileModule:
             if (!RunModule(cx, filename, file, compileOnly)) {
                 return false;
             }
             break;
@@ -2136,18 +2159,23 @@ Evaluate(JSContext* cx, unsigned argc, V
 
             if (loadBytecode) {
                 JS::TranscodeResult rv = JS::DecodeScript(cx, loadBuffer, &script);
                 if (!ConvertTranscodeResultToJSException(cx, rv)) {
                     return false;
                 }
             } else {
                 mozilla::Range<const char16_t> chars = codeChars.twoByteRange();
-                JS::SourceBufferHolder srcBuf(chars.begin().get(), chars.length(),
-                                              JS::SourceBufferHolder::NoOwnership);
+                JS::SourceText<char16_t> srcBuf;
+                if (!srcBuf.init(cx, chars.begin().get(), chars.length(),
+                                 JS::SourceOwnership::Borrowed))
+                {
+                    return false;
+                }
+
                 if (envChain.length() == 0) {
                     (void) JS::Compile(cx, options, srcBuf, &script);
                 } else {
                     (void) JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script);
                 }
             }
 
             if (!script) {
@@ -2378,18 +2406,22 @@ Run(JSContext* cx, unsigned argc, Value*
         return false;
     }
 
     AutoStableStringChars chars(cx);
     if (!chars.initTwoByte(cx, str)) {
         return false;
     }
 
-    JS::SourceBufferHolder srcBuf(chars.twoByteRange().begin().get(), str->length(),
-                                  JS::SourceBufferHolder::NoOwnership);
+    JS::SourceText<char16_t> srcBuf;
+    if (!srcBuf.init(cx, chars.twoByteRange().begin().get(), str->length(),
+                     JS::SourceOwnership::Borrowed))
+    {
+        return false;
+    }
 
     RootedScript script(cx);
     int64_t startClock = PRMJ_Now();
     {
         /* FIXME: This should use UTF-8 (bug 987069). */
         UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
         if (!filename) {
             return false;
@@ -3858,20 +3890,24 @@ EvalInContext(JSContext* cx, unsigned ar
         }
 
         sobj = ToWindowIfWindowProxy(sobj);
 
         if (!(sobj->getClass()->flags & JSCLASS_IS_GLOBAL)) {
             JS_ReportErrorASCII(cx, "Invalid scope argument to evalcx");
             return false;
         }
+
         JS::CompileOptions opts(cx);
         opts.setFileAndLine(filename.get(), lineno);
-        JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership);
-        if (!JS::Evaluate(cx, opts, srcBuf, args.rval())) {
+
+        JS::SourceText<char16_t> srcBuf;
+        if (!srcBuf.init(cx, src, srclen, JS::SourceOwnership::Borrowed) ||
+            !JS::Evaluate(cx, opts, srcBuf, args.rval()))
+        {
             return false;
         }
     }
 
     if (!cx->compartment()->wrap(cx, args.rval())) {
         return false;
     }
 
@@ -3974,19 +4010,21 @@ WorkerMain(WorkerInput* input)
         JSAutoRealm ar(cx, global);
 
         JS::CompileOptions options(cx);
         options.setFileAndLine("<string>", 1)
                .setIsRunOnce(true);
 
         AutoReportException are(cx);
         RootedScript script(cx);
-        JS::SourceBufferHolder srcBuf(input->chars.get(), input->length,
-                                      JS::SourceBufferHolder::NoOwnership);
-        if (!JS::Compile(cx, options, srcBuf, &script)) {
+        JS::SourceText<char16_t> srcBuf;
+        if (!srcBuf.init(cx, input->chars.get(), input->length,
+                         JS::SourceOwnership::Borrowed) ||
+            !JS::Compile(cx, options, srcBuf, &script))
+        {
             break;
         }
         RootedValue result(cx);
         JS_ExecuteScript(cx, script, &result);
     } while (0);
 
     KillWatchdog(cx);
     JS_SetGrayGCRootsTracer(cx, nullptr, nullptr);
@@ -4651,23 +4689,31 @@ Compile(JSContext* cx, unsigned argc, Va
         return false;
     }
 
     JS::CompileOptions options(cx);
     options.setIntroductionType("js shell compile")
            .setFileAndLine("<string>", 1)
            .setIsRunOnce(true)
            .setNoScriptRval(true);
+
+    JS::SourceText<char16_t> srcBuf;
+    if (!srcBuf.init(cx, stableChars.twoByteRange().begin().get(), scriptContents->length(),
+                     JS::SourceOwnership::Borrowed))
+    {
+        return false;
+    }
+
     RootedScript script(cx);
-    JS::SourceBufferHolder srcBuf(stableChars.twoByteRange().begin().get(),
-                                  scriptContents->length(),
-                                  JS::SourceBufferHolder::NoOwnership);
-    bool ok = JS::Compile(cx, options, srcBuf, &script);
+    if (!JS::Compile(cx, options, srcBuf, &script)) {
+        return false;
+    }
+
     args.rval().setUndefined();
-    return ok;
+    return true;
 }
 
 static ShellCompartmentPrivate*
 EnsureShellCompartmentPrivate(JSContext* cx)
 {
     Compartment* comp = cx->compartment();
     auto priv = static_cast<ShellCompartmentPrivate*>(JS_GetCompartmentPrivate(comp));
     if (!priv) {
@@ -4719,18 +4765,20 @@ ParseModule(JSContext* cx, unsigned argc
     }
 
     AutoStableStringChars stableChars(cx);
     if (!stableChars.initTwoByte(cx, scriptContents)) {
         return false;
     }
 
     const char16_t* chars = stableChars.twoByteRange().begin().get();
-    JS::SourceBufferHolder srcBuf(chars, scriptContents->length(),
-                                  JS::SourceBufferHolder::NoOwnership);
+    JS::SourceText<char16_t> srcBuf;
+    if (!srcBuf.init(cx, chars, scriptContents->length(), JS::SourceOwnership::Borrowed)) {
+        return false;
+    }
 
     RootedObject module(cx, frontend::CompileModule(cx, options, srcBuf));
     if (!module) {
         return false;
     }
 
     args.rval().setObject(*module);
     return true;
@@ -5261,17 +5309,17 @@ Parse(JSContext* cx, unsigned argc, Valu
             return false;
         }
 
         Rooted<ModuleObject*> module(cx, ModuleObject::create(cx));
         if (!module) {
             return false;
         }
 
-        ModuleBuilder builder(cx, module, parser.anyChars);
+        ModuleBuilder builder(cx, module, &parser);
 
         ModuleSharedContext modulesc(cx, module, nullptr, builder);
         pn = parser.moduleBody(&modulesc);
     }
     if (!pn) {
         return false;
     }
 #ifdef DEBUG
@@ -5431,20 +5479,19 @@ OffThreadCompileScript(JSContext* cx, un
     }
 
     OffThreadJob* job = NewOffThreadJob(cx, ScriptKind::Script,
                                         OffThreadJob::Source(std::move(ownedChars)));
     if (!job) {
         return false;
     }
 
-    JS::SourceBufferHolder srcBuf(job->sourceChars(), length,
-                                  JS::SourceBufferHolder::NoOwnership);
-    if (!JS::CompileOffThread(cx, options, srcBuf,
-                              OffThreadCompileScriptCallback, job))
+    JS::SourceText<char16_t> srcBuf;
+    if (!srcBuf.init(cx, job->sourceChars(), length, JS::SourceOwnership::Borrowed) ||
+        !JS::CompileOffThread(cx, options, srcBuf, OffThreadCompileScriptCallback, job))
     {
         job->cancel();
         DeleteOffThreadJob(cx, job);
         return false;
     }
 
     args.rval().setInt32(job->id);
     return true;
@@ -5525,20 +5572,19 @@ OffThreadCompileModule(JSContext* cx, un
     }
 
     OffThreadJob* job = NewOffThreadJob(cx, ScriptKind::Module,
                                         OffThreadJob::Source(std::move(ownedChars)));
     if (!job) {
         return false;
     }
 
-    JS::SourceBufferHolder srcBuf(job->sourceChars(), length,
-                                  JS::SourceBufferHolder::NoOwnership);
-    if (!JS::CompileOffThreadModule(cx, options, srcBuf,
-                                    OffThreadCompileScriptCallback, job))
+    JS::SourceText<char16_t> srcBuf;
+    if (!srcBuf.init(cx, job->sourceChars(), length, JS::SourceOwnership::Borrowed) ||
+        !JS::CompileOffThreadModule(cx, options, srcBuf, OffThreadCompileScriptCallback, job))
     {
         job->cancel();
         DeleteOffThreadJob(cx, job);
         return false;
     }
 
     args.rval().setInt32(job->id);
     return true;
@@ -8206,19 +8252,22 @@ EntryPoints(JSContext* cx, unsigned argc
             if (!codeString || !codeString->ensureFlat(cx)) {
                 return false;
             }
 
             AutoStableStringChars stableChars(cx);
             if (!stableChars.initTwoByte(cx, codeString)) {
                 return false;
             }
-            JS::SourceBufferHolder srcBuf(stableChars.twoByteRange().begin().get(),
-                                          codeString->length(),
-                                          JS::SourceBufferHolder::NoOwnership);
+            JS::SourceText<char16_t> srcBuf;
+            if (!srcBuf.init(cx, stableChars.twoByteRange().begin().get(), codeString->length(),
+                             JS::SourceOwnership::Borrowed))
+            {
+                return false;
+            }
 
             CompileOptions options(cx);
             options.setIntroductionType("entryPoint eval")
                    .setFileAndLine("entryPoint eval", 1);
 
             js::shell::ShellAutoEntryMonitor sarep(cx);
             if (!JS::Evaluate(cx, options, srcBuf, &dummy)) {
                 return false;
@@ -10247,30 +10296,32 @@ ProcessArgs(JSContext* cx, OptionParser*
     }
 
     /* |scriptArgs| gets bound on the global before any code is run. */
     if (!BindScriptArgs(cx, op)) {
         return false;
     }
 
     MultiStringRange filePaths = op->getMultiStringOption('f');
+    MultiStringRange utf8FilePaths = op->getMultiStringOption('u');
     MultiStringRange codeChunks = op->getMultiStringOption('e');
     MultiStringRange modulePaths = op->getMultiStringOption('m');
     MultiStringRange binASTPaths(nullptr, nullptr);
 #if defined(JS_BUILD_BINAST)
     binASTPaths = op->getMultiStringOption('B');
 #endif // JS_BUILD_BINAST
 
     if (filePaths.empty() &&
+        utf8FilePaths.empty() &&
         codeChunks.empty() &&
         modulePaths.empty() &&
         binASTPaths.empty() &&
         !op->getStringArg("script"))
     {
-        return Process(cx, nullptr, true); /* Interactive. */
+        return Process(cx, nullptr, true, FileScript); /* Interactive. */
     }
 
     if (const char* path = op->getStringOption("module-load-path")) {
         RootedString jspath(cx, JS_NewStringCopyZ(cx, path));
         if (!jspath) {
             return false;
         }
 
@@ -10287,74 +10338,105 @@ ProcessArgs(JSContext* cx, OptionParser*
     if (!sc->moduleLoadPath) {
         return false;
     }
 
     if (!InitModuleLoader(cx)) {
         return false;
     }
 
-    while (!filePaths.empty() || !codeChunks.empty() || !modulePaths.empty() || !binASTPaths.empty()) {
+    while (!filePaths.empty() ||
+           !utf8FilePaths.empty() ||
+           !codeChunks.empty() ||
+           !modulePaths.empty() ||
+           !binASTPaths.empty())
+    {
         size_t fpArgno = filePaths.empty() ? SIZE_MAX : filePaths.argno();
+        size_t ufpArgno = utf8FilePaths.empty() ? SIZE_MAX : utf8FilePaths.argno();
         size_t ccArgno = codeChunks.empty() ? SIZE_MAX : codeChunks.argno();
         size_t mpArgno = modulePaths.empty() ? SIZE_MAX : modulePaths.argno();
         size_t baArgno = binASTPaths.empty() ? SIZE_MAX : binASTPaths.argno();
 
-        if (fpArgno < ccArgno && fpArgno < mpArgno && fpArgno < baArgno) {
+        if (fpArgno < ufpArgno && fpArgno < ccArgno && fpArgno < mpArgno && fpArgno < baArgno) {
             char* path = filePaths.front();
             if (!Process(cx, path, false, FileScript)) {
                 return false;
             }
+
             filePaths.popFront();
-        } else if (ccArgno < fpArgno && ccArgno < mpArgno && ccArgno < baArgno) {
+            continue;
+        }
+
+        if (ufpArgno < fpArgno && ufpArgno < ccArgno && ufpArgno < mpArgno && ufpArgno < baArgno) {
+            char* path = utf8FilePaths.front();
+            if (!Process(cx, path, false, FileScriptUtf8)) {
+                return false;
+            }
+
+            utf8FilePaths.popFront();
+            continue;
+        }
+
+        if (ccArgno < fpArgno && ccArgno < ufpArgno && ccArgno < mpArgno && ccArgno < baArgno) {
             const char* code = codeChunks.front();
 
             JS::CompileOptions opts(cx);
             opts.setFileAndLine("-e", 1);
 
             // This might be upgradable to UTF-8, but for now keep assuming the
             // worst.
             RootedValue rval(cx);
             if (!JS::EvaluateLatin1(cx, opts, code, strlen(code), &rval)) {
                 return false;
             }
 
             codeChunks.popFront();
             if (sc->quitting) {
                 break;
             }
-        } else if (baArgno < fpArgno && baArgno < ccArgno && baArgno < mpArgno) {
+
+            continue;
+        }
+
+        if (baArgno < fpArgno && baArgno < ufpArgno && baArgno < ccArgno && baArgno < mpArgno) {
             char* path = binASTPaths.front();
             if (!Process(cx, path, false, FileBinAST)) {
                 return false;
             }
+
             binASTPaths.popFront();
-        } else {
-            MOZ_ASSERT(mpArgno < fpArgno && mpArgno < ccArgno && mpArgno < baArgno);
-            char* path = modulePaths.front();
-            if (!Process(cx, path, false, FileModule)) {
-                return false;
-            }
-            modulePaths.popFront();
-        }
+            continue;
+        }
+
+        MOZ_ASSERT(mpArgno < fpArgno &&
+                   mpArgno < ufpArgno &&
+                   mpArgno < ccArgno &&
+                   mpArgno < baArgno);
+
+        char* path = modulePaths.front();
+        if (!Process(cx, path, false, FileModule)) {
+            return false;
+        }
+
+        modulePaths.popFront();
     }
 
     if (sc->quitting) {
         return false;
     }
 
     /* The |script| argument is processed after all options. */
     if (const char* path = op->getStringArg("script")) {
-        if (!Process(cx, path, false)) {
+        if (!Process(cx, path, false, FileScript)) {
             return false;
         }
     }
 
     if (op->getBoolOption('i')) {
-        if (!Process(cx, nullptr, true)) {
+        if (!Process(cx, nullptr, true, FileScript)) {
             return false;
         }
     }
 
     return true;
 }
 
 static bool
@@ -10952,16 +11034,19 @@ main(int argc, char** argv, char** envp)
         "run left to right. If provided, the optional script argument is run after "
         "all options have been processed. Just-In-Time compilation modes may be enabled via "
         "command line options.");
     op.setDescriptionWidth(72);
     op.setHelpWidth(80);
     op.setVersion(JS_GetImplementationVersion());
 
     if (!op.addMultiStringOption('f', "file", "PATH", "File path to run")
+        || !op.addMultiStringOption('u', "utf8-file", "PATH",
+                                    "File path to run, directly parsing file contents as UTF-8 "
+                                    "without first inflating to UTF-16")
         || !op.addMultiStringOption('m', "module", "PATH", "Module path to run")
 #if defined(JS_BUILD_BINAST)
         || !op.addMultiStringOption('B', "binast", "PATH", "BinAST path to run")
 #else
         || !op.addMultiStringOption('B', "binast", "", "No-op")
 #endif // JS_BUILD_BINAST
         || !op.addMultiStringOption('e', "execute", "CODE", "Inline code to run")
         || !op.addBoolOption('i', "shell", "Enter prompt after running code")
--- a/js/src/util/StringBuffer.h
+++ b/js/src/util/StringBuffer.h
@@ -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/. */
 
 #ifndef util_StringBuffer_h
 #define util_StringBuffer_h
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MaybeOneOf.h"
+#include "mozilla/Utf8.h"
 
 #include "js/Vector.h"
 #include "vm/JSContext.h"
 
 namespace js {
 
 /*
  * String builder that eagerly checks for over-allocation past the maximum
@@ -154,16 +155,24 @@ class StringBuffer
 
     MOZ_MUST_USE bool append(const Latin1Char* begin, const Latin1Char* end) {
         return isLatin1() ? latin1Chars().append(begin, end) : twoByteChars().append(begin, end);
     }
     MOZ_MUST_USE bool append(const Latin1Char* chars, size_t len) {
         return append(chars, chars + len);
     }
 
+    /**
+     * Interpret the provided count of UTF-8 code units as UTF-8, and append
+     * the represented code points to this.  If the code units contain invalid
+     * UTF-8, leave the internal buffer in a consistent but unspecified state,
+     * report an error, and return false.
+     */
+    MOZ_MUST_USE bool append(const mozilla::Utf8Unit* units, size_t len);
+
     MOZ_MUST_USE bool append(const JS::ConstCharPtr chars, size_t len) {
         return append(chars.get(), chars.get() + len);
     }
     MOZ_MUST_USE bool appendN(Latin1Char c, size_t n) {
         return isLatin1() ? latin1Chars().appendN(c, n) : twoByteChars().appendN(c, n);
     }
 
     inline MOZ_MUST_USE bool append(JSString* str);
--- a/js/src/vm/CharacterEncoding.cpp
+++ b/js/src/vm/CharacterEncoding.cpp
@@ -3,23 +3,29 @@
  * 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 "js/CharacterEncoding.h"
 
 #include "mozilla/Range.h"
 #include "mozilla/Sprintf.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Utf8.h"
 
 #include <algorithm>
 #include <type_traits>
 
+#include "util/StringBuffer.h"
 #include "util/Unicode.h" // unicode::REPLACEMENT_CHARACTER
 #include "vm/JSContext.h"
 
+using mozilla::IsAscii;
+using mozilla::Utf8Unit;
+
 using namespace js;
 
 Latin1CharsZ
 JS::LossyTwoByteCharsToNewLatin1CharsZ(JSContext* cx,
                                        const mozilla::Range<const char16_t> tbchars)
 {
     MOZ_ASSERT(cx);
     size_t len = tbchars.length();
@@ -602,8 +608,73 @@ JS::StringIsASCII(const char* s)
     while (*s) {
         if (*s & 0x80) {
             return false;
         }
         s++;
     }
     return true;
 }
+
+bool
+StringBuffer::append(const Utf8Unit* units, size_t len)
+{
+    if (isLatin1()) {
+        Latin1CharBuffer& latin1 = latin1Chars();
+
+        while (len > 0) {
+            if (!IsAscii(*units)) {
+                break;
+            }
+
+            if (!latin1.append(units->toUnsignedChar())) {
+                return false;
+            }
+
+            ++units;
+            --len;
+        }
+        if (len == 0) {
+            return true;
+        }
+
+        // Non-ASCII doesn't *necessarily* mean we couldn't keep appending to
+        // |latin1|, but it's only possible for [U+0080, U+0100) code points,
+        // and handling the full complexity of UTF-8 only for that very small
+        // additional range isn't worth it.  Inflate to two-byte storage before
+        // appending the remaining code points.
+        if (!inflateChars()) {
+            return false;
+        }
+    }
+
+    UTF8Chars remainingUtf8(units, len);
+
+    // Determine how many UTF-16 code units are required to represent the
+    // remaining units.
+    size_t utf16Len = 0;
+    auto countInflated = [&utf16Len](char16_t c) -> LoopDisposition {
+        utf16Len++;
+        return LoopDisposition::Continue;
+    };
+    if (!InflateUTF8ToUTF16<OnUTF8Error::Throw>(cx, remainingUtf8, countInflated)) {
+        return false;
+    }
+
+    TwoByteCharBuffer& buf = twoByteChars();
+
+    size_t i = buf.length();
+    if (!buf.growByUninitialized(utf16Len)) {
+        return false;
+    }
+    MOZ_ASSERT(i + utf16Len == buf.length(),
+               "growByUninitialized assumed to increase length immediately");
+
+    char16_t* toFill = &buf[i];
+    auto appendUtf16 = [&toFill](char16_t unit) {
+        *toFill++ = unit;
+        return LoopDisposition::Continue;
+    };
+
+    MOZ_ALWAYS_TRUE(InflateUTF8ToUTF16<OnUTF8Error::Throw>(cx, remainingUtf8, appendUtf16));
+    MOZ_ASSERT(toFill == buf.end());
+    return true;
+}
--- a/js/src/vm/CompilationAndEvaluation.cpp
+++ b/js/src/vm/CompilationAndEvaluation.cpp
@@ -5,90 +5,127 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Same-thread compilation and evaluation APIs. */
 
 #include "js/CompilationAndEvaluation.h"
 
 #include "mozilla/Maybe.h" // mozilla::None, mozilla::Some
 #include "mozilla/TextUtils.h" // mozilla::IsAscii
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
 
-#include <algorithm> // std::all_of
+#include <utility> // std::move
 
+#include "jsfriendapi.h" // js::GetErrorMessage
 #include "jstypes.h" // JS_PUBLIC_API
 
+#include "frontend/BytecodeCompilation.h" // frontend::CompileGlobalScript
 #include "frontend/FullParseHandler.h" // frontend::FullParseHandler
 #include "frontend/ParseContext.h" // frontend::UsedNameTracker
 #include "frontend/Parser.h" // frontend::Parser, frontend::ParseGoal
 #include "js/CharacterEncoding.h" // JS::UTF8Chars, JS::UTF8CharsToNewTwoByteCharsZ
 #include "js/RootingAPI.h" // JS::Rooted
-#include "js/SourceBufferHolder.h" // JS::SourceBufferHolder
+#include "js/SourceText.h" // JS::SourceText
 #include "js/TypeDecls.h" // JS::HandleObject, JS::MutableHandleScript
+#include "js/Utility.h" // JS::UniqueTwoByteChars
 #include "js/Value.h" // JS::Value
 #include "util/CompleteFile.h" // js::FileContents, js::ReadCompleteFile
 #include "util/StringBuffer.h" // js::StringBuffer
 #include "vm/Debugger.h" // js::Debugger
 #include "vm/EnvironmentObject.h" // js::CreateNonSyntacticEnvironmentChain
 #include "vm/Interpreter.h" // js::Execute
 #include "vm/JSContext.h" // JSContext
 
 #include "vm/JSContext-inl.h" // JSContext::check
 
+using mozilla::Utf8Unit;
+
 using JS::CompileOptions;
 using JS::HandleObject;
 using JS::ReadOnlyCompileOptions;
-using JS::SourceBufferHolder;
+using JS::SourceOwnership;
+using JS::SourceText;
+using JS::UniqueTwoByteChars;
 using JS::UTF8Chars;
 using JS::UTF8CharsToNewTwoByteCharsZ;
 
 using namespace js;
 
+JS_PUBLIC_API(void)
+JS::detail::ReportSourceTooLong(JSContext* cx)
+{
+    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SOURCE_TOO_LONG);
+}
+
+template<typename Unit>
 static bool
 CompileSourceBuffer(JSContext* cx, const ReadOnlyCompileOptions& options,
-                    SourceBufferHolder& srcBuf, JS::MutableHandleScript script)
+                    SourceText<Unit>& srcBuf, JS::MutableHandleScript script)
 {
     ScopeKind scopeKind = options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global;
 
     MOZ_ASSERT(!cx->zone()->isAtomsZone());
     AssertHeapIsIdle();
     CHECK_THREAD(cx);
 
-    script.set(frontend::CompileGlobalScript(cx, scopeKind, options, srcBuf));
+    frontend::GlobalScriptInfo info(cx, options, scopeKind);
+    script.set(frontend::CompileGlobalScript(info, srcBuf));
     return !!script;
 }
 
 static bool
 CompileLatin1(JSContext* cx, const ReadOnlyCompileOptions& options,
               const char* bytes, size_t length, JS::MutableHandleScript script)
 {
-    char16_t* chars = InflateString(cx, bytes, length);
+    auto chars = UniqueTwoByteChars(InflateString(cx, bytes, length));
     if (!chars) {
         return false;
     }
 
-    SourceBufferHolder source(chars, length, SourceBufferHolder::GiveOwnership);
+    SourceText<char16_t> source;
+    if (!source.init(cx, std::move(chars), length)) {
+        return false;
+    }
+
     return CompileSourceBuffer(cx, options, source, script);
 }
 
 static bool
 CompileUtf8(JSContext* cx, const ReadOnlyCompileOptions& options,
             const char* bytes, size_t length, JS::MutableHandleScript script)
 {
-    char16_t* chars = UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(bytes, length), &length).get();
+    auto chars = UniqueTwoByteChars(UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(bytes, length),
+                                                                &length).get());
     if (!chars) {
         return false;
     }
 
-    SourceBufferHolder source(chars, length, SourceBufferHolder::GiveOwnership);
+    SourceText<char16_t> source;
+    if (!source.init(cx, std::move(chars), length)) {
+        return false;
+    }
+
+    return CompileSourceBuffer(cx, options, source, script);
+}
+
+static bool
+CompileUtf8DontInflate(JSContext* cx, const ReadOnlyCompileOptions& options,
+                       const char* bytes, size_t length, JS::MutableHandleScript script)
+{
+    SourceText<Utf8Unit> source;
+    if (!source.init(cx, bytes, length, SourceOwnership::Borrowed)) {
+        return false;
+    }
+
     return CompileSourceBuffer(cx, options, source, script);
 }
 
 bool
 JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options,
-            SourceBufferHolder& srcBuf, JS::MutableHandleScript script)
+            SourceText<char16_t>& srcBuf, JS::MutableHandleScript script)
 {
     return CompileSourceBuffer(cx, options, srcBuf, script);
 }
 
 bool
 JS::CompileLatin1(JSContext* cx, const ReadOnlyCompileOptions& options,
                   const char* bytes, size_t length, JS::MutableHandleScript script)
 {
@@ -98,45 +135,66 @@ JS::CompileLatin1(JSContext* cx, const R
 bool
 JS::CompileUtf8(JSContext* cx, const ReadOnlyCompileOptions& options,
                 const char* bytes, size_t length, JS::MutableHandleScript script)
 {
     return ::CompileUtf8(cx, options, bytes, length, script);
 }
 
 bool
+JS::CompileUtf8DontInflate(JSContext* cx, const ReadOnlyCompileOptions& options,
+                           const char* bytes, size_t length, JS::MutableHandleScript script)
+{
+    return ::CompileUtf8DontInflate(cx, options, bytes, length, script);
+}
+
+bool
 JS::CompileUtf8File(JSContext* cx, const ReadOnlyCompileOptions& options,
                     FILE* file, JS::MutableHandleScript script)
 {
     FileContents buffer(cx);
     if (!ReadCompleteFile(cx, file, buffer)) {
         return false;
     }
 
     return ::CompileUtf8(cx, options,
                          reinterpret_cast<const char*>(buffer.begin()), buffer.length(), script);
 }
 
 bool
+JS::CompileUtf8FileDontInflate(JSContext* cx, const ReadOnlyCompileOptions& options,
+                               FILE* file, JS::MutableHandleScript script)
+{
+    FileContents buffer(cx);
+    if (!ReadCompleteFile(cx, file, buffer)) {
+        return false;
+    }
+
+    return ::CompileUtf8DontInflate(cx, options,
+                                    reinterpret_cast<const char*>(buffer.begin()), buffer.length(),
+                                    script);
+}
+
+bool
 JS::CompileUtf8Path(JSContext* cx, const ReadOnlyCompileOptions& optionsArg,
             const char* filename, JS::MutableHandleScript script)
 {
     AutoFile file;
     if (!file.open(cx, filename)) {
         return false;
     }
 
     CompileOptions options(cx, optionsArg);
     options.setFileAndLine(filename, 1);
     return CompileUtf8File(cx, options, file.fp(), script);
 }
 
 bool
 JS::CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& optionsArg,
-                                SourceBufferHolder& srcBuf, JS::MutableHandleScript script)
+                                SourceText<char16_t>& srcBuf, JS::MutableHandleScript script)
 {
     CompileOptions options(cx, optionsArg);
     options.setNonSyntacticScope(true);
     return CompileSourceBuffer(cx, options, srcBuf, script);
 }
 
 bool
 JS::CompileLatin1ForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& optionsArg,
@@ -207,17 +265,17 @@ JS_Utf8BufferIsCompilableUnit(JSContext*
  * enclosingScope is a scope, if any (e.g. a WithScope).  If the scope is the
  * global scope, this must be null.
  *
  * enclosingEnv is an environment to use, if it's not the global.
  */
 static bool
 CompileFunction(JSContext* cx, const ReadOnlyCompileOptions& optionsArg,
                 HandleAtom name, bool isInvalidName,
-                SourceBufferHolder& srcBuf, uint32_t parameterListEnd,
+                SourceText<char16_t>& srcBuf, uint32_t parameterListEnd,
                 HandleObject enclosingEnv, HandleScope enclosingScope,
                 MutableHandleFunction fun)
 {
     MOZ_ASSERT(!cx->zone()->isAtomsZone());
     AssertHeapIsIdle();
     CHECK_THREAD(cx);
     cx->check(enclosingEnv);
     RootedAtom funAtom(cx);
@@ -249,17 +307,17 @@ CompileFunction(JSContext* cx, const Rea
     }
 
     return true;
 }
 
 static MOZ_MUST_USE bool
 BuildFunctionString(const char* name, size_t nameLen,
                     unsigned nargs, const char* const* argnames,
-                    const SourceBufferHolder& srcBuf, StringBuffer* out,
+                    const SourceText<char16_t>& srcBuf, StringBuffer* out,
                     uint32_t* parameterListEnd)
 {
     MOZ_ASSERT(out);
     MOZ_ASSERT(parameterListEnd);
 
     if (!out->ensureTwoByteChars()) {
        return false;
     }
@@ -301,17 +359,17 @@ BuildFunctionString(const char* name, si
 
     return true;
 }
 
 JS_PUBLIC_API(bool)
 JS::CompileFunction(JSContext* cx, AutoObjectVector& envChain,
                     const ReadOnlyCompileOptions& options,
                     const char* name, unsigned nargs, const char* const* argnames,
-                    SourceBufferHolder& srcBuf, MutableHandleFunction fun)
+                    SourceText<char16_t>& srcBuf, MutableHandleFunction fun)
 {
     RootedObject env(cx);
     RootedScope scope(cx);
     if (!CreateNonSyntacticEnvironmentChain(cx, envChain, &env, &scope)) {
         return false;
     }
 
     size_t nameLen = 0;
@@ -334,34 +392,47 @@ JS::CompileFunction(JSContext* cx, AutoO
     StringBuffer funStr(cx);
     if (!BuildFunctionString(isInvalidName ? nullptr : name, nameLen, nargs, argnames, srcBuf,
                              &funStr, &parameterListEnd))
     {
         return false;
     }
 
     size_t newLen = funStr.length();
-    SourceBufferHolder newSrcBuf(funStr.stealChars(), newLen, SourceBufferHolder::GiveOwnership);
+    UniqueTwoByteChars stolen(funStr.stealChars());
+    if (!stolen) {
+        return false;
+    }
+
+    SourceText<char16_t> newSrcBuf;
+    if (!newSrcBuf.init(cx, std::move(stolen), newLen)) {
+        return false;
+    }
 
     return CompileFunction(cx, options, nameAtom, isInvalidName, newSrcBuf, parameterListEnd, env,
                            scope, fun);
 }
 
 JS_PUBLIC_API(bool)
 JS::CompileFunctionUtf8(JSContext* cx, AutoObjectVector& envChain,
                         const ReadOnlyCompileOptions& options,
                         const char* name, unsigned nargs, const char* const* argnames,
                         const char* bytes, size_t length, MutableHandleFunction fun)
 {
-    char16_t* chars = UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(bytes, length), &length).get();
+    auto chars = UniqueTwoByteChars(UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(bytes, length),
+                                                                &length).get());
     if (!chars) {
         return false;
     }
 
-    SourceBufferHolder source(chars, length, SourceBufferHolder::GiveOwnership);
+    SourceText<char16_t> source;
+    if (!source.init(cx, std::move(chars), length)) {
+        return false;
+    }
+
     return CompileFunction(cx, envChain, options, name, nargs, argnames,
                            source, fun);
 }
 
 JS_PUBLIC_API(bool)
 JS::InitScriptSourceElement(JSContext* cx, HandleScript script,
                             HandleObject element, HandleString elementAttrName)
 {
@@ -476,88 +547,99 @@ JS::CloneAndExecuteScript(JSContext* cx,
         js::Debugger::onNewScript(cx, script);
     }
     return ExecuteScript(cx, envChain, script, rval.address());
 }
 
 static bool
 Evaluate(JSContext* cx, ScopeKind scopeKind, HandleObject env,
          const ReadOnlyCompileOptions& optionsArg,
-         SourceBufferHolder& srcBuf, MutableHandleValue rval)
+         SourceText<char16_t>& srcBuf, MutableHandleValue rval)
 {
     CompileOptions options(cx, optionsArg);
     MOZ_ASSERT(!cx->zone()->isAtomsZone());
     AssertHeapIsIdle();
     CHECK_THREAD(cx);
     cx->check(env);
     MOZ_ASSERT_IF(!IsGlobalLexicalEnvironment(env), scopeKind == ScopeKind::NonSyntactic);
 
     options.setIsRunOnce(true);
-    RootedScript script(cx, frontend::CompileGlobalScript(cx, scopeKind, options, srcBuf));
-    if (!script) {
-        return false;
+
+    RootedScript script(cx);
+    {
+        frontend::GlobalScriptInfo info(cx, options, scopeKind);
+        script = frontend::CompileGlobalScript(info, srcBuf);
+        if (!script) {
+            return false;
+        }
     }
 
-    bool result = Execute(cx, script, *env,
-                          options.noScriptRval ? nullptr : rval.address());
-
-    return result;
+    return Execute(cx, script, *env, options.noScriptRval ? nullptr : rval.address());
 }