Merge inbound to mozilla-central. a=merge
authorCsoregi Natalia <ncsoregi@mozilla.com>
Wed, 06 Mar 2019 11:56:17 +0200
changeset 520413 3e0cf2f77f07b258e8911ab5fd71a378c46fa1ec
parent 520392 b452fad68dc3504dd0b41de76112dd64f50466c6 (current diff)
parent 520412 52e2caa87f892d8075d9e1a358d88bdfb9c89af4 (diff)
child 520452 352a1b792577eb06f743c62c9241634ed3cc7cf0
child 520474 5cdbf803ecc052cdff722cc0d3d0393af1922660
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
3e0cf2f77f07 / 67.0a1 / 20190306095759 / files
nightly linux64
3e0cf2f77f07 / 67.0a1 / 20190306095759 / files
nightly mac
3e0cf2f77f07 / 67.0a1 / 20190306095759 / files
nightly win32
3e0cf2f77f07 / 67.0a1 / 20190306095759 / files
nightly win64
3e0cf2f77f07 / 67.0a1 / 20190306095759 / 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
devtools/client/debugger/new/src/client/firefox/commands.js
devtools/client/debugger/new/src/client/firefox/types.js
devtools/client/debugger/new/test/mochitest/helpers.js
--- a/devtools/client/debugger/new/src/client/firefox/commands.js
+++ b/devtools/client/debugger/new/src/client/firefox/commands.js
@@ -179,33 +179,53 @@ function locationKey(location) {
   const { sourceUrl, sourceId, line, column } = location;
   return `${(sourceUrl: any)}:${(sourceId: any)}:${line}:${(column: any)}`;
 }
 
 function waitForWorkers(shouldWait: boolean) {
   shouldWaitForWorkers = shouldWait;
 }
 
+function maybeGenerateLogGroupId(options) {
+  if (options.logValue && tabTarget.traits && tabTarget.traits.canRewind) {
+    return { ...options, logGroupId: `logGroup-${Math.random()}` };
+  }
+  return options;
+}
+
+function maybeClearLogpoint(location) {
+  const bp = breakpoints[locationKey(location)];
+  if (bp && bp.options.logGroupId && tabTarget.activeConsole) {
+    tabTarget.activeConsole.emit(
+      "clearLogpointMessages",
+      bp.options.logGroupId
+    );
+  }
+}
+
 async function setBreakpoint(
   location: BreakpointLocation,
   options: BreakpointOptions
 ) {
+  maybeClearLogpoint(location);
+  options = maybeGenerateLogGroupId(options);
   breakpoints[locationKey(location)] = { location, options };
   await threadClient.setBreakpoint(location, options);
 
   // Set breakpoints in other threads as well, but do not wait for the requests
   // to complete, so that we don't get hung up if one of the threads stops
   // responding. We don't strictly need to wait for the main thread to finish
   // setting its breakpoint, but this leads to more consistent behavior if the
   // user sets a breakpoint and immediately starts interacting with the page.
   // If the main thread stops responding then we're toast regardless.
   await forEachWorkerThread(thread => thread.setBreakpoint(location, options));
 }
 
 async function removeBreakpoint(location: BreakpointLocation) {
+  maybeClearLogpoint(location);
   delete breakpoints[locationKey(location)];
   await threadClient.removeBreakpoint(location);
 
   // Remove breakpoints without waiting for the thread to respond, for the same
   // reason as in setBreakpoint.
   await forEachWorkerThread(thread => thread.removeBreakpoint(location));
 }
 
--- a/devtools/client/debugger/new/src/client/firefox/types.js
+++ b/devtools/client/debugger/new/src/client/firefox/types.js
@@ -217,26 +217,28 @@ export type TabTarget = {
       func: Function,
       params?: { frameActor: ?FrameId }
     ) => Promise<{ result: ?Object }>,
     autocomplete: (
       input: string,
       cursor: number,
       func: Function,
       frameId: ?string
-    ) => void
+    ) => void,
+    emit: (string, any) => void
   },
   form: { consoleActor: any },
   root: any,
   navigateTo: ({ url: string }) => Promise<*>,
   listWorkers: () => Promise<*>,
   reload: () => Promise<*>,
   destroy: () => void,
   isBrowsingContext: boolean,
-  isContentProcess: boolean
+  isContentProcess: boolean,
+  traits: Object
 };
 
 /**
  * Clients for accessing the Firefox debug server and browser
  * @memberof firefox/clients
  * @static
  */
 
--- a/devtools/client/debugger/new/src/components/Editor/Preview/index.js
+++ b/devtools/client/debugger/new/src/components/Editor/Preview/index.js
@@ -46,20 +46,21 @@ function inPopup(e) {
     relatedTarget.classList.contains("debug-expression");
 
   return pop;
 }
 
 function getElementFromPos(pos: DOMRect) {
   // We need to use element*s*AtPoint because the tooltip overlays
   // the token and thus an undesirable element may be returned
-  const elementsAtPoint = [
-    // $FlowIgnore
-    ...document.elementsFromPoint(pos.x + pos.width / 2, pos.y + pos.height / 2)
-  ];
+  // $FlowIgnore
+  const elementsAtPoint = [...document.elementsFromPoint(
+    pos.x + pos.width / 2,
+    pos.y + pos.height / 2
+  )];
 
   return elementsAtPoint.find(el => el.className.startsWith("cm-"));
 }
 
 class Preview extends PureComponent<Props, State> {
   target = null;
   constructor(props) {
     super(props);
--- a/devtools/client/debugger/new/src/types.js
+++ b/devtools/client/debugger/new/src/types.js
@@ -126,17 +126,18 @@ export type Breakpoint = {|
 |};
 
 /**
  * Options for a breakpoint that can be modified by the user.
  */
 export type BreakpointOptions = {
   hidden?: boolean,
   condition?: string | null,
-  logValue?: string | null
+  logValue?: string | null,
+  logGroupId?: string | null
 };
 
 export type BreakpointActor = {|
   +actor: ActorId,
   +source: SourceActor
 |};
 
 /**
--- a/devtools/client/debugger/new/test/mochitest/helpers.js
+++ b/devtools/client/debugger/new/test/mochitest/helpers.js
@@ -737,30 +737,37 @@ async function navigate(dbg, url, ...sou
  * @memberof mochitest/actions
  * @param {Object} dbg
  * @param {String} source
  * @param {Number} line
  * @param {Number} col
  * @return {Promise}
  * @static
  */
-function addBreakpoint(dbg, source, line, column) {
+function addBreakpoint(dbg, source, line, column, options) {
   source = findSource(dbg, source);
   const sourceId = source.id;
-  dbg.actions.addBreakpoint({ sourceId, line, column });
+  dbg.actions.addBreakpoint({ sourceId, line, column }, options);
   return waitForDispatch(dbg, "ADD_BREAKPOINT");
 }
 
 function disableBreakpoint(dbg, source, line, column) {
   const location = { sourceId: source.id, sourceUrl: source.url, line, column };
   const bp = dbg.selectors.getBreakpointForLocation(dbg.getState(), location);
   dbg.actions.disableBreakpoint(bp);
   return waitForDispatch(dbg, "DISABLE_BREAKPOINT");
 }
 
+function setBreakpointOptions(dbg, source, line, column, options) {
+  source = findSource(dbg, source);
+  const sourceId = source.id;
+  dbg.actions.setBreakpointOptions({ sourceId, line, column }, options);
+  return waitForDispatch(dbg, "SET_BREAKPOINT_OPTIONS");
+}
+
 function findBreakpoint(dbg, url, line) {
   const {
     selectors: { getBreakpoint },
     getState
   } = dbg;
   const source = findSource(dbg, url);
   let column;
   if (
--- a/devtools/client/webconsole/actions/messages.js
+++ b/devtools/client/webconsole/actions/messages.js
@@ -12,16 +12,17 @@ const {
 const { IdGenerator } = require("devtools/client/webconsole/utils/id-generator");
 const { batchActions } = require("devtools/client/shared/redux/middleware/debounce");
 
 const {
   MESSAGES_ADD,
   NETWORK_MESSAGE_UPDATE,
   NETWORK_UPDATE_REQUEST,
   MESSAGES_CLEAR,
+  MESSAGES_CLEAR_LOGPOINT,
   MESSAGE_OPEN,
   MESSAGE_CLOSE,
   MESSAGE_TYPE,
   MESSAGE_TABLE_RECEIVE,
   PAUSED_EXCECUTION_POINT,
   PRIVATE_MESSAGES_CLEAR,
 } = require("../constants");
 
@@ -53,16 +54,23 @@ function messagesAdd(packets, idGenerato
 }
 
 function messagesClear() {
   return {
     type: MESSAGES_CLEAR,
   };
 }
 
+function messagesClearLogpoint(logpointId) {
+  return {
+    type: MESSAGES_CLEAR_LOGPOINT,
+    logpointId,
+  };
+}
+
 function setPauseExecutionPoint(executionPoint) {
   return {
     type: PAUSED_EXCECUTION_POINT,
     executionPoint,
   };
 }
 
 function privateMessagesClear() {
@@ -135,16 +143,17 @@ function networkUpdateRequest(id, data) 
     id,
     data,
   };
 }
 
 module.exports = {
   messagesAdd,
   messagesClear,
+  messagesClearLogpoint,
   messageOpen,
   messageClose,
   messageTableDataGet,
   networkMessageUpdate,
   networkUpdateRequest,
   privateMessagesClear,
   // for test purpose only.
   messageTableDataReceive,
--- a/devtools/client/webconsole/constants.js
+++ b/devtools/client/webconsole/constants.js
@@ -20,16 +20,17 @@ const actionTypes = {
   FILTERS_CLEAR: "FILTERS_CLEAR",
   HISTORY_LOADED: "HISTORY_LOADED",
   INITIALIZE: "INITIALIZE",
   MESSAGE_CLOSE: "MESSAGE_CLOSE",
   MESSAGE_OPEN: "MESSAGE_OPEN",
   MESSAGE_TABLE_RECEIVE: "MESSAGE_TABLE_RECEIVE",
   MESSAGES_ADD: "MESSAGES_ADD",
   MESSAGES_CLEAR: "MESSAGES_CLEAR",
+  MESSAGES_CLEAR_LOGPOINT: "MESSAGES_CLEAR_LOGPOINT",
   NETWORK_MESSAGE_UPDATE: "NETWORK_MESSAGE_UPDATE",
   NETWORK_UPDATE_REQUEST: "NETWORK_UPDATE_REQUEST",
   PERSIST_TOGGLE: "PERSIST_TOGGLE",
   PRIVATE_MESSAGES_CLEAR: "PRIVATE_MESSAGES_CLEAR",
   REMOVE_NOTIFICATION: "REMOVE_NOTIFICATION",
   REMOVED_ACTORS_CLEAR: "REMOVED_ACTORS_CLEAR",
   REVERSE_SEARCH_INPUT_TOGGLE: "REVERSE_SEARCH_INPUT_TOGGLE",
   SELECT_NETWORK_MESSAGE_TAB: "SELECT_NETWORK_MESSAGE_TAB",
--- a/devtools/client/webconsole/enhancers/actor-releaser.js
+++ b/devtools/client/webconsole/enhancers/actor-releaser.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   MESSAGES_ADD,
   MESSAGES_CLEAR,
   PRIVATE_MESSAGES_CLEAR,
+  MESSAGES_CLEAR_LOGPOINT,
   REMOVED_ACTORS_CLEAR,
 } = require("devtools/client/webconsole/constants");
 
 /**
  * This enhancer is responsible for releasing actors on the backend.
  * When messages with arguments are removed from the store we should also
  * clean up the backend.
  */
@@ -20,17 +21,18 @@ function enableActorReleaser(webConsoleU
   return next => (reducer, initialState, enhancer) => {
     function releaseActorsEnhancer(state, action) {
       state = reducer(state, action);
 
       const type = action.type;
       const proxy = webConsoleUI ? webConsoleUI.proxy : null;
       if (
         proxy &&
-        ([MESSAGES_ADD, MESSAGES_CLEAR, PRIVATE_MESSAGES_CLEAR].includes(type))
+          ([MESSAGES_ADD, MESSAGES_CLEAR, PRIVATE_MESSAGES_CLEAR,
+            MESSAGES_CLEAR_LOGPOINT].includes(type))
       ) {
         releaseActors(state.messages.removedActors, proxy);
 
         // Reset `removedActors` in message reducer.
         state = reducer(state, {
           type: REMOVED_ACTORS_CLEAR,
         });
       }
--- a/devtools/client/webconsole/reducers/messages.js
+++ b/devtools/client/webconsole/reducers/messages.js
@@ -53,32 +53,35 @@ const MessageState = overrides => Object
   // in order to properly release them.
   // This array is not supposed to be consumed by any UI component.
   removedActors: [],
   // Map of the form {messageId : numberOfRepeat}
   repeatById: {},
   // Map of the form {messageId : networkInformation}
   // `networkInformation` holds request, response, totalTime, ...
   networkMessagesUpdateById: {},
+  // Set of logpoint IDs that have been removed
+  removedLogpointIds: new Set(),
   pausedExecutionPoint: null,
 }, overrides));
 
 function cloneState(state) {
   return {
     messagesById: new Map(state.messagesById),
     replayProgressMessages: new Set(state.replayProgressMessages),
     visibleMessages: [...state.visibleMessages],
     filteredMessagesCount: {...state.filteredMessagesCount},
     messagesUiById: [...state.messagesUiById],
     messagesTableDataById: new Map(state.messagesTableDataById),
     groupsById: new Map(state.groupsById),
     currentGroup: state.currentGroup,
     removedActors: [...state.removedActors],
     repeatById: {...state.repeatById},
     networkMessagesUpdateById: {...state.networkMessagesUpdateById},
+    removedLogpointIds: new Set(state.removedLogpointIds),
     pausedExecutionPoint: state.pausedExecutionPoint,
   };
 }
 
 function addMessage(state, filtersState, prefsState, newMessage) {
   const {
     messagesById,
     replayProgressMessages,
@@ -87,26 +90,35 @@ function addMessage(state, filtersState,
     repeatById,
   } = state;
 
   if (newMessage.type === constants.MESSAGE_TYPE.NULL_MESSAGE) {
     // When the message has a NULL type, we don't add it.
     return state;
   }
 
-  if (newMessage.executionPoint) {
+  if (newMessage.executionPoint && !newMessage.logpointId) {
     // When replaying old behaviors in a tab, we might see the same messages
     // multiple times. Ignore duplicate messages with the same progress values.
+    // We don't need to do this for logpoint messages, which will only arrive once.
     const progress = newMessage.executionPoint.progress;
     if (replayProgressMessages.has(progress)) {
       return state;
     }
     state.replayProgressMessages.add(progress);
   }
 
+  // After messages with a given logpoint ID have been removed, ignore all
+  // future messages with that ID.
+  if (newMessage.logpointId &&
+      state.removedLogpointIds &&
+      state.removedLogpointIds.has(newMessage.logpointId)) {
+    return state;
+  }
+
   if (newMessage.type === constants.MESSAGE_TYPE.END_GROUP) {
     // Compute the new current group.
     state.currentGroup = getNewCurrentGroup(currentGroup, groupsById);
     return state;
   }
 
   if (newMessage.allowRepeating && messagesById.size > 0) {
     const lastMessage = [...messagesById.values()][messagesById.size - 1];
@@ -220,32 +232,51 @@ function messages(state = MessageState()
         // Store all actors from removed messages. This array is used by
         // `releaseActorsEnhancer` to release all of those backend actors.
         removedActors: [...state.messagesById.values()].reduce((res, msg) => {
           res.push(...getAllActorsInMessage(msg));
           return res;
         }, []),
       });
 
-    case constants.PRIVATE_MESSAGES_CLEAR:
+    case constants.PRIVATE_MESSAGES_CLEAR: {
       const removedIds = [];
       for (const [id, message] of messagesById) {
         if (message.private === true) {
           removedIds.push(id);
         }
       }
 
       // If there's no private messages, there's no need to change the state.
       if (removedIds.length === 0) {
         return state;
       }
 
       return removeMessagesFromState({
         ...state,
       }, removedIds);
+    }
+
+    case constants.MESSAGES_CLEAR_LOGPOINT: {
+      const removedIds = [];
+      for (const [id, message] of messagesById) {
+        if (message.logpointId == action.logpointId) {
+          removedIds.push(id);
+        }
+      }
+
+      if (removedIds.length === 0) {
+        return state;
+      }
+
+      return removeMessagesFromState({
+        ...state,
+        removedLogpointIds: new Set([...state.removedLogpointIds, action.logpointId]),
+      }, removedIds);
+    }
 
     case constants.MESSAGE_OPEN:
       const openState = {...state};
       openState.messagesUiById = [...messagesUiById, action.id];
       const currMessage = messagesById.get(action.id);
 
       // If the message is a group
       if (isGroupType(currMessage.type)) {
--- a/devtools/client/webconsole/utils/messages.js
+++ b/devtools/client/webconsole/utils/messages.js
@@ -189,16 +189,17 @@ function transformConsoleAPICallPacket(p
     messageText,
     stacktrace: message.stacktrace ? message.stacktrace : null,
     frame,
     timeStamp: message.timeStamp,
     userProvidedStyles: message.styles,
     prefix: message.prefix,
     private: message.private,
     executionPoint: message.executionPoint,
+    logpointId: message.logpointId,
   });
 }
 
 function transformNavigationMessagePacket(packet) {
   const { url } = packet;
   return new ConsoleMessage({
     source: MESSAGE_SOURCE.CONSOLE_API,
     type: MESSAGE_TYPE.NAVIGATION_MARKER,
--- a/devtools/client/webconsole/webconsole-connection-proxy.js
+++ b/devtools/client/webconsole/webconsole-connection-proxy.js
@@ -34,16 +34,17 @@ function WebConsoleConnectionProxy(webCo
   this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
   this._onTabNavigated = this._onTabNavigated.bind(this);
   this._onTabWillNavigate = this._onTabWillNavigate.bind(this);
   this._onAttachConsole = this._onAttachConsole.bind(this);
   this._onCachedMessages = this._onCachedMessages.bind(this);
   this._connectionTimeout = this._connectionTimeout.bind(this);
   this._onLastPrivateContextExited =
     this._onLastPrivateContextExited.bind(this);
+  this._clearLogpointMessages = this._clearLogpointMessages.bind(this);
 }
 
 WebConsoleConnectionProxy.prototype = {
   /**
    * The owning WebConsoleUI instance.
    *
    * @see WebConsoleUI
    * @type object
@@ -188,16 +189,17 @@ WebConsoleConnectionProxy.prototype = {
 
     this.webConsoleClient.on("networkEvent", this._onNetworkEvent);
     this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate);
     this.webConsoleClient.on("logMessage", this._onLogMessage);
     this.webConsoleClient.on("pageError", this._onPageError);
     this.webConsoleClient.on("consoleAPICall", this._onConsoleAPICall);
     this.webConsoleClient.on("lastPrivateContextExited",
                              this._onLastPrivateContextExited);
+    this.webConsoleClient.on("clearLogpointMessages", this._clearLogpointMessages);
 
     const msgs = ["PageError", "ConsoleAPI"];
     const cachedMessages = await this.webConsoleClient.getCachedMessages(msgs);
     this._onCachedMessages(cachedMessages);
   },
 
   /**
    * Dispatch a message add on the new frontend and emit an event for tests.
@@ -301,16 +303,22 @@ WebConsoleConnectionProxy.prototype = {
   _onConsoleAPICall: function(packet) {
     if (!this.webConsoleUI) {
       return;
     }
     packet.type = "consoleAPICall";
     this.dispatchMessageAdd(packet);
   },
 
+  _clearLogpointMessages(logpointId) {
+    if (this.webConsoleUI) {
+      this.webConsoleUI.wrapper.dispatchClearLogpointMessages(logpointId);
+    }
+  },
+
   /**
    * The "networkEvent" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
    * @param object networkInfo
    *        The network request information.
    */
@@ -416,16 +424,17 @@ WebConsoleConnectionProxy.prototype = {
 
     this.webConsoleClient.off("logMessage", this._onLogMessage);
     this.webConsoleClient.off("pageError", this._onPageError);
     this.webConsoleClient.off("consoleAPICall", this._onConsoleAPICall);
     this.webConsoleClient.off("lastPrivateContextExited",
                                this._onLastPrivateContextExited);
     this.webConsoleClient.off("networkEvent", this._onNetworkEvent);
     this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate);
+    this.webConsoleClient.off("clearLogpointMessages", this._clearLogpointMessages);
     this.target.off("will-navigate", this._onTabWillNavigate);
     this.target.off("navigate", this._onTabNavigated);
 
     this.client = null;
     this.webConsoleClient = null;
     this.target = null;
     this.connected = false;
     this.webConsoleUI = null;
--- a/devtools/client/webconsole/webconsole-wrapper.js
+++ b/devtools/client/webconsole/webconsole-wrapper.js
@@ -543,16 +543,20 @@ class WebConsoleWrapper {
     this.setTimeoutIfNeeded();
   }
 
   batchedMessagesAdd(messages) {
     this.queuedMessageAdds = this.queuedMessageAdds.concat(messages);
     this.setTimeoutIfNeeded();
   }
 
+  dispatchClearLogpointMessages(logpointId) {
+    store.dispatch(actions.messagesClearLogpoint(logpointId));
+  }
+
   dispatchClearHistory() {
     store.dispatch(actions.clearHistory());
   }
 
   /**
    * Returns a Promise that resolves once any async dispatch is finally dispatched.
    */
   waitAsyncDispatches() {
--- a/devtools/client/webreplay/mochitest/browser.ini
+++ b/devtools/client/webreplay/mochitest/browser.ini
@@ -1,23 +1,20 @@
 [DEFAULT]
 tags = devtools-webreplay
 subsuite = devtools-webreplay
 
-# Feel free to set this to true if an impending change breaks Web Replay and
-# fixing it would be annoying or difficult. This will avoid running all tests
-# that use Web Replay; we don't want this experimental feature to impede
-# development in the rest of Gecko.
-#
-# Please file a bug against the 'Core > Web Replay' component if you do so,
-# so that the problem can be fixed and tests reenabled.
 skip-if = os != "mac" || debug || !nightly_build
 
 support-files =
   head.js
+  !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
+  !/devtools/client/debugger/new/test/mochitest/helpers.js
+  !/devtools/client/debugger/new/test/mochitest/helpers/context.js
   examples/doc_rr_basic.html
   examples/doc_rr_continuous.html
   examples/doc_rr_logs.html
   examples/doc_rr_recovery.html
   examples/doc_rr_error.html
 
 [browser_dbg_rr_breakpoints-01.js]
 [browser_dbg_rr_breakpoints-02.js]
@@ -31,8 +28,10 @@ support-files =
 [browser_dbg_rr_stepping-04.js]
 [browser_dbg_rr_recovery-01.js]
 skip-if = true # See bug 1481009
 [browser_dbg_rr_replay-01.js]
 [browser_dbg_rr_replay-02.js]
 [browser_dbg_rr_replay-03.js]
 [browser_dbg_rr_console_warp-01.js]
 [browser_dbg_rr_console_warp-02.js]
+[browser_dbg_rr_logpoint-01.js]
+[browser_dbg_rr_logpoint-02.js]
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-01.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-01.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test basic breakpoint functionality in web replay.
 add_task(async function() {
   const dbg = await attachRecordingDebugger(
     "doc_rr_basic.html",
     { waitForRecording: true }
   );
   const {threadClient, tab, toolbox} = dbg;
 
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-02.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-02.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test unhandled divergence while evaluating at a breakpoint with Web Replay.
 add_task(async function() {
   const dbg = await attachRecordingDebugger("doc_rr_basic.html",
                                             { waitForRecording: true });
   const {threadClient, tab, toolbox} = dbg;
 
   const bp = await setBreakpoint(threadClient, "doc_rr_basic.html", 21);
   await rewindToLine(threadClient, 21);
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-03.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-03.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test some issues when stepping around after hitting a breakpoint while recording.
 add_task(async function() {
   const dbg = await attachRecordingDebugger("doc_rr_continuous.html");
   const {threadClient, tab, toolbox} = dbg;
 
   await threadClient.interrupt();
   const bp1 = await setBreakpoint(threadClient, "doc_rr_continuous.html", 19);
   await resumeToLine(threadClient, 19);
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-04.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-04.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test navigating back to earlier breakpoints while recording, then resuming
 // recording.
 add_task(async function() {
   const dbg = await attachRecordingDebugger("doc_rr_continuous.html");
   const {threadClient, tab, toolbox} = dbg;
 
   const bp = await setBreakpoint(threadClient, "doc_rr_continuous.html", 14);
   await resumeToLine(threadClient, 14);
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-05.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-05.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test hitting breakpoints when rewinding past the point where the breakpoint
 // script was created.
 add_task(async function() {
   const dbg = await attachRecordingDebugger(
     "doc_rr_basic.html",
     { waitForRecording: true }
   );
 
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_console_warp-01.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_console_warp-01.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test basic console time warping functionality in web replay.
 add_task(async function() {
   const dbg = await attachRecordingDebugger(
     "doc_rr_error.html",
     { waitForRecording: true }
   );
 
   const {tab, toolbox, threadClient} = dbg;
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_console_warp-02.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_console_warp-02.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test basic console time warping functionality in web replay.
 add_task(async function() {
   const dbg = await attachRecordingDebugger(
     "doc_rr_logs.html",
     { waitForRecording: true }
   );
 
   const {tab, toolbox, threadClient} = dbg;
new file mode 100644
--- /dev/null
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_logpoint-01.js
@@ -0,0 +1,46 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint-disable no-undef */
+
+"use strict";
+
+// Test basic logpoint functionality in web replay. When logpoints are added,
+// new messages should appear in the correct order and allow time warping.
+add_task(async function() {
+  const dbg = await attachRecordingDebugger(
+    "doc_rr_basic.html",
+    { waitForRecording: true }
+  );
+
+  const {tab, toolbox, threadClient} = dbg;
+  const console = await getDebuggerSplitConsole(dbg);
+  const hud = console.hud;
+
+  const bp1 = await setBreakpoint(threadClient, "doc_rr_basic.html", 21,
+                                  { logValue: `"Logpoint Number " + number` });
+  const bp2 = await setBreakpoint(threadClient, "doc_rr_basic.html", 6,
+                                  { logValue: `"Logpoint Beginning"` });
+  const bp3 = await setBreakpoint(threadClient, "doc_rr_basic.html", 8,
+                                  { logValue: `"Logpoint Ending"` });
+
+  const messages = await waitForMessageCount(hud, "Logpoint", 12);
+  ok(messages[0].textContent.includes("Beginning"));
+  for (let i = 1; i <= 10; i++) {
+    ok(messages[i].textContent.includes("Number " + i));
+  }
+  ok(messages[11].textContent.includes("Ending"));
+
+  await warpToMessage(hud, dbg, "Number 5");
+  await threadClient.interrupt();
+
+  await checkEvaluateInTopFrame(threadClient, "number", 5);
+  await reverseStepOverToLine(threadClient, 20);
+
+  await threadClient.removeBreakpoint(bp1);
+  await threadClient.removeBreakpoint(bp2);
+  await threadClient.removeBreakpoint(bp3);
+  await toolbox.destroy();
+  await gBrowser.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_logpoint-02.js
@@ -0,0 +1,40 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint-disable no-undef */
+
+"use strict";
+
+// Test that logpoints appear and disappear as expected as breakpoints are
+// modified. Also test that conditional logpoints work.
+add_task(async function() {
+  const dbg = await attachRecordingDebugger(
+    "doc_rr_basic.html",
+    { waitForRecording: true }
+  );
+
+  const {tab, toolbox} = dbg;
+  const console = await getDebuggerSplitConsole(dbg);
+  const hud = console.hud;
+
+  await addBreakpoint(dbg, "doc_rr_basic.html", 21, undefined,
+                      { logValue: `"Logpoint Number " + number` });
+  await addBreakpoint(dbg, "doc_rr_basic.html", 6, undefined,
+                      { logValue: `"Logpoint Beginning"` });
+  await addBreakpoint(dbg, "doc_rr_basic.html", 8, undefined,
+                      { logValue: `"Logpoint Ending"` });
+  await waitForMessageCount(hud, "Logpoint", 12);
+
+  await disableBreakpoint(dbg, findSource(dbg, "doc_rr_basic.html"), 6);
+  await waitForMessageCount(hud, "Logpoint", 11);
+
+  await setBreakpointOptions(dbg, "doc_rr_basic.html", 21, undefined,
+    { logValue: `"Logpoint Number " + number`, condition: `number % 2 == 0` });
+  await waitForMessageCount(hud, "Logpoint", 6);
+
+  await dbg.actions.removeAllBreakpoints();
+
+  await toolbox.destroy();
+  await gBrowser.removeTab(tab);
+});
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_record.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_record.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test basic recording of a tab without any debugging.
 add_task(async function() {
   const recordingTab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
   gBrowser.selectedTab = recordingTab;
   openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
   await once(Services.ppmm, "RecordingFinished");
 
   await gBrowser.removeTab(recordingTab);
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_recovery-01.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_recovery-01.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test basic recovery of crashed child processes in web replay.
 add_task(async function() {
   const tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
   gBrowser.selectedTab = tab;
   openTrustedLinkIn(EXAMPLE_URL + "doc_rr_recovery.html", "current");
   await once(Services.ppmm, "RecordingFinished");
 
   const toolbox = await attachDebugger(tab), client = toolbox.threadClient;
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_replay-01.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_replay-01.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Basic test for saving a recording and then replaying it in a new tab.
 add_task(async function() {
   const recordingFile = newRecordingFile();
   const recordingTab = BrowserTestUtils.addTab(gBrowser, null,
                                                { recordExecution: "*" });
   gBrowser.selectedTab = recordingTab;
   openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
   await once(Services.ppmm, "RecordingFinished");
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_replay-02.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_replay-02.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test ending a recording at a breakpoint and then separately replaying to the end.
 add_task(async function() {
   waitForExplicitFinish();
 
   const recordingFile = newRecordingFile();
   const recordingTab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
   gBrowser.selectedTab = recordingTab;
   openTrustedLinkIn(EXAMPLE_URL + "doc_rr_continuous.html", "current");
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_replay-03.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_replay-03.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test for saving a recording and then replaying it in a new tab,
 // with rewinding disabled.
 add_task(async function() {
   await pushPref("devtools.recordreplay.enableRewinding", false);
 
   const recordingFile = newRecordingFile();
   const recordingTab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
   gBrowser.selectedTab = recordingTab;
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-01.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-01.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test basic step-over/back functionality in web replay.
 add_task(async function() {
   const tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
   gBrowser.selectedTab = tab;
   openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
   await once(Services.ppmm, "RecordingFinished");
 
   const toolbox = await attachDebugger(tab), client = toolbox.threadClient;
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-02.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-02.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test fixes for some simple stepping bugs.
 add_task(async function() {
   const tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
   gBrowser.selectedTab = tab;
   openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
   await once(Services.ppmm, "RecordingFinished");
 
   const toolbox = await attachDebugger(tab), client = toolbox.threadClient;
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-03.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-03.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Test stepping back while recording, then resuming recording.
 add_task(async function() {
   const tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
   gBrowser.selectedTab = tab;
   openTrustedLinkIn(EXAMPLE_URL + "doc_rr_continuous.html", "current");
 
   const toolbox = await attachDebugger(tab), client = toolbox.threadClient;
   await client.interrupt();
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-04.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-04.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
-// To disable all Web Replay tests, see browser.ini
-
 // Stepping past the beginning or end of a frame should act like a step-out.
 add_task(async function() {
   const tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
   gBrowser.selectedTab = tab;
   openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
   await once(Services.ppmm, "RecordingFinished");
 
   const toolbox = await attachDebugger(tab), client = toolbox.threadClient;
--- a/devtools/client/webreplay/mochitest/head.js
+++ b/devtools/client/webreplay/mochitest/head.js
@@ -40,22 +40,22 @@ async function attachRecordingDebugger(u
   const dbg = createDebuggerContext(toolbox);
   const threadClient = dbg.toolbox.threadClient;
 
   await threadClient.interrupt();
   return {...dbg, tab, threadClient};
 }
 
 // Return a promise that resolves when a breakpoint has been set.
-async function setBreakpoint(threadClient, expectedFile, lineno) {
+async function setBreakpoint(threadClient, expectedFile, lineno, options = {}) {
   const {sources} = await threadClient.getSources();
   ok(sources.length == 1, "Got one source");
   ok(RegExp(expectedFile).test(sources[0].url), "Source is " + expectedFile);
   const location = { sourceUrl: sources[0].url, line: lineno };
-  await threadClient.setBreakpoint(location, {});
+  await threadClient.setBreakpoint(location, options);
   return location;
 }
 
 function resumeThenPauseAtLineFunctionFactory(method) {
   return async function(threadClient, lineno) {
     threadClient[method]();
     await threadClient.addOneTimeListener("paused", async function(event, packet) {
       const {frames} = await threadClient.getFrames(0, 1);
@@ -114,16 +114,26 @@ function findMessages(hud, text, selecto
 
   return elements;
 }
 
 function waitForMessages(hud, text, selector = ".message") {
   return waitUntilPredicate(() => findMessages(hud, text, selector));
 }
 
+async function waitForMessageCount(hud, text, length, selector = ".message") {
+  let messages;
+  await waitUntil(() => {
+    messages = findMessages(hud, text, selector);
+    return messages && messages.length == length;
+  });
+  ok(messages.length == length, "Found expected message count");
+  return messages;
+}
+
 async function warpToMessage(hud, threadClient, text) {
   let messages = await waitForMessages(hud, text);
   ok(messages.length == 1, "Found one message");
   const message = messages.pop();
 
   const menuPopup = await openConsoleContextMenu(hud, message);
   console.log(`.>> menu`, menuPopup);
 
--- a/devtools/server/actors/breakpoint.js
+++ b/devtools/server/actors/breakpoint.js
@@ -39,17 +39,17 @@ function BreakpointActor(threadActor, lo
   this.threadActor = threadActor;
   this.location = location;
   this.options = null;
 }
 
 BreakpointActor.prototype = {
   setOptions(options) {
     for (const [script, offsets] of this.scripts) {
-      this._updateOptionsForScript(script, offsets, this.options, options);
+      this._updateOptionsForScript(script, offsets, options);
     }
 
     this.options = options;
   },
 
   destroy: function() {
     this.removeScripts();
   },
@@ -68,52 +68,50 @@ BreakpointActor.prototype = {
    *        Any offsets in the script the breakpoint is associated with.
    */
   addScript: function(script, offsets) {
     this.scripts.set(script, offsets.concat(this.scripts.get(offsets) || []));
     for (const offset of offsets) {
       script.setBreakpoint(offset, this);
     }
 
-    this._updateOptionsForScript(script, offsets, null, this.options);
+    this._updateOptionsForScript(script, offsets, this.options);
   },
 
   /**
    * Remove the breakpoints from associated scripts and clear the script cache.
    */
   removeScripts: function() {
-    for (const [script, offsets] of this.scripts) {
-      this._updateOptionsForScript(script, offsets, this.options, null);
+    for (const [script] of this.scripts) {
       script.clearBreakpoint(this);
     }
     this.scripts.clear();
   },
 
   // Update any state affected by changing options on a script this breakpoint
   // is associated with.
-  _updateOptionsForScript(script, offsets, oldOptions, newOptions) {
-    if (this.threadActor.dbg.replaying) {
-      // When replaying, logging breakpoints are handled using an API to get logged
-      // messages from throughout the recording.
-      const oldLogValue = oldOptions && oldOptions.logValue;
-      const newLogValue = newOptions && newOptions.logValue;
-      if (oldLogValue != newLogValue) {
-        for (const offset of offsets) {
-          const { lineNumber, columnNumber } = script.getOffsetLocation(offset);
-          script.replayVirtualConsoleLog(offset, newLogValue, (point, rv) => {
+  _updateOptionsForScript(script, offsets, options) {
+    // When replaying, logging breakpoints are handled using an API to get logged
+    // messages from throughout the recording.
+    if (this.threadActor.dbg.replaying && options.logValue) {
+      for (const offset of offsets) {
+        const { lineNumber, columnNumber } = script.getOffsetLocation(offset);
+        script.replayVirtualConsoleLog(
+          offset, options.logValue, options.condition, (executionPoint, rv) => {
             const message = {
               filename: script.url,
               lineNumber,
               columnNumber,
-              executionPoint: point,
+              executionPoint,
               "arguments": ["return" in rv ? rv.return : rv.throw],
+              logpointId: options.logGroupId,
             };
             this.threadActor._parent._consoleActor.onConsoleAPICall(message);
-          });
-        }
+          }
+        );
       }
     }
   },
 
   // Get a string message to display when a frame evaluation throws.
   getThrownMessage(completion) {
     try {
       if (completion.throw.getOwnPropertyDescriptor) {
--- a/devtools/server/actors/replay/debugger.js
+++ b/devtools/server/actors/replay/debugger.js
@@ -357,43 +357,60 @@ ReplayDebugger.prototype = {
   /////////////////////////////////////////////////////////
 
   _forEachSearch(callback) {
     for (const { position } of this._searches) {
       callback(position);
     }
   },
 
-  _virtualConsoleLog(position, text, callback) {
-    this._searches.push({ position, text, callback, results: [] });
+  _virtualConsoleLog(position, text, condition, callback) {
+    this._searches.push({ position, text, condition, callback, results: [] });
     this._searchControl.reset();
   },
 
+  _evaluateVirtualConsoleLog(search) {
+    const frameData = this._searchControl.sendRequest({
+      type: "getFrame",
+      index: NewestFrameIndex,
+    });
+    if (!("index" in frameData)) {
+      return null;
+    }
+    if (search.condition) {
+      const rv = this._searchControl.sendRequest({
+        type: "frameEvaluate",
+        index: frameData.index,
+        text: search.condition,
+        convertOptions: { snapshot: true },
+      });
+      const crv = this._convertCompletionValue(rv);
+      if ("return" in crv && !crv.return) {
+        return null;
+      }
+    }
+    const rv = this._searchControl.sendRequest({
+      type: "frameEvaluate",
+      index: frameData.index,
+      text: search.text,
+      convertOptions: { snapshot: true },
+    });
+    return this._convertCompletionValue(rv);
+  },
+
   _onSearchPause(point) {
-    for (const { position, text, callback, results } of this._searches) {
-      if (RecordReplayControl.positionSubsumes(position, point.position)) {
-        if (!results.some(existing => point.progress == existing.progress)) {
-          let evaluateResult;
-          if (text) {
-            const frameData = this._searchControl.sendRequest({
-              type: "getFrame",
-              index: NewestFrameIndex,
-            });
-            if ("index" in frameData) {
-              const rv = this._searchControl.sendRequest({
-                type: "frameEvaluate",
-                index: frameData.index,
-                text,
-                convertOptions: { snapshot: true },
-              });
-              evaluateResult = this._convertCompletionValue(rv);
-            }
+    for (const search of this._searches) {
+      if (RecordReplayControl.positionSubsumes(search.position, point.position)) {
+        if (!search.results.some(existing => point.progress == existing.progress)) {
+          search.results.push(point);
+
+          const evaluateResult = this._evaluateVirtualConsoleLog(search);
+          if (evaluateResult) {
+            search.callback(point, evaluateResult);
           }
-          results.push(point);
-          callback(point, evaluateResult);
         }
       }
     }
   },
 
   /////////////////////////////////////////////////////////
   // Breakpoint management
   /////////////////////////////////////////////////////////
@@ -742,19 +759,19 @@ ReplayDebuggerScript.prototype = {
   },
 
   clearBreakpoint(handler) {
     this._dbg._clearMatchingBreakpoints(({position, data}) => {
       return position.script == this._data.id && handler == data;
     });
   },
 
-  replayVirtualConsoleLog(offset, text, callback) {
+  replayVirtualConsoleLog(offset, text, condition, callback) {
     this._dbg._virtualConsoleLog({ kind: "Break", script: this._data.id, offset },
-                                 text, callback);
+                                 text, condition, callback);
   },
 
   get isGeneratorFunction() { NYI(); },
   get isAsyncFunction() { NYI(); },
   getChildScripts: NYI,
   getAllOffsets: NYI,
   getBreakpoints: NYI,
   clearAllBreakpoints: NYI,
--- a/dom/xslt/tests/XSLTMark/XSLTMark.xul
+++ b/dom/xslt/tests/XSLTMark/XSLTMark.xul
@@ -40,14 +40,14 @@
   </groupbox>
 </hbox>
 <hbox>
   <textbox id="currentStatus" readonly="true" flex="1"/>
   <progressmeter id="currentProgress" mode="normal" value="0" flex="2"/>
   <progressmeter id="totalProgress" mode="normal" value="0" flex="2"/>
 </hbox>
 <hbox flex="1">
-  <html:textarea id="transformOutput" class="out" readonly="true" flex="1"/>
+  <html:textarea id="transformOutput" class="out" readonly="readonly" flex="1"/>
 </hbox>
 <hbox flex="1">
-  <html:textarea id="transformDetailedOutput" class="out" readonly="true" flex="1"/>
+  <html:textarea id="transformDetailedOutput" class="out" readonly="readonly" flex="1"/>
 </hbox>
 </window>
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -217,17 +217,17 @@ bool CompositorBridgeParentBase::StopSha
   return PCompositorBridgeParent::SendReleaseSharedCompositorFrameMetrics(
       aScrollId, aApzcId);
 }
 
 CompositorBridgeParent::LayerTreeState::LayerTreeState()
     : mApzcTreeManagerParent(nullptr),
       mParent(nullptr),
       mLayerManager(nullptr),
-      mCrossProcessParent(nullptr),
+      mContentCompositorBridgeParent(nullptr),
       mLayerTree(nullptr),
       mUpdatedPluginDataAvailable(false) {}
 
 CompositorBridgeParent::LayerTreeState::~LayerTreeState() {
   if (mController) {
     mController->Destroy();
   }
 }
@@ -1675,17 +1675,17 @@ mozilla::ipc::IPCResult CompositorBridge
           mLayerManager, GetAnimationStorage());
       // Trigger composition to handle a case that mLayerTree was not composited
       // yet by previous CompositorBridgeParent, since nsRefreshDriver might
       // wait composition complete.
       scheduleComposition = true;
     }
     if (mWrBridge) {
       childWrBridge = sIndirectLayerTrees[child].mWrBridge;
-      cpcp = sIndirectLayerTrees[child].mCrossProcessParent;
+      cpcp = sIndirectLayerTrees[child].mContentCompositorBridgeParent;
     }
     parent = sIndirectLayerTrees[child].mApzcTreeManagerParent;
   }
 
   if (scheduleComposition) {
     ScheduleComposition();
   }
 
@@ -1981,17 +1981,17 @@ void CompositorBridgeParent::FinishPendi
 
 CompositorController*
 CompositorBridgeParent::LayerTreeState::GetCompositorController() const {
   return mParent;
 }
 
 MetricsSharingController*
 CompositorBridgeParent::LayerTreeState::CrossProcessSharingController() const {
-  return mCrossProcessParent;
+  return mContentCompositorBridgeParent;
 }
 
 MetricsSharingController*
 CompositorBridgeParent::LayerTreeState::InProcessSharingController() const {
   return mParent;
 }
 
 void CompositorBridgeParent::DidComposite(const VsyncId& aId,
@@ -2115,33 +2115,33 @@ void CompositorBridgeParent::NotifyDidCo
     if (!notifications.IsEmpty()) {
       Unused << ImageBridgeParent::NotifyImageComposites(notifications);
     }
   }
 
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   ForEachIndirectLayerTree([&](LayerTreeState* lts,
                                const LayersId& aLayersId) -> void {
-    if (lts->mCrossProcessParent && lts->mParent == this) {
-      ContentCompositorBridgeParent* cpcp = lts->mCrossProcessParent;
+    if (lts->mContentCompositorBridgeParent && lts->mParent == this) {
+      ContentCompositorBridgeParent* cpcp = lts->mContentCompositorBridgeParent;
       cpcp->DidCompositeLocked(aLayersId, aId, aCompositeStart, aCompositeEnd);
     }
   });
 }
 
 void CompositorBridgeParent::InvalidateRemoteLayers() {
   MOZ_ASSERT(CompositorLoop() == MessageLoop::current());
 
   Unused << PCompositorBridgeParent::SendInvalidateLayers(LayersId{0});
 
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   ForEachIndirectLayerTree(
       [](LayerTreeState* lts, const LayersId& aLayersId) -> void {
-        if (lts->mCrossProcessParent) {
-          ContentCompositorBridgeParent* cpcp = lts->mCrossProcessParent;
+        if (lts->mContentCompositorBridgeParent) {
+          ContentCompositorBridgeParent* cpcp = lts->mContentCompositorBridgeParent;
           Unused << cpcp->SendInvalidateLayers(aLayersId);
         }
       });
 }
 
 static void UpdateIndirectTree(LayersId aId, Layer* aRoot,
                                const TargetConfig& aTargetConfig) {
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -491,17 +491,17 @@ class CompositorBridgeParent final : pub
     RefPtr<GeckoContentController> mController;
     APZCTreeManagerParent* mApzcTreeManagerParent;
     RefPtr<CompositorBridgeParent> mParent;
     HostLayerManager* mLayerManager;
     RefPtr<WebRenderBridgeParent> mWrBridge;
     // Pointer to the ContentCompositorBridgeParent. Used by APZCs to share
     // their FrameMetrics with the corresponding child process that holds
     // the PCompositorBridgeChild
-    ContentCompositorBridgeParent* mCrossProcessParent;
+    ContentCompositorBridgeParent* mContentCompositorBridgeParent;
     TargetConfig mTargetConfig;
     LayerTransactionParent* mLayerTree;
     nsTArray<PluginWindowData> mPluginData;
     bool mUpdatedPluginDataAvailable;
 
     CompositorController* GetCompositorController() const;
     MetricsSharingController* CrossProcessSharingController() const;
     MetricsSharingController* InProcessSharingController() const;
--- a/gfx/layers/ipc/ContentCompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/ContentCompositorBridgeParent.cpp
@@ -83,17 +83,17 @@ ContentCompositorBridgeParent::AllocPLay
 
   CompositorBridgeParent::LayerTreeState* state = nullptr;
   LayerTreeMap::iterator itr = sIndirectLayerTrees.find(aId);
   if (sIndirectLayerTrees.end() != itr) {
     state = &itr->second;
   }
 
   if (state && state->mLayerManager) {
-    state->mCrossProcessParent = this;
+    state->mContentCompositorBridgeParent = this;
     HostLayerManager* lm = state->mLayerManager;
     CompositorAnimationStorage* animStorage =
         state->mParent ? state->mParent->GetAnimationStorage() : nullptr;
     TimeDuration vsyncRate =
         state->mParent ? state->mParent->GetVsyncInterval() : TimeDuration();
     LayerTransactionParent* p =
         new LayerTransactionParent(lm, this, animStorage, aId, vsyncRate);
     p->AddIPDLReference();
@@ -249,17 +249,17 @@ ContentCompositorBridgeParent::AllocPWeb
   RefPtr<CompositorAnimationStorage> animStorage = cbp->GetAnimationStorage();
   WebRenderBridgeParent* parent = new WebRenderBridgeParent(
       this, aPipelineId, nullptr, root->CompositorScheduler(), std::move(api),
       std::move(holder), std::move(animStorage), cbp->GetVsyncInterval());
   parent->AddRef();  // IPDL reference
 
   {  // scope lock
     MonitorAutoLock lock(*sIndirectLayerTreesLock);
-    sIndirectLayerTrees[layersId].mCrossProcessParent = this;
+    sIndirectLayerTrees[layersId].mContentCompositorBridgeParent = this;
     sIndirectLayerTrees[layersId].mWrBridge = parent;
   }
 
   return parent;
 }
 
 bool ContentCompositorBridgeParent::DeallocPWebRenderBridgeParent(
     PWebRenderBridgeParent* aActor) {
@@ -275,17 +275,17 @@ bool ContentCompositorBridgeParent::Deal
 }
 
 mozilla::ipc::IPCResult ContentCompositorBridgeParent::RecvNotifyChildCreated(
     const LayersId& child, CompositorOptions* aOptions) {
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   for (LayerTreeMap::iterator it = sIndirectLayerTrees.begin();
        it != sIndirectLayerTrees.end(); it++) {
     CompositorBridgeParent::LayerTreeState* lts = &it->second;
-    if (lts->mParent && lts->mCrossProcessParent == this) {
+    if (lts->mParent && lts->mContentCompositorBridgeParent == this) {
       lts->mParent->NotifyChildCreated(child);
       *aOptions = lts->mParent->GetOptions();
       return IPC_OK();
     }
   }
   return IPC_FAIL_NO_REASON(this);
 }
 
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -69,25 +69,25 @@ struct FrameStats {
  * The PCompositorBridge protocol is a top-level protocol for the compositor.
  * There is an instance of the protocol for each compositor, plus one for each
  * content process. In other words:
  * - There is a CompositorBridgeParent/CompositorBridgeChild pair created
  *   for each "top level browser window", which has its own compositor. The
  *   CompositorBridgeChild instance lives in the UI process, and the
  *   CompositorBridgeParent lives in the GPU process (if there is one) or the
  *   UI process otherwise.
- * - There is also a CrossProcessCompositorBridgeParent/CompositorBridgeChild
- *   pair created for each content process. The CrossProcessCompositorBridgeParent
+ * - There is also a ContentCompositorBridgeParent/CompositorBridgeChild
+ *   pair created for each content process. The ContentCompositorBridgeParent
  *   lives in the GPU process (if there is one) or the UI process otherwise. The
  *   CompositorBridgeChild is a singleton in the content process. Note that
  *   a given content process may have multiple browser instances (represented
  *   by TabChild instances), that are attached to different windows, and therefore
  *   rendered by different compositors. This means that when a browser instance
  *   sends messages via its CompositorBridgeChild, the corresponding
- *   CrossProcessCompositorBridgeParent has to use the layers id in the message
+ *   ContentCompositorBridgeParent has to use the layers id in the message
  *   to find the correct compositor or CompositorBridgeParent to pass the message
  *   on to.
  *
  * One of the main goals of this protocol is to manage the PLayerTransaction sub-
  * protocol, which is per-browser. A lot of the functions in the protocol are
  * basically multiplexing/demultiplexing stuff in PLayerTransaction.
  */
 sync protocol PCompositorBridge
--- a/image/SVGDocumentWrapper.cpp
+++ b/image/SVGDocumentWrapper.cpp
@@ -20,19 +20,17 @@
 #include "nsIStreamListener.h"
 #include "nsIXMLContentSink.h"
 #include "nsNetCID.h"
 #include "nsComponentManagerUtils.h"
 #include "mozilla/SMILAnimationController.h"
 #include "nsServiceManagerUtils.h"
 #include "mozilla/dom/SVGSVGElement.h"
 #include "SVGObserverUtils.h"
-#include "mozilla/dom/SVGAnimatedLength.h"
 #include "nsMimeTypes.h"
-#include "DOMSVGLength.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/ImageTracker.h"
 
 namespace mozilla {
 
 using namespace dom;
 using namespace gfx;
 
--- a/js/src/jit-test/tests/wasm/gc/anyref-global-postbarrier.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref-global-postbarrier.js
@@ -60,17 +60,25 @@ test(exportsObj);
 // Test stacks reported in profiling mode in a separate way, to not perturb
 // the behavior of the tested functions.
 if (!isSingleStepProfilingEnabled)
     quit(0);
 
 enableGeckoProfiling();
 
 const EXPECTED_STACKS = [
-    ['', '!>', '0,!>', '<,0,!>', 'GC postbarrier,0,!>', '<,0,!>', '0,!>', '!>', ''],
+    // Expected output for (simulator+baseline).
+    ['', '!>', '0,!>', '<,0,!>', 'GC postbarrier,0,!>',
+     '<,0,!>', '0,!>', '!>', ''],
+
+    // Expected output for (simulator+via-Ion).
+    ['', '!>', '0,!>', '<,0,!>', 'filtering GC postbarrier,0,!>',
+     '<,0,!>', '0,!>', '!>', ''],
+
+    // Expected output for other configurations.
     ['', '!>', '0,!>', '!>', ''],
 ];
 
 function testStacks(exports) {
     // Test post-write barrier in wasm code.
     {
         let nomnom = new Baguette(15);
         startProfiling();
--- a/js/src/jit-test/tests/wasm/gc/anyref-global-prebarrier.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref-global-prebarrier.js
@@ -29,17 +29,40 @@ if (!isSingleStepProfilingEnabled) {
     quit(0);
 }
 
 enableGeckoProfiling();
 startProfiling();
 gczeal(4, 1);
 e.set(obj);
 gczeal(0);
-assertEqPreciseStacks(endProfiling(), [['', '!>', '0,!>', '!>', '']]);
+assertEqPreciseStacks(
+    endProfiling(),
+    [
+        // Expected output for (simulator+via-Ion).
+        ['', '!>', '0,!>', '<,0,!>', 'filtering GC postbarrier,0,!>',
+         '<,0,!>', '0,!>', '!>', ''],
+
+        // Expected output for (simulator+baseline).
+        ['', '!>', '0,!>', '<,0,!>', 'GC postbarrier,0,!>',
+         '<,0,!>', '0,!>', '!>', ''],
+
+        // Expected output for other configurations.
+        ['', '!>', '0,!>', '!>', ''],
+    ]);
 
 startProfiling();
 gczeal(4, 1);
 e.set(null);
 gczeal(0);
 
 // We're losing stack info in the prebarrier code.
-assertEqPreciseStacks(endProfiling(), [['', '!>', '0,!>', '', '0,!>', '!>', '']]);
+assertEqPreciseStacks(
+    endProfiling(),
+    [
+        // Expected output for (simulator+via-Ion).
+        ['', '!>', '0,!>', '', '0,!>', '<,0,!>', 'filtering GC postbarrier,0,!>',
+         '<,0,!>', '0,!>', '!>', ''],
+
+        // Expected output for other configurations.
+        ['', '!>', '0,!>', '', '0,!>', '!>', ''],
+    ]);
+
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -5912,16 +5912,17 @@ static void DumpTrackedOptimizations(Tra
   if (!JitSpewEnabled(JitSpew_OptimizationTracking)) {
     return;
   }
 
   optimizations->spew(JitSpew_OptimizationTracking);
 }
 
 bool CodeGenerator::generateBody() {
+  JitSpew(JitSpew_Codegen, "==== BEGIN CodeGenerator::generateBody ====\n");
   IonScriptCounts* counts = maybeCreateScriptCounts();
 
 #if defined(JS_ION_PERF)
   PerfSpewer* perfSpewer = &perfSpewer_;
   if (gen->compilingWasm()) {
     perfSpewer = &gen->perfSpewer();
   }
 #endif
@@ -6055,16 +6056,17 @@ bool CodeGenerator::generateBody() {
       return false;
     }
 
 #if defined(JS_ION_PERF)
     perfSpewer->endBasicBlock(masm);
 #endif
   }
 
+  JitSpew(JitSpew_Codegen, "==== END CodeGenerator::generateBody ====\n");
   return true;
 }
 
 // Out-of-line object allocation for LNewArray.
 class OutOfLineNewArray : public OutOfLineCodeBase<CodeGenerator> {
   LNewArray* lir_;
 
  public:
@@ -7250,17 +7252,21 @@ void CodeGenerator::visitGetNextEntryFor
   if (lir->mir()->mode() == MGetNextEntryForIterator::Map) {
     emitGetNextEntryForIterator<MapIteratorObject, ValueMap>(lir);
   } else {
     MOZ_ASSERT(lir->mir()->mode() == MGetNextEntryForIterator::Set);
     emitGetNextEntryForIterator<SetIteratorObject, ValueSet>(lir);
   }
 }
 
-void CodeGenerator::emitWasmCallBase(MWasmCall* mir, bool needsBoundsCheck) {
+template <size_t Defs>
+void CodeGenerator::emitWasmCallBase(LWasmCallBase<Defs>* lir) {
+  MWasmCall* mir = lir->mir();
+  bool needsBoundsCheck = lir->needsBoundsCheck();
+
   MOZ_ASSERT((sizeof(wasm::Frame) + masm.framePushed()) % WasmStackAlignment ==
              0);
   static_assert(
       WasmStackAlignment >= ABIStackAlignment &&
           WasmStackAlignment % ABIStackAlignment == 0,
       "The wasm stack alignment should subsume the ABI-required alignment");
 
 #ifdef DEBUG
@@ -7299,37 +7305,46 @@ void CodeGenerator::emitWasmCallBase(MWa
       break;
     case wasm::CalleeDesc::BuiltinInstanceMethod:
       masm.wasmCallBuiltinInstanceMethod(desc, mir->instanceArg(),
                                          callee.builtin());
       switchRealm = false;
       break;
   }
 
+  // Note the assembler offset for the associated LSafePoint.
+  markSafepointAt(masm.currentOffset(), lir);
+
+  // Now that all the outbound in-memory args are on the stack, note the
+  // required lower boundary point of the associated StackMap.
+  lir->safepoint()->setFramePushedAtStackMapBase(
+      masm.framePushed() - mir->stackArgAreaSizeUnaligned());
+  MOZ_ASSERT(!lir->safepoint()->isWasmTrap());
+
   if (reloadRegs) {
     masm.loadWasmTlsRegFromFrame();
     masm.loadWasmPinnedRegsFromTls();
     if (switchRealm) {
       masm.switchToWasmTlsRealm(ABINonArgReturnReg0, ABINonArgReturnReg1);
     }
   } else {
     MOZ_ASSERT(!switchRealm);
   }
 }
 
 void CodeGenerator::visitWasmCall(LWasmCall* ins) {
-  emitWasmCallBase(ins->mir(), ins->needsBoundsCheck());
+  emitWasmCallBase(ins);
 }
 
 void CodeGenerator::visitWasmCallVoid(LWasmCallVoid* ins) {
-  emitWasmCallBase(ins->mir(), ins->needsBoundsCheck());
+  emitWasmCallBase(ins);
 }
 
 void CodeGenerator::visitWasmCallI64(LWasmCallI64* ins) {
-  emitWasmCallBase(ins->mir(), ins->needsBoundsCheck());
+  emitWasmCallBase(ins);
 }
 
 void CodeGenerator::visitWasmLoadSlot(LWasmLoadSlot* ins) {
   MIRType type = ins->type();
   Register container = ToRegister(ins->containerRef());
   Address addr(container, ins->offset());
   AnyRegister dst = ToAnyRegister(ins->output());
 
@@ -10203,27 +10218,351 @@ void CodeGenerator::visitRest(LRest* lir
     masm.movePtr(ImmPtr(nullptr), temp2);
   }
   masm.bind(&joinAlloc);
 
   emitRest(lir, temp2, numActuals, temp0, temp1, numFormals, templateObject,
            false, ToRegister(lir->output()));
 }
 
+// A stackmap creation helper.  Create a stackmap from a vector of booleans.
+// The caller owns the resulting stackmap.
+
+typedef Vector<bool, 128, SystemAllocPolicy> StackMapBoolVector;
+
+static wasm::StackMap* ConvertStackMapBoolVectorToStackMap(
+                          const StackMapBoolVector& vec, bool hasRefs) {
+  wasm::StackMap* stackMap = wasm::StackMap::create(vec.length());
+  if (!stackMap) {
+    return nullptr;
+  }
+
+  bool hasRefsObserved = false;
+  size_t i = 0;
+  for (bool b : vec) {
+    if (b) {
+      stackMap->setBit(i);
+      hasRefsObserved = true;
+    }
+    i++;
+  }
+  MOZ_RELEASE_ASSERT(hasRefs == hasRefsObserved);
+
+  return stackMap;
+}
+
+// Create a stackmap from the given safepoint, with the structure:
+//
+//   <reg dump area, if trap>
+//   |       ++ <body (general spill)>
+//   |               ++ <space for Frame>
+//   |                       ++ <inbound args>
+//   |                                       |
+//   Lowest Addr                             Highest Addr
+//
+// The caller owns the resulting stackmap.  This assumes a grow-down stack.
+//
+// For non-debug builds, if the stackmap would contain no pointers, no
+// stackmap is created, and nullptr is returned.  For a debug build, a
+// stackmap is always created and returned.
+static bool CreateStackMapFromLSafepoint(LSafepoint& safepoint,
+                                         const MachineState& trapExitLayout,
+                                         size_t trapExitLayoutNumWords,
+                                         size_t nInboundStackArgBytes,
+                                         wasm::StackMap** result) {
+  // Ensure this is defined on all return paths.
+  *result = nullptr;
+
+  // The size of the wasm::Frame itself.
+  const size_t nFrameBytes = sizeof(wasm::Frame);
+
+  // This is the number of bytes in the general spill area, below the Frame.
+  const size_t nBodyBytes = safepoint.framePushedAtStackMapBase();
+
+  // This is the number of bytes in the general spill area, the Frame, and the
+  // incoming args, but not including any trap (register dump) area.
+  const size_t nNonTrapBytes = nBodyBytes + nFrameBytes + nInboundStackArgBytes;
+  MOZ_ASSERT(nNonTrapBytes % sizeof(void*) == 0);
+
+  // This is the total number of bytes covered by the map.
+  const DebugOnly<size_t> nTotalBytes = nNonTrapBytes +
+      (safepoint.isWasmTrap() ? (trapExitLayoutNumWords * sizeof(void*)) : 0);
+
+  // Create the stackmap initially in this vector.  Since most frames will
+  // contain 128 or fewer words, heap allocation is avoided in the majority of
+  // cases.  vec[0] is for the lowest address in the map, vec[N-1] is for the
+  // highest address in the map.
+  StackMapBoolVector vec;
+
+  // Keep track of whether we've actually seen any refs.
+  bool hasRefs = false;
+
+  // REG DUMP AREA, if any.
+  const LiveGeneralRegisterSet gcRegs = safepoint.gcRegs();
+  GeneralRegisterForwardIterator gcRegsIter(gcRegs);
+  if (safepoint.isWasmTrap()) {
+    // Deal with roots in registers.  This can only happen for safepoints
+    // associated with a trap.  For safepoints associated with a call, we
+    // don't expect to have any live values in registers, hence no roots in
+    // registers.
+    if (!vec.appendN(false, trapExitLayoutNumWords)) {
+      return false;
+    }
+    for (; gcRegsIter.more(); ++gcRegsIter) {
+      Register reg = *gcRegsIter;
+      size_t offsetFromTop =
+        reinterpret_cast<size_t>(trapExitLayout.address(reg));
+
+      // If this doesn't hold, the associated register wasn't saved by
+      // the trap exit stub.  Better to crash now than much later, in
+      // some obscure place, and possibly with security consequences.
+      MOZ_RELEASE_ASSERT(offsetFromTop < trapExitLayoutNumWords);
+
+      // offsetFromTop is an offset in words down from the highest
+      // address in the exit stub save area.  Switch it around to be an
+      // offset up from the bottom of the (integer register) save area.
+      size_t offsetFromBottom = trapExitLayoutNumWords - 1 - offsetFromTop;
+
+      vec[offsetFromBottom] = true;
+      hasRefs = true;
+    }
+  } else {
+    // This map is associated with a call instruction.  We expect there to be
+    // no live ref-carrying registers, and if there are we're in deep trouble.
+    MOZ_RELEASE_ASSERT(!gcRegsIter.more());
+  }
+
+  // BODY (GENERAL SPILL) AREA and FRAME and INCOMING ARGS
+  // Deal with roots on the stack.
+  size_t wordsSoFar = vec.length();
+  if (!vec.appendN(false, nNonTrapBytes / sizeof(void*))) {
+    return false;
+  }
+  const LSafepoint::SlotList& gcSlots = safepoint.gcSlots();
+  for (SafepointSlotEntry gcSlot : gcSlots) {
+    // The following needs to correspond with JitFrameLayout::slotRef
+    // gcSlot.stack == 0 means the slot is in the args area
+    if (gcSlot.stack) {
+      // It's a slot in the body allocation, so .slot is interpreted
+      // as an index downwards from the Frame*
+      MOZ_ASSERT(gcSlot.slot <= nBodyBytes);
+      uint32_t offsetInBytes = nBodyBytes - gcSlot.slot;
+      MOZ_ASSERT(offsetInBytes % sizeof(void*) == 0);
+      vec[wordsSoFar + offsetInBytes / sizeof(void*)] = true;
+    } else {
+      // It's an argument slot
+      MOZ_ASSERT(gcSlot.slot < nInboundStackArgBytes);
+      uint32_t offsetInBytes = nBodyBytes + nFrameBytes + gcSlot.slot;
+      MOZ_ASSERT(offsetInBytes % sizeof(void*) == 0);
+      vec[wordsSoFar + offsetInBytes / sizeof(void*)] = true;
+    }
+    hasRefs = true;
+  }
+
+#ifndef DEBUG
+  // We saw no references, and this is a non-debug build, so don't bother
+  // building the stackmap.
+  if (!hasRefs) {
+    return true;
+  }
+#endif
+
+  // Convert vec into a wasm::StackMap.
+  MOZ_ASSERT(vec.length() * sizeof(void*) == nTotalBytes);
+  wasm::StackMap* stackMap = ConvertStackMapBoolVectorToStackMap(vec, hasRefs);
+  if (!stackMap) {
+    return false;
+  }
+  if (safepoint.isWasmTrap()) {
+    stackMap->setExitStubWords(trapExitLayoutNumWords);
+  }
+
+  // Record in the map, how far down from the highest address the Frame* is.
+  // Take the opportunity to check that we haven't marked any part of the
+  // Frame itself as a pointer.
+  stackMap->setFrameOffsetFromTop((nInboundStackArgBytes + nFrameBytes)
+                                  / sizeof(void*));
+#ifdef DEBUG
+  for (uint32_t i = 0; i < nFrameBytes / sizeof(void*); i++) {
+    MOZ_ASSERT(stackMap->getBit(stackMap->numMappedWords -
+                                stackMap->frameOffsetFromTop + i) == 0);
+  }
+#endif
+
+  *result = stackMap;
+  return true;
+}
+
+// Generate a stackmap for a function's stack-overflow-at-entry trap, with
+// the structure:
+//
+//    <reg dump area>
+//    |       ++ <space reserved before trap, if any>
+//    |               ++ <space for Frame>
+//    |                       ++ <inbound arg area>
+//    |                                           |
+//    Lowest Addr                                 Highest Addr
+//
+// The caller owns the resulting stackmap.  This assumes a grow-down stack.
+//
+// For non-debug builds, if the stackmap would contain no pointers, no
+// stackmap is created, and nullptr is returned.  For a debug build, a
+// stackmap is always created and returned.
+//
+// The "space reserved before trap" is the space reserved by
+// MacroAssembler::wasmReserveStackChecked, in the case where the frame is
+// "small", as determined by that function.
+static bool CreateStackMapForFunctionEntryTrap(
+                const wasm::ValTypeVector& argTypes,
+                const MachineState& trapExitLayout,
+                size_t trapExitLayoutWords,
+                size_t nBytesReservedBeforeTrap,
+                size_t nInboundStackArgBytes,
+                wasm::StackMap** result) {
+  // Ensure this is defined on all return paths.
+  *result = nullptr;
+
+  // The size of the wasm::Frame itself.
+  const size_t nFrameBytes = sizeof(wasm::Frame);
+
+  // The size of the register dump (trap) area.
+  const size_t trapExitLayoutBytes = trapExitLayoutWords * sizeof(void*);
+
+  // This is the total number of bytes covered by the map.
+  const DebugOnly<size_t> nTotalBytes =
+      trapExitLayoutBytes + nBytesReservedBeforeTrap + nFrameBytes +
+      nInboundStackArgBytes;
+
+  // Create the stackmap initially in this vector.  Since most frames will
+  // contain 128 or fewer words, heap allocation is avoided in the majority of
+  // cases.  vec[0] is for the lowest address in the map, vec[N-1] is for the
+  // highest address in the map.
+  StackMapBoolVector vec;
+
+  // Keep track of whether we've actually seen any refs.
+  bool hasRefs = false;
+
+  // REG DUMP AREA
+  wasm::ExitStubMapVector trapExitExtras;
+  if (!GenerateStackmapEntriesForTrapExit(argTypes, trapExitLayout,
+                                          trapExitLayoutWords,
+                                          &trapExitExtras)) {
+    return false;
+  }
+  MOZ_ASSERT(trapExitExtras.length() == trapExitLayoutWords);
+
+  if (!vec.appendN(false, trapExitLayoutWords)) {
+    return false;
+  }
+  for (size_t i = 0; i < trapExitLayoutWords; i++) {
+    vec[i] = trapExitExtras[i];
+    hasRefs |= vec[i];
+  }
+
+  // SPACE RESERVED BEFORE TRAP
+  MOZ_ASSERT(nBytesReservedBeforeTrap % sizeof(void*) == 0);
+  if (!vec.appendN(false, nBytesReservedBeforeTrap / sizeof(void*))) {
+    return false;
+  }
+
+  // SPACE FOR FRAME
+  if (!vec.appendN(false, nFrameBytes / sizeof(void*))) {
+    return false;
+  }
+
+  // INBOUND ARG AREA
+  MOZ_ASSERT(nInboundStackArgBytes % sizeof(void*) == 0);
+  const size_t numStackArgWords = nInboundStackArgBytes / sizeof(void*);
+
+  const size_t wordsSoFar = vec.length();
+  if (!vec.appendN(false, numStackArgWords)) {
+    return false;
+  }
+
+  for (ABIArgIter<const wasm::ValTypeVector> i(argTypes); !i.done(); i++) {
+    ABIArg argLoc = *i;
+    const wasm::ValType& ty = argTypes[i.index()];
+    MOZ_ASSERT(ToMIRType(ty) != MIRType::Pointer);
+    if (argLoc.kind() != ABIArg::Stack || !ty.isReference()) {
+      continue;
+    }
+    uint32_t offset = argLoc.offsetFromArgBase();
+    MOZ_ASSERT(offset < nInboundStackArgBytes);
+    MOZ_ASSERT(offset % sizeof(void*) == 0);
+    vec[wordsSoFar + offset / sizeof(void*)] = true;
+    hasRefs = true;
+  }
+
+#ifndef DEBUG
+  // We saw no references, and this is a non-debug build, so don't bother
+  // building the stackmap.
+  if (!hasRefs) {
+    return true;
+  }
+#endif
+
+  // Convert vec into a wasm::StackMap.
+  MOZ_ASSERT(vec.length() * sizeof(void*) == nTotalBytes);
+  wasm::StackMap* stackMap = ConvertStackMapBoolVectorToStackMap(vec, hasRefs);
+  if (!stackMap) {
+    return false;
+  }
+  stackMap->setExitStubWords(trapExitLayoutWords);
+
+  stackMap->setFrameOffsetFromTop(nFrameBytes / sizeof(void*)
+                                  + numStackArgWords);
+#ifdef DEBUG
+  for (uint32_t i = 0; i < nFrameBytes / sizeof(void*); i++) {
+    MOZ_ASSERT(stackMap->getBit(stackMap->numMappedWords -
+                                stackMap->frameOffsetFromTop + i) == 0);
+  }
+#endif
+
+  *result = stackMap;
+  return true;
+}
+
 bool CodeGenerator::generateWasm(wasm::FuncTypeIdDesc funcTypeId,
                                  wasm::BytecodeOffset trapOffset,
-                                 wasm::FuncOffsets* offsets) {
+                                 const wasm::ValTypeVector& argTypes,
+                                 const MachineState& trapExitLayout,
+                                 size_t trapExitLayoutNumWords,
+                                 wasm::FuncOffsets* offsets,
+                                 wasm::StackMaps* stackMaps) {
   JitSpew(JitSpew_Codegen, "# Emitting wasm code");
 
+  size_t nInboundStackArgBytes = StackArgAreaSizeUnaligned(argTypes);
+
   wasm::GenerateFunctionPrologue(masm, funcTypeId, mozilla::Nothing(), offsets);
 
+  MOZ_ASSERT(masm.framePushed() == 0);
+
   if (omitOverRecursedCheck()) {
     masm.reserveStack(frameSize());
   } else {
-    masm.wasmReserveStackChecked(frameSize(), trapOffset);
+    std::pair<CodeOffset, uint32_t> pair =
+        masm.wasmReserveStackChecked(frameSize(), trapOffset);
+    CodeOffset trapInsnOffset = pair.first;
+    size_t nBytesReservedBeforeTrap = pair.second;
+
+    wasm::StackMap* functionEntryStackMap = nullptr;
+    if (!CreateStackMapForFunctionEntryTrap(argTypes, trapExitLayout,
+            trapExitLayoutNumWords, nBytesReservedBeforeTrap,
+            nInboundStackArgBytes, &functionEntryStackMap)) {
+      return false;
+    }
+    // In debug builds, we'll always have a stack map, even if there are no
+    // refs to track.
+    MOZ_ALWAYS_TRUE(functionEntryStackMap);
+    if (functionEntryStackMap &&
+        !stackMaps->add((uint8_t*)(uintptr_t)trapInsnOffset.offset(),
+                        functionEntryStackMap)) {
+      functionEntryStackMap->destroy();
+      return false;
+    }
   }
 
   MOZ_ASSERT(masm.framePushed() == frameSize());
 
   if (!generateBody()) {
     return false;
   }
 
@@ -10247,21 +10586,40 @@ bool CodeGenerator::generateWasm(wasm::F
   offsets->end = masm.currentOffset();
 
   MOZ_ASSERT(!masm.failureLabel()->used());
   MOZ_ASSERT(snapshots_.listSize() == 0);
   MOZ_ASSERT(snapshots_.RVATableSize() == 0);
   MOZ_ASSERT(recovers_.size() == 0);
   MOZ_ASSERT(bailouts_.empty());
   MOZ_ASSERT(graph.numConstants() == 0);
-  MOZ_ASSERT(safepointIndices_.empty());
   MOZ_ASSERT(osiIndices_.empty());
   MOZ_ASSERT(icList_.empty());
   MOZ_ASSERT(safepoints_.size() == 0);
   MOZ_ASSERT(!scriptCounts_);
+
+  // Convert the safepoints to stackmaps and add them to our running
+  // collection thereof.
+  for (SafepointIndex& index : safepointIndices_) {
+    wasm::StackMap* stackMap = nullptr;
+    if (!CreateStackMapFromLSafepoint(*index.safepoint(), trapExitLayout,
+            trapExitLayoutNumWords, nInboundStackArgBytes, &stackMap)) {
+      return false;
+    }
+    // In debug builds, we'll always have a stack map.
+    MOZ_ALWAYS_TRUE(stackMap);
+    if (!stackMap) {
+      continue;
+    }
+    if (!stackMaps->add((uint8_t*)(uintptr_t)index.displacement(), stackMap)) {
+      stackMap->destroy();
+      return false;
+    }
+  }
+
   return true;
 }
 
 bool CodeGenerator::generate() {
   JitSpew(JitSpew_Codegen, "# Emitting code for script %s:%u:%u",
           gen->info().script()->filename(), gen->info().script()->lineno(),
           gen->info().script()->column());
 
@@ -12994,16 +13352,24 @@ void CodeGenerator::visitInterruptCheck(
   masm.bind(ool->rejoin());
 }
 
 void CodeGenerator::visitWasmInterruptCheck(LWasmInterruptCheck* lir) {
   MOZ_ASSERT(gen->compilingWasm());
 
   masm.wasmInterruptCheck(ToRegister(lir->tlsPtr()),
                           lir->mir()->bytecodeOffset());
+
+  markSafepointAt(masm.currentOffset(), lir);
+
+  // Note that masm.framePushed() doesn't include the register dump area.
+  // That will be taken into account when the StackMap is created from the
+  // LSafepoint.
+  lir->safepoint()->setFramePushedAtStackMapBase(masm.framePushed());
+  lir->safepoint()->setIsWasmTrap();
 }
 
 void CodeGenerator::visitWasmTrap(LWasmTrap* lir) {
   MOZ_ASSERT(gen->compilingWasm());
   const MWasmTrap* mir = lir->mir();
 
   masm.wasmTrap(mir->trap(), mir->bytecodeOffset());
 }
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -25,16 +25,18 @@
 #elif defined(JS_CODEGEN_MIPS64)
 #  include "jit/mips64/CodeGenerator-mips64.h"
 #elif defined(JS_CODEGEN_NONE)
 #  include "jit/none/CodeGenerator-none.h"
 #else
 #  error "Unknown architecture!"
 #endif
 
+#include "wasm/WasmGC.h"
+
 namespace js {
 namespace jit {
 
 enum class SwitchTableType { Inline, OutOfLine };
 
 template <SwitchTableType tableType>
 class OutOfLineSwitch;
 class OutOfLineTestObject;
@@ -68,17 +70,21 @@ class CodeGenerator final : public CodeG
  public:
   CodeGenerator(MIRGenerator* gen, LIRGraph* graph,
                 MacroAssembler* masm = nullptr);
   ~CodeGenerator();
 
   MOZ_MUST_USE bool generate();
   MOZ_MUST_USE bool generateWasm(wasm::FuncTypeIdDesc funcTypeId,
                                  wasm::BytecodeOffset trapOffset,
-                                 wasm::FuncOffsets* offsets);
+                                 const wasm::ValTypeVector& argTys,
+                                 const MachineState& trapExitLayout,
+                                 size_t trapExitLayoutNumWords,
+                                 wasm::FuncOffsets* offsets,
+                                 wasm::StackMaps* stackMaps);
 
   MOZ_MUST_USE bool link(JSContext* cx, CompilerConstraintList* constraints);
 
   void emitOOLTestObject(Register objreg, Label* ifTruthy, Label* ifFalsy,
                          Register scratch);
   void emitIntToString(Register input, Register output, Label* ool);
 
   void visitOutOfLineRegExpMatcher(OutOfLineRegExpMatcher* ool);
@@ -230,17 +236,18 @@ class CodeGenerator final : public CodeG
                                  Register temp1, Register temp2);
 
   template <class IteratorObject, class OrderedHashTable>
   void emitGetNextEntryForIterator(LGetNextEntryForIterator* lir);
 
   template <class OrderedHashTable>
   void emitLoadIteratorValues(Register result, Register temp, Register front);
 
-  void emitWasmCallBase(MWasmCall* mir, bool needsBoundsCheck);
+  template <size_t Defs>
+  void emitWasmCallBase(LWasmCallBase<Defs>* lir);
 
   template <size_t NumDefs>
   void emitIonToWasmCallBase(LIonToWasmCallBase<NumDefs>* lir);
 
   IonScriptCounts* maybeCreateScriptCounts();
 
   // This function behaves like testValueTruthy with the exception that it can
   // choose to let control flow fall through when the object is truthy, as
--- a/js/src/jit/LIR.cpp
+++ b/js/src/jit/LIR.cpp
@@ -161,16 +161,19 @@ LMoveGroup* LBlock::getExitMoveGroup(Tem
 void LBlock::dump(GenericPrinter& out) {
   out.printf("block%u:\n", mir()->id());
   for (size_t i = 0; i < numPhis(); ++i) {
     getPhi(i)->dump(out);
     out.printf("\n");
   }
   for (LInstructionIterator iter = begin(); iter != end(); iter++) {
     iter->dump(out);
+    if (iter->safepoint()) {
+      out.printf(" SAFEPOINT(0x%p) ", iter->safepoint());
+    }
     out.printf("\n");
   }
 }
 
 void LBlock::dump() {
   Fprinter out(stderr);
   dump(out);
   out.finish();
--- a/js/src/jit/LIR.h
+++ b/js/src/jit/LIR.h
@@ -1347,16 +1347,35 @@ class LSafepoint : public TempObject {
 #endif
 
   // The subset of liveRegs which contains pointers to slots/elements.
   LiveGeneralRegisterSet slotsOrElementsRegs_;
 
   // List of slots which have slots/elements pointers.
   SlotList slotsOrElementsSlots_;
 
+  // Wasm only: with what kind of instruction is this LSafepoint associated?
+  // true => wasm trap, false => wasm call.
+  bool isWasmTrap_;
+
+  // Wasm only: what is the value of masm.framePushed() that corresponds to
+  // the lowest-addressed word covered by the StackMap that we will generate
+  // from this LSafepoint?  This depends on the instruction:
+  //
+  // if isWasmTrap_ == true:
+  //    masm.framePushed() unmodified.  Note that when constructing the
+  //    StackMap we will add entries below this point to take account of
+  //    registers dumped on the stack as a result of the trap.
+  //
+  // if isWasmTrap_ == false:
+  //    masm.framePushed() - StackArgAreaSizeUnaligned(arg types for the call),
+  //    because the map does not include the outgoing args themselves, but
+  //    it does cover any and all alignment space above them.
+  uint32_t framePushedAtStackMapBase_;
+
  public:
   void assertInvariants() {
     // Every register in valueRegs and gcRegs should also be in liveRegs.
 #ifndef JS_NUNBOX32
     MOZ_ASSERT((valueRegs().bits() & ~liveRegs().gprs().bits()) == 0);
 #endif
     MOZ_ASSERT((gcRegs().bits() & ~liveRegs().gprs().bits()) == 0);
   }
@@ -1366,17 +1385,19 @@ class LSafepoint : public TempObject {
         osiCallPointOffset_(0),
         gcSlots_(alloc),
         valueSlots_(alloc)
 #ifdef JS_NUNBOX32
         ,
         nunboxParts_(alloc)
 #endif
         ,
-        slotsOrElementsSlots_(alloc) {
+        slotsOrElementsSlots_(alloc),
+        isWasmTrap_(false),
+        framePushedAtStackMapBase_(0) {
     assertInvariants();
   }
   void addLiveRegister(AnyRegister reg) {
     liveRegs_.addUnchecked(reg);
     assertInvariants();
   }
   const LiveRegisterSet& liveRegs() const { return liveRegs_; }
 #ifdef CHECK_OSIPOINT_REGISTERS
@@ -1609,16 +1630,27 @@ class LSafepoint : public TempObject {
     // would be wrong.
     return osiCallPointOffset_ + Assembler::PatchWrite_NearCallSize();
   }
   uint32_t osiCallPointOffset() const { return osiCallPointOffset_; }
   void setOsiCallPointOffset(uint32_t osiCallPointOffset) {
     MOZ_ASSERT(!osiCallPointOffset_);
     osiCallPointOffset_ = osiCallPointOffset;
   }
+
+  bool isWasmTrap() const { return isWasmTrap_; }
+  void setIsWasmTrap() { isWasmTrap_ = true; }
+
+  uint32_t framePushedAtStackMapBase() const {
+    return framePushedAtStackMapBase_;
+  }
+  void setFramePushedAtStackMapBase(uint32_t n) {
+    MOZ_ASSERT(framePushedAtStackMapBase_ == 0);
+    framePushedAtStackMapBase_ = n;
+  }
 };
 
 class LInstruction::InputIterator {
  private:
   LInstruction& ins_;
   size_t idx_;
   bool snapshot_;
 
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2550,16 +2550,18 @@ void LIRGenerator::visitInterruptCheck(M
   add(lir, ins);
   assignSafepoint(lir, ins);
 }
 
 void LIRGenerator::visitWasmInterruptCheck(MWasmInterruptCheck* ins) {
   auto* lir =
       new (alloc()) LWasmInterruptCheck(useRegisterAtStart(ins->tlsPtr()));
   add(lir, ins);
+
+  assignWasmSafepoint(lir, ins);
 }
 
 void LIRGenerator::visitWasmTrap(MWasmTrap* ins) {
   add(new (alloc()) LWasmTrap, ins);
 }
 
 void LIRGenerator::visitWasmReinterpret(MWasmReinterpret* ins) {
   if (ins->type() == MIRType::Int64) {
@@ -4508,16 +4510,18 @@ void LIRGenerator::visitWasmCall(MWasmCa
     return;
   }
 
   if (ins->type() == MIRType::None) {
     add(lir, ins);
   } else {
     defineReturn(lir, ins);
   }
+
+  assignWasmSafepoint(lir, ins);
 }
 
 void LIRGenerator::visitSetDOMProperty(MSetDOMProperty* ins) {
   MDefinition* val = ins->value();
 
   Register cxReg, objReg, privReg, valueReg;
   GetTempRegForIntArg(0, 0, &cxReg);
   GetTempRegForIntArg(1, 0, &objReg);
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -603,24 +603,26 @@ void MTest::filtersUndefinedOrNull(bool 
 
 #ifdef JS_JITSPEW
 void MDefinition::printOpcode(GenericPrinter& out) const {
   PrintOpcodeName(out, op());
   for (size_t j = 0, e = numOperands(); j < e; j++) {
     out.printf(" ");
     if (getUseFor(j)->hasProducer()) {
       getOperand(j)->printName(out);
+      out.printf(":%s", StringFromMIRType(getOperand(j)->type()));
     } else {
       out.printf("(null)");
     }
   }
 }
 
 void MDefinition::dump(GenericPrinter& out) const {
   printName(out);
+  out.printf(":%s", StringFromMIRType(type()));
   out.printf(" = ");
   printOpcode(out);
   out.printf("\n");
 
   if (isInstruction()) {
     if (MResumePoint* resume = toInstruction()->resumePoint()) {
       resume->dump(out);
     }
@@ -1241,17 +1243,17 @@ HashNumber MWasmFloatConstant::valueHash
 }
 
 bool MWasmFloatConstant::congruentTo(const MDefinition* ins) const {
   return ins->isWasmFloatConstant() && type() == ins->type() &&
          u.bits_ == ins->toWasmFloatConstant()->u.bits_;
 }
 
 HashNumber MWasmNullConstant::valueHash() const {
-  return ConstantValueHash(MIRType::Pointer, 0);
+  return ConstantValueHash(MIRType::RefOrNull, 0);
 }
 
 #ifdef JS_JITSPEW
 void MControlInstruction::printOpcode(GenericPrinter& out) const {
   MDefinition::printOpcode(out);
   for (size_t j = 0; j < numSuccessors(); j++) {
     if (getSuccessor(j)) {
       out.printf(" block%u", getSuccessor(j)->id());
@@ -5487,18 +5489,21 @@ MDefinition* MWasmUnsignedToFloat32::fol
     }
   }
 
   return this;
 }
 
 MWasmCall* MWasmCall::New(TempAllocator& alloc, const wasm::CallSiteDesc& desc,
                           const wasm::CalleeDesc& callee, const Args& args,
-                          MIRType resultType, MDefinition* tableIndex) {
-  MWasmCall* call = new (alloc) MWasmCall(desc, callee);
+                          MIRType resultType,
+                          uint32_t stackArgAreaSizeUnaligned,
+                          MDefinition* tableIndex) {
+  MWasmCall* call = new (alloc) MWasmCall(desc, callee,
+                                          stackArgAreaSizeUnaligned);
   call->setResultType(resultType);
 
   if (!call->argRegs_.init(alloc, args.length())) {
     return nullptr;
   }
   for (size_t i = 0; i < call->argRegs_.length(); i++) {
     call->argRegs_[i] = args[i].reg;
   }
@@ -5516,20 +5521,21 @@ MWasmCall* MWasmCall::New(TempAllocator&
   }
 
   return call;
 }
 
 MWasmCall* MWasmCall::NewBuiltinInstanceMethodCall(
     TempAllocator& alloc, const wasm::CallSiteDesc& desc,
     const wasm::SymbolicAddress builtin, const ABIArg& instanceArg,
-    const Args& args, MIRType resultType) {
+    const Args& args, MIRType resultType, uint32_t stackArgAreaSizeUnaligned) {
   auto callee = wasm::CalleeDesc::builtinInstanceMethod(builtin);
   MWasmCall* call =
-      MWasmCall::New(alloc, desc, callee, args, resultType, nullptr);
+      MWasmCall::New(alloc, desc, callee, args, resultType,
+                     stackArgAreaSizeUnaligned, nullptr);
   if (!call) {
     return nullptr;
   }
 
   MOZ_ASSERT(instanceArg != ABIArg());
   call->instanceArg_ = instanceArg;
   return call;
 }
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -12023,47 +12023,54 @@ class MWasmStackArg : public MUnaryInstr
   uint32_t spOffset() const { return spOffset_; }
   void incrementOffset(uint32_t inc) { spOffset_ += inc; }
 };
 
 class MWasmCall final : public MVariadicInstruction, public NoTypePolicy::Data {
   wasm::CallSiteDesc desc_;
   wasm::CalleeDesc callee_;
   FixedList<AnyRegister> argRegs_;
+  uint32_t stackArgAreaSizeUnaligned_;
   ABIArg instanceArg_;
 
-  MWasmCall(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee)
-      : MVariadicInstruction(classOpcode), desc_(desc), callee_(callee) {}
+  MWasmCall(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee,
+            uint32_t stackArgAreaSizeUnaligned)
+      : MVariadicInstruction(classOpcode), desc_(desc), callee_(callee),
+        stackArgAreaSizeUnaligned_(stackArgAreaSizeUnaligned) {}
 
  public:
   INSTRUCTION_HEADER(WasmCall)
 
   struct Arg {
     AnyRegister reg;
     MDefinition* def;
     Arg(AnyRegister reg, MDefinition* def) : reg(reg), def(def) {}
   };
   typedef Vector<Arg, 8, SystemAllocPolicy> Args;
 
   static MWasmCall* New(TempAllocator& alloc, const wasm::CallSiteDesc& desc,
                         const wasm::CalleeDesc& callee, const Args& args,
-                        MIRType resultType, MDefinition* tableIndex = nullptr);
+                        MIRType resultType, uint32_t stackArgAreaSizeUnaligned,
+                        MDefinition* tableIndex = nullptr);
 
   static MWasmCall* NewBuiltinInstanceMethodCall(
       TempAllocator& alloc, const wasm::CallSiteDesc& desc,
       const wasm::SymbolicAddress builtin, const ABIArg& instanceArg,
-      const Args& args, MIRType resultType);
+      const Args& args, MIRType resultType, uint32_t stackArgAreaSizeUnaligned);
 
   size_t numArgs() const { return argRegs_.length(); }
   AnyRegister registerForArg(size_t index) const {
     MOZ_ASSERT(index < numArgs());
     return argRegs_[index];
   }
   const wasm::CallSiteDesc& desc() const { return desc_; }
   const wasm::CalleeDesc& callee() const { return callee_; }
+  uint32_t stackArgAreaSizeUnaligned() const {
+    return stackArgAreaSizeUnaligned_;
+  }
 
   bool possiblyCalls() const override { return true; }
 
   const ABIArg& instanceArg() const { return instanceArg_; }
 };
 
 class MWasmSelect : public MTernaryInstruction, public NoTypePolicy::Data {
   MWasmSelect(MDefinition* trueExpr, MDefinition* falseExpr,
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -3422,41 +3422,43 @@ void MacroAssembler::wasmInterruptCheck(
                                         wasm::BytecodeOffset bytecodeOffset) {
   Label ok;
   branch32(Assembler::Equal, Address(tls, offsetof(wasm::TlsData, interrupt)),
            Imm32(0), &ok);
   wasmTrap(wasm::Trap::CheckInterrupt, bytecodeOffset);
   bind(&ok);
 }
 
-void MacroAssembler::wasmReserveStackChecked(uint32_t amount,
-                                             wasm::BytecodeOffset trapOffset) {
-  // If the frame is large, don't bump sp until after the stack limit check so
-  // that the trap handler isn't called with a wild sp.
-
+std::pair<CodeOffset, uint32_t>
+MacroAssembler::wasmReserveStackChecked(uint32_t amount,
+                                        wasm::BytecodeOffset trapOffset) {
   if (amount > MAX_UNCHECKED_LEAF_FRAME_SIZE) {
+    // The frame is large.  Don't bump sp until after the stack limit check so
+    // that the trap handler isn't called with a wild sp.
     Label ok;
     Register scratch = ABINonArgReg0;
     moveStackPtrTo(scratch);
     subPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, stackLimit)), scratch);
     branchPtr(Assembler::GreaterThan, scratch, Imm32(amount), &ok);
     wasmTrap(wasm::Trap::StackOverflow, trapOffset);
+    CodeOffset trapInsnOffset = CodeOffset(currentOffset());
     bind(&ok);
+    reserveStack(amount);
+    return std::pair<CodeOffset, uint32_t>(trapInsnOffset, 0);
   }
 
   reserveStack(amount);
-
-  if (amount <= MAX_UNCHECKED_LEAF_FRAME_SIZE) {
-    Label ok;
-    branchStackPtrRhs(Assembler::Below,
-                      Address(WasmTlsReg, offsetof(wasm::TlsData, stackLimit)),
-                      &ok);
-    wasmTrap(wasm::Trap::StackOverflow, trapOffset);
-    bind(&ok);
-  }
+  Label ok;
+  branchStackPtrRhs(Assembler::Below,
+                    Address(WasmTlsReg, offsetof(wasm::TlsData, stackLimit)),
+                    &ok);
+  wasmTrap(wasm::Trap::StackOverflow, trapOffset);
+  CodeOffset trapInsnOffset = CodeOffset(currentOffset());
+  bind(&ok);
+  return std::pair<CodeOffset,uint32_t>(trapInsnOffset, amount);
 }
 
 CodeOffset MacroAssembler::wasmCallImport(const wasm::CallSiteDesc& desc,
                                           const wasm::CalleeDesc& callee) {
   // Load the callee, before the caller's registers are clobbered.
   uint32_t globalDataOffset = callee.importGlobalDataOffset();
   loadWasmGlobalPtr(globalDataOffset + offsetof(wasm::FuncImportTls, code),
                     ABINonArgReg0);
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1753,18 +1753,22 @@ class MacroAssembler : public MacroAssem
  public:
   // ========================================================================
   // wasm support
 
   CodeOffset wasmTrapInstruction() PER_SHARED_ARCH;
 
   void wasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset);
   void wasmInterruptCheck(Register tls, wasm::BytecodeOffset bytecodeOffset);
-  void wasmReserveStackChecked(uint32_t amount,
-                               wasm::BytecodeOffset trapOffset);
+
+  // Returns a pair: the offset of the undefined (trapping) instruction, and
+  // the number of extra bytes of stack allocated prior to the trap
+  // instruction proper.
+  std::pair<CodeOffset, uint32_t>
+  wasmReserveStackChecked(uint32_t amount, wasm::BytecodeOffset trapOffset);
 
   // Emit a bounds check against the wasm heap limit, jumping to 'label' if
   // 'cond' holds. If JitOptions.spectreMaskIndex is true, in speculative
   // executions 'index' is saturated in-place to 'boundsCheckLimit'.
   void wasmBoundsCheck(Condition cond, Register index,
                        Register boundsCheckLimit, Label* label)
       DEFINED_ON(arm, arm64, mips32, mips64, x86_shared);
 
--- a/js/src/jit/shared/Lowering-shared.cpp
+++ b/js/src/jit/shared/Lowering-shared.cpp
@@ -282,8 +282,21 @@ void LIRGeneratorShared::assignSafepoint
 
   osiPoint_ = new (alloc()) LOsiPoint(ins->safepoint(), postSnapshot);
 
   if (!lirGraph_.noteNeedsSafepoint(ins)) {
     abort(AbortReason::Alloc, "noteNeedsSafepoint failed");
     return;
   }
 }
+
+void LIRGeneratorShared::assignWasmSafepoint(LInstruction* ins,
+                                             MInstruction* mir) {
+  MOZ_ASSERT(!osiPoint_);
+  MOZ_ASSERT(!ins->safepoint());
+
+  ins->initSafepoint(alloc());
+
+  if (!lirGraph_.noteNeedsSafepoint(ins)) {
+    abort(AbortReason::Alloc, "noteNeedsSafepoint failed");
+    return;
+  }
+}
--- a/js/src/jit/shared/Lowering-shared.h
+++ b/js/src/jit/shared/Lowering-shared.h
@@ -318,16 +318,19 @@ class LIRGeneratorShared {
   void assignSnapshot(LInstruction* ins, BailoutKind kind);
 
   // Marks this instruction as needing to call into either the VM or GC. This
   // function may build a snapshot that captures the result of its own
   // instruction, and as such, should generally be called after define*().
   void assignSafepoint(LInstruction* ins, MInstruction* mir,
                        BailoutKind kind = Bailout_DuringVMCall);
 
+  // Marks this instruction as needing a wasm safepoint.
+  void assignWasmSafepoint(LInstruction* ins, MInstruction* mir);
+
   void lowerConstantDouble(double d, MInstruction* mir) {
     define(new (alloc()) LDouble(d), mir);
   }
   void lowerConstantFloat32(float f, MInstruction* mir) {
     define(new (alloc()) LFloat32(f), mir);
   }
 
  public:
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -2150,49 +2150,19 @@ struct StackMapGenerator {
   // stub, as generated by GenerateTrapExit().
   //
   // The resulting map must correspond precisely with the stack layout
   // created for the integer registers as saved by (code generated by)
   // GenerateTrapExit().  To do that we use trapExitLayout_ and
   // trapExitLayoutNumWords_, which together comprise a description of the
   // layout and are created by GenerateTrapExitMachineState().
   MOZ_MUST_USE bool generateStackmapEntriesForTrapExit(
-      const ValTypeVector& args, ExitStubMapVector& extras) {
-    MOZ_ASSERT(extras.empty());
-
-    // If this doesn't hold, we can't distinguish saved and not-saved
-    // registers in the MachineState.  See MachineState::MachineState().
-    MOZ_ASSERT(trapExitLayoutNumWords_ < 0x100);
-
-    if (!extras.appendN(false, trapExitLayoutNumWords_)) {
-      return false;
-    }
-
-    for (ABIArgIter<const ValTypeVector> i(args); !i.done(); i++) {
-      if (!i->argInRegister() || i.mirType() != MIRType::RefOrNull) {
-        continue;
-      }
-
-      size_t offsetFromTop =
-          reinterpret_cast<size_t>(trapExitLayout_.address(i->gpr()));
-
-      // If this doesn't hold, the associated register wasn't saved by
-      // the trap exit stub.  Better to crash now than much later, in
-      // some obscure place, and possibly with security consequences.
-      MOZ_RELEASE_ASSERT(offsetFromTop < trapExitLayoutNumWords_);
-
-      // offsetFromTop is an offset in words down from the highest
-      // address in the exit stub save area.  Switch it around to be an
-      // offset up from the bottom of the (integer register) save area.
-      size_t offsetFromBottom = trapExitLayoutNumWords_ - 1 - offsetFromTop;
-
-      extras[offsetFromBottom] = true;
-    }
-
-    return true;
+      const ValTypeVector& args, ExitStubMapVector* extras) {
+    return GenerateStackmapEntriesForTrapExit(args, trapExitLayout_,
+                                              trapExitLayoutNumWords_, extras);
   }
 
   // Creates a stackmap associated with the instruction denoted by
   // |assemblerOffset|, incorporating pointers from the current operand
   // stack |stk|, incorporating possible extra pointers in |extra| at the
   // lower addressed end, and possibly with the associated frame having a
   // ref-typed DebugFrame as indicated by |refDebugFrame|.
   MOZ_MUST_USE bool createStackMap(const char* who,
@@ -4140,17 +4110,17 @@ class BaseCompiler final : public BaseCo
     }
 
     // Generate a stack-overflow check and its associated stack map.
 
     fr.checkStack(ABINonArgReg0, BytecodeOffset(func_.lineOrBytecode));
 
     const ValTypeVector& args = funcType().args();
     ExitStubMapVector extras;
-    if (!stackMapGenerator_.generateStackmapEntriesForTrapExit(args, extras)) {
+    if (!stackMapGenerator_.generateStackmapEntriesForTrapExit(args, &extras)) {
       return false;
     }
     if (!createStackMap("stack check", extras, masm.currentOffset(),
                         HasRefTypedDebugFrame::No)) {
       return false;
     }
 
     size_t reservedBytes = fr.fixedAllocSize() - masm.framePushed();
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -18,16 +18,17 @@
 
 #ifndef wasm_code_h
 #define wasm_code_h
 
 #include "jit/shared/Assembler-shared.h"
 #include "js/HashTable.h"
 #include "threading/ExclusiveData.h"
 #include "vm/MutexIDs.h"
+#include "wasm/WasmGC.h"
 #include "wasm/WasmTypes.h"
 
 namespace js {
 
 struct AsmJSMetadata;
 
 namespace wasm {
 
--- a/js/src/wasm/WasmGC.cpp
+++ b/js/src/wasm/WasmGC.cpp
@@ -18,16 +18,54 @@
 
 #include "wasm/WasmGC.h"
 #include "wasm/WasmInstance.h"
 #include "jit/MacroAssembler-inl.h"
 
 namespace js {
 namespace wasm {
 
+bool GenerateStackmapEntriesForTrapExit(
+    const ValTypeVector& args, const MachineState& trapExitLayout,
+    const size_t trapExitLayoutNumWords,  ExitStubMapVector* extras) {
+  MOZ_ASSERT(extras->empty());
+
+  // If this doesn't hold, we can't distinguish saved and not-saved
+  // registers in the MachineState.  See MachineState::MachineState().
+  MOZ_ASSERT(trapExitLayoutNumWords < 0x100);
+
+  if (!extras->appendN(false, trapExitLayoutNumWords)) {
+    return false;
+  }
+
+  for (ABIArgIter<const ValTypeVector> i(args); !i.done(); i++) {
+    MOZ_ASSERT(i.mirType() != MIRType::Pointer);
+    if (!i->argInRegister() || i.mirType() != MIRType::RefOrNull) {
+      continue;
+    }
+
+    size_t offsetFromTop =
+       reinterpret_cast<size_t>(trapExitLayout.address(i->gpr()));
+
+    // If this doesn't hold, the associated register wasn't saved by
+    // the trap exit stub.  Better to crash now than much later, in
+    // some obscure place, and possibly with security consequences.
+    MOZ_RELEASE_ASSERT(offsetFromTop < trapExitLayoutNumWords);
+
+    // offsetFromTop is an offset in words down from the highest
+    // address in the exit stub save area.  Switch it around to be an
+    // offset up from the bottom of the (integer register) save area.
+    size_t offsetFromBottom = trapExitLayoutNumWords - 1 - offsetFromTop;
+
+    (*extras)[offsetFromBottom] = true;
+  }
+
+  return true;
+}
+
 void EmitWasmPreBarrierGuard(MacroAssembler& masm, Register tls,
                              Register scratch, Register valueAddr,
                              Label* skipBarrier) {
   // If no incremental GC has started, we don't need the barrier.
   masm.loadPtr(
     Address(tls, offsetof(TlsData, addressOfNeedsIncrementalBarrier)),
     scratch);
   masm.branchTest32(Assembler::Zero, Address(scratch, 0), Imm32(0x1),
--- a/js/src/wasm/WasmGC.h
+++ b/js/src/wasm/WasmGC.h
@@ -21,16 +21,226 @@
 
 #include "jit/MacroAssembler.h"
 
 namespace js {
 namespace wasm {
 
 using namespace js::jit;
 
+// Definitions for stack maps.
+
+typedef Vector<bool, 32, SystemAllocPolicy> ExitStubMapVector;
+
+struct StackMap final {
+  // A StackMap is a bit-array containing numMappedWords bits, one bit per
+  // word of stack.  Bit index zero is for the lowest addressed word in the
+  // range.
+  //
+  // This is a variable-length structure whose size must be known at creation
+  // time.
+  //
+  // Users of the map will know the address of the wasm::Frame that is covered
+  // by this map.  In order that they can calculate the exact address range
+  // covered by the map, the map also stores the offset, from the highest
+  // addressed word of the map, of the embedded wasm::Frame.  This is an
+  // offset down from the highest address, rather than up from the lowest, so
+  // as to limit its range to 11 bits, where
+  // 11 == ceil(log2(MaxParams * sizeof-biggest-param-type-in-words))
+  //
+  // The map may also cover a ref-typed DebugFrame.  If so that can be noted,
+  // since users of the map need to trace pointers in such a DebugFrame.
+  //
+  // Finally, for sanity checking only, for stack maps associated with a wasm
+  // trap exit stub, the number of words used by the trap exit stub save area
+  // is also noted.  This is used in Instance::traceFrame to check that the
+  // TrapExitDummyValue is in the expected place in the frame.
+
+  // The total number of stack words covered by the map ..
+  uint32_t numMappedWords : 30;
+
+  // .. of which this many are "exit stub" extras
+  uint32_t numExitStubWords : 6;
+
+  // Where is Frame* relative to the top?  This is an offset in words.
+  uint32_t frameOffsetFromTop : 11;
+
+  // Notes the presence of a ref-typed DebugFrame.
+  uint32_t hasRefTypedDebugFrame : 1;
+
+ private:
+  static constexpr uint32_t maxMappedWords = (1 << 30) - 1;
+  static constexpr uint32_t maxExitStubWords = (1 << 6) - 1;
+  static constexpr uint32_t maxFrameOffsetFromTop = (1 << 11) - 1;
+
+  uint32_t bitmap[1];
+
+  explicit StackMap(uint32_t numMappedWords)
+      : numMappedWords(numMappedWords),
+        numExitStubWords(0),
+        frameOffsetFromTop(0),
+        hasRefTypedDebugFrame(0) {
+    const uint32_t nBitmap = calcNBitmap(numMappedWords);
+    memset(bitmap, 0, nBitmap * sizeof(bitmap[0]));
+  }
+
+ public:
+  static StackMap* create(uint32_t numMappedWords) {
+    uint32_t nBitmap = calcNBitmap(numMappedWords);
+    char* buf =
+        (char*)js_malloc(sizeof(StackMap) + (nBitmap - 1) * sizeof(bitmap[0]));
+    if (!buf) {
+      return nullptr;
+    }
+    return ::new (buf) StackMap(numMappedWords);
+  }
+
+  void destroy() { js_free((char*)this); }
+
+  // Record the number of words in the map used as a wasm trap exit stub
+  // save area.  See comment above.
+  void setExitStubWords(uint32_t nWords) {
+    MOZ_ASSERT(numExitStubWords == 0);
+    MOZ_RELEASE_ASSERT(nWords <= maxExitStubWords);
+    MOZ_ASSERT(nWords <= numMappedWords);
+    numExitStubWords = nWords;
+  }
+
+  // Record the offset from the highest-addressed word of the map, that the
+  // wasm::Frame lives at.  See comment above.
+  void setFrameOffsetFromTop(uint32_t nWords) {
+    MOZ_ASSERT(frameOffsetFromTop == 0);
+    MOZ_RELEASE_ASSERT(nWords <= maxFrameOffsetFromTop);
+    MOZ_ASSERT(frameOffsetFromTop < numMappedWords);
+    frameOffsetFromTop = nWords;
+  }
+
+  // If the frame described by this StackMap includes a DebugFrame for a
+  // ref-typed return value, call here to record that fact.
+  void setHasRefTypedDebugFrame() {
+    MOZ_ASSERT(hasRefTypedDebugFrame == 0);
+    hasRefTypedDebugFrame = 1;
+  }
+
+  inline void setBit(uint32_t bitIndex) {
+    MOZ_ASSERT(bitIndex < numMappedWords);
+    uint32_t wordIndex = bitIndex / wordsPerBitmapElem;
+    uint32_t wordOffset = bitIndex % wordsPerBitmapElem;
+    bitmap[wordIndex] |= (1 << wordOffset);
+  }
+
+  inline uint32_t getBit(uint32_t bitIndex) const {
+    MOZ_ASSERT(bitIndex < numMappedWords);
+    uint32_t wordIndex = bitIndex / wordsPerBitmapElem;
+    uint32_t wordOffset = bitIndex % wordsPerBitmapElem;
+    return (bitmap[wordIndex] >> wordOffset) & 1;
+  }
+
+ private:
+  static constexpr uint32_t wordsPerBitmapElem = sizeof(bitmap[0]) * 8;
+
+  static uint32_t calcNBitmap(uint32_t numMappedWords) {
+    MOZ_RELEASE_ASSERT(numMappedWords <= maxMappedWords);
+    uint32_t nBitmap =
+        (numMappedWords + wordsPerBitmapElem - 1) / wordsPerBitmapElem;
+    return nBitmap == 0 ? 1 : nBitmap;
+  }
+};
+
+// This is the expected size for a map that covers 32 or fewer words.
+static_assert(sizeof(StackMap) == 12, "wasm::StackMap has unexpected size");
+
+class StackMaps {
+ public:
+  // A Maplet holds a single code-address-to-map binding.  Note that the
+  // code address is the lowest address of the instruction immediately
+  // following the instruction of interest, not of the instruction of
+  // interest itself.  In practice (at least for the Wasm Baseline compiler)
+  // this means that |nextInsnAddr| points either immediately after a call
+  // instruction, after a trap instruction or after a no-op.
+  struct Maplet {
+    uint8_t* nextInsnAddr;
+    StackMap* map;
+    Maplet(uint8_t* nextInsnAddr, StackMap* map)
+        : nextInsnAddr(nextInsnAddr), map(map) {}
+    void offsetBy(uintptr_t delta) { nextInsnAddr += delta; }
+    bool operator<(const Maplet& other) const {
+      return uintptr_t(nextInsnAddr) < uintptr_t(other.nextInsnAddr);
+    }
+  };
+
+ private:
+  bool sorted_;
+  Vector<Maplet, 0, SystemAllocPolicy> mapping_;
+
+ public:
+  StackMaps() : sorted_(false) {}
+  ~StackMaps() {
+    for (size_t i = 0; i < mapping_.length(); i++) {
+      mapping_[i].map->destroy();
+      mapping_[i].map = nullptr;
+    }
+  }
+  MOZ_MUST_USE bool add(uint8_t* nextInsnAddr, StackMap* map) {
+    MOZ_ASSERT(!sorted_);
+    return mapping_.append(Maplet(nextInsnAddr, map));
+  }
+  MOZ_MUST_USE bool add(const Maplet& maplet) {
+    return add(maplet.nextInsnAddr, maplet.map);
+  }
+  void clear() {
+    for (size_t i = 0; i < mapping_.length(); i++) {
+      mapping_[i].nextInsnAddr = nullptr;
+      mapping_[i].map = nullptr;
+    }
+    mapping_.clear();
+  }
+  bool empty() const { return mapping_.empty(); }
+  size_t length() const { return mapping_.length(); }
+  Maplet get(size_t i) const { return mapping_[i]; }
+  Maplet move(size_t i) {
+    Maplet m = mapping_[i];
+    mapping_[i].map = nullptr;
+    return m;
+  }
+  void offsetBy(uintptr_t delta) {
+    for (size_t i = 0; i < mapping_.length(); i++) mapping_[i].offsetBy(delta);
+  }
+  void sort() {
+    MOZ_ASSERT(!sorted_);
+    std::sort(mapping_.begin(), mapping_.end());
+    sorted_ = true;
+  }
+  const StackMap* findMap(uint8_t* nextInsnAddr) const {
+    struct Comparator {
+      int operator()(Maplet aVal) const {
+        if (uintptr_t(mTarget) < uintptr_t(aVal.nextInsnAddr)) {
+          return -1;
+        }
+        if (uintptr_t(mTarget) > uintptr_t(aVal.nextInsnAddr)) {
+          return 1;
+        }
+        return 0;
+      }
+      explicit Comparator(uint8_t* aTarget) : mTarget(aTarget) {}
+      const uint8_t* mTarget;
+    };
+
+    size_t result;
+    if (BinarySearchIf(mapping_, 0, mapping_.length(), Comparator(nextInsnAddr),
+                       &result)) {
+      return mapping_[result].map;
+    }
+
+    return nullptr;
+  }
+};
+
+// Supporting code for creation of stackmaps.
+
 // StackArgAreaSizeUnaligned returns the size, in bytes, of the stack arg area
 // size needed to pass |argTypes|, excluding any alignment padding beyond the
 // size of the area as a whole.  The size is as determined by the platforms
 // native ABI.
 //
 // StackArgAreaSizeAligned returns the same, but rounded up to the nearest 16
 // byte boundary.
 //
@@ -82,16 +292,25 @@ static inline size_t AlignStackArgAreaSi
   return AlignBytes(unalignedSize, 16u);
 }
 
 template <class T>
 static inline size_t StackArgAreaSizeAligned(const T& argTypes) {
   return AlignStackArgAreaSize(StackArgAreaSizeUnaligned(argTypes));
 }
 
+// At a resumable wasm trap, the machine's registers are saved on the stack by
+// (code generated by) GenerateTrapExit().  This function writes into |args| a
+// vector of booleans describing the ref-ness of the saved integer registers.
+// |args[0]| corresponds to the low addressed end of the described section of
+// the save area.
+MOZ_MUST_USE bool GenerateStackmapEntriesForTrapExit(
+    const ValTypeVector& args, const MachineState& trapExitLayout,
+    const size_t trapExitLayoutNumWords,  ExitStubMapVector* extras);
+
 // Shared write barrier code.
 //
 // A barriered store looks like this:
 //
 //   Label skipPreBarrier;
 //   EmitWasmPreBarrierGuard(..., &skipPreBarrier);
 //   <COMPILER-SPECIFIC ACTIONS HERE>
 //   EmitWasmPreBarrierCall(...);
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -1393,16 +1393,22 @@ uintptr_t Instance::traceFrame(JSTracer*
   const uintptr_t scanStart = uintptr_t(frame) +
                               (map->frameOffsetFromTop * sizeof(void*)) -
                               numMappedBytes;
   MOZ_ASSERT(0 == scanStart % sizeof(void*));
 
   // Do what we can to assert that, for consecutive wasm frames, their stack
   // maps also abut exactly.  This is a useful sanity check on the sizing of
   // stack maps.
+  //
+  // In debug builds, the stackmap construction machinery goes to considerable
+  // efforts to ensure that the stackmaps for consecutive frames abut exactly.
+  // This is so as to ensure there are no areas of stack inadvertently ignored
+  // by a stackmap, nor covered by two stackmaps.  Hence any failure of this
+  // assertion is serious and should be investigated.
   MOZ_ASSERT_IF(highestByteVisitedInPrevFrame != 0,
                 highestByteVisitedInPrevFrame + 1 == scanStart);
 
   uintptr_t* stackWords = (uintptr_t*)scanStart;
 
   // If we have some exit stub words, this means the map also covers an area
   // created by a exit stub, and so the highest word of that should be a
   // constant created by (code created by) GenerateTrapExit.
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -19,19 +19,21 @@
 #include "wasm/WasmIonCompile.h"
 
 #include "mozilla/MathAlgorithms.h"
 
 #include "jit/CodeGenerator.h"
 
 #include "wasm/WasmBaselineCompile.h"
 #include "wasm/WasmBuiltins.h"
+#include "wasm/WasmGC.h"
 #include "wasm/WasmGenerator.h"
 #include "wasm/WasmOpIter.h"
 #include "wasm/WasmSignalHandlers.h"
+#include "wasm/WasmStubs.h"
 #include "wasm/WasmValidate.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::IsPowerOfTwo;
 using mozilla::Maybe;
@@ -1058,17 +1060,18 @@ class FunctionCompiler {
     if (inDeadCode()) {
       *def = nullptr;
       return true;
     }
 
     CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Func);
     MIRType ret = ToMIRType(funcType.ret());
     auto callee = CalleeDesc::function(funcIndex);
-    auto* ins = MWasmCall::New(alloc(), desc, callee, call.regArgs_, ret);
+    auto* ins = MWasmCall::New(alloc(), desc, callee, call.regArgs_, ret,
+                               StackArgAreaSizeUnaligned(funcType.args()));
     if (!ins) {
       return false;
     }
 
     curBlock_->add(ins);
     *def = ins;
     return true;
   }
@@ -1102,17 +1105,19 @@ class FunctionCompiler {
     } else {
       MOZ_ASSERT(funcType.id.kind() != FuncTypeIdDescKind::None);
       const TableDesc& table = env_.tables[tableIndex];
       callee = CalleeDesc::wasmTable(table, funcType.id);
     }
 
     CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Dynamic);
     auto* ins = MWasmCall::New(alloc(), desc, callee, call.regArgs_,
-                               ToMIRType(funcType.ret()), index);
+                               ToMIRType(funcType.ret()),
+                               StackArgAreaSizeUnaligned(funcType.args()),
+                               index);
     if (!ins) {
       return false;
     }
 
     curBlock_->add(ins);
     *def = ins;
     return true;
   }
@@ -1123,17 +1128,18 @@ class FunctionCompiler {
     if (inDeadCode()) {
       *def = nullptr;
       return true;
     }
 
     CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Dynamic);
     auto callee = CalleeDesc::import(globalDataOffset);
     auto* ins = MWasmCall::New(alloc(), desc, callee, call.regArgs_,
-                               ToMIRType(funcType.ret()));
+                               ToMIRType(funcType.ret()),
+                               StackArgAreaSizeUnaligned(funcType.args()));
     if (!ins) {
       return false;
     }
 
     curBlock_->add(ins);
     *def = ins;
     return true;
   }
@@ -1144,17 +1150,18 @@ class FunctionCompiler {
     if (inDeadCode()) {
       *def = nullptr;
       return true;
     }
 
     CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Symbolic);
     auto callee = CalleeDesc::builtin(builtin.identity);
     auto* ins =
-        MWasmCall::New(alloc(), desc, callee, call.regArgs_, builtin.retType);
+        MWasmCall::New(alloc(), desc, callee, call.regArgs_, builtin.retType,
+                       StackArgAreaSizeUnaligned(builtin));
     if (!ins) {
       return false;
     }
 
     curBlock_->add(ins);
     *def = ins;
     return true;
   }
@@ -1166,17 +1173,17 @@ class FunctionCompiler {
     if (inDeadCode()) {
       *def = nullptr;
       return true;
     }
 
     CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Symbolic);
     auto* ins = MWasmCall::NewBuiltinInstanceMethodCall(
         alloc(), desc, builtin.identity, call.instanceArg_, call.regArgs_,
-        builtin.retType);
+        builtin.retType, StackArgAreaSizeUnaligned(builtin));
     if (!ins) {
       return false;
     }
 
     curBlock_->add(ins);
     *def = ins;
     return true;
   }
@@ -4152,23 +4159,35 @@ bool wasm::IonCompileFunctions(const Mod
   WasmMacroAssembler masm(alloc);
 
   // Swap in already-allocated empty vectors to avoid malloc/free.
   MOZ_ASSERT(code->empty());
   if (!code->swap(masm)) {
     return false;
   }
 
+  // Create a description of the stack layout created by GenerateTrapExit().
+  MachineState trapExitLayout;
+  size_t trapExitLayoutNumWords;
+  GenerateTrapExitMachineState(&trapExitLayout, &trapExitLayoutNumWords);
+
   for (const FuncCompileInput& func : inputs) {
+    JitSpew(JitSpew_Codegen, "# ========================================");
+    JitSpew(JitSpew_Codegen, "# ==");
+    JitSpew(JitSpew_Codegen,
+            "# wasm::IonCompileFunctions: starting on function index %d",
+            (int)func.index);
+
     Decoder d(func.begin, func.end, func.lineOrBytecode, error);
 
     // Build the local types vector.
 
+    const ValTypeVector& argTys = env.funcTypes[func.index]->args();
     ValTypeVector locals;
-    if (!locals.appendAll(env.funcTypes[func.index]->args())) {
+    if (!locals.appendAll(argTys)) {
       return false;
     }
     if (!DecodeLocalEntries(d, env.types, env.gcTypesEnabled(), &locals)) {
       return false;
     }
 
     // Set up for Ion compilation.
 
@@ -4212,25 +4231,33 @@ bool wasm::IonCompileFunctions(const Mod
       }
 
       FuncTypeIdDesc funcTypeId = env.funcTypes[func.index]->id;
 
       CodeGenerator codegen(&mir, lir, &masm);
 
       BytecodeOffset prologueTrapOffset(func.lineOrBytecode);
       FuncOffsets offsets;
-      if (!codegen.generateWasm(funcTypeId, prologueTrapOffset, &offsets)) {
+      if (!codegen.generateWasm(funcTypeId, prologueTrapOffset, argTys,
+                                trapExitLayout, trapExitLayoutNumWords,
+                                &offsets, &code->stackMaps)) {
         return false;
       }
 
       if (!code->codeRanges.emplaceBack(func.index, func.lineOrBytecode,
                                         offsets)) {
         return false;
       }
     }
+
+    JitSpew(JitSpew_Codegen,
+            "# wasm::IonCompileFunctions: completed function index %d",
+            (int)func.index);
+    JitSpew(JitSpew_Codegen, "# ==");
+    JitSpew(JitSpew_Codegen, "# ========================================");
   }
 
   masm.finish();
   if (masm.oom()) {
     return false;
   }
 
   return code->swap(masm);
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -1801,222 +1801,16 @@ class CallSiteTarget {
     MOZ_ASSERT(kind_ == TrapExit);
     MOZ_ASSERT(packed_ < uint32_t(Trap::Limit));
     return Trap(packed_);
   }
 };
 
 typedef Vector<CallSiteTarget, 0, SystemAllocPolicy> CallSiteTargetVector;
 
-typedef Vector<bool, 32, SystemAllocPolicy> ExitStubMapVector;
-
-struct StackMap final {
-  // A StackMap is a bit-array containing numMappedWords bits, one bit per
-  // word of stack.  Bit index zero is for the lowest addressed word in the
-  // range.
-  //
-  // This is a variable-length structure whose size must be known at creation
-  // time.
-  //
-  // Users of the map will know the address of the wasm::Frame that is covered
-  // by this map.  In order that they can calculate the exact address range
-  // covered by the map, the map also stores the offset, from the highest
-  // addressed word of the map, of the embedded wasm::Frame.  This is an
-  // offset down from the highest address, rather than up from the lowest, so
-  // as to limit its range to 11 bits, where
-  // 11 == ceil(log2(MaxParams * sizeof-biggest-param-type-in-words))
-  //
-  // The map may also cover a ref-typed DebugFrame.  If so that can be noted,
-  // since users of the map need to trace pointers in such a DebugFrame.
-  //
-  // Finally, for sanity checking only, for stack maps associated with a wasm
-  // trap exit stub, the number of words used by the trap exit stub save area
-  // is also noted.  This is used in Instance::traceFrame to check that the
-  // TrapExitDummyValue is in the expected place in the frame.
-
-  // The total number of stack words covered by the map ..
-  uint32_t numMappedWords : 30;
-
-  // .. of which this many are "exit stub" extras
-  uint32_t numExitStubWords : 6;
-
-  // Where is Frame* relative to the top?  This is an offset in words.
-  uint32_t frameOffsetFromTop : 11;
-
-  // Notes the presence of a ref-typed DebugFrame.
-  uint32_t hasRefTypedDebugFrame : 1;
-
- private:
-  static constexpr uint32_t maxMappedWords = (1 << 30) - 1;
-  static constexpr uint32_t maxExitStubWords = (1 << 6) - 1;
-  static constexpr uint32_t maxFrameOffsetFromTop = (1 << 11) - 1;
-
-  uint32_t bitmap[1];
-
-  explicit StackMap(uint32_t numMappedWords)
-      : numMappedWords(numMappedWords),
-        numExitStubWords(0),
-        frameOffsetFromTop(0),
-        hasRefTypedDebugFrame(0) {
-    const uint32_t nBitmap = calcNBitmap(numMappedWords);
-    memset(bitmap, 0, nBitmap * sizeof(bitmap[0]));
-  }
-
- public:
-  static StackMap* create(uint32_t numMappedWords) {
-    uint32_t nBitmap = calcNBitmap(numMappedWords);
-    char* buf =
-        (char*)js_malloc(sizeof(StackMap) + (nBitmap - 1) * sizeof(bitmap[0]));
-    if (!buf) {
-      return nullptr;
-    }
-    return ::new (buf) StackMap(numMappedWords);
-  }
-
-  void destroy() { js_free((char*)this); }
-
-  // Record the number of words in the map used as a wasm trap exit stub
-  // save area.  See comment above.
-  void setExitStubWords(uint32_t nWords) {
-    MOZ_ASSERT(numExitStubWords == 0);
-    MOZ_RELEASE_ASSERT(nWords <= maxExitStubWords);
-    MOZ_ASSERT(nWords <= numMappedWords);
-    numExitStubWords = nWords;
-  }
-
-  // Record the offset from the highest-addressed word of the map, that the
-  // wasm::Frame lives at.  See comment above.
-  void setFrameOffsetFromTop(uint32_t nWords) {
-    MOZ_ASSERT(frameOffsetFromTop == 0);
-    MOZ_RELEASE_ASSERT(nWords <= maxFrameOffsetFromTop);
-    MOZ_ASSERT(frameOffsetFromTop < numMappedWords);
-    frameOffsetFromTop = nWords;
-  }
-
-  // If the frame described by this StackMap includes a DebugFrame for a
-  // ref-typed return value, call here to record that fact.
-  void setHasRefTypedDebugFrame() {
-    MOZ_ASSERT(hasRefTypedDebugFrame == 0);
-    hasRefTypedDebugFrame = 1;
-  }
-
-  inline void setBit(uint32_t bitIndex) {
-    MOZ_ASSERT(bitIndex < numMappedWords);
-    uint32_t wordIndex = bitIndex / wordsPerBitmapElem;
-    uint32_t wordOffset = bitIndex % wordsPerBitmapElem;
-    bitmap[wordIndex] |= (1 << wordOffset);
-  }
-
-  inline uint32_t getBit(uint32_t bitIndex) const {
-    MOZ_ASSERT(bitIndex < numMappedWords);
-    uint32_t wordIndex = bitIndex / wordsPerBitmapElem;
-    uint32_t wordOffset = bitIndex % wordsPerBitmapElem;
-    return (bitmap[wordIndex] >> wordOffset) & 1;
-  }
-
- private:
-  static constexpr uint32_t wordsPerBitmapElem = sizeof(bitmap[0]) * 8;
-
-  static uint32_t calcNBitmap(uint32_t numMappedWords) {
-    MOZ_RELEASE_ASSERT(numMappedWords <= maxMappedWords);
-    uint32_t nBitmap =
-        (numMappedWords + wordsPerBitmapElem - 1) / wordsPerBitmapElem;
-    return nBitmap == 0 ? 1 : nBitmap;
-  }
-};
-
-// This is the expected size for a map that covers 32 or fewer words.
-static_assert(sizeof(StackMap) == 12, "wasm::StackMap has unexpected size");
-
-class StackMaps {
- public:
-  // A Maplet holds a single code-address-to-map binding.  Note that the
-  // code address is the lowest address of the instruction immediately
-  // following the instruction of interest, not of the instruction of
-  // interest itself.  In practice (at least for the Wasm Baseline compiler)
-  // this means that |nextInsnAddr| points either immediately after a call
-  // instruction, after a trap instruction or after a no-op.
-  struct Maplet {
-    uint8_t* nextInsnAddr;
-    StackMap* map;
-    Maplet(uint8_t* nextInsnAddr, StackMap* map)
-        : nextInsnAddr(nextInsnAddr), map(map) {}
-    void offsetBy(uintptr_t delta) { nextInsnAddr += delta; }
-    bool operator<(const Maplet& other) const {
-      return uintptr_t(nextInsnAddr) < uintptr_t(other.nextInsnAddr);
-    }
-  };
-
- private:
-  bool sorted_;
-  Vector<Maplet, 0, SystemAllocPolicy> mapping_;
-
- public:
-  StackMaps() : sorted_(false) {}
-  ~StackMaps() {
-    for (size_t i = 0; i < mapping_.length(); i++) {
-      mapping_[i].map->destroy();
-      mapping_[i].map = nullptr;
-    }
-  }
-  MOZ_MUST_USE bool add(uint8_t* nextInsnAddr, StackMap* map) {
-    MOZ_ASSERT(!sorted_);
-    return mapping_.append(Maplet(nextInsnAddr, map));
-  }
-  MOZ_MUST_USE bool add(const Maplet& maplet) {
-    return add(maplet.nextInsnAddr, maplet.map);
-  }
-  void clear() {
-    for (size_t i = 0; i < mapping_.length(); i++) {
-      mapping_[i].nextInsnAddr = nullptr;
-      mapping_[i].map = nullptr;
-    }
-    mapping_.clear();
-  }
-  bool empty() const { return mapping_.empty(); }
-  size_t length() const { return mapping_.length(); }
-  Maplet get(size_t i) const { return mapping_[i]; }
-  Maplet move(size_t i) {
-    Maplet m = mapping_[i];
-    mapping_[i].map = nullptr;
-    return m;
-  }
-  void offsetBy(uintptr_t delta) {
-    for (size_t i = 0; i < mapping_.length(); i++) mapping_[i].offsetBy(delta);
-  }
-  void sort() {
-    MOZ_ASSERT(!sorted_);
-    std::sort(mapping_.begin(), mapping_.end());
-    sorted_ = true;
-  }
-  const StackMap* findMap(uint8_t* nextInsnAddr) const {
-    struct Comparator {
-      int operator()(Maplet aVal) const {
-        if (uintptr_t(mTarget) < uintptr_t(aVal.nextInsnAddr)) {
-          return -1;
-        }
-        if (uintptr_t(mTarget) > uintptr_t(aVal.nextInsnAddr)) {
-          return 1;
-        }
-        return 0;
-      }
-      explicit Comparator(uint8_t* aTarget) : mTarget(aTarget) {}
-      const uint8_t* mTarget;
-    };
-
-    size_t result;
-    if (BinarySearchIf(mapping_, 0, mapping_.length(), Comparator(nextInsnAddr),
-                       &result)) {
-      return mapping_[result].map;
-    }
-
-    return nullptr;
-  }
-};
-
 // A wasm::SymbolicAddress represents a pointer to a well-known function that is
 // embedded in wasm code. Since wasm code is serialized and later deserialized
 // into a different address space, symbolic addresses must be used for *all*
 // pointers into the address space. The MacroAssembler records a list of all
 // SymbolicAddresses and the offsets of their use in the code for later patching
 // during static linking.
 
 enum class SymbolicAddress {
--- a/layout/base/nsILayoutDebugger.h
+++ b/layout/base/nsILayoutDebugger.h
@@ -6,25 +6,25 @@
 
 /* XPCOM interface for layout-debug extension to reach layout internals */
 
 #ifndef nsILayoutDebugger_h___
 #define nsILayoutDebugger_h___
 
 #include "nsISupports.h"
 
-class nsIPresShell;
+#ifdef DEBUG
 
 // 1295f7c0-96b3-41fc-93ed-c95dfb712ce7
-#define NS_ILAYOUT_DEBUGGER_IID                      \
-  {                                                  \
-    0x1295f7c0, 0x96b3, 0x41fc, {                    \
-      0x93, 0xed, 0xc9, 0x5d, 0xfb, 0x71, 0x2c, 0xe7 \
-    }                                                \
-  }
+#  define NS_ILAYOUT_DEBUGGER_IID                      \
+    {                                                  \
+      0x1295f7c0, 0x96b3, 0x41fc, {                    \
+        0x93, 0xed, 0xc9, 0x5d, 0xfb, 0x71, 0x2c, 0xe7 \
+      }                                                \
+    }
 
 /**
  * API for access and control of layout debugging
  */
 class nsILayoutDebugger : public nsISupports {
  public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ILAYOUT_DEBUGGER_IID)
 
@@ -34,9 +34,13 @@ class nsILayoutDebugger : public nsISupp
 
   NS_IMETHOD SetShowEventTargetFrameBorder(bool aEnable) = 0;
 
   NS_IMETHOD GetShowEventTargetFrameBorder(bool* aResult) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsILayoutDebugger, NS_ILAYOUT_DEBUGGER_IID)
 
+nsresult NS_NewLayoutDebugger(nsILayoutDebugger** aResult);
+
+#endif /* DEBUG */
+
 #endif /* nsILayoutDebugger_h___ */
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -1,14 +1,16 @@
 /* -*- 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 "nsLayoutModule.h"
+
 #include "base/basictypes.h"
 
 #include "XPCModule.h"
 #include "mozilla/Components.h"
 #include "mozilla/ModuleUtils.h"
 #include "nsImageModule.h"
 #include "nsLayoutStatics.h"
 #include "nsContentCID.h"
@@ -120,20 +122,16 @@ void Shutdown() {
   MOZ_ASSERT(gInitialized, "module not initialized");
   if (!gInitialized) return;
 
   gInitialized = false;
 
   nsLayoutStatics::Release();
 }
 
-#ifdef DEBUG
-nsresult NS_NewLayoutDebugger(nsILayoutDebugger** aResult);
-#endif
-
 nsresult NS_CreateFrameTraversal(nsIFrameTraversal** aResult);
 
 already_AddRefed<nsIContentViewer> NS_NewContentViewer();
 nsresult NS_NewContentDocumentLoaderFactory(nsIDocumentLoaderFactory** aResult);
 nsresult NS_NewContentPolicy(nsIContentPolicy** aResult);
 
 nsresult NS_NewEventListenerService(nsIEventListenerService** aResult);
 nsresult NS_NewGlobalMessageManager(nsISupports** aResult);
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -2600,17 +2600,17 @@ already_AddRefed<PaintedLayer> Container
                           didResetScrollPositionForLayerPixelAlignment);
   PreparePaintedLayerForUse(layer, data, aAnimatedGeometryRoot, aReferenceFrame,
                             aTopLeft,
                             didResetScrollPositionForLayerPixelAlignment);
 
   return layer.forget();
 }
 
-void ReleaseLayerUserData(void* aData) {
+static void ReleaseLayerUserData(void* aData) {
   PaintedDisplayItemLayerUserData* userData =
       static_cast<PaintedDisplayItemLayerUserData*>(aData);
   userData->Release();
 }
 
 already_AddRefed<PaintedLayer> ContainerState::CreatePaintedLayer(
     PaintedLayerData* aData) {
   LayerManager::PaintedLayerCreationHint creationHint =
--- a/layout/painting/RetainedDisplayListBuilder.cpp
+++ b/layout/painting/RetainedDisplayListBuilder.cpp
@@ -1080,17 +1080,18 @@ static void AddFramesForContainingBlock(
 // Placeholder descendants of aFrame don't contribute to aFrame's overflow area.
 // Find all the containing blocks that might own placeholders under us, walk
 // their OOF frames list, and manually invalidate any frames that are
 // descendants of a modified frame (us, or another frame we'll get to soon).
 // This is combined with the work required for MarkFrameForDisplayIfVisible,
 // so that we can avoid an extra ancestor walk, and we can reuse the flag
 // to detect when we've already visited an ancestor (and thus all further
 // ancestors must also be visited).
-void FindContainingBlocks(nsIFrame* aFrame, nsTArray<nsIFrame*>& aExtraFrames) {
+static void FindContainingBlocks(nsIFrame* aFrame,
+                                 nsTArray<nsIFrame*>& aExtraFrames) {
   for (nsIFrame* f = aFrame; f;
        f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
     if (f->ForceDescendIntoIfVisible()) return;
     f->SetForceDescendIntoIfVisible(true);
     CRR_LOG("Considering OOFs for %p\n", f);
 
     AddFramesForContainingBlock(f, f->GetChildList(nsIFrame::kFloatList),
                                 aExtraFrames);
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -415,17 +415,17 @@ bool FontFaceSet::HasRuleFontFace(FontFa
     if (mRuleFaces[i].mFontFace == aFontFace) {
       return true;
     }
   }
   return false;
 }
 #endif
 
-bool IsPdfJs(nsIPrincipal* aPrincipal) {
+static bool IsPdfJs(nsIPrincipal* aPrincipal) {
   if (!aPrincipal) {
     return false;
   }
   nsCOMPtr<nsIURI> uri;
   aPrincipal->GetURI(getter_AddRefs(uri));
   return uri && uri->GetSpecOrDefault().EqualsLiteral(
                     "resource://pdf.js/web/viewer.html");
 }
--- a/layout/style/GeckoBindings.cpp
+++ b/layout/style/GeckoBindings.cpp
@@ -2247,34 +2247,16 @@ void Gecko_ReportUnexpectedCSSError(cons
 
   if (suffix) {
     reporter.ReportUnexpected(suffix);
   }
   nsDependentCSubstring sourceValue(source, sourceLen);
   reporter.OutputError(lineNumber, colNumber, sourceValue);
 }
 
-void Gecko_AddBufferToCrashReport(const void* addr, size_t len) {
-  MOZ_ASSERT(NS_IsMainThread());
-  nsCOMPtr<nsICrashReporter> cr =
-      do_GetService("@mozilla.org/toolkit/crash-reporter;1");
-  NS_ENSURE_TRUE_VOID(cr);
-  cr->RegisterAppMemory((uint64_t)addr, len);
-}
-
-void Gecko_AnnotateCrashReport(const char* key_str, const char* value_str) {
-  MOZ_ASSERT(NS_IsMainThread());
-  nsDependentCString key(key_str);
-  nsDependentCString value(value_str);
-  nsCOMPtr<nsICrashReporter> cr =
-      do_GetService("@mozilla.org/toolkit/crash-reporter;1");
-  NS_ENSURE_TRUE_VOID(cr);
-  cr->AnnotateCrashReport(key, value);
-}
-
 void Gecko_ContentList_AppendAll(nsSimpleContentList* aList,
                                  const Element** aElements, size_t aLength) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aElements);
   MOZ_ASSERT(aLength);
   MOZ_ASSERT(aList);
 
   aList->SetCapacity(aLength);
--- a/layout/style/GeckoBindings.h
+++ b/layout/style/GeckoBindings.h
@@ -726,18 +726,16 @@ void Gecko_RegisterProfilerThread(const 
 void Gecko_UnregisterProfilerThread();
 
 bool Gecko_DocumentRule_UseForPresentation(
     const mozilla::dom::Document*, const nsACString* aPattern,
     mozilla::css::DocumentMatchingFunction);
 
 // Allocator hinting.
 void Gecko_SetJemallocThreadLocalArena(bool enabled);
-void Gecko_AddBufferToCrashReport(const void* addr, size_t len);
-void Gecko_AnnotateCrashReport(uint32_t key, const char* value_str);
 
 // Pseudo-element flags.
 #define CSS_PSEUDO_ELEMENT(name_, value_, flags_) \
   const uint32_t SERVO_CSS_PSEUDO_ELEMENT_FLAGS_##name_ = flags_;
 #include "nsCSSPseudoElementList.h"
 #undef CSS_PSEUDO_ELEMENT
 
 bool Gecko_ErrorReportingEnabled(const mozilla::StyleSheet* sheet,
--- a/layout/svg/SVGObserverUtils.cpp
+++ b/layout/svg/SVGObserverUtils.cpp
@@ -961,17 +961,17 @@ void SVGRenderingObserver::DebugObserver
 
 typedef nsInterfaceHashtable<nsRefPtrHashKey<URLAndReferrerInfo>,
                              nsIMutationObserver>
     URIObserverHashtable;
 
 using PaintingPropertyDescriptor =
     const mozilla::FramePropertyDescriptor<nsSVGPaintingProperty>*;
 
-void DestroyFilterProperty(SVGFilterObserverListForCSSProp* aProp) {
+static void DestroyFilterProperty(SVGFilterObserverListForCSSProp* aProp) {
   // SVGFilterObserverListForCSSProp is cycle-collected, so dropping the last
   // reference doesn't necessarily destroy it. We need to tell it that the
   // frame has now become invalid.
   aProp->DetachFromFrame();
 
   aProp->Release();
 }
 
--- a/security/manager/pki/resources/content/certViewer.xul
+++ b/security/manager/pki/resources/content/certViewer.xul
@@ -140,17 +140,17 @@
                   onselect="displaySelected();" hidecolumnpicker="true">
           <treecols>
             <treecol flex="1" id="certDataCol" primary="true" hideheader="true"/>
           </treecols>
           <treechildren/>
         </tree>
 
         <label class="header" data-l10n-id="certmgr-fields" control="certDumpVal"/>
-        <html:textarea id="certDumpVal" flex="1" readonly="true"
+        <html:textarea id="certDumpVal" flex="1" readonly="readonly"
                        style="height: 11em; font-family: -moz-fixed;"/>
         <separator class="thin"/>
         <hbox>
           <button id="export_cert" class="normal" data-l10n-id="certmgr-export"
                   oncommand="exportToFile(window, getCurrentCert());"/>
         </hbox>
       </vbox>
     </tabpanels>
--- a/security/manager/pki/resources/content/clientauthask.xul
+++ b/security/manager/pki/resources/content/clientauthask.xul
@@ -37,12 +37,12 @@
 <description style="font-weight: bold;">&clientAuthAsk.message2;</description>
 <!-- The items in this menulist must never be sorted,
      but remain in the order filled by the application
 -->
 <menulist id="nicknames" oncommand="onCertSelected();">
     <menupopup/>
 </menulist>
 <description>&clientAuthAsk.message3;</description>
-<html:textarea readonly="true" id="details" style="height: 11em;"/>
+<html:textarea readonly="readonly" id="details" style="height: 11em;"/>
 <checkbox id="rememberBox" checked="true"/>
 
 </dialog>
--- a/security/manager/ssl/tests/unit/test_nonascii_path.js
+++ b/security/manager/ssl/tests/unit/test_nonascii_path.js
@@ -14,19 +14,17 @@ let profd = env.get("XPCSHELL_TEST_PROFI
 let file = Cc["@mozilla.org/file/local;1"]
              .createInstance(Ci.nsIFile);
 file.initWithPath(profd);
 file.append("'รท1");
 env.set("XPCSHELL_TEST_PROFILE_DIR", file.path);
 
 file = do_get_profile(); // must be called before getting nsIX509CertDB
 Assert.ok(/[^\x20-\x7f]/.test(file.path), "the profile path should contain a non-ASCII character");
-// Due to 8.3 names being enabled on Thunderbird's Buildbot, this will fail. It passes
-// on TaskCluster, so this check can be removed when Buildbot is dropped.
-if (mozinfo.os == "win" && AppConstants.MOZ_APP_NAME != "thunderbird") {
+if (mozinfo.os == "win") {
   file.QueryInterface(Ci.nsILocalFileWin);
   Assert.ok(/[^\x20-\x7f]/.test(file.canonicalPath), "the profile short path should contain a non-ASCII character");
 }
 
 // Restore the original value.
 env.set("XPCSHELL_TEST_PROFILE_DIR", profd);
 
 const certdb  = Cc["@mozilla.org/security/x509certdb;1"]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/CSS2/lists/list-style-021.xht.ini
@@ -0,0 +1,2 @@
+[list-style-021.xht]
+  disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1383451#c89
--- a/toolkit/mozapps/update/content/updates.xul
+++ b/toolkit/mozapps/update/content/updates.xul
@@ -140,17 +140,17 @@
   </wizardpage>
 
   <wizardpage id="errors" pageid="errors" object="gErrorsPage"
               onpageshow="gErrorsPage.onPageShow();">
     <updateheader label="&error.title;"/>
     <vbox class="update-content" flex="1">
       <label id="errorIntro">&error.label;</label>
       <separator/>
-      <html:textarea class="plain" readonly="true" id="errorReason" rows="3"/>
+      <html:textarea class="plain" readonly="readonly" id="errorReason" rows="3"/>
       <separator/>
       <label id="errorManual">&errorManual.label;</label>
       <hbox>
         <label id="errorLinkLabel" value="" is="text-link"
                onclick="openUpdateURL(event);"/>
       </hbox>
     </vbox>
   </wizardpage>