Merge inbound to mozilla-central a=merge
authorarthur.iakab <aiakab@mozilla.com>
Thu, 15 Nov 2018 11:54:15 +0200
changeset 502971 dca9c72df68b
parent 502915 48720735b142 (current diff)
parent 502970 a10cbfd5f411 (diff)
child 502989 9a65ee9355e8
child 503011 efcfebe333b6
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)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
dca9c72df68b / 65.0a1 / 20181115100051 / files
nightly linux64
dca9c72df68b / 65.0a1 / 20181115100051 / files
nightly mac
dca9c72df68b / 65.0a1 / 20181115100051 / files
nightly win32
dca9c72df68b / 65.0a1 / 20181115100051 / files
nightly win64
dca9c72df68b / 65.0a1 / 20181115100051 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central a=merge
browser/components/extensions/test/browser/browser_ext_legacy_extension_context_contentscript.js
dom/base/nsDocument.cpp
dom/html/test/test_fullscreen-api.html
js/public/SourceBufferHolder.h
layout/painting/nsDisplayList.cpp
modules/libpref/init/all.js
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
tools/profiler/core/platform.cpp
tools/profiler/public/GeckoProfiler.h
--- 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);
<