Merge mozilla-central to autoland. CLOSED TREE
authorCsoregi Natalia <ncsoregi@mozilla.com>
Fri, 25 Jan 2019 12:10:42 +0200
changeset 512540 878bd06dc8589af32a22a94eef3a85b2df6a31cf
parent 512538 0750fe612685873d9ec80760984a87c2a4b069a9 (diff)
parent 512539 0b28e8a97af41f05727b0da6228e343bf6e57da4 (current diff)
child 512541 21ad3aeb636f3e28853927b4a7b36262320d6f58
push id10566
push userarchaeopteryx@coole-files.de
push dateMon, 28 Jan 2019 12:41:12 +0000
treeherdermozilla-beta@69a3d7c8d04b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. CLOSED TREE
--- a/browser/components/urlbar/tests/browser/browser.ini
+++ b/browser/components/urlbar/tests/browser/browser.ini
@@ -1,14 +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/.
 
 [DEFAULT]
 prefs=browser.urlbar.quantumbar=true
+tags=quantumbar
 support-files =
   dummy_page.html
   head.js
   head-common.js
 
 [browser_bug562649.js]
 support-files = file_bug562649.html
 [browser_bug623155.js]
--- a/browser/components/urlbar/tests/legacy/browser.ini
+++ b/browser/components/urlbar/tests/legacy/browser.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 prefs=browser.urlbar.quantumbar=false
+tags=urlbar
 support-files =
   ../browser/dummy_page.html
   ../browser/head-common.js
   head.js
 
 # XXX Bug 1514162: These are tests that have not yet been ported to the new
 # QuantumBar.
 #
--- a/browser/themes/shared/urlbar-autocomplete.inc.css
+++ b/browser/themes/shared/urlbar-autocomplete.inc.css
@@ -27,16 +27,17 @@
   --urlbar-popup-action-color: #30e60b;
 }
 
 #urlbar-results {
   -moz-appearance: none;
   background: var(--autocomplete-popup-background);
   color: var(--autocomplete-popup-color);
   border: 1px solid var(--autocomplete-popup-border-color);
+  font: menu;
 }
 
 .urlbarView-body-inner {
   box-sizing: border-box;
 }
 
 .urlbarView-results {
   padding: @urlbarViewPadding@;
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/ServiceWorkerAction.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/ServiceWorkerAction.js
@@ -46,17 +46,17 @@ class ServiceWorkerAction extends PureCo
   _renderAction() {
     const { dispatch, runtimeDetails, target } = this.props;
     const { isActive, isRunning } = target.details;
     const { isMultiE10s } = runtimeDetails;
 
     if (!isRunning) {
       const startLabel = this.props.getString("about-debugging-worker-action-start");
       return this._renderButton({
-        className: "default-button",
+        className: "default-button js-start-button",
         disabled: isMultiE10s,
         label: startLabel,
         onClick: this.start.bind(this),
       });
     }
 
     if (!isActive) {
       // Only debug button is available if the service worker is not active.
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -50,16 +50,18 @@ skip-if = (os == "win" && ccov) # Bug 15
 [browser_aboutdebugging_runtime_connection-prompt.js]
 [browser_aboutdebugging_runtime_usbclient_closed.js]
 [browser_aboutdebugging_select_network_runtime.js]
 [browser_aboutdebugging_select_page_with_serviceworker.js]
 [browser_aboutdebugging_serviceworker_multie10s.js]
 [browser_aboutdebugging_serviceworker_push.js]
 [browser_aboutdebugging_serviceworker_pushservice_url.js]
 [browser_aboutdebugging_serviceworker_runtime-page.js]
+[browser_aboutdebugging_serviceworker_start.js]
+[browser_aboutdebugging_serviceworker_timeout.js]
 [browser_aboutdebugging_sidebar_network_runtimes.js]
 [browser_aboutdebugging_sidebar_usb_runtime.js]
 [browser_aboutdebugging_sidebar_usb_runtime_connect.js]
 [browser_aboutdebugging_sidebar_usb_runtime_refresh.js]
 [browser_aboutdebugging_sidebar_usb_runtime_select.js]
 [browser_aboutdebugging_sidebar_usb_status.js]
 skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
 [browser_aboutdebugging_sidebar_usb_unknown_runtime.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_serviceworker_start.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from helper-serviceworker.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-serviceworker.js", this);
+
+const SW_TAB_URL = URL_ROOT + "resources/service-workers/empty-sw.html";
+const SW_URL = URL_ROOT + "resources/service-workers/empty-sw.js";
+
+// This is a migration from:
+// https://searchfox.org/mozilla-central/source/devtools/client/aboutdebugging/test/browser_service_workers_start.js
+
+/**
+ * Test that service workers can be started using about:debugging.
+ */
+add_task(async function() {
+  await enableServiceWorkerDebugging();
+
+  // Setting a low idle_timeout and idle_extended_timeout will allow the service worker
+  // to reach the STOPPED state quickly, which will allow us to test the start button.
+  // The default value is 30000 milliseconds.
+  info("Set a low service worker idle timeout");
+  await pushPref("dom.serviceWorkers.idle_timeout", 1000);
+  await pushPref("dom.serviceWorkers.idle_extended_timeout", 1000);
+
+  const { document, tab } = await openAboutDebugging();
+
+  // Open a tab that registers a basic service worker.
+  const swTab = await addTab(SW_TAB_URL);
+
+  // Wait for the registration to make sure service worker has been started, and that we
+  // are not just reading STOPPED as the initial state.
+  await waitForRegistration(swTab);
+
+  info("Wait until the service worker stops");
+  const targetElement = await waitForServiceWorkerStopped(SW_URL, document);
+
+  // Retrieve the Start button for the worker.
+  const startButton = targetElement.querySelector(".js-start-button");
+  ok(startButton, "Found its start button");
+
+  info("Click on the start button and wait for the service worker to be running");
+  const onServiceWorkerRunning = waitForServiceWorkerRunning(SW_URL, document);
+  startButton.click();
+  const updatedTarget = await onServiceWorkerRunning;
+
+  // Check that the buttons are displayed as expected.
+  const hasInspectButton = updatedTarget.querySelector(".js-debug-target-inspect-button");
+  const hasStartButton = updatedTarget.querySelector(".js-start-button");
+  ok(hasInspectButton, "Service worker has an inspect button");
+  ok(!hasStartButton, "Service worker does not have a start button");
+
+  info("Unregister service worker");
+  await unregisterServiceWorker(swTab);
+
+  info("Wait until the service worker disappears from about:debugging");
+  await waitUntil(() => !findDebugTargetByText(SW_URL, document));
+
+  info("Remove tabs");
+  await removeTab(swTab);
+  await removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_serviceworker_timeout.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This test will be idle for a long period to give a chance to the service worker to
+// timeout.
+requestLongerTimeout(2);
+
+/* import-globals-from helper-serviceworker.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-serviceworker.js", this);
+
+const SW_TAB_URL = URL_ROOT + "resources/service-workers/empty-sw.html";
+const SW_URL = URL_ROOT + "resources/service-workers/empty-sw.js";
+const SW_TIMEOUT = 1000;
+
+// This is a migration from:
+// https://searchfox.org/mozilla-central/source/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
+
+/**
+ * Test that service workers will _not_ timeout and be stopped when a toolbox is attached
+ * to them. Feature implemented in Bug 1228382.
+ */
+add_task(async function() {
+  await enableServiceWorkerDebugging();
+
+  // Setting a low idle_timeout and idle_extended_timeout will allow the service worker
+  // to reach the STOPPED state quickly, which will allow us to test the start button.
+  // The default value is 30000 milliseconds.
+  info("Set a low service worker idle timeout");
+  await pushPref("dom.serviceWorkers.idle_timeout", SW_TIMEOUT);
+  await pushPref("dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT);
+
+  const { document, tab } = await openAboutDebugging();
+
+  // Open a tab that registers a basic service worker.
+  const swTab = await addTab(SW_TAB_URL);
+
+  // Wait for the registration to make sure service worker has been started, and that we
+  // are not just reading STOPPED as the initial state.
+  await waitForRegistration(swTab);
+
+  info("Wait until the service worker stops");
+  await waitForServiceWorkerStopped(SW_URL, document);
+
+  info("Click on the start button and wait for the service worker to be running");
+  const onServiceWorkerRunning = waitForServiceWorkerRunning(SW_URL, document);
+  const startButton = getStartButton(SW_URL, document);
+  startButton.click();
+  await onServiceWorkerRunning;
+
+  const inspectButton = getInspectButton(SW_URL, document);
+  ok(!!inspectButton, "Service worker target has an inspect button");
+
+  info("Click on inspect and wait for the toolbox to open");
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  inspectButton.click();
+  const toolbox = await onToolboxReady;
+
+  // Wait for more 10 times the service worker timeout to check that the toolbox prevents
+  // the worker from being destroyed.
+  await wait(SW_TIMEOUT * 10);
+
+  // Check that the service worker is still running, even after waiting 10 times the
+  // service worker timeout.
+  const hasInspectButton = !!getInspectButton(SW_URL, document);
+  ok(hasInspectButton, "Service worker target still has an inspect button");
+
+  info("Destroy the toolbox");
+  await toolbox.destroy();
+
+  // After stopping the toolbox, the service worker instance should be released and the
+  // service worker registration should be displayed as stopped again.
+  info("Wait until the service worker stops after closing the toolbox");
+  await waitForServiceWorkerStopped(SW_URL, document);
+
+  info("Unregister service worker");
+  await unregisterServiceWorker(swTab);
+
+  info("Wait until the service worker disappears from about:debugging");
+  await waitUntil(() => !findDebugTargetByText(SW_URL, document));
+
+  info("Remove tabs");
+  await removeTab(swTab);
+  await removeTab(tab);
+});
+
+function getStartButton(workerText, doc) {
+  const target = findDebugTargetByText(workerText, doc);
+  return target ? target.querySelector(".js-start-button") : null;
+}
+
+function getInspectButton(workerText, doc) {
+  const target = findDebugTargetByText(workerText, doc);
+  return target ? target.querySelector(".js-debug-target-inspect-button") : null;
+}
--- a/devtools/client/aboutdebugging-new/test/browser/helper-serviceworker.js
+++ b/devtools/client/aboutdebugging-new/test/browser/helper-serviceworker.js
@@ -35,27 +35,46 @@ function onTabMessage(tab, message) {
     mm.addMessageListener(message, function listener() {
       mm.removeMessageListener(message, listener);
       resolve();
     });
   });
 }
 /* exported onTabMessage */
 
-async function waitForServiceWorkerRunning(workerText, document) {
+async function _waitForServiceWorkerStatus(workerText, status, document) {
   await waitUntil(() => {
     const target = findDebugTargetByText(workerText, document);
-    const status = target && target.querySelector(".js-worker-status");
-    return status && status.textContent === "Running";
+    const statusElement = target && target.querySelector(".js-worker-status");
+    return statusElement && statusElement.textContent === status;
   });
 
   return findDebugTargetByText(workerText, document);
 }
 /* exported waitForServiceWorkerRunning */
 
+async function waitForServiceWorkerStopped(workerText, document) {
+  return _waitForServiceWorkerStatus(workerText, "Stopped", document);
+}
+/* exported waitForServiceWorkerStopped */
+
+async function waitForServiceWorkerRunning(workerText, document) {
+  return _waitForServiceWorkerStatus(workerText, "Running", document);
+}
+/* exported waitForServiceWorkerRunning */
+
+async function waitForRegistration(tab) {
+  info("Wait until the registration appears on the window");
+  const swBrowser = tab.linkedBrowser;
+  await asyncWaitUntil(async () => ContentTask.spawn(swBrowser, {}, function() {
+    return content.wrappedJSObject.getRegistration();
+  }));
+}
+/* exported waitForRegistration */
+
 /**
  * Helper to listen once on a message sent using postMessage from the provided tab.
  *
  * @param {Tab} tab
  *        The tab on which the message will be received.
  * @param {String} message
  *        The name of the expected message.
  */
copy from devtools/client/aboutdebugging/test/service-workers/empty-sw.html
copy to devtools/client/aboutdebugging-new/test/browser/resources/service-workers/empty-sw.html
--- a/devtools/client/aboutdebugging/test/service-workers/empty-sw.html
+++ b/devtools/client/aboutdebugging-new/test/browser/resources/service-workers/empty-sw.html
@@ -3,20 +3,28 @@
 <head>
   <meta charset="UTF-8">
   <title>Service worker test</title>
 </head>
 <body>
 <script type="text/javascript">
 "use strict";
 
-var sw = navigator.serviceWorker.register("empty-sw.js");
-sw.then(
-  function() {
-    dump("SW registered\n");
-  },
-  function(e) {
-    dump("SW not registered: " + e + "\n");
+let registration;
+
+const registerServiceWorker = async function() {
+  try {
+    registration = await navigator.serviceWorker.register("empty-sw.js");
+    dump("Push service worker registered\n");
+  } catch (e) {
+    dump("Push service worker not registered: " + e + "\n");
   }
-);
+};
+
+// Helper called from helper-serviceworker.js to unregister the service worker.
+window.getRegistration = function() {
+  return registration;
+};
+// Register the service worker.
+registerServiceWorker();
 </script>
 </body>
 </html>
copy from devtools/client/aboutdebugging/test/service-workers/empty-sw.js
copy to devtools/client/aboutdebugging-new/test/browser/resources/service-workers/empty-sw.js
--- a/devtools/client/themes/components-frame.css
+++ b/devtools/client/themes/components-frame.css
@@ -9,17 +9,18 @@
  */
 
 .theme-light {
   --frame-link-line-color: var(--theme-highlight-blue);
   --frame-link-source: var(--theme-highlight-purple);
 }
 
 .theme-dark {
-  --frame-link-source: #6B89FF;
+  --frame-link-line-color: hsl(210, 40%, 60%);
+  --frame-link-source: var(--blue-40);
 }
 
 .stack-trace {
   display: grid;
   grid-template-columns: auto auto;
   justify-content: start;
 }
 
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -193,16 +193,17 @@
 
   /* Force RTL direction to crop the source link at the beginning. */
   direction: rtl;
   overflow: hidden;
   text-overflow: ellipsis;
 
   -moz-user-select: none;
   margin-bottom: 2px;
+  margin-left: 1ch;
 }
 
 .ruleview-rule-source-label {
   white-space: nowrap;
   margin: 0;
   cursor: pointer;
 
   /* Create an LTR embed to avoid special characters being shifted to the start due to the
--- a/js/src/builtin/Array.cpp
+++ b/js/src/builtin/Array.cpp
@@ -3672,19 +3672,23 @@ static bool ArrayFromCallArgs(JSContext*
 
   args.rval().setObject(*obj);
   return true;
 }
 
 static bool array_of(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
-  if (IsArrayConstructor(args.thisv()) || !IsConstructor(args.thisv())) {
-    // IsArrayConstructor(this) will usually be true in practice. This is
-    // the most common path.
+  bool isArrayConstructor =
+      IsArrayConstructor(args.thisv()) &&
+      args.thisv().toObject().nonCCWRealm() == cx->realm();
+
+  if (isArrayConstructor || !IsConstructor(args.thisv())) {
+    // isArrayConstructor will usually be true in practice. This is the most
+    // common path.
     return ArrayFromCallArgs(cx, args);
   }
 
   // Step 4.
   RootedObject obj(cx);
   {
     FixedConstructArgs<1> cargs(cx);
 
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -1231,17 +1231,18 @@ static MOZ_MUST_USE bool NewPromiseCapab
   // This can't be used in Promise.all and Promise.race because we have to
   // pass the reject (and resolve, in the race case) function to thenables
   // in the list passed to all/race, which (potentially) means exposing them
   // to content.
   //
   // For Promise.all and Promise.race we can only optimize away the creation
   // of the GetCapabilitiesExecutor function, and directly allocate the
   // result promise instead of invoking the Promise constructor.
-  if (IsNativeFunction(cVal, PromiseConstructor)) {
+  if (IsNativeFunction(cVal, PromiseConstructor) &&
+      cVal.toObject().nonCCWRealm() == cx->realm()) {
     PromiseObject* promise;
     if (canOmitResolutionFunctions) {
       promise = CreatePromiseObjectWithoutResolutionFunctions(cx);
     } else {
       promise = CreatePromiseWithDefaultResolutionFunctions(
           cx, capability.resolve(), capability.reject());
     }
     if (!promise) {
@@ -4060,17 +4061,18 @@ static bool Promise_catch_impl(JSContext
   }
 
   // Step 1.
   RootedValue thenVal(cx);
   if (!GetProperty(cx, thisVal, cx->names().then, &thenVal)) {
     return false;
   }
 
-  if (IsNativeFunction(thenVal, &Promise_then)) {
+  if (IsNativeFunction(thenVal, &Promise_then) &&
+      thenVal.toObject().nonCCWRealm() == cx->realm()) {
     return Promise_then_impl(cx, thisVal, onFulfilled, onRejected, args.rval(),
                              rvalUsed);
   }
 
   return Call(cx, thenVal, thisVal, UndefinedHandleValue, onRejected,
               args.rval());
 }
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -536,17 +536,17 @@ Maybe<uint32_t> BytecodeEmitter::getOffs
     }
   }
 
   return Some(nextpn->pn_pos.begin);
 }
 
 void BytecodeEmitter::checkTypeSet(JSOp op) {
   if (CodeSpec[op].format & JOF_TYPESET) {
-    if (typesetCount < UINT16_MAX) {
+    if (typesetCount < JSScript::MaxBytecodeTypeSets) {
       typesetCount++;
     }
   }
 }
 
 bool BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand) {
   MOZ_ASSERT(operand <= UINT16_MAX);
   if (!emit3(op, UINT16_LO(operand), UINT16_HI(operand))) {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/realms/bug1513665.js
@@ -0,0 +1,24 @@
+load(libdir + "asserts.js");
+
+var g = newGlobal();
+
+function testArrayOf() {
+    var a = Array.of.call(g.Array);
+    assertEq(a instanceof g.Array, true);
+}
+testArrayOf();
+
+function testPromiseThen() {
+    var p = Promise.resolve(0);
+    p.constructor = g.Promise;
+    var r = p.then(() => {});
+    assertEq(r instanceof g.Promise, true);
+}
+testPromiseThen();
+
+function testPromiseCatch() {
+    Boolean.prototype.then = g.Promise.prototype.then;
+    assertThrowsInstanceOf(() => Promise.prototype.catch.call(false),
+                           g.TypeError);
+}
+testPromiseCatch();
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -264,30 +264,27 @@ MethodStatus BaselineCompiler::compile()
     previousOffset = entry.nativeOffset;
   }
 
   if (pcEntries.oom()) {
     ReportOutOfMemory(cx);
     return Method_Error;
   }
 
-  // Note: There is an extra entry in the bytecode type map for the search
-  // hint, see below.
-  size_t bytecodeTypeMapEntries = script->nTypeSets() + 1;
   size_t resumeEntries =
       script->hasResumeOffsets() ? script->resumeOffsets().size() : 0;
   UniquePtr<BaselineScript> baselineScript(
-      BaselineScript::New(
-          script, bailoutPrologueOffset_.offset(),
-          debugOsrPrologueOffset_.offset(), debugOsrEpilogueOffset_.offset(),
-          profilerEnterFrameToggleOffset_.offset(),
-          profilerExitFrameToggleOffset_.offset(),
-          handler.retAddrEntries().length(), pcMappingIndexEntries.length(),
-          pcEntries.length(), bytecodeTypeMapEntries, resumeEntries,
-          traceLoggerToggleOffsets_.length()),
+      BaselineScript::New(script, bailoutPrologueOffset_.offset(),
+                          debugOsrPrologueOffset_.offset(),
+                          debugOsrEpilogueOffset_.offset(),
+                          profilerEnterFrameToggleOffset_.offset(),
+                          profilerExitFrameToggleOffset_.offset(),
+                          handler.retAddrEntries().length(),
+                          pcMappingIndexEntries.length(), pcEntries.length(),
+                          resumeEntries, traceLoggerToggleOffsets_.length()),
       JS::DeletePolicy<BaselineScript>(cx->runtime()));
   if (!baselineScript) {
     ReportOutOfMemory(cx);
     return Method_Error;
   }
 
   baselineScript->setMethod(code);
   baselineScript->setTemplateEnvironment(templateEnv);
@@ -322,23 +319,16 @@ MethodStatus BaselineCompiler::compile()
     baselineScript->setUsesEnvironmentChain();
   }
 
 #ifdef JS_TRACE_LOGGING
   // Initialize the tracelogger instrumentation.
   baselineScript->initTraceLogger(script, traceLoggerToggleOffsets_);
 #endif
 
-  uint32_t* bytecodeMap = baselineScript->bytecodeTypeMap();
-  FillBytecodeTypeMap(script, bytecodeMap);
-
-  // The last entry in the last index found, and is used to avoid binary
-  // searches for the sought entry when queries are in linear order.
-  bytecodeMap[script->nTypeSets()] = 0;
-
   // Compute yield/await native resume addresses.
   baselineScript->computeResumeNativeOffsets(script);
 
   if (compileDebugInstrumentation()) {
     baselineScript->setHasDebugInstrumentation();
   }
 
   // Always register a native => bytecode mapping entry, since profiler can be
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -326,41 +326,36 @@ MethodStatus jit::CanEnterBaselineMethod
   return CanEnterBaselineJIT(cx, script, /* osrFrame = */ nullptr);
 };
 
 BaselineScript* BaselineScript::New(
     JSScript* jsscript, uint32_t bailoutPrologueOffset,
     uint32_t debugOsrPrologueOffset, uint32_t debugOsrEpilogueOffset,
     uint32_t profilerEnterToggleOffset, uint32_t profilerExitToggleOffset,
     size_t retAddrEntries, size_t pcMappingIndexEntries, size_t pcMappingSize,
-    size_t bytecodeTypeMapEntries, size_t resumeEntries,
-    size_t traceLoggerToggleOffsetEntries) {
+    size_t resumeEntries, size_t traceLoggerToggleOffsetEntries) {
   static const unsigned DataAlignment = sizeof(uintptr_t);
 
   size_t retAddrEntriesSize = retAddrEntries * sizeof(RetAddrEntry);
   size_t pcMappingIndexEntriesSize =
       pcMappingIndexEntries * sizeof(PCMappingIndexEntry);
-  size_t bytecodeTypeMapSize = bytecodeTypeMapEntries * sizeof(uint32_t);
   size_t resumeEntriesSize = resumeEntries * sizeof(uintptr_t);
   size_t tlEntriesSize = traceLoggerToggleOffsetEntries * sizeof(uint32_t);
 
   size_t paddedRetAddrEntriesSize =
       AlignBytes(retAddrEntriesSize, DataAlignment);
   size_t paddedPCMappingIndexEntriesSize =
       AlignBytes(pcMappingIndexEntriesSize, DataAlignment);
   size_t paddedPCMappingSize = AlignBytes(pcMappingSize, DataAlignment);
-  size_t paddedBytecodeTypesMapSize =
-      AlignBytes(bytecodeTypeMapSize, DataAlignment);
   size_t paddedResumeEntriesSize = AlignBytes(resumeEntriesSize, DataAlignment);
   size_t paddedTLEntriesSize = AlignBytes(tlEntriesSize, DataAlignment);
 
   size_t allocBytes = paddedRetAddrEntriesSize +
                       paddedPCMappingIndexEntriesSize + paddedPCMappingSize +
-                      paddedBytecodeTypesMapSize + paddedResumeEntriesSize +
-                      paddedTLEntriesSize;
+                      paddedResumeEntriesSize + paddedTLEntriesSize;
 
   BaselineScript* script =
       jsscript->zone()->pod_malloc_with_extra<BaselineScript, uint8_t>(
           allocBytes);
   if (!script) {
     return nullptr;
   }
   new (script) BaselineScript(bailoutPrologueOffset, debugOsrPrologueOffset,
@@ -377,19 +372,16 @@ BaselineScript* BaselineScript::New(
   script->pcMappingIndexOffset_ = offsetCursor;
   script->pcMappingIndexEntries_ = pcMappingIndexEntries;
   offsetCursor += paddedPCMappingIndexEntriesSize;
 
   script->pcMappingOffset_ = offsetCursor;
   script->pcMappingSize_ = pcMappingSize;
   offsetCursor += paddedPCMappingSize;
 
-  script->bytecodeTypeMapOffset_ = bytecodeTypeMapEntries ? offsetCursor : 0;
-  offsetCursor += paddedBytecodeTypesMapSize;
-
   script->resumeEntriesOffset_ = resumeEntries ? offsetCursor : 0;
   offsetCursor += paddedResumeEntriesSize;
 
   script->traceLoggerToggleOffsetsOffset_ = tlEntriesSize ? offsetCursor : 0;
   script->numTraceLoggerToggleOffsets_ = traceLoggerToggleOffsetEntries;
   offsetCursor += paddedTLEntriesSize;
 
   MOZ_ASSERT(offsetCursor == sizeof(BaselineScript) + allocBytes);
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -290,20 +290,16 @@ struct BaselineScript final {
   uint32_t retAddrEntries_ = 0;
 
   uint32_t pcMappingIndexOffset_ = 0;
   uint32_t pcMappingIndexEntries_ = 0;
 
   uint32_t pcMappingOffset_ = 0;
   uint32_t pcMappingSize_ = 0;
 
-  // List mapping indexes of bytecode type sets to the offset of the opcode
-  // they correspond to, for use by TypeScript::BytecodeTypes.
-  uint32_t bytecodeTypeMapOffset_ = 0;
-
   // We store the native code address corresponding to each bytecode offset in
   // the script's resumeOffsets list.
   uint32_t resumeEntriesOffset_ = 0;
 
   // By default tracelogger is disabled. Therefore we disable the logging code
   // by default. We store the offsets we must patch to enable the logging.
   uint32_t traceLoggerToggleOffsetsOffset_ = 0;
   uint32_t numTraceLoggerToggleOffsets_ = 0;
@@ -339,18 +335,17 @@ struct BaselineScript final {
         profilerExitToggleOffset_(profilerExitToggleOffset) {}
 
  public:
   static BaselineScript* New(
       JSScript* jsscript, uint32_t bailoutPrologueOffset,
       uint32_t debugOsrPrologueOffset, uint32_t debugOsrEpilogueOffset,
       uint32_t profilerEnterToggleOffset, uint32_t profilerExitToggleOffset,
       size_t retAddrEntries, size_t pcMappingIndexEntries, size_t pcMappingSize,
-      size_t bytecodeTypeMapEntries, size_t resumeEntries,
-      size_t traceLoggerToggleOffsetEntries);
+      size_t resumeEntries, size_t traceLoggerToggleOffsetEntries);
 
   static void Trace(JSTracer* trc, BaselineScript* script);
   static void Destroy(FreeOp* fop, BaselineScript* script);
 
   static inline size_t offsetOfMethod() {
     return offsetof(BaselineScript, method_);
   }
 
@@ -497,22 +492,16 @@ struct BaselineScript final {
 
   static size_t offsetOfFlags() { return offsetof(BaselineScript, flags_); }
   static size_t offsetOfResumeEntriesOffset() {
     return offsetof(BaselineScript, resumeEntriesOffset_);
   }
 
   static void writeBarrierPre(Zone* zone, BaselineScript* script);
 
-  uint32_t* bytecodeTypeMap() {
-    MOZ_ASSERT(bytecodeTypeMapOffset_);
-    return reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(this) +
-                                       bytecodeTypeMapOffset_);
-  }
-
   uint8_t maxInliningDepth() const { return maxInliningDepth_; }
   void setMaxInliningDepth(uint32_t depth) {
     MOZ_ASSERT(depth <= UINT8_MAX);
     maxInliningDepth_ = depth;
   }
   void resetMaxInliningDepth() { maxInliningDepth_ = UINT8_MAX; }
 
   uint16_t inlinedBytecodeLength() const { return inlinedBytecodeLength_; }
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2156,17 +2156,17 @@ static bool CanIonCompileOrInlineScript(
   if (script->functionHasExtraBodyVarScope() &&
       script->functionExtraBodyVarScope()->hasEnvironment()) {
     // This restriction will be lifted when intra-function scope chains
     // are compilable by Ion. See bug 1273858.
     *reason = "has extra var environment";
     return false;
   }
 
-  if (script->nTypeSets() >= UINT16_MAX) {
+  if (script->numBytecodeTypeSets() >= JSScript::MaxBytecodeTypeSets) {
     // In this case multiple bytecode ops can share a single observed
     // TypeSet (see bug 1303710).
     *reason = "too many typesets";
     return false;
   }
 
   return true;
 }
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -173,17 +173,17 @@ IonBuilder::IonBuilder(JSContext* analys
   script_ = info->script();
   scriptHasIonScript_ = script_->hasIonScript();
   pc = info->startPC();
 
   MOZ_ASSERT(script()->hasBaselineScript() ==
              (info->analysisMode() != Analysis_ArgumentsUsage));
   MOZ_ASSERT(!!analysisContext ==
              (info->analysisMode() == Analysis_DefiniteProperties));
-  MOZ_ASSERT(script_->nTypeSets() < UINT16_MAX);
+  MOZ_ASSERT(script_->numBytecodeTypeSets() < JSScript::MaxBytecodeTypeSets);
 
   if (!info->isAnalysis()) {
     script()->baselineScript()->setIonCompiledOrInlined();
   }
 }
 
 void IonBuilder::clearForBackEnd() {
   MOZ_ASSERT(!analysisContext);
@@ -746,28 +746,18 @@ AbortReasonOr<Ok> IonBuilder::init() {
   if (inlineCallInfo_) {
     // If we're inlining, the actual this/argument types are not necessarily
     // a subset of the script's observed types. |argTypes| is never accessed
     // for inlined scripts, so we just null it.
     thisTypes = inlineCallInfo_->thisArg()->resultTypeSet();
     argTypes = nullptr;
   }
 
-  // The baseline script normally has the bytecode type map, but compute
-  // it ourselves if we do not have a baseline script.
-  if (script()->hasBaselineScript()) {
-    bytecodeTypeMap = script()->baselineScript()->bytecodeTypeMap();
-  } else {
-    bytecodeTypeMap = alloc_->lifoAlloc()->newArrayUninitialized<uint32_t>(
-        script()->nTypeSets());
-    if (!bytecodeTypeMap) {
-      return abort(AbortReason::Alloc);
-    }
-    FillBytecodeTypeMap(script(), bytecodeTypeMap);
-  }
+  AutoSweepTypeScript sweep(script());
+  bytecodeTypeMap = script()->types(sweep)->bytecodeTypeMap();
 
   return Ok();
 }
 
 AbortReasonOr<Ok> IonBuilder::build() {
   // Spew IC info for inlined script, but only when actually compiling,
   // not when analyzing it.
 #ifdef JS_STRUCTURED_SPEW
--- a/js/src/vm/JSFunction.h
+++ b/js/src/vm/JSFunction.h
@@ -226,16 +226,17 @@ class JSFunction : public js::NativeObje
   bool needsFunctionEnvironmentObjects() const {
     return needsCallObject() || needsNamedLambdaEnvironment();
   }
 
   bool needsSomeEnvironmentObject() const {
     return needsFunctionEnvironmentObjects() || needsExtraBodyVarEnvironment();
   }
 
+  static constexpr size_t NArgsBits = sizeof(nargs_) * CHAR_BIT;
   size_t nargs() const { return nargs_; }
 
   uint16_t flags() const { return flags_; }
 
   FunctionKind kind() const {
     return static_cast<FunctionKind>((flags_ & FUNCTION_KIND_MASK) >>
                                      FUNCTION_KIND_SHIFT);
   }
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -360,17 +360,17 @@ XDRResult js::XDRScript(XDRState<mode>* 
   };
 
   uint32_t length, lineno, column, nfixed, nslots;
   uint32_t natoms, nsrcnotes;
   uint32_t nconsts, nobjects, nscopes, nregexps, ntrynotes, nscopenotes,
       nresumeoffsets;
   uint32_t prologueLength;
   uint32_t funLength = 0;
-  uint32_t nTypeSets = 0;
+  uint32_t numBytecodeTypeSets = 0;
   uint32_t scriptBits = 0;
   uint32_t bodyScopeIndex = 0;
   uint32_t immutableFlags = 0;
 
   JSContext* cx = xdr->cx();
   RootedScript script(cx);
   natoms = nsrcnotes = 0;
   nconsts = nobjects = nscopes = nregexps = ntrynotes = nscopenotes =
@@ -425,17 +425,17 @@ XDRResult js::XDRScript(XDRState<mode>* 
     }
     if (script->hasScopeNotes()) {
       nscopenotes = script->scopeNotes().size();
     }
     if (script->hasResumeOffsets()) {
       nresumeoffsets = script->resumeOffsets().size();
     }
 
-    nTypeSets = script->nTypeSets();
+    numBytecodeTypeSets = script->numBytecodeTypeSets();
     funLength = script->funLength();
 
     if (script->analyzedArgsUsage() && script->needsArgsObj()) {
       scriptBits |= (1 << NeedsArgsObj);
     }
     MOZ_ASSERT_IF(sourceObjectArg,
                   sourceObjectArg->source() == script->scriptSource());
     if (!sourceObjectArg) {
@@ -452,17 +452,17 @@ XDRResult js::XDRScript(XDRState<mode>* 
   MOZ_TRY(xdr->codeUint32(&natoms));
   MOZ_TRY(xdr->codeUint32(&nsrcnotes));
   MOZ_TRY(xdr->codeUint32(&nconsts));
   MOZ_TRY(xdr->codeUint32(&nobjects));
   MOZ_TRY(xdr->codeUint32(&nscopes));
   MOZ_TRY(xdr->codeUint32(&ntrynotes));
   MOZ_TRY(xdr->codeUint32(&nscopenotes));
   MOZ_TRY(xdr->codeUint32(&nresumeoffsets));
-  MOZ_TRY(xdr->codeUint32(&nTypeSets));
+  MOZ_TRY(xdr->codeUint32(&numBytecodeTypeSets));
   MOZ_TRY(xdr->codeUint32(&funLength));
   MOZ_TRY(xdr->codeUint32(&scriptBits));
   MOZ_TRY(xdr->codeUint32(&immutableFlags));
 
   MOZ_ASSERT(!!(scriptBits & (1 << OwnSource)) == !sourceObjectArg);
   RootedScriptSourceObject sourceObject(cx, sourceObjectArg);
 
   if (mode == XDR_DECODE) {
@@ -537,18 +537,18 @@ XDRResult js::XDRScript(XDRState<mode>* 
                                            nresumeoffsets)) {
       return xdr->fail(JS::TranscodeResult_Throw);
     }
 
     MOZ_ASSERT(!script->mainOffset());
     script->mainOffset_ = prologueLength;
     script->funLength_ = funLength;
 
-    MOZ_ASSERT(nTypeSets <= UINT16_MAX);
-    script->nTypeSets_ = uint16_t(nTypeSets);
+    MOZ_ASSERT(numBytecodeTypeSets <= UINT16_MAX);
+    script->numBytecodeTypeSets_ = uint16_t(numBytecodeTypeSets);
 
     scriptp.set(script);
 
     script->immutableFlags_ = immutableFlags;
 
     if (script->hasFlag(ImmutableFlags::ArgsHasVarBinding)) {
       // Call setArgumentsHasVarBinding to initialize the
       // NeedsArgsAnalysis flag.
@@ -3283,17 +3283,17 @@ static inline uint8_t* AllocScriptData(J
   uint32_t numTryNotes = 0;
   uint32_t numScopeNotes = 0;
   uint32_t nresumeoffsets = 0;
   if (!createPrivateScriptData(cx, script, numScopes, numConsts, numObjects,
                                numTryNotes, numScopeNotes, nresumeoffsets)) {
     return false;
   }
 
-  script->nTypeSets_ = 0;
+  script->numBytecodeTypeSets_ = 0;
 
   RootedScope enclosing(cx, &cx->global()->emptyGlobalScope());
   Scope* functionProtoScope = FunctionScope::create(cx, nullptr, false, false,
                                                     functionProto, enclosing);
   if (!functionProtoScope) {
     return false;
   }
 
@@ -3397,17 +3397,17 @@ static void InitAtomMap(frontend::AtomIn
           cx, script, bce->scopeList.length(), bce->numberList.length(),
           bce->objectList.length, bce->tryNoteList.length(),
           bce->scopeNoteList.length(), bce->resumeOffsetList.length())) {
     return false;
   }
 
   MOZ_ASSERT(script->mainOffset() == 0);
   script->mainOffset_ = bce->mainOffset();
-  script->nTypeSets_ = bce->typesetCount;
+  script->numBytecodeTypeSets_ = bce->typesetCount;
   script->lineno_ = bce->firstLine;
 
   // The + 1 is to account for the final SN_MAKE_TERMINATOR that is appended
   // when the notes are copied to their final destination by copySrcNotes.
   uint32_t nsrcnotes = bce->notes().length() + 1;
   uint32_t codeLength = bce->code().length();
   if (!script->createSharedScriptData(cx, codeLength, nsrcnotes, natoms)) {
     return false;
@@ -4028,17 +4028,17 @@ bool js::detail::CopyScript(JSContext* c
   dst->setScriptData(src->scriptData());
 
   dst->lineno_ = src->lineno();
   dst->mainOffset_ = src->mainOffset();
   dst->nfixed_ = src->nfixed();
   dst->nslots_ = src->nslots();
   dst->bodyScopeIndex_ = src->bodyScopeIndex_;
   dst->funLength_ = src->funLength();
-  dst->nTypeSets_ = src->nTypeSets();
+  dst->numBytecodeTypeSets_ = src->numBytecodeTypeSets();
 
   dst->immutableFlags_ = src->immutableFlags_;
   dst->setFlag(JSScript::ImmutableFlags::HasNonSyntacticScope,
                scopes[0]->hasOnChain(ScopeKind::NonSyntactic));
 
   if (src->argumentsHasVarBinding()) {
     dst->setArgumentsHasVarBinding();
     if (src->analyzedArgsUsage()) {
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -1774,17 +1774,17 @@ class JSScript : public js::gc::TenuredC
    * reset when a script is successfully jit-compiled.
    */
   uint16_t warmUpResetCount = 0;
 
   /* ES6 function length. */
   uint16_t funLength_ = 0;
 
   /* Number of type sets used in this script for dynamic type monitoring. */
-  uint16_t nTypeSets_ = 0;
+  uint16_t numBytecodeTypeSets_ = 0;
 
   //
   // End of fields.  Start methods.
   //
 
  private:
   template <js::XDRMode mode>
   friend js::XDRResult js::XDRScript(js::XDRState<mode>* xdr,
@@ -1981,17 +1981,24 @@ class JSScript : public js::gc::TenuredC
     // Only functions have parameters.
     js::Scope* scope = bodyScope();
     if (!scope->is<js::FunctionScope>()) {
       return false;
     }
     return scope->as<js::FunctionScope>().hasParameterExprs();
   }
 
-  size_t nTypeSets() const { return nTypeSets_; }
+  // If there are more than MaxBytecodeTypeSets JOF_TYPESET ops in the script,
+  // the first MaxBytecodeTypeSets - 1 JOF_TYPESET ops have their own TypeSet
+  // and all other JOF_TYPESET ops share the last TypeSet.
+  static constexpr size_t MaxBytecodeTypeSets = UINT16_MAX;
+  static_assert(sizeof(numBytecodeTypeSets_) == 2,
+                "MaxBytecodeTypeSets must match sizeof(numBytecodeTypeSets_)");
+
+  size_t numBytecodeTypeSets() const { return numBytecodeTypeSets_; }
 
   size_t funLength() const { return funLength_; }
 
   static size_t offsetOfFunLength() { return offsetof(JSScript, funLength_); }
 
   uint32_t sourceStart() const { return sourceStart_; }
 
   uint32_t sourceEnd() const { return sourceEnd_; }
--- a/js/src/vm/TypeInference-inl.h
+++ b/js/src/vm/TypeInference-inl.h
@@ -622,91 +622,89 @@ extern void TypeMonitorResult(JSContext*
                               StackTypeSet* types, TypeSet::Type type);
 extern void TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc,
                               const Value& rval);
 
 /////////////////////////////////////////////////////////////////////
 // Script interface functions
 /////////////////////////////////////////////////////////////////////
 
-/* static */ inline unsigned TypeScript::NumTypeSets(JSScript* script) {
-  size_t num = script->nTypeSets() + 1 /* this */;
-  if (JSFunction* fun = script->functionNonDelazifying()) {
-    num += fun->nargs();
-  }
-  return num;
-}
-
 /* static */ inline StackTypeSet* TypeScript::ThisTypes(JSScript* script) {
   AutoSweepTypeScript sweep(script);
-  TypeScript* types = script->types(sweep);
-  return types ? types->typeArray() + script->nTypeSets() : nullptr;
+  if (TypeScript* types = script->types(sweep)) {
+    return types->typeArray() + script->numBytecodeTypeSets();
+  }
+  return nullptr;
 }
 
 /*
  * Note: for non-escaping arguments, argTypes reflect only the initial type of
  * the variable (e.g. passed values for argTypes, or undefined for localTypes)
  * and not types from subsequent assignments.
  */
 
 /* static */ inline StackTypeSet* TypeScript::ArgTypes(JSScript* script,
                                                        unsigned i) {
   MOZ_ASSERT(i < script->functionNonDelazifying()->nargs());
   AutoSweepTypeScript sweep(script);
-  TypeScript* types = script->types(sweep);
-  return types ? types->typeArray() + script->nTypeSets() + 1 + i : nullptr;
+  if (TypeScript* types = script->types(sweep)) {
+    return types->typeArray() + script->numBytecodeTypeSets() + 1 + i;
+  }
+  return nullptr;
 }
 
 template <typename TYPESET>
 /* static */ inline TYPESET* TypeScript::BytecodeTypes(JSScript* script,
                                                        jsbytecode* pc,
                                                        uint32_t* bytecodeMap,
                                                        uint32_t* hint,
                                                        TYPESET* typeArray) {
   MOZ_ASSERT(CodeSpec[*pc].format & JOF_TYPESET);
   uint32_t offset = script->pcToOffset(pc);
 
   // See if this pc is the next typeset opcode after the last one looked up.
-  if ((*hint + 1) < script->nTypeSets() && bytecodeMap[*hint + 1] == offset) {
+  size_t numBytecodeTypeSets = script->numBytecodeTypeSets();
+  if ((*hint + 1) < numBytecodeTypeSets && bytecodeMap[*hint + 1] == offset) {
     (*hint)++;
     return typeArray + *hint;
   }
 
   // See if this pc is the same as the last one looked up.
   if (bytecodeMap[*hint] == offset) {
     return typeArray + *hint;
   }
 
   // Fall back to a binary search.  We'll either find the exact offset, or
   // there are more JOF_TYPESET opcodes than nTypeSets in the script (as can
   // happen if the script is very long) and we'll use the last location.
   size_t loc;
-#ifdef DEBUG
   bool found =
-#endif
-      mozilla::BinarySearch(bytecodeMap, 0, script->nTypeSets() - 1, offset,
-                            &loc);
+      mozilla::BinarySearch(bytecodeMap, 0, numBytecodeTypeSets, offset, &loc);
+  if (found) {
+    MOZ_ASSERT(bytecodeMap[loc] == offset);
+  } else {
+    MOZ_ASSERT(numBytecodeTypeSets == JSScript::MaxBytecodeTypeSets);
+    loc = numBytecodeTypeSets - 1;
+  }
 
-  MOZ_ASSERT_IF(found, bytecodeMap[loc] == offset);
   *hint = mozilla::AssertedCast<uint32_t>(loc);
   return typeArray + *hint;
 }
 
 /* static */ inline StackTypeSet* TypeScript::BytecodeTypes(JSScript* script,
                                                             jsbytecode* pc) {
   MOZ_ASSERT(CurrentThreadCanAccessZone(script->zone()));
   AutoSweepTypeScript sweep(script);
   TypeScript* types = script->types(sweep);
   if (!types) {
     return nullptr;
   }
-  uint32_t* hint =
-      script->baselineScript()->bytecodeTypeMap() + script->nTypeSets();
-  return BytecodeTypes(script, pc, script->baselineScript()->bytecodeTypeMap(),
-                       hint, types->typeArray());
+  uint32_t* hint = types->bytecodeTypeMapHint();
+  return BytecodeTypes(script, pc, types->bytecodeTypeMap(), hint,
+                       types->typeArray());
 }
 
 /* static */ inline void TypeScript::Monitor(JSContext* cx, JSScript* script,
                                              jsbytecode* pc,
                                              const js::Value& rval) {
   TypeMonitorResult(cx, script, pc, rval);
 }
 
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -1186,19 +1186,20 @@ CompilerConstraintList* js::NewCompilerC
 }
 
 /* static */ bool TypeScript::FreezeTypeSets(
     CompilerConstraintList* constraints, JSScript* script,
     TemporaryTypeSet** pThisTypes, TemporaryTypeSet** pArgTypes,
     TemporaryTypeSet** pBytecodeTypes) {
   LifoAlloc* alloc = constraints->alloc();
   AutoSweepTypeScript sweep(script);
-  StackTypeSet* existing = script->types(sweep)->typeArray();
-
-  size_t count = NumTypeSets(script);
+  TypeScript* typeScript = script->types(sweep);
+  StackTypeSet* existing = typeScript->typeArray();
+
+  size_t count = typeScript->numTypeSets();
   TemporaryTypeSet* types =
       alloc->newArrayUninitialized<TemporaryTypeSet>(count);
   if (!types) {
     return false;
   }
 
   for (size_t i = 0; i < count; i++) {
     if (!existing[i].cloneIntoUninitialized(alloc, &types[i])) {
@@ -1524,17 +1525,17 @@ bool js::FinishCompilation(JSContext* cx
                          ? entry.script->functionNonDelazifying()->nargs()
                          : 0;
     for (size_t i = 0; i < nargs; i++) {
       if (!CheckFrozenTypeSet(sweep, cx, &entry.argTypes[i],
                               TypeScript::ArgTypes(entry.script, i))) {
         succeeded = false;
       }
     }
-    for (size_t i = 0; i < entry.script->nTypeSets(); i++) {
+    for (size_t i = 0; i < entry.script->numBytecodeTypeSets(); i++) {
       if (!CheckFrozenTypeSet(sweep, cx, &entry.bytecodeTypes[i],
                               &types->typeArray()[i])) {
         succeeded = false;
       }
     }
 
     // Add this compilation to the inlinedCompilations list of each inlined
     // script, so we can invalidate it on changes to stack type sets.
@@ -1545,18 +1546,17 @@ bool js::FinishCompilation(JSContext* cx
     }
 
     // If necessary, add constraints to trigger invalidation on the script
     // after any future changes to the stack type sets.
     if (entry.script->hasFreezeConstraints()) {
       continue;
     }
 
-    size_t count = TypeScript::NumTypeSets(entry.script);
-
+    size_t count = types->numTypeSets();
     StackTypeSet* array = types->typeArray();
     for (size_t i = 0; i < count; i++) {
       if (!array[i].addConstraint(
               cx,
               cx->typeLifoAlloc().new_<TypeConstraintFreezeStack>(entry.script),
               false)) {
         succeeded = false;
       }
@@ -1614,17 +1614,17 @@ void js::FinishDefinitePropertiesAnalysi
 
     unsigned nargs = entry.script->functionNonDelazifying()
                          ? entry.script->functionNonDelazifying()->nargs()
                          : 0;
     for (size_t j = 0; j < nargs; j++) {
       MOZ_ASSERT(TypeScript::ArgTypes(script, j)->isSubset(&entry.argTypes[j]));
     }
 
-    for (size_t j = 0; j < script->nTypeSets(); j++) {
+    for (size_t j = 0; j < script->numBytecodeTypeSets(); j++) {
       MOZ_ASSERT(script->types(sweep)->typeArray()[j].isSubset(
           &entry.bytecodeTypes[j]));
     }
   }
 #endif
 
   for (size_t i = 0; i < constraints->numFrozenScripts(); i++) {
     const CompilerConstraintList::FrozenScript& entry =
@@ -1642,17 +1642,17 @@ void js::FinishDefinitePropertiesAnalysi
     unsigned nargs = script->functionNonDelazifying()
                          ? script->functionNonDelazifying()->nargs()
                          : 0;
     for (size_t j = 0; j < nargs; j++) {
       CheckDefinitePropertiesTypeSet(sweep, cx, &entry.argTypes[j],
                                      TypeScript::ArgTypes(script, j));
     }
 
-    for (size_t j = 0; j < script->nTypeSets(); j++) {
+    for (size_t j = 0; j < script->numBytecodeTypeSets(); j++) {
       CheckDefinitePropertiesTypeSet(sweep, cx, &entry.bytecodeTypes[j],
                                      &types->typeArray()[j]);
     }
   }
 }
 
 namespace {
 
@@ -3431,18 +3431,19 @@ bool js::AddClearDefiniteFunctionUsesInS
   // analysis was done is stable. We only need to look at type sets which
   // contain a single object, as IonBuilder does not inline polymorphic sites
   // during the definite properties analysis.
 
   TypeSet::ObjectKey* calleeKey =
       TypeSet::ObjectType(calleeScript->functionNonDelazifying()).objectKey();
 
   AutoSweepTypeScript sweep(script);
-  unsigned count = TypeScript::NumTypeSets(script);
-  StackTypeSet* typeArray = script->types(sweep)->typeArray();
+  TypeScript* typeScript = script->types(sweep);
+  unsigned count = typeScript->numTypeSets();
+  StackTypeSet* typeArray = typeScript->typeArray();
 
   for (unsigned i = 0; i < count; i++) {
     StackTypeSet* types = &typeArray[i];
     if (!types->unknownObject() && types->getObjectCount() == 1) {
       if (calleeKey != types->getObject(0)) {
         // Also check if the object is the Function.call or
         // Function.apply native. IonBuilder uses the presence of these
         // functions during inlining.
@@ -3495,29 +3496,29 @@ void js::TypeMonitorCallSlow(JSContext* 
   }
 
   /* Watch for fewer actuals than formals to the call. */
   for (; arg < nargs; arg++) {
     TypeScript::SetArgument(cx, script, arg, UndefinedValue());
   }
 }
 
-void js::FillBytecodeTypeMap(JSScript* script, uint32_t* bytecodeMap) {
+static void FillBytecodeTypeMap(JSScript* script, uint32_t* bytecodeMap) {
   uint32_t added = 0;
   for (jsbytecode* pc = script->code(); pc < script->codeEnd();
        pc += GetBytecodeLength(pc)) {
     JSOp op = JSOp(*pc);
     if (CodeSpec[op].format & JOF_TYPESET) {
       bytecodeMap[added++] = script->pcToOffset(pc);
-      if (added == script->nTypeSets()) {
+      if (added == script->numBytecodeTypeSets()) {
         break;
       }
     }
   }
-  MOZ_ASSERT(added == script->nTypeSets());
+  MOZ_ASSERT(added == script->numBytecodeTypeSets());
 }
 
 void js::TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc,
                            TypeSet::Type type) {
   cx->check(script, type);
 
   AutoEnterAnalysis enter(cx);
 
@@ -3562,58 +3563,86 @@ void js::TypeMonitorResult(JSContext* cx
 
   TypeMonitorResult(cx, script, pc, TypeSet::GetValueType(rval));
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeScript
 /////////////////////////////////////////////////////////////////////
 
+static size_t NumTypeSets(JSScript* script) {
+  size_t num = script->numBytecodeTypeSets() + 1 /* this */;
+  if (JSFunction* fun = script->functionNonDelazifying()) {
+    num += fun->nargs();
+  }
+
+  // We rely on |num| being in a safe range to prevent overflow when allocating
+  // TypeScript.
+  static_assert(JSScript::MaxBytecodeTypeSets == UINT16_MAX,
+                "JSScript typesets should have safe range to avoid overflow");
+  static_assert(JSFunction::NArgsBits == 16,
+                "JSFunction nargs should have safe range to avoid overflow");
+
+  return num;
+}
+
+TypeScript::TypeScript(JSScript* script, ICScriptPtr&& icScript,
+                       uint32_t numTypeSets)
+    : icScript_(std::move(icScript)),
+      numTypeSets_(numTypeSets),
+      bytecodeTypeMapHint_(0) {
+  StackTypeSet* array = typeArray();
+  for (unsigned i = 0; i < numTypeSets; i++) {
+    new (&array[i]) StackTypeSet();
+  }
+
+  FillBytecodeTypeMap(script, bytecodeTypeMap());
+}
+
 bool JSScript::makeTypes(JSContext* cx) {
   MOZ_ASSERT(!types_);
   cx->check(this);
 
   AutoEnterAnalysis enter(cx);
 
   UniquePtr<jit::ICScript> icScript(jit::ICScript::create(cx, this));
   if (!icScript) {
     return false;
   }
 
   // We need to call prepareForDestruction on ICScript before we |delete| it.
   auto prepareForDestruction = mozilla::MakeScopeExit(
       [&] { icScript->prepareForDestruction(cx->zone()); });
 
-  unsigned count = TypeScript::NumTypeSets(this);
-
-  size_t size = TypeScript::SizeIncludingTypeArray(count);
+  size_t numTypeSets = NumTypeSets(this);
+  size_t bytecodeTypeMapEntries = numBytecodeTypeSets();
+
+  // Calculate allocation size. This cannot overflow, see comment in
+  // NumTypeSets.
+  static_assert(sizeof(TypeScript) ==
+                    sizeof(StackTypeSet) + offsetof(TypeScript, typeArray_),
+                "typeArray_ must be last member of TypeScript");
+  size_t allocSize =
+      (offsetof(TypeScript, typeArray_) + numTypeSets * sizeof(StackTypeSet) +
+       bytecodeTypeMapEntries * sizeof(uint32_t));
+
   auto typeScript =
-      reinterpret_cast<TypeScript*>(cx->pod_calloc<uint8_t>(size));
+      reinterpret_cast<TypeScript*>(cx->pod_malloc<uint8_t>(allocSize));
   if (!typeScript) {
     return false;
   }
 
   prepareForDestruction.release();
-  typeScript->icScript_ = std::move(icScript);
-
-#ifdef JS_CRASH_DIAGNOSTICS
-  {
-    StackTypeSet* typeArray = typeScript->typeArray();
-    for (unsigned i = 0; i < count; i++) {
-      typeArray[i].initMagic();
-    }
-  }
-#endif
-
-  types_ = typeScript;
+
+  types_ = new (typeScript) TypeScript(this, std::move(icScript), numTypeSets);
   setTypesGeneration(cx->zone()->types.generation);
 
 #ifdef DEBUG
   StackTypeSet* typeArray = typeScript->typeArray();
-  for (unsigned i = 0; i < nTypeSets(); i++) {
+  for (unsigned i = 0; i < numBytecodeTypeSets(); i++) {
     InferSpew(ISpewOps, "typeSet: %sT%p%s bytecode%u %p",
               InferSpewColor(&typeArray[i]), &typeArray[i],
               InferSpewColorReset(), i, this);
   }
   TypeSet* thisTypes = TypeScript::ThisTypes(this);
   InferSpew(ISpewOps, "typeSet: %sT%p%s this %p", InferSpewColor(thisTypes),
             thisTypes, InferSpewColorReset(), this);
   unsigned nargs =
@@ -4685,17 +4714,17 @@ void ObjectGroup::sweep(const AutoSweepO
         continue;
       }
       inlinedCompilations[dest] = inlinedCompilations[i];
       dest++;
     }
     inlinedCompilations.shrinkTo(dest);
   }
 
-  unsigned num = TypeScript::NumTypeSets(this);
+  unsigned num = types_->numTypeSets();
   StackTypeSet* typeArray = types_->typeArray();
 
   // Remove constraints and references to dead objects from stack type sets.
   for (unsigned i = 0; i < num; i++) {
     typeArray[i].sweep(sweep, zone());
   }
 
   if (zone()->types.hadOOMSweepingTypes()) {
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -205,52 +205,62 @@ class TypeScript {
   // The freeze constraints added to stack type sets will only directly
   // invalidate the script containing those stack type sets. This Vector
   // contains compilations that inlined this script, so we can invalidate
   // them as well.
   RecompileInfoVector inlinedCompilations_;
 
   // ICScript and TypeScript have the same lifetimes, so we store a pointer to
   // ICScript here to not increase sizeof(JSScript).
-  js::UniquePtr<js::jit::ICScript> icScript_;
+  using ICScriptPtr = js::UniquePtr<js::jit::ICScript>;
+  ICScriptPtr icScript_;
+
+  // Number of TypeSets in typeArray_.
+  uint32_t numTypeSets_;
 
-  // Variable-size array
+  // This field is used to avoid binary searches for the sought entry when
+  // bytecode map queries are in linear order.
+  uint32_t bytecodeTypeMapHint_;
+
+  // Variable-size array. This is followed by the bytecode type map.
   StackTypeSet typeArray_[1];
 
  public:
+  TypeScript(JSScript* script, ICScriptPtr&& icScript, uint32_t numTypeSets);
+
   RecompileInfoVector& inlinedCompilations() { return inlinedCompilations_; }
   MOZ_MUST_USE bool addInlinedCompilation(RecompileInfo info) {
     if (!inlinedCompilations_.empty() && inlinedCompilations_.back() == info) {
       return true;
     }
     return inlinedCompilations_.append(info);
   }
 
+  uint32_t numTypeSets() const { return numTypeSets_; }
+
+  uint32_t* bytecodeTypeMapHint() { return &bytecodeTypeMapHint_; }
+
   jit::ICScript* icScript() const {
     MOZ_ASSERT(icScript_);
     return icScript_.get();
   }
 
   /* Array of type sets for variables and JOF_TYPESET ops. */
   StackTypeSet* typeArray() const {
     // Ensure typeArray_ is the last data member of TypeScript.
     JS_STATIC_ASSERT(sizeof(TypeScript) ==
                      sizeof(typeArray_) + offsetof(TypeScript, typeArray_));
     return const_cast<StackTypeSet*>(typeArray_);
   }
 
-  static inline size_t SizeIncludingTypeArray(size_t arraySize) {
-    // Ensure typeArray_ is the last data member of TypeScript.
-    JS_STATIC_ASSERT(sizeof(TypeScript) ==
-                     sizeof(StackTypeSet) + offsetof(TypeScript, typeArray_));
-    return offsetof(TypeScript, typeArray_) + arraySize * sizeof(StackTypeSet);
+  uint32_t* bytecodeTypeMap() {
+    MOZ_ASSERT(numTypeSets_ > 0);
+    return reinterpret_cast<uint32_t*>(typeArray_ + numTypeSets_);
   }
 
-  static inline unsigned NumTypeSets(JSScript* script);
-
   static inline StackTypeSet* ThisTypes(JSScript* script);
   static inline StackTypeSet* ArgTypes(JSScript* script, unsigned i);
 
   /* Get the type set for values observed at an opcode. */
   static inline StackTypeSet* BytecodeTypes(JSScript* script, jsbytecode* pc);
 
   template <typename TYPESET>
   static inline TYPESET* BytecodeTypes(JSScript* script, jsbytecode* pc,
@@ -318,18 +328,16 @@ class MOZ_RAII AutoKeepTypeScripts {
   AutoKeepTypeScripts(const AutoKeepTypeScripts&) = delete;
   void operator=(const AutoKeepTypeScripts&) = delete;
 
  public:
   explicit inline AutoKeepTypeScripts(JSContext* cx);
   inline ~AutoKeepTypeScripts();
 };
 
-void FillBytecodeTypeMap(JSScript* script, uint32_t* bytecodeMap);
-
 class RecompileInfo;
 
 // Generate the type constraints for the compilation. Sets |isValidOut| based on
 // whether the type constraints still hold.
 bool FinishCompilation(JSContext* cx, HandleScript script,
                        CompilerConstraintList* constraints,
                        IonCompilationId compilationId, bool* isValidOut);
 
--- a/js/src/vm/TypeSet.h
+++ b/js/src/vm/TypeSet.h
@@ -670,23 +670,16 @@ class ConstraintTypeSet : public TypeSet
 
  protected:
   /* Chain of constraints which propagate changes out from this type set. */
   TypeConstraint* constraintList_ = nullptr;
 
  public:
   ConstraintTypeSet() = default;
 
-#ifdef JS_CRASH_DIAGNOSTICS
-  void initMagic() {
-    MOZ_ASSERT(!magic_);
-    magic_ = ConstraintTypeSetMagic;
-  }
-#endif
-
   void checkMagic() const {
 #ifdef JS_CRASH_DIAGNOSTICS
     if (MOZ_UNLIKELY(magic_ != ConstraintTypeSetMagic)) {
       ReportMagicWordFailure(magic_, ConstraintTypeSetMagic, uintptr_t(flags),
                              uintptr_t(objectSet));
     }
 #endif
   }
--- a/toolkit/components/extensions/schemas/telemetry.json
+++ b/toolkit/components/extensions/schemas/telemetry.json
@@ -134,17 +134,17 @@
             }
           }
         }
       ]
     },
     {
       "name": "canUpload",
       "type": "function",
-      "description": "Checks if Telemetry is enabled.",
+      "description": "Checks if Telemetry upload is enabled.",
       "parameters": [],
       "async": true
     },
     {
       "name": "scalarAdd",
       "type": "function",
       "description": "Adds the value to the given scalar.",
       "async": true,
--- a/toolkit/components/telemetry/docs/collection/custom-pings.rst
+++ b/toolkit/components/telemetry/docs/collection/custom-pings.rst
@@ -1,8 +1,10 @@
+.. _submitting-customping:
+
 =======================
 Submitting custom pings
 =======================
 
 Custom pings can be submitted from JavaScript using:
 
 .. code-block:: js
 
--- a/toolkit/components/telemetry/docs/collection/events.rst
+++ b/toolkit/components/telemetry/docs/collection/events.rst
@@ -207,16 +207,18 @@ Example:
   Services.telemetry.setEventRecordingEnabled("ui", false);
   // ... now "ui" events will not be recorded anymore.
 
 .. note::
 
   Even if your event category isn't enabled, counts of events that attempted to be recorded will
   be :ref:`summarized <events.event-summary>`.
 
+.. _registerevents:
+
 ``registerEvents()``
 ~~~~~~~~~~~~~~~~~~~~
 
 .. code-block:: js
 
   Services.telemetry.registerEvents(category, eventData);
 
 Register new events from add-ons.
--- a/toolkit/components/telemetry/docs/collection/index.rst
+++ b/toolkit/components/telemetry/docs/collection/index.rst
@@ -11,23 +11,24 @@ In cases where this isn't possible and m
 *Note:* Every new data collection must go through a `data collection review <https://wiki.mozilla.org/Firefox/Data_Collection>`_.
 
 The current data collection possibilities include:
 
 * :doc:`scalars` allow recording of a single value (string, boolean, a number)
 * :doc:`histograms` can efficiently record multiple data points
 * ``environment`` data records information about the system and settings a session occurs in
 * :doc:`events` can record richer data on individual occurrences of specific actions
-* :doc:`measuring elapsed time <measuring-time>`
-* :doc:`custom pings <custom-pings>`
-* :doc:`stack capture <stack-capture>` allow recording application call stacks
+* :doc:`Measuring elapsed time <measuring-time>`
+* :doc:`Custom pings <custom-pings>`
+* :doc:`Stack capture <stack-capture>` allow recording application call stacks
 * :doc:`Use counters <use-counters>` measure the usage of web platform features
 * :doc:`Experiment annotations <experiments>`
 * :doc:`Remote content uptake <uptake>`
 * :doc:`Hybrid Content Telemetry <hybrid-content>` allows recording telemetry from semi-privileged hosted content
+* :doc:`WebExtension API <webextension-api>` can be used in privileged webextensions
 
 .. toctree::
    :maxdepth: 2
    :titlesonly:
    :hidden:
    :glob:
 
    scalars
--- a/toolkit/components/telemetry/docs/collection/scalars.rst
+++ b/toolkit/components/telemetry/docs/collection/scalars.rst
@@ -29,16 +29,18 @@ Probes in privileged JavaScript code can
   Services.telemetry.keyedScalarAdd(aName, aKey, aValue);
   Services.telemetry.keyedScalarSet(aName, aKey, aValue);
   Services.telemetry.keyedScalarSetMaximum(aName, aKey, aValue);
 
 These functions can throw if, for example, an operation is performed on a scalar type that doesn't support it
 (e.g. calling scalarSetMaximum on a scalar of the string kind). Please look at the `code documentation <https://dxr.mozilla.org/mozilla-central/search?q=regexp%3ATelemetryScalar%3A%3A(Set%7CAdd)+file%3ATelemetryScalar.cpp&redirect=false>`_ for
 additional information.
 
+.. _registerscalars:
+
 ``registerScalars()``
 ~~~~~~~~~~~~~~~~~~~~~
 
 .. code-block:: js
 
   Services.telemetry.registerScalars(category, scalarData);
 
 Register new scalars from add-ons.
--- a/toolkit/components/telemetry/docs/collection/uptake.rst
+++ b/toolkit/components/telemetry/docs/collection/uptake.rst
@@ -23,18 +23,19 @@ Usage
 -----
 
 .. code-block:: js
 
    const { UptakeTelemetry } = ChromeUtils.import("resource://services-common/uptake-telemetry.js", {});
 
    UptakeTelemetry.report(source, status);
 
-- ``source`` - a ``string`` that is an identifier for the update source (eg. ``addons-blocklist``)
-- ``status`` - one of the following status constants:
+- ``source``, a ``string`` that is an identifier for the update source (eg. ``addons-blocklist``)
+- ``status``, one of the following status constants:
+
   - ``UptakeTelemetry.STATUS.UP_TO_DATE``: Local content was already up-to-date with remote content.
   - ``UptakeTelemetry.STATUS.SUCCESS``: Local content was updated successfully.
   - ``UptakeTelemetry.STATUS.BACKOFF``: Remote server asked clients to backoff.
   - ``UptakeTelemetry.STATUS.PREF_DISABLED``: Update is disabled in user preferences.
   - ``UptakeTelemetry.STATUS.PARSE_ERROR``: Parsing server response has failed.
   - ``UptakeTelemetry.STATUS.CONTENT_ERROR``: Server response has unexpected content.
   - ``UptakeTelemetry.STATUS.SIGNATURE_ERROR``: Signature verification after diff-based sync has failed.
   - ``UptakeTelemetry.STATUS.SIGNATURE_RETRY_ERROR``: Signature verification after full fetch has failed.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/docs/collection/webextension-api.rst
@@ -0,0 +1,158 @@
+.. _webextension-telemetry:
+
+==============================
+WebExtension API for Telemetry
+==============================
+
+Use the ``browser.telemetry`` API to send telemetry data to the Mozilla Telemetry service. Restricted to Mozilla privileged webextensions.
+
+Types
+-----
+
+``ScalarType``
+~~~~~~~~~~~~~~
+
+Type of scalar: 'count' for numeric values, 'string' for string values, 'boolean' for boolean values. Maps to ``nsITelemetry.SCALAR_TYPE_*``.
+
+``ScalarData``
+~~~~~~~~~~~~~~
+
+Represents registration data for a Telemetry scalar.
+
+Properties:
+
+* ``kind`` - See ScalarType_.
+* ``keyed`` - *(optional, boolean)* True if this is a keyed scalar. Defaults to ``false``.
+* ``record_on_release`` - *(optional, boolean)* True if this data should be recorded on release. Defaults to ``false``.
+* ``expired`` - *(optional, boolean)* True if this scalar entry is expired. Operations on an expired scalar don't error (operations on an undefined scalar do), but the operations are no-ops. No data will be recorded. Defaults to ``false``.
+
+``EventData``
+~~~~~~~~~~~~~
+
+Represents registration data for a Telemetry event.
+
+Properties:
+
+* ``methods`` - *(array)* List of methods for this event entry.
+* ``objects`` - *(array)* List of objects for this event entry.
+* ``extra_keys`` - *(array)* List of allowed extra keys for this event entry.
+* ``record_on_release`` - *(optional, boolean)* True if this data should be recorded on release. Defaults to ``false``.
+* ``expired`` - *(optional, boolean)* True if this event entry is expired. Recording an expired event doesn't error (operations on undefined events do). No data will be recorded. Defaults to ``false``.
+
+Functions
+---------
+
+``submitPing``
+~~~~~~~~~~~~~~
+
+.. code-block:: js
+
+  browser.telemetry.submitPing(type, message, options);
+
+Submits a custom ping to the Telemetry backend. See :ref:`submitting-customping`.
+
+* ``type`` - *(string)* The type of the ping.
+* ``message`` - *(object)* The data payload for the ping.
+* ``options`` - *(optional, object)* Options object.
+
+  * ``addClientId`` - *(optional, boolean)* True if the ping should contain the client id. Defaults to ``false``.
+  * ``addEnvironment`` - *(optional, boolean)* True if the ping should contain the environment data. Defaults to ``false``.
+  * ``overrideEnvironment`` - *(optional, object)* Set to override the environment data. Default: not set.
+  * ``usePingSender`` - *(optional, boolean)* If true, send the ping using the PingSender. Defaults to ``false``.
+
+
+``canUpload``
+~~~~~~~~~~~~~
+
+.. code-block:: js
+
+  browser.telemetry.canUpload();
+
+Checks if Telemetry upload is enabled.
+
+``scalarAdd``
+~~~~~~~~~~~~~
+
+.. code-block:: js
+
+  browser.telemetry.scalarAdd(name, value);
+
+Adds the value to the given scalar.
+
+* ``name`` - *(string)* The scalar name.
+* ``value`` - *(integer)* The numeric value to add to the scalar. Only unsigned integers supported.
+
+``scalarSet``
+~~~~~~~~~~~~~
+
+.. code-block:: js
+
+  browser.telemetry.scalarSet(name, value);
+
+Sets the named scalar to the given value. Throws if the value type doesn't match the scalar type.
+
+* ``name`` - *(string)* The scalar name.
+* ``value`` - *(string|boolean|integer|object)* The value to set the scalar to.
+
+``scalarSetMaximum``
+~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: js
+
+  browser.telemetry.scalarSetMaximum(name, value);
+
+Sets the scalar to the maximum of the current and the passed value
+
+* ``name`` - *(string)* The scalar name.
+* ``value`` - *(integer)* The numeric value to set the scalar to. Only unsigned integers supported.
+
+``recordEvent``
+~~~~~~~~~~~~~~~
+
+.. code-block:: js
+
+  browser.telemetry.recordEvent(category, method, object, value, extra);
+
+Record an event in Telemetry. Throws when trying to record an unknown event.
+
+* ``category`` - *(string)* The category name.
+* ``method`` - *(string)* The method name.
+* ``object`` - *(string)* The object name.
+* ``value`` - *(optional, integer)* An optional string value to record.
+* ``extra`` - *(optional, object)* An optional object of the form (string -> string). It should only contain registered extra keys.
+
+``registerScalars``
+~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: js
+
+  browser.telemetry.registerScalars(category, data);
+
+Register new scalars to record them from addons. See :ref:`registerscalars` for more details.
+
+* ``category`` - *(string)* The unique category the scalars are registered in.
+* ``data`` - *(object)* An object that contains registration data for multiple scalars. Each property name is the scalar name, and the corresponding property value is an object of ScalarData_ type.
+
+``registerEvents``
+~~~~~~~~~~~~~~~~~~
+
+.. code-block:: js
+
+  browser.telemetry.registerEvents(category, data);
+
+Register new events to record them from addons. See :ref:`registerevents` for more details.
+
+* ``category`` - *(string)* The unique category the events are registered in.
+* ``data`` - *(object)* An object that contains registration data for 1+ events. Each property name is the category name, and the corresponding property value is an object of EventData_ type.
+
+``setEventRecordingEnabled``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: js
+
+  browser.telemetry.setEventRecordingEnabled(category, enabled);
+
+Enable recording of events in a category. Events default to recording disabled. This allows to toggle recording for all events in the specified category.
+
+* ``category`` - *(string)* The category name.
+* ``enabled`` - *(boolean)* Whether recording is enabled for events in that category.
--- a/widget/gtk/mozcontainer.cpp
+++ b/widget/gtk/mozcontainer.cpp
@@ -500,32 +500,35 @@ struct wl_surface *moz_container_get_wl_
     struct wl_compositor *compositor =
         sGdkWaylandDisplayGetWlCompositor(display);
     container->surface = wl_compositor_create_surface(compositor);
 
     nsWaylandDisplay *waylandDisplay = WaylandDisplayGet(display);
     container->subsurface = wl_subcompositor_get_subsurface(
         waylandDisplay->GetSubcompositor(), container->surface,
         moz_container_get_gtk_container_surface(container));
-    WaylandDisplayRelease(waylandDisplay);
 
     GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(container));
     gint x, y;
     gdk_window_get_position(window, &x, &y);
     wl_subsurface_set_position(container->subsurface, x, y);
     wl_subsurface_set_desync(container->subsurface);
 
     // Route input to parent wl_surface owned by Gtk+ so we get input
     // events from Gtk+.
     wl_region *region = wl_compositor_create_region(compositor);
     wl_surface_set_input_region(container->surface, region);
     wl_region_destroy(region);
 
     wl_surface_set_buffer_scale(container->surface,
                                 moz_container_get_scale(container));
+
+    wl_surface_commit(container->surface);
+    wl_display_flush(waylandDisplay->GetDisplay());
+    WaylandDisplayRelease(waylandDisplay);
   }
 
   return container->surface;
 }
 
 struct wl_egl_window *moz_container_get_wl_egl_window(MozContainer *container) {
   if (!container->eglwindow) {
     wl_surface *surface = moz_container_get_wl_surface(container);