Merge mozilla-inbound to mozilla-central. a=merge
authorDaniel Varga <dvarga@mozilla.com>
Sat, 09 Feb 2019 23:47:19 +0200
changeset 458413 aa525133e148825b006d76a9d2ad33b5b56c5957
parent 458412 0eb73752cf21eb1fc4b440f32ece2e443fb993f5 (current diff)
parent 458395 fbd51b1c7b04186f9358d3e5c7dc86a3de47d642 (diff)
child 458414 3abe61f5dc5c5afbade56821d96fe616404ed408
child 458427 b79d7da6617b52467fbce619fa84231a1026af0d
push id111821
push userdvarga@mozilla.com
push dateSat, 09 Feb 2019 21:55:30 +0000
treeherdermozilla-inbound@aa525133e148 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
aa525133e148 / 67.0a1 / 20190209214755 / files
nightly linux64
aa525133e148 / 67.0a1 / 20190209214755 / files
nightly mac
aa525133e148 / 67.0a1 / 20190209214755 / files
nightly win32
aa525133e148 / 67.0a1 / 20190209214755 / files
nightly win64
aa525133e148 / 67.0a1 / 20190209214755 / 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 mozilla-inbound to mozilla-central. a=merge
--- a/devtools/client/inspector/rules/models/class-list.js
+++ b/devtools/client/inspector/rules/models/class-list.js
@@ -23,45 +23,45 @@ const CLASSES = new WeakMap();
  * disabled.
  * It also reacts to DOM mutations so the list of classes is up to date with what is in
  * the DOM.
  * It can also be used to enable/disable a given class, or add classes.
  *
  * @param {Inspector} inspector
  *        The current inspector instance.
  */
-function ClassList(inspector) {
-  EventEmitter.decorate(this);
+class ClassList {
+  constructor(inspector) {
+    EventEmitter.decorate(this);
 
-  this.inspector = inspector;
+    this.inspector = inspector;
 
-  this.onMutations = this.onMutations.bind(this);
-  this.inspector.on("markupmutation", this.onMutations);
+    this.onMutations = this.onMutations.bind(this);
+    this.inspector.on("markupmutation", this.onMutations);
 
-  this.classListProxyNode = this.inspector.panelDoc.createElement("div");
-}
+    this.classListProxyNode = this.inspector.panelDoc.createElement("div");
+  }
 
-ClassList.prototype = {
   destroy() {
     this.inspector.off("markupmutation", this.onMutations);
     this.inspector = null;
     this.classListProxyNode = null;
-  },
+  }
 
   /**
    * The current node selection (which only returns if the node is an ELEMENT_NODE type
    * since that's the only type this model can work with.)
    */
   get currentNode() {
     if (this.inspector.selection.isElementNode() &&
         !this.inspector.selection.isPseudoElementNode()) {
       return this.inspector.selection.nodeFront;
     }
     return null;
-  },
+  }
 
   /**
    * The class states for the current node selection. See the documentation of the CLASSES
    * constant.
    */
   get currentClasses() {
     if (!this.currentNode) {
       return [];
@@ -74,58 +74,58 @@ ClassList.prototype = {
         .map(name => {
           return { name, isApplied: true };
         });
 
       CLASSES.set(this.currentNode, nodeClasses);
     }
 
     return CLASSES.get(this.currentNode);
-  },
+  }
 
   /**
    * Same as currentClasses, but returns it in the form of a className string, where only
    * enabled classes are added.
    */
   get currentClassesPreview() {
     return this.currentClasses.filter(({ isApplied }) => isApplied)
                               .map(({ name }) => name)
                               .join(" ");
-  },
+  }
 
   /**
    * Set the state for a given class on the current node.
    *
    * @param {String} name
    *        The class which state should be changed.
    * @param {Boolean} isApplied
    *        True if the class should be enabled, false otherwise.
    * @return {Promise} Resolves when the change has been made in the DOM.
    */
   setClassState(name, isApplied) {
     // Do the change in our local model.
     const nodeClasses = this.currentClasses;
     nodeClasses.find(({ name: cName }) => cName === name).isApplied = isApplied;
 
     return this.applyClassState();
-  },
+  }
 
   /**
    * Add several classes to the current node at once.
    *
    * @param {String} classNameString
    *        The string that contains all classes.
    * @return {Promise} Resolves when the change has been made in the DOM.
    */
   addClassName(classNameString) {
     this.classListProxyNode.className = classNameString;
     return Promise.all([...new Set([...this.classListProxyNode.classList])].map(name => {
       return this.addClass(name);
     }));
-  },
+  }
 
   /**
    * Add a class to the current node at once.
    *
    * @param {String} name
    *        The class to be added.
    * @return {Promise} Resolves when the change has been made in the DOM.
    */
@@ -134,17 +134,17 @@ ClassList.prototype = {
     if (this.currentClasses.some(({ name: cName }) => cName === name)) {
       return Promise.resolve();
     }
 
     // Change the local model, so we retain the state of the existing classes.
     this.currentClasses.push({ name, isApplied: true });
 
     return this.applyClassState();
-  },
+  }
 
   /**
    * Used internally by other functions like addClass or setClassState. Actually applies
    * the class change to the DOM.
    *
    * @return {Promise} Resolves when the change has been made in the DOM.
    */
   applyClassState() {
@@ -160,17 +160,17 @@ ClassList.prototype = {
       node: this.currentNode,
       className: this.currentClassesPreview,
     };
 
     // Apply the change to the node.
     const mod = this.currentNode.startModifyingAttributes();
     mod.setAttribute("class", this.currentClassesPreview);
     return mod.apply();
-  },
+  }
 
   onMutations(mutations) {
     for (const {type, target, attributeName} of mutations) {
       // Only care if this mutation is for the class attribute.
       if (type !== "attributes" || attributeName !== "class") {
         continue;
       }
 
@@ -180,12 +180,12 @@ ClassList.prototype = {
 
       if (!isMutationForOurChange) {
         CLASSES.delete(target);
         if (target === this.currentNode) {
           this.emit("current-node-class-changed");
         }
       }
     }
-  },
-};
+  }
+}
 
 module.exports = ClassList;
--- a/devtools/client/inspector/rules/models/element-style.js
+++ b/devtools/client/inspector/rules/models/element-style.js
@@ -14,95 +14,96 @@ loader.lazyRequireGetter(this, "parseDec
 loader.lazyRequireGetter(this, "parseNamedDeclarations", "devtools/shared/css/parsing-utils", true);
 loader.lazyRequireGetter(this, "parseSingleValue", "devtools/shared/css/parsing-utils", true);
 loader.lazyRequireGetter(this, "isCssVariable", "devtools/shared/fronts/css-properties", true);
 
 /**
  * ElementStyle is responsible for the following:
  *   Keeps track of which properties are overridden.
  *   Maintains a list of Rule objects for a given element.
- *
- * @param  {Element} element
- *         The element whose style we are viewing.
- * @param  {CssRuleView} ruleView
- *         The instance of the rule-view panel.
- * @param  {Object} store
- *         The ElementStyle can use this object to store metadata
- *         that might outlast the rule view, particularly the current
- *         set of disabled properties.
- * @param  {PageStyleFront} pageStyle
- *         Front for the page style actor that will be providing
- *         the style information.
- * @param  {Boolean} showUserAgentStyles
- *         Should user agent styles be inspected?
  */
-function ElementStyle(element, ruleView, store, pageStyle, showUserAgentStyles) {
-  this.element = element;
-  this.ruleView = ruleView;
-  this.store = store || {};
-  this.pageStyle = pageStyle;
-  this.showUserAgentStyles = showUserAgentStyles;
-  this.rules = [];
-  this.cssProperties = this.ruleView.cssProperties;
-  this.variables = new Map();
+class ElementStyle {
+  /**
+   * @param  {Element} element
+   *         The element whose style we are viewing.
+   * @param  {CssRuleView} ruleView
+   *         The instance of the rule-view panel.
+   * @param  {Object} store
+   *         The ElementStyle can use this object to store metadata
+   *         that might outlast the rule view, particularly the current
+   *         set of disabled properties.
+   * @param  {PageStyleFront} pageStyle
+   *         Front for the page style actor that will be providing
+   *         the style information.
+   * @param  {Boolean} showUserAgentStyles
+   *         Should user agent styles be inspected?
+   */
+  constructor(element, ruleView, store, pageStyle, showUserAgentStyles) {
+    this.element = element;
+    this.ruleView = ruleView;
+    this.store = store || {};
+    this.pageStyle = pageStyle;
+    this.showUserAgentStyles = showUserAgentStyles;
+    this.rules = [];
+    this.cssProperties = this.ruleView.cssProperties;
+    this.variables = new Map();
 
-  // We don't want to overwrite this.store.userProperties so we only create it
-  // if it doesn't already exist.
-  if (!("userProperties" in this.store)) {
-    this.store.userProperties = new UserProperties();
+    // We don't want to overwrite this.store.userProperties so we only create it
+    // if it doesn't already exist.
+    if (!("userProperties" in this.store)) {
+      this.store.userProperties = new UserProperties();
+    }
+
+    if (!("disabled" in this.store)) {
+      this.store.disabled = new WeakMap();
+    }
+
+    this.onStyleSheetUpdated = this.onStyleSheetUpdated.bind(this);
+
+    if (this.ruleView.isNewRulesView) {
+      this.pageStyle.on("stylesheet-updated", this.onStyleSheetUpdated);
+    }
   }
 
-  if (!("disabled" in this.store)) {
-    this.store.disabled = new WeakMap();
-  }
-
-  this.onStyleSheetUpdated = this.onStyleSheetUpdated.bind(this);
-
-  if (this.ruleView.isNewRulesView) {
-    this.pageStyle.on("stylesheet-updated", this.onStyleSheetUpdated);
-  }
-}
-
-ElementStyle.prototype = {
-  destroy: function() {
+  destroy() {
     if (this.destroyed) {
       return;
     }
 
     this.destroyed = true;
 
     for (const rule of this.rules) {
       if (rule.editor) {
         rule.editor.destroy();
       }
     }
 
     if (this.ruleView.isNewRulesView) {
       this.pageStyle.off("stylesheet-updated", this.onStyleSheetUpdated);
     }
-  },
+  }
 
   /**
    * Called by the Rule object when it has been changed through the
    * setProperty* methods.
    */
-  _changed: function() {
+  _changed() {
     if (this.onChanged) {
       this.onChanged();
     }
-  },
+  }
 
   /**
    * Refresh the list of rules to be displayed for the active element.
    * Upon completion, this.rules[] will hold a list of Rule objects.
    *
    * Returns a promise that will be resolved when the elementStyle is
    * ready.
    */
-  populate: function() {
+  populate() {
     const populated = this.pageStyle.getApplied(this.element, {
       inherited: true,
       matchedSelectors: true,
       filter: this.showUserAgentStyles ? "ua" : undefined,
     }).then(entries => {
       if (this.destroyed || this.populated !== populated) {
         return promise.resolve(undefined);
       }
@@ -135,70 +136,70 @@ ElementStyle.prototype = {
       // the connection may already be closed.
       if (this.destroyed) {
         return promise.resolve(undefined);
       }
       return promiseWarn(e);
     });
     this.populated = populated;
     return this.populated;
-  },
+  }
 
   /**
    * Returns the Rule object of the given rule id.
    *
    * @param  {String} id
    *         The id of the Rule object.
    * @return {Rule|undefined} of the given rule id or undefined if it cannot be found.
    */
-  getRule: function(id) {
+  getRule(id) {
     return this.rules.find(rule => rule.domRule.actorID === id);
-  },
+  }
 
   /**
    * Get the font families in use by the element.
    *
    * Returns a promise that will be resolved to a list of CSS family
    * names. The list might have duplicates.
    */
-  getUsedFontFamilies: function() {
+  getUsedFontFamilies() {
     return new Promise((resolve, reject) => {
       this.ruleView.styleWindow.requestIdleCallback(async () => {
         try {
           const fonts = await this.pageStyle.getUsedFontFaces(
             this.element, { includePreviews: false });
           resolve(fonts.map(font => font.CSSFamilyName));
         } catch (e) {
           reject(e);
         }
       });
     });
-  },
+  }
 
   /**
    * Put pseudo elements in front of others.
    */
-  _sortRulesForPseudoElement: function() {
+  _sortRulesForPseudoElement() {
     this.rules = this.rules.sort((a, b) => {
       return (a.pseudoElement || "z") > (b.pseudoElement || "z");
     });
-  },
+  }
 
   /**
    * Add a rule if it's one we care about. Filters out duplicates and
    * inherited styles with no inherited properties.
    *
    * @param  {Object} options
    *         Options for creating the Rule, see the Rule constructor.
    * @param  {Array} existingRules
    *         Rules to reuse if possible. If a rule is reused, then it
    *         it will be deleted from this array.
    * @return {Boolean} true if we added the rule.
    */
-  _maybeAddRule: function(options, existingRules) {
+  _maybeAddRule(options, existingRules) {
     // If we've already included this domRule (for example, when a
     // common selector is inherited), ignore it.
     if (options.system ||
         (options.rule && this.rules.some(rule => rule.domRule === options.rule))) {
       return false;
     }
 
     let rule = null;
@@ -221,39 +222,39 @@ ElementStyle.prototype = {
 
     // Ignore inherited rules with no visible properties.
     if (options.inherited && !rule.hasAnyVisibleProperties()) {
       return false;
     }
 
     this.rules.push(rule);
     return true;
-  },
+  }
 
   /**
    * Calls markOverridden with all supported pseudo elements
    */
-  markOverriddenAll: function() {
+  markOverriddenAll() {
     this.variables.clear();
     this.markOverridden();
 
     for (const pseudo of this.cssProperties.pseudoElements) {
       this.markOverridden(pseudo);
     }
-  },
+  }
 
   /**
    * Mark the properties listed in this.rules for a given pseudo element
    * with an overridden flag if an earlier property overrides it.
    *
    * @param  {String} pseudo
    *         Which pseudo element to flag as overridden.
    *         Empty string or undefined will default to no pseudo element.
    */
-  markOverridden: function(pseudo = "") {
+  markOverridden(pseudo = "") {
     // Gather all the text properties applied by these rules, ordered
     // from more- to less-specific. Text properties from keyframes rule are
     // excluded from being marked as overridden since a number of criteria such
     // as time, and animation overlay are required to be check in order to
     // determine if the property is overridden.
     const textProps = [];
     for (const rule of this.rules) {
       if ((rule.matchedSelectors.length > 0 ||
@@ -338,62 +339,62 @@ ElementStyle.prototype = {
     // _overriddenDirty state on all computed properties.
     for (const textProp of textProps) {
       // _updatePropertyOverridden will return true if the
       // overridden state has changed for the text property.
       if (this._updatePropertyOverridden(textProp)) {
         textProp.updateEditor();
       }
     }
-  },
+  }
 
   /**
    * Adds a new declaration to the rule.
    *
    * @param {String} ruleId
    *        The id of the Rule to be modified.
    * @param {String} value
    *        The new declaration value.
    */
-  addNewDeclaration: function(ruleId, value) {
+  addNewDeclaration(ruleId, value) {
     const rule = this.getRule(ruleId);
     if (!rule) {
       return;
     }
 
     const declarationsToAdd = parseNamedDeclarations(this.cssProperties.isKnown,
       value, true);
     if (!declarationsToAdd.length) {
       return;
     }
 
     this._addMultipleDeclarations(rule, declarationsToAdd);
-  },
+  }
 
   /**
    * Adds a new rule. The rules view is updated from a "stylesheet-updated" event
    * emitted the PageStyleActor as a result of the rule being inserted into the
    * the stylesheet.
    */
   async addNewRule() {
     await this.pageStyle.addNewRule(this.element, this.element.pseudoClassLocks);
-  },
+  }
 
   /**
    * Given the id of the rule and the new declaration name, modifies the existing
    * declaration name to the new given value.
    *
    * @param  {String} ruleID
    *         The Rule id of the given CSS declaration.
    * @param  {String} declarationId
    *         The TextProperty id for the CSS declaration.
    * @param  {String} name
    *         The new declaration name.
    */
-  modifyDeclarationName: async function(ruleID, declarationId, name) {
+  async modifyDeclarationName(ruleID, declarationId, name) {
     const rule = this.getRule(ruleID);
     if (!rule) {
       return;
     }
 
     const declaration = rule.getDeclaration(declarationId);
     if (!declaration || declaration.name === name) {
       return;
@@ -406,54 +407,54 @@ ElementStyle.prototype = {
       return;
     }
 
     await declaration.setName(declarations[0].name);
 
     if (!declaration.enabled) {
       await declaration.setEnabled(true);
     }
-  },
+  }
 
   /**
    * Helper function to addNewDeclaration() and modifyDeclarationValue() for
    * adding multiple declarations to a rule.
    *
    * @param  {Rule} rule
    *         The Rule object to write new declarations to.
    * @param  {Array<Object>} declarationsToAdd
    *         An array of object containg the parsed declaration data to be added.
    * @param  {TextProperty|null} siblingDeclaration
    *         Optional declaration next to which the new declaration will be added.
    */
-  _addMultipleDeclarations: function(rule, declarationsToAdd, siblingDeclaration = null) {
+  _addMultipleDeclarations(rule, declarationsToAdd, siblingDeclaration = null) {
     for (const { commentOffsets, name, value, priority } of declarationsToAdd) {
       const isCommented = Boolean(commentOffsets);
       const enabled = !isCommented;
       siblingDeclaration = rule.createProperty(name, value, priority, enabled,
         siblingDeclaration);
     }
-  },
+  }
 
   /**
    * Parse a value string and break it into pieces, starting with the
    * first value, and into an array of additional declarations (if any).
    *
    * Example: Calling with "red; width: 100px" would return
    * { firstValue: "red", propertiesToAdd: [{ name: "width", value: "100px" }] }
    *
    * @param  {String} value
    *         The string to parse.
    * @return {Object} An object with the following properties:
    *         firstValue: A string containing a simple value, like
    *                     "red" or "100px!important"
    *         declarationsToAdd: An array with additional declarations, following the
    *                            parseDeclarations format of { name, value, priority }
    */
-  _getValueAndExtraProperties: function(value) {
+  _getValueAndExtraProperties(value) {
     // The inplace editor will prevent manual typing of multiple declarations,
     // but we need to deal with the case during a paste event.
     // Adding multiple declarations inside of value editor sets value with the
     // first, then adds any more onto the declaration list (below this declarations).
     let firstValue = value;
     let declarationsToAdd = [];
 
     const declarations = parseDeclarations(this.cssProperties.isKnown, value);
@@ -473,30 +474,30 @@ ElementStyle.prototype = {
         declarationsToAdd = declarations.slice(1);
       }
     }
 
     return {
       declarationsToAdd,
       firstValue,
     };
-  },
+  }
 
   /**
    * Given the id of the rule and the new declaration value, modifies the existing
    * declaration value to the new given value.
    *
    * @param  {String} ruleId
    *         The Rule id of the given CSS declaration.
    * @param  {String} declarationId
    *         The TextProperty id for the CSS declaration.
    * @param  {String} value
    *         The new declaration value.
    */
-  modifyDeclarationValue: async function(ruleId, declarationId, value) {
+  async modifyDeclarationValue(ruleId, declarationId, value) {
     const rule = this.getRule(ruleId);
     if (!rule) {
       return;
     }
 
     const declaration = rule.getDeclaration(declarationId);
     if (!declaration) {
       return;
@@ -514,27 +515,27 @@ ElementStyle.prototype = {
     // First, set this declaration value (common case, only modified a property)
     await declaration.setValue(parsedValue.value, parsedValue.priority);
 
     if (!declaration.enabled) {
       await declaration.setEnabled(true);
     }
 
     this._addMultipleDeclarations(rule, declarationsToAdd, declaration);
-  },
+  }
 
   /**
    * Modifies the existing rule's selector to the new given value.
    *
    * @param {String} ruleId
    *        The id of the Rule to be modified.
    * @param {String} selector
    *        The new selector value.
    */
-  modifySelector: async function(ruleId, selector) {
+  async modifySelector(ruleId, selector) {
     try {
       const rule = this.getRule(ruleId);
       if (!rule) {
         return;
       }
 
       const response = await rule.domRule.modifySelector(this.element, selector);
       const { ruleProps, isMatching } = response;
@@ -581,93 +582,93 @@ ElementStyle.prototype = {
         this.rules.splice(newIndex, 1);
         this.rules.splice(oldIndex, 0, newRule);
       }
 
       this._changed();
     } catch (e) {
       console.error(e);
     }
-  },
+  }
 
   /**
    * Toggles the enabled state of the given CSS declaration.
    *
    * @param {String} ruleId
    *        The Rule id of the given CSS declaration.
    * @param {String} declarationId
    *        The TextProperty id for the CSS declaration.
    */
-  toggleDeclaration: function(ruleId, declarationId) {
+  toggleDeclaration(ruleId, declarationId) {
     const rule = this.getRule(ruleId);
     if (!rule) {
       return;
     }
 
     const declaration = rule.getDeclaration(declarationId);
     if (!declaration) {
       return;
     }
 
     declaration.setEnabled(!declaration.enabled);
-  },
+  }
 
   /**
    * Mark a given TextProperty as overridden or not depending on the
    * state of its computed properties. Clears the _overriddenDirty state
    * on all computed properties.
    *
    * @param  {TextProperty} prop
    *         The text property to update.
    * @return {Boolean} true if the TextProperty's overridden state (or any of
    *         its computed properties overridden state) changed.
    */
-  _updatePropertyOverridden: function(prop) {
+  _updatePropertyOverridden(prop) {
     let overridden = true;
     let dirty = false;
 
     for (const computedProp of prop.computed) {
       if (!computedProp.overridden) {
         overridden = false;
       }
 
       dirty = computedProp._overriddenDirty || dirty;
       delete computedProp._overriddenDirty;
     }
 
     dirty = (!!prop.overridden !== overridden) || dirty;
     prop.overridden = overridden;
     return dirty;
-  },
+  }
 
  /**
   * Returns the current value of a CSS variable; or null if the
   * variable is not defined.
   *
   * @param  {String} name
   *         The name of the variable.
   * @return {String} the variable's value or null if the variable is
   *         not defined.
   */
-  getVariable: function(name) {
+  getVariable(name) {
     return this.variables.get(name);
-  },
+  }
 
   /**
    * Handler for page style events "stylesheet-updated". Refreshes the list of rules on
    * the page.
    */
-  onStyleSheetUpdated: async function() {
+  async onStyleSheetUpdated() {
     // Repopulate the element style once the current modifications are done.
     const promises = [];
     for (const rule of this.rules) {
       if (rule._applyingModifications) {
         promises.push(rule._applyingModifications);
       }
     }
 
     await Promise.all(promises);
     await this.populate();
     this._changed();
-  },
-};
+  }
+}
 
 module.exports = ElementStyle;
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -19,174 +19,169 @@ const STYLE_INSPECTOR_PROPERTIES = "devt
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
 
 /**
  * Rule is responsible for the following:
  *   Manages a single style declaration or rule.
  *   Applies changes to the properties in a rule.
  *   Maintains a list of TextProperty objects.
- *
- * @param {ElementStyle} elementStyle
- *        The ElementStyle to which this rule belongs.
- * @param {Object} options
- *        The information used to construct this rule.  Properties include:
- *          rule: A StyleRuleActor
- *          inherited: An element this rule was inherited from.  If omitted,
- *            the rule applies directly to the current element.
- *          isSystem: Is this a user agent style?
- *          isUnmatched: True if the rule does not match the current selected
- *            element, otherwise, false.
  */
-function Rule(elementStyle, options) {
-  this.elementStyle = elementStyle;
-  this.domRule = options.rule;
-  this.matchedSelectors = options.matchedSelectors || [];
-  this.pseudoElement = options.pseudoElement || "";
+class Rule {
+  /**
+   * @param {ElementStyle} elementStyle
+   *        The ElementStyle to which this rule belongs.
+   * @param {Object} options
+   *        The information used to construct this rule. Properties include:
+   *          rule: A StyleRuleActor
+   *          inherited: An element this rule was inherited from. If omitted,
+   *            the rule applies directly to the current element.
+   *          isSystem: Is this a user agent style?
+   *          isUnmatched: True if the rule does not match the current selected
+   *            element, otherwise, false.
+   */
+  constructor(elementStyle, options) {
+    this.elementStyle = elementStyle;
+    this.domRule = options.rule;
+    this.matchedSelectors = options.matchedSelectors || [];
+    this.pseudoElement = options.pseudoElement || "";
+    this.isSystem = options.isSystem;
+    this.isUnmatched = options.isUnmatched || false;
+    this.inherited = options.inherited || null;
+    this.keyframes = options.keyframes || null;
 
-  this.isSystem = options.isSystem;
-  this.isUnmatched = options.isUnmatched || false;
-  this.inherited = options.inherited || null;
-  this.keyframes = options.keyframes || null;
-
-  if (this.domRule && this.domRule.mediaText) {
-    this.mediaText = this.domRule.mediaText;
-  }
-
-  this.cssProperties = this.elementStyle.ruleView.cssProperties;
+    this.mediaText = this.domRule && this.domRule.mediaText ? this.domRule.mediaText : "";
+    this.cssProperties = this.elementStyle.ruleView.cssProperties;
 
-  // Populate the text properties with the style's current authoredText
-  // value, and add in any disabled properties from the store.
-  this.textProps = this._getTextProperties();
-  this.textProps = this.textProps.concat(this._getDisabledProperties());
+    // Populate the text properties with the style's current authoredText
+    // value, and add in any disabled properties from the store.
+    this.textProps = this._getTextProperties();
+    this.textProps = this.textProps.concat(this._getDisabledProperties());
 
-  this.getUniqueSelector = this.getUniqueSelector.bind(this);
-}
-
-Rule.prototype = {
-  mediaText: "",
+    this.getUniqueSelector = this.getUniqueSelector.bind(this);
+  }
 
   get declarations() {
     return this.textProps;
-  },
+  }
 
   get inheritance() {
     if (!this.inherited) {
       return null;
     }
 
     return {
       inherited: this.inherited,
       inheritedSource: this.inheritedSource,
     };
-  },
+  }
 
   get selector() {
     return {
       getUniqueSelector: this.getUniqueSelector,
       matchedSelectors: this.matchedSelectors,
       selectors: this.domRule.selectors,
       selectorText: this.keyframes ? this.domRule.keyText : this.selectorText,
     };
-  },
+  }
 
   get sourceLink() {
     return {
       column: this.ruleColumn,
       line: this.ruleLine,
       mediaText: this.mediaText,
       title: this.title,
     };
-  },
+  }
 
   get title() {
     let title = CssLogic.shortSource(this.sheet);
     if (this.domRule.type !== ELEMENT_STYLE && this.ruleLine > 0) {
       title += ":" + this.ruleLine;
     }
 
     return title + (this.mediaText ? " @media " + this.mediaText : "");
-  },
+  }
 
   get inheritedSource() {
     if (this._inheritedSource) {
       return this._inheritedSource;
     }
     this._inheritedSource = "";
     if (this.inherited) {
       let eltText = this.inherited.displayName;
       if (this.inherited.id) {
         eltText += "#" + this.inherited.id;
       }
       this._inheritedSource =
         STYLE_INSPECTOR_L10N.getFormatStr("rule.inheritedFrom", eltText);
     }
     return this._inheritedSource;
-  },
+  }
 
   get keyframesName() {
     if (this._keyframesName) {
       return this._keyframesName;
     }
     this._keyframesName = "";
     if (this.keyframes) {
       this._keyframesName =
         STYLE_INSPECTOR_L10N.getFormatStr("rule.keyframe", this.keyframes.name);
     }
     return this._keyframesName;
-  },
+  }
 
   get keyframesRule() {
     if (!this.keyframes) {
       return null;
     }
 
     return {
       id: this.keyframes.actorID,
       keyframesName: this.keyframesName,
     };
-  },
+  }
 
   get selectorText() {
     return this.domRule.selectors ? this.domRule.selectors.join(", ") :
       CssLogic.l10n("rule.sourceElement");
-  },
+  }
 
   /**
    * The rule's stylesheet.
    */
   get sheet() {
     return this.domRule ? this.domRule.parentStyleSheet : null;
-  },
+  }
 
   /**
    * The rule's line within a stylesheet
    */
   get ruleLine() {
     return this.domRule ? this.domRule.line : -1;
-  },
+  }
 
   /**
    * The rule's column within a stylesheet
    */
   get ruleColumn() {
     return this.domRule ? this.domRule.column : null;
-  },
+  }
 
   /**
    * Returns the TextProperty with the given id or undefined if it cannot be found.
    *
    * @param {String} id
    *        A TextProperty id.
    * @return {TextProperty|undefined} with the given id in the current Rule or undefined
    * if it cannot be found.
    */
-  getDeclaration: function(id) {
+  getDeclaration(id) {
     return this.textProps.find(textProp => textProp.id === id);
-  },
+  }
 
   /**
    * Returns an unique selector for the CSS rule.
    */
   async getUniqueSelector() {
     let selector = "";
 
     if (this.domRule.selectors) {
@@ -197,44 +192,44 @@ Rule.prototype = {
       // selector from the node which rule this is inherited from.
       selector = await this.inherited.getUniqueSelector();
     } else {
       // This is an inline style from the current node.
       selector = this.elementStyle.ruleView.inspector.selectionCssSelector;
     }
 
     return selector;
-  },
+  }
 
   /**
    * Returns true if the rule matches the creation options
    * specified.
    *
    * @param {Object} options
    *        Creation options. See the Rule constructor for documentation.
    */
-  matches: function(options) {
+  matches(options) {
     return this.domRule === options.rule;
-  },
+  }
 
   /**
    * Create a new TextProperty to include in the rule.
    *
    * @param {String} name
    *        The text property name (such as "background" or "border-top").
    * @param {String} value
    *        The property's value (not including priority).
    * @param {String} priority
    *        The property's priority (either "important" or an empty string).
    * @param {Boolean} enabled
    *        True if the property should be enabled.
    * @param {TextProperty} siblingProp
    *        Optional, property next to which the new property will be added.
    */
-  createProperty: function(name, value, priority, enabled, siblingProp) {
+  createProperty(name, value, priority, enabled, siblingProp) {
     const prop = new TextProperty(this, name, value, priority, enabled);
 
     let ind;
     if (siblingProp) {
       ind = this.textProps.indexOf(siblingProp) + 1;
       this.textProps.splice(ind, 0, prop);
     } else {
       ind = this.textProps.length;
@@ -244,24 +239,24 @@ Rule.prototype = {
     this.applyProperties((modifications) => {
       modifications.createProperty(ind, name, value, priority, enabled);
       // Now that the rule has been updated, the server might have given us data
       // that changes the state of the property. Update it now.
       prop.updateEditor();
     });
 
     return prop;
-  },
+  }
 
   /**
    * Helper function for applyProperties that is called when the actor
    * does not support as-authored styles.  Store disabled properties
    * in the element style's store.
    */
-  _applyPropertiesNoAuthored: function(modifications) {
+  _applyPropertiesNoAuthored(modifications) {
     this.elementStyle.markOverriddenAll();
 
     const disabledProps = [];
 
     for (const prop of this.textProps) {
       if (prop.invisible) {
         continue;
       }
@@ -312,55 +307,55 @@ Rule.prototype = {
             value: "",
             priority: "",
           };
         }
 
         textProp.priority = cssProp.priority;
       }
     });
-  },
+  }
 
   /**
    * A helper for applyProperties that applies properties in the "as
    * authored" case; that is, when the StyleRuleActor supports
    * setRuleText.
    */
-  _applyPropertiesAuthored: function(modifications) {
+  _applyPropertiesAuthored(modifications) {
     return modifications.apply().then(() => {
       // The rewriting may have required some other property values to
       // change, e.g., to insert some needed terminators.  Update the
       // relevant properties here.
       for (const index in modifications.changedDeclarations) {
         const newValue = modifications.changedDeclarations[index];
         this.textProps[index].updateValue(newValue);
       }
       // Recompute and redisplay the computed properties.
       for (const prop of this.textProps) {
         if (!prop.invisible && prop.enabled) {
           prop.updateComputed();
           prop.updateEditor();
         }
       }
     });
-  },
+  }
 
   /**
    * Reapply all the properties in this rule, and update their
    * computed styles.  Will re-mark overridden properties.  Sets the
    * |_applyingModifications| property to a promise which will resolve
    * when the edit has completed.
    *
    * @param {Function} modifier a function that takes a RuleModificationList
    *        (or RuleRewriter) as an argument and that modifies it
    *        to apply the desired edit
    * @return {Promise} a promise which will resolve when the edit
    *        is complete
    */
-  applyProperties: function(modifier) {
+  applyProperties(modifier) {
     // If there is already a pending modification, we have to wait
     // until it settles before applying the next modification.
     const resultPromise =
         promise.resolve(this._applyingModifications).then(() => {
           const modifications = this.domRule.startModifyingProperties(
             this.cssProperties);
           modifier(modifications);
           if (this.domRule.canSetRuleText) {
@@ -373,128 +368,128 @@ Rule.prototype = {
           if (resultPromise === this._applyingModifications) {
             this._applyingModifications = null;
             this.elementStyle._changed();
           }
         }).catch(promiseWarn);
 
     this._applyingModifications = resultPromise;
     return resultPromise;
-  },
+  }
 
   /**
    * Renames a property.
    *
    * @param {TextProperty} property
    *        The property to rename.
    * @param {String} name
    *        The new property name (such as "background" or "border-top").
    * @return {Promise}
    */
-  setPropertyName: function(property, name) {
+  setPropertyName(property, name) {
     if (name === property.name) {
       return Promise.resolve();
     }
 
     const oldName = property.name;
     property.name = name;
     const index = this.textProps.indexOf(property);
     return this.applyProperties(modifications => {
       modifications.renameProperty(index, oldName, name);
     });
-  },
+  }
 
   /**
    * Sets the value and priority of a property, then reapply all properties.
    *
    * @param {TextProperty} property
    *        The property to manipulate.
    * @param {String} value
    *        The property's value (not including priority).
    * @param {String} priority
    *        The property's priority (either "important" or an empty string).
    * @return {Promise}
    */
-  setPropertyValue: function(property, value, priority) {
+  setPropertyValue(property, value, priority) {
     if (value === property.value && priority === property.priority) {
       return Promise.resolve();
     }
 
     property.value = value;
     property.priority = priority;
 
     const index = this.textProps.indexOf(property);
     return this.applyProperties(modifications => {
       modifications.setProperty(index, property.name, value, priority);
     });
-  },
+  }
 
   /**
    * Just sets the value and priority of a property, in order to preview its
    * effect on the content document.
    *
    * @param {TextProperty} property
    *        The property which value will be previewed
    * @param {String} value
    *        The value to be used for the preview
    * @param {String} priority
    *        The property's priority (either "important" or an empty string).
    **@return {Promise}
    */
-  previewPropertyValue: function(property, value, priority) {
+  previewPropertyValue(property, value, priority) {
     const modifications = this.domRule.startModifyingProperties(this.cssProperties);
     modifications.setProperty(this.textProps.indexOf(property),
                               property.name, value, priority);
     return modifications.apply().then(() => {
       // Ensure dispatching a ruleview-changed event
       // also for previews
       this.elementStyle._changed();
     });
-  },
+  }
 
   /**
    * Disables or enables given TextProperty.
    *
    * @param {TextProperty} property
    *        The property to enable/disable
    * @param {Boolean} value
    */
-  setPropertyEnabled: function(property, value) {
+  setPropertyEnabled(property, value) {
     if (property.enabled === !!value) {
       return;
     }
     property.enabled = !!value;
     const index = this.textProps.indexOf(property);
     this.applyProperties((modifications) => {
       modifications.setPropertyEnabled(index, property.name, property.enabled);
     });
-  },
+  }
 
   /**
    * Remove a given TextProperty from the rule and update the rule
    * accordingly.
    *
    * @param {TextProperty} property
    *        The property to be removed
    */
-  removeProperty: function(property) {
+  removeProperty(property) {
     const index = this.textProps.indexOf(property);
     this.textProps.splice(index, 1);
     // Need to re-apply properties in case removing this TextProperty
     // exposes another one.
     this.applyProperties((modifications) => {
       modifications.removeProperty(index, property.name);
     });
-  },
+  }
 
   /**
    * Get the list of TextProperties from the style. Needs
    * to parse the style's authoredText.
    */
-  _getTextProperties: function() {
+  _getTextProperties() {
     const textProps = [];
     const store = this.elementStyle.store;
 
     // Starting with FF49, StyleRuleActors provide parsed declarations.
     let props = this.domRule.declarations;
     if (!props.length) {
       // If the authored text has an invalid property, it will show up
       // as nameless.  Skip these as we don't currently have a good
@@ -514,22 +509,22 @@ Rule.prototype = {
                                                    prop.value);
       const textProp = new TextProperty(this, name, value, prop.priority,
                                       !("commentOffsets" in prop),
                                       invisible);
       textProps.push(textProp);
     }
 
     return textProps;
-  },
+  }
 
   /**
    * Return the list of disabled properties from the store for this rule.
    */
-  _getDisabledProperties: function() {
+  _getDisabledProperties() {
     const store = this.elementStyle.store;
 
     // Include properties from the disabled property store, if any.
     const disabledProps = store.disabled.get(this.domRule);
     if (!disabledProps) {
       return [];
     }
 
@@ -539,23 +534,23 @@ Rule.prototype = {
       const value = store.userProperties.getProperty(this.domRule, prop.name,
                                                    prop.value);
       const textProp = new TextProperty(this, prop.name, value, prop.priority);
       textProp.enabled = false;
       textProps.push(textProp);
     }
 
     return textProps;
-  },
+  }
 
   /**
    * Reread the current state of the rules and rebuild text
    * properties as needed.
    */
-  refresh: function(options) {
+  refresh(options) {
     this.matchedSelectors = options.matchedSelectors || [];
     const newTextProps = this._getTextProperties();
 
     // The element style rule behaves differently on refresh. We basically need to update
     // it to reflect the new text properties exactly. The order might have changed, some
     // properties might have been removed, etc. And we don't need to mark anything as
     // disabled here. The element style rule should always reflect the content of the
     // style attribute.
@@ -597,17 +592,17 @@ Rule.prototype = {
 
     // Add brand new properties.
     this.textProps = this.textProps.concat(brandNewProps);
 
     // Refresh the editor if one already exists.
     if (this.editor) {
       this.editor.populate();
     }
-  },
+  }
 
   /**
    * Update the current TextProperties that match a given property
    * from the authoredText.  Will choose one existing TextProperty to update
    * with the new property's value, and will disable all others.
    *
    * When choosing the best match to reuse, properties will be chosen
    * by assigning a rank and choosing the highest-ranked property:
@@ -621,17 +616,17 @@ Rule.prototype = {
    * If no existing properties match the property, nothing happens.
    *
    * @param {TextProperty} newProp
    *        The current version of the property, as parsed from the
    *        authoredText in Rule._getTextProperties().
    * @return {Boolean} true if a property was updated, false if no properties
    *         were updated.
    */
-  _updateTextProperty: function(newProp) {
+  _updateTextProperty(newProp) {
     const match = { rank: 0, prop: null };
 
     for (const prop of this.textProps) {
       if (prop.name !== newProp.name) {
         continue;
       }
 
       // Mark this property visited.
@@ -672,31 +667,31 @@ Rule.prototype = {
     // If we found a match, update its value with the new text property
     // value.
     if (match.prop) {
       match.prop.set(newProp);
       return true;
     }
 
     return false;
-  },
+  }
 
   /**
    * Jump between editable properties in the UI. If the focus direction is
    * forward, begin editing the next property name if available or focus the
    * new property editor otherwise. If the focus direction is backward,
    * begin editing the previous property value or focus the selector editor if
    * this is the first element in the property list.
    *
    * @param {TextProperty} textProperty
    *        The text property that will be left to focus on a sibling.
    * @param {Number} direction
    *        The move focus direction number.
    */
-  editClosestTextProperty: function(textProperty, direction) {
+  editClosestTextProperty(textProperty, direction) {
     let index = this.textProps.indexOf(textProperty);
 
     if (direction === Services.focus.MOVEFOCUS_FORWARD) {
       for (++index; index < this.textProps.length; ++index) {
         if (!this.textProps[index].invisible) {
           break;
         }
       }
@@ -712,43 +707,43 @@ Rule.prototype = {
         }
       }
       if (index < 0) {
         textProperty.editor.ruleEditor.selectorText.click();
       } else {
         this.textProps[index].editor.valueSpan.click();
       }
     }
-  },
+  }
 
   /**
    * Return a string representation of the rule.
    */
-  stringifyRule: function() {
+  stringifyRule() {
     const selectorText = this.selectorText;
     let cssText = "";
     const terminator = Services.appinfo.OS === "WINNT" ? "\r\n" : "\n";
 
     for (const textProp of this.textProps) {
       if (!textProp.invisible) {
         cssText += "\t" + textProp.stringifyProperty() + terminator;
       }
     }
 
     return selectorText + " {" + terminator + cssText + "}";
-  },
+  }
 
   /**
    * See whether this rule has any non-invisible properties.
    * @return {Boolean} true if there is any visible property, or false
    *         if all properties are invisible
    */
-  hasAnyVisibleProperties: function() {
+  hasAnyVisibleProperties() {
     for (const prop of this.textProps) {
       if (!prop.invisible) {
         return true;
       }
     }
     return false;
-  },
-};
+  }
+}
 
 module.exports = Rule;
--- a/devtools/client/inspector/rules/models/text-property.js
+++ b/devtools/client/inspector/rules/models/text-property.js
@@ -13,86 +13,86 @@ loader.lazyRequireGetter(this, "escapeCS
 /**
  * TextProperty is responsible for the following:
  *   Manages a single property from the authoredText attribute of the
  *     relevant declaration.
  *   Maintains a list of computed properties that come from this
  *     property declaration.
  *   Changes to the TextProperty are sent to its related Rule for
  *     application.
- *
- * @param {Rule} rule
- *        The rule this TextProperty came from.
- * @param {String} name
- *        The text property name (such as "background" or "border-top").
- * @param {String} value
- *        The property's value (not including priority).
- * @param {String} priority
- *        The property's priority (either "important" or an empty string).
- * @param {Boolean} enabled
- *        Whether the property is enabled.
- * @param {Boolean} invisible
- *        Whether the property is invisible. In an inherited rule, only show
- *        the inherited declarations. The other declarations are considered
- *        invisible and does not show up in the UI. These are needed so that
- *        the index of a property in Rule.textProps is the same as the index
- *        coming from parseDeclarations.
  */
-function TextProperty(rule, name, value, priority, enabled = true,
-                      invisible = false) {
-  this.id = name + "_" + generateUUID().toString();
-  this.rule = rule;
-  this.name = name;
-  this.value = value;
-  this.priority = priority;
-  this.enabled = !!enabled;
-  this.invisible = invisible;
-  this.cssProperties = this.rule.elementStyle.ruleView.cssProperties;
-  this.panelDoc = this.rule.elementStyle.ruleView.inspector.panelDoc;
+class TextProperty {
+  /**
+   * @param {Rule} rule
+   *        The rule this TextProperty came from.
+   * @param {String} name
+   *        The text property name (such as "background" or "border-top").
+   * @param {String} value
+   *        The property's value (not including priority).
+   * @param {String} priority
+   *        The property's priority (either "important" or an empty string).
+   * @param {Boolean} enabled
+   *        Whether the property is enabled.
+   * @param {Boolean} invisible
+   *        Whether the property is invisible. In an inherited rule, only show
+   *        the inherited declarations. The other declarations are considered
+   *        invisible and does not show up in the UI. These are needed so that
+   *        the index of a property in Rule.textProps is the same as the index
+   *        coming from parseDeclarations.
+   */
+  constructor(rule, name, value, priority, enabled = true, invisible = false) {
+    this.id = name + "_" + generateUUID().toString();
+    this.rule = rule;
+    this.name = name;
+    this.value = value;
+    this.priority = priority;
+    this.enabled = !!enabled;
+    this.invisible = invisible;
+    this.cssProperties = this.rule.elementStyle.ruleView.cssProperties;
+    this.panelDoc = this.rule.elementStyle.ruleView.inspector.panelDoc;
 
-  this.updateComputed();
-}
+    this.updateComputed();
+  }
 
-TextProperty.prototype = {
   get computedProperties() {
     return this.computed
       .filter(computed => computed.name !== this.name)
       .map(computed => {
         return {
           isOverridden: computed.overridden,
           name: computed.name,
           priority: computed.priority,
           value: computed.value,
         };
       });
-  },
+  }
 
   /**
    * See whether this property's name is known.
    *
    * @return {Boolean} true if the property name is known, false otherwise.
    */
   get isKnownProperty() {
     return this.cssProperties.isKnown(this.name);
-  },
+  }
 
   /**
    * Update the editor associated with this text property,
    * if any.
    */
-  updateEditor: function() {
+  updateEditor() {
     if (this.editor) {
       this.editor.update();
     }
-  },
+  }
 
   /**
    * Update the list of computed properties for this text property.
    */
-  updateComputed: function() {
+  updateComputed() {
     if (!this.name) {
       return;
     }
 
     // This is a bit funky.  To get the list of computed properties
     // for this text property, we'll set the property on a dummy element
     // and see what the computed style looks like.
     const dummyElement = this.rule.elementStyle.ruleView.dummyElement;
@@ -110,138 +110,138 @@ TextProperty.prototype = {
     for (const prop of subProps) {
       this.computed.push({
         textProp: this,
         name: prop,
         value: dummyStyle.getPropertyValue(prop),
         priority: dummyStyle.getPropertyPriority(prop),
       });
     }
-  },
+  }
 
   /**
    * Set all the values from another TextProperty instance into
    * this TextProperty instance.
    *
    * @param {TextProperty} prop
    *        The other TextProperty instance.
    */
-  set: function(prop) {
+  set(prop) {
     let changed = false;
     for (const item of ["name", "value", "priority", "enabled"]) {
       if (this[item] !== prop[item]) {
         this[item] = prop[item];
         changed = true;
       }
     }
 
     if (changed) {
       this.updateEditor();
     }
-  },
+  }
 
-  setValue: function(value, priority, force = false) {
+  setValue(value, priority, force = false) {
     const store = this.rule.elementStyle.store;
 
     if (this.editor && value !== this.editor.committed.value || force) {
       store.userProperties.setProperty(this.rule.domRule, this.name, value);
     }
 
     return this.rule.setPropertyValue(this, value, priority)
       .then(() => this.updateEditor());
-  },
+  }
 
   /**
    * Called when the property's value has been updated externally, and
    * the property and editor should update to reflect that value.
    *
    * @param {String} value
    *        Property value
    */
-  updateValue: function(value) {
+  updateValue(value) {
     if (value !== this.value) {
       this.value = value;
       this.updateEditor();
     }
-  },
+  }
 
-  setName: async function(name) {
+  async setName(name) {
     if (name !== this.name && this.editor) {
       const store = this.rule.elementStyle.store;
       store.userProperties.setProperty(this.rule.domRule, name,
                                        this.editor.committed.value);
     }
 
     await this.rule.setPropertyName(this, name);
     this.updateEditor();
-  },
+  }
 
-  setEnabled: function(value) {
+  setEnabled(value) {
     this.rule.setPropertyEnabled(this, value);
     this.updateEditor();
-  },
+  }
 
-  remove: function() {
+  remove() {
     this.rule.removeProperty(this);
-  },
+  }
 
   /**
    * Return a string representation of the rule property.
    */
-  stringifyProperty: function() {
+  stringifyProperty() {
     // Get the displayed property value
     let declaration = this.name + ": " + this.editor.valueSpan.textContent +
       ";";
 
     // Comment out property declarations that are not enabled
     if (!this.enabled) {
       declaration = "/* " + escapeCSSComment(declaration) + " */";
     }
 
     return declaration;
-  },
+  }
 
   /**
    * Validate this property. Does it make sense for this value to be assigned
    * to this property name?
    *
    * @return {Boolean} true if the whole CSS declaration is valid, false otherwise.
    */
-  isValid: function() {
+  isValid() {
     const selfIndex = this.rule.textProps.indexOf(this);
 
     // When adding a new property in the rule-view, the TextProperty object is
     // created right away before the rule gets updated on the server, so we're
     // not going to find the corresponding declaration object yet. Default to
     // true.
     if (!this.rule.domRule.declarations[selfIndex]) {
       return true;
     }
 
     return this.rule.domRule.declarations[selfIndex].isValid;
-  },
+  }
 
   /**
    * Validate the name of this property.
    *
    * @return {Boolean} true if the property name is valid, false otherwise.
    */
-  isNameValid: function() {
+  isNameValid() {
     const selfIndex = this.rule.textProps.indexOf(this);
 
     // When adding a new property in the rule-view, the TextProperty object is
     // created right away before the rule gets updated on the server, so we're
     // not going to find the corresponding declaration object yet. Default to
     // true.
     if (!this.rule.domRule.declarations[selfIndex]) {
       return true;
     }
 
     // Starting with FF61, StyleRuleActor provides an accessor to signal if the property
     // name is valid. If we don't have this, assume the name is valid. In use, rely on
     // isValid() as a guard against false positives.
     return (this.rule.domRule.declarations[selfIndex].isNameValid !== undefined)
       ? this.rule.domRule.declarations[selfIndex].isNameValid
       : true;
-  },
-};
+  }
+}
 
 module.exports = TextProperty;
--- a/devtools/client/inspector/rules/models/user-properties.js
+++ b/devtools/client/inspector/rules/models/user-properties.js
@@ -3,83 +3,83 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /**
  * Store of CSSStyleDeclarations mapped to properties that have been changed by
  * the user.
  */
-function UserProperties() {
-  this.map = new Map();
-}
+class UserProperties {
+  constructor() {
+    this.map = new Map();
+  }
 
-UserProperties.prototype = {
   /**
    * Get a named property for a given CSSStyleDeclaration.
    *
    * @param {CSSStyleDeclaration} style
    *        The CSSStyleDeclaration against which the property is mapped.
    * @param {String} name
    *        The name of the property to get.
    * @param {String} value
    *        Default value.
    * @return {String}
    *        The property value if it has previously been set by the user, null
    *        otherwise.
    */
-  getProperty: function(style, name, value) {
+  getProperty(style, name, value) {
     const key = this.getKey(style);
     const entry = this.map.get(key, null);
 
     if (entry && name in entry) {
       return entry[name];
     }
     return value;
-  },
+  }
 
   /**
    * Set a named property for a given CSSStyleDeclaration.
    *
    * @param {CSSStyleDeclaration} style
    *        The CSSStyleDeclaration against which the property is to be mapped.
    * @param {String} name
    *        The name of the property to set.
    * @param {String} userValue
    *        The value of the property to set.
    */
-  setProperty: function(style, name, userValue) {
+  setProperty(style, name, userValue) {
     const key = this.getKey(style, name);
     const entry = this.map.get(key, null);
 
     if (entry) {
       entry[name] = userValue;
     } else {
       const props = {};
       props[name] = userValue;
       this.map.set(key, props);
     }
-  },
+  }
 
   /**
    * Check whether a named property for a given CSSStyleDeclaration is stored.
    *
    * @param {CSSStyleDeclaration} style
    *        The CSSStyleDeclaration against which the property would be mapped.
    * @param {String} name
    *        The name of the property to check.
    */
-  contains: function(style, name) {
+  contains(style, name) {
     const key = this.getKey(style, name);
     const entry = this.map.get(key, null);
     return !!entry && name in entry;
-  },
+  }
 
-  getKey: function(style, name) {
+  getKey(style, name) {
     return style.actorID + ":" + name;
-  },
+  }
 
-  clear: function() {
+  clear() {
     this.map.clear();
-  },
-};
+  }
+}
 
 module.exports = UserProperties;
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -2075,17 +2075,17 @@ nsresult nsGlobalWindowOuter::SetNewDocu
       JS::ExposeObjectToActiveJS(newInnerGlobal);
       JS::Rooted<JSObject*> outerObject(
           cx, NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
       if (!outerObject) {
         NS_ERROR("out of memory");
         return NS_ERROR_FAILURE;
       }
 
-      JS::Rooted<JSObject*> obj(cx, GetWrapperPreserveColor());
+      JS::Rooted<JSObject*> obj(cx, GetWrapper());
 
       MOZ_ASSERT(js::IsWindowProxy(obj));
 
       js::SetProxyReservedSlot(obj, OUTER_WINDOW_SLOT,
                                js::PrivateValue(nullptr));
       js::SetProxyReservedSlot(outerObject, OUTER_WINDOW_SLOT,
                                js::PrivateValue(nullptr));
       js::SetProxyReservedSlot(obj, HOLDER_WEAKMAP_SLOT, JS::UndefinedValue());
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -339,31 +339,41 @@ class JS_FRIEND_API GCCellPtr {
 
   bool mayBeOwnedByOtherRuntimeSlow() const;
 
   JS::TraceKind outOfLineKind() const;
 
   uintptr_t ptr;
 };
 
-// Unwraps the given GCCellPtr and calls the given functor with a template
-// argument of the actual type of the pointer.
-template <typename F, typename... Args>
-auto DispatchTyped(F f, GCCellPtr thing, Args&&... args) {
+// Unwraps the given GCCellPtr, calls the functor |f| with a template argument
+// of the actual type of the pointer, and returns the result.
+template <typename F>
+auto MapGCThingTyped(GCCellPtr thing, F&& f) {
   switch (thing.kind()) {
 #define JS_EXPAND_DEF(name, type, _) \
   case JS::TraceKind::name:          \
-    return f(&thing.as<type>(), std::forward<Args>(args)...);
+    return f(&thing.as<type>());
     JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF);
 #undef JS_EXPAND_DEF
     default:
-      MOZ_CRASH("Invalid trace kind in DispatchTyped for GCCellPtr.");
+      MOZ_CRASH("Invalid trace kind in MapGCThingTyped for GCCellPtr.");
   }
 }
 
+// Unwraps the given GCCellPtr and calls the functor |f| with a template
+// argument of the actual type of the pointer. Doesn't return anything.
+template <typename F>
+void ApplyGCThingTyped(GCCellPtr thing, F&& f) {
+  // This function doesn't do anything but is supplied for symmetry with other
+  // MapGCThingTyped/ApplyGCThingTyped implementations that have to wrap the
+  // functor to return a dummy value that is ignored.
+  MapGCThingTyped(thing, f);
+}
+
 } /* namespace JS */
 
 // These are defined in the toplevel namespace instead of within JS so that
 // they won't shadow other operator== overloads (see bug 1456512.)
 
 inline bool operator==(const JS::GCCellPtr& ptr1, const JS::GCCellPtr& ptr2) {
   return ptr1.asCell() == ptr2.asCell();
 }
--- a/js/public/Id.h
+++ b/js/public/Id.h
@@ -15,16 +15,18 @@
 // jsid is not a valid id and only arises as an exceptional API return value,
 // such as in JS_NextProperty. Embeddings must not pass JSID_VOID into JSAPI
 // entry points expecting a jsid and do not need to handle JSID_VOID in hooks
 // receiving a jsid except when explicitly noted in the API contract.
 //
 // A jsid is not implicitly convertible to or from a Value; JS_ValueToId or
 // JS_IdToValue must be used instead.
 
+#include "mozilla/Maybe.h"
+
 #include "jstypes.h"
 
 #include "js/HeapAPI.h"
 #include "js/RootingAPI.h"
 #include "js/TypeDecls.h"
 #include "js/Utility.h"
 
 // All jsids with the low bit set are integer ids. This means the other type
@@ -202,27 +204,35 @@ struct BarrierMethods<jsid> {
   static void postBarrier(jsid* idp, jsid prev, jsid next) {}
   static void exposeToJS(jsid id) {
     if (JSID_IS_GCTHING(id)) {
       js::gc::ExposeGCThingToActiveJS(JSID_TO_GCTHING(id));
     }
   }
 };
 
-// If the jsid is a GC pointer type, convert to that type and call |f| with
-// the pointer. If the jsid is not a GC type, calls F::defaultValue.
-template <typename F, typename... Args>
-auto DispatchTyped(F f, const jsid& id, Args&&... args) {
+// If the jsid is a GC pointer type, convert to that type and call |f| with the
+// pointer and return the result wrapped in a Maybe, otherwise return None().
+template <typename F>
+auto MapGCThingTyped(const jsid& id, F&& f) {
   if (JSID_IS_STRING(id)) {
-    return f(JSID_TO_STRING(id), std::forward<Args>(args)...);
+    return mozilla::Some(f(JSID_TO_STRING(id)));
   }
   if (JSID_IS_SYMBOL(id)) {
-    return f(JSID_TO_SYMBOL(id), std::forward<Args>(args)...);
+    return mozilla::Some(f(JSID_TO_SYMBOL(id)));
   }
   MOZ_ASSERT(!JSID_IS_GCTHING(id));
-  return F::defaultValue(id);
+  using ReturnType = decltype(f(static_cast<JSString*>(nullptr)));
+  return mozilla::Maybe<ReturnType>();
+}
+
+// If the jsid is a GC pointer type, convert to that type and call |f| with the
+// pointer. Return whether this happened.
+template <typename F>
+bool ApplyGCThingTyped(const jsid& id, F&& f) {
+  return MapGCThingTyped(id, [&f](auto t) { f(t); return true; }).isSome();
 }
 
 #undef id
 
 }  // namespace js
 
 #endif /* js_Id_h */
--- a/js/public/TraceKind.h
+++ b/js/public/TraceKind.h
@@ -221,25 +221,38 @@ auto DispatchTraceKindTyped(F f, JS::Tra
     JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF);
 #undef JS_EXPAND_DEF
     default:
       MOZ_CRASH("Invalid trace kind in DispatchTraceKindTyped.");
   }
 }
 #undef JS_DEPENDENT_TEMPLATE_HINT
 
-template <typename F, typename... Args>
-auto DispatchTraceKindTyped(F f, void* thing, JS::TraceKind traceKind,
-                            Args&&... args) {
+// Given a GC thing specified by pointer and trace kind, calls the functor |f|
+// with a template argument of the actual type of the pointer and returns the
+// result.
+template <typename F>
+auto MapGCThingTyped(void* thing, JS::TraceKind traceKind, F&& f) {
   switch (traceKind) {
 #define JS_EXPAND_DEF(name, type, _) \
   case JS::TraceKind::name:          \
-    return f(static_cast<type*>(thing), std::forward<Args>(args)...);
+    return f(static_cast<type*>(thing));
     JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF);
 #undef JS_EXPAND_DEF
     default:
-      MOZ_CRASH("Invalid trace kind in DispatchTraceKindTyped.");
+      MOZ_CRASH("Invalid trace kind in MapGCThingTyped.");
   }
 }
 
+// Given a GC thing specified by pointer and trace kind, calls the functor |f|
+// with a template argument of the actual type of the pointer and ignores the
+// result.
+template <typename F>
+void ApplyGCThingTyped(void* thing, JS::TraceKind traceKind, F&& f) {
+  // This function doesn't do anything but is supplied for symmetry with other
+  // MapGCThingTyped/ApplyGCThingTyped implementations that have to wrap the
+  // functor to return a dummy value that is ignored.
+  MapGCThingTyped(thing, traceKind, std::move(f));
+}
+
 }  // namespace JS
 
 #endif  // js_TraceKind_h
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -10,16 +10,17 @@
 #define js_Value_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Casting.h"
 #include "mozilla/Compiler.h"
 #include "mozilla/EndianUtils.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
 
 #include <limits> /* for std::numeric_limits */
 
 #include "js-config.h"
 #include "jstypes.h"
 
 #include "js/GCAPI.h"
 #include "js/RootingAPI.h"
@@ -1303,64 +1304,58 @@ class HeapBase<JS::Value, Wrapper>
     if (arg) {
       setObject(*arg);
     } else {
       setNull();
     }
   }
 };
 
-/*
- * If the Value is a GC pointer type, convert to that type and call |f| with
- * the pointer. If the Value is not a GC type, calls F::defaultValue.
- */
-template <typename F, typename... Args>
-auto DispatchTyped(F f, const JS::Value& val, Args&&... args) {
+
+// If the Value is a GC pointer type, call |f| with the pointer cast to that
+// type and return the result wrapped in a Maybe, otherwise return None().
+template <typename F>
+auto MapGCThingTyped(const JS::Value& val, F&& f) {
   if (val.isString()) {
     JSString* str = val.toString();
     MOZ_ASSERT(gc::IsCellPointerValid(str));
-    return f(str, std::forward<Args>(args)...);
+    return mozilla::Some(f(str));
   }
   if (val.isObject()) {
     JSObject* obj = &val.toObject();
     MOZ_ASSERT(gc::IsCellPointerValid(obj));
-    return f(obj, std::forward<Args>(args)...);
+    return mozilla::Some(f(obj));
   }
   if (val.isSymbol()) {
     JS::Symbol* sym = val.toSymbol();
     MOZ_ASSERT(gc::IsCellPointerValid(sym));
-    return f(sym, std::forward<Args>(args)...);
+    return mozilla::Some(f(sym));
   }
 #ifdef ENABLE_BIGINT
   if (val.isBigInt()) {
     JS::BigInt* bi = val.toBigInt();
     MOZ_ASSERT(gc::IsCellPointerValid(bi));
-    return f(bi, std::forward<Args>(args)...);
+    return mozilla::Some(f(bi));
   }
 #endif
   if (MOZ_UNLIKELY(val.isPrivateGCThing())) {
     MOZ_ASSERT(gc::IsCellPointerValid(val.toGCThing()));
-    return DispatchTyped(f, val.toGCCellPtr(), std::forward<Args>(args)...);
+    return mozilla::Some(MapGCThingTyped(val.toGCCellPtr(), std::move(f)));
   }
   MOZ_ASSERT(!val.isGCThing());
-  return F::defaultValue(val);
+  using ReturnType = decltype(f(static_cast<JSObject*>(nullptr)));
+  return mozilla::Maybe<ReturnType>();
 }
 
-template <class S>
-struct VoidDefaultAdaptor {
-  static void defaultValue(const S&) {}
-};
-template <class S>
-struct IdentityDefaultAdaptor {
-  static S defaultValue(const S& v) { return v; }
-};
-template <class S, bool v>
-struct BoolDefaultAdaptor {
-  static bool defaultValue(const S&) { return v; }
-};
+// If the Value is a GC pointer type, call |f| with the pointer cast to that
+// type. Return whether this happened.
+template <typename F>
+bool ApplyGCThingTyped(const JS::Value& val, F&& f) {
+  return MapGCThingTyped(val, [&f](auto t) { f(t); return true; }).isSome();
+}
 
 static inline JS::Value PoisonedObjectValue(uintptr_t poison) {
   JS::Value v;
   v.setObjectNoCheck(reinterpret_cast<JSObject*>(poison));
   return v;
 }
 
 }  // namespace js
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -2,16 +2,17 @@
  * 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 "builtin/ModuleObject.h"
 
 #include "mozilla/EnumSet.h"
+#include "mozilla/ScopeExit.h"
 
 #include "builtin/Promise.h"
 #include "builtin/SelfHostingDefines.h"
 #include "frontend/ParseNode.h"
 #include "frontend/SharedContext.h"
 #include "gc/FreeOp.h"
 #include "gc/Policy.h"
 #include "gc/Tracer.h"
@@ -1672,19 +1673,16 @@ JSObject* js::CallModuleResolveHook(JSCo
     return nullptr;
   }
 
   return result;
 }
 
 JSObject* js::StartDynamicModuleImport(JSContext* cx, HandleScript script,
                                        HandleValue specifierArg) {
-  RootedValue referencingPrivate(cx,
-                                 script->sourceObject()->canonicalPrivate());
-
   RootedObject promiseConstructor(cx, JS::GetPromiseConstructor(cx));
   if (!promiseConstructor) {
     return nullptr;
   }
 
   RootedObject promiseObject(cx, JS::NewPromiseObject(cx, nullptr));
   if (!promiseObject) {
     return nullptr;
@@ -1705,17 +1703,23 @@ JSObject* js::StartDynamicModuleImport(J
   RootedString specifier(cx, ToString(cx, specifierArg));
   if (!specifier) {
     if (!RejectPromiseWithPendingError(cx, promise)) {
       return nullptr;
     }
     return promise;
   }
 
+  RootedValue referencingPrivate(cx,
+                                 script->sourceObject()->canonicalPrivate());
+  cx->runtime()->addRefScriptPrivate(referencingPrivate);
+
   if (!importHook(cx, referencingPrivate, specifier, promise)) {
+    cx->runtime()->releaseScriptPrivate(referencingPrivate);
+
     // If there's no exception pending then the script is terminating
     // anyway, so just return nullptr.
     if (!cx->isExceptionPending() ||
         !RejectPromiseWithPendingError(cx, promise)) {
       return nullptr;
     }
     return promise;
   }
@@ -1724,16 +1728,20 @@ JSObject* js::StartDynamicModuleImport(J
 }
 
 bool js::FinishDynamicModuleImport(JSContext* cx,
                                    HandleValue referencingPrivate,
                                    HandleString specifier,
                                    HandleObject promiseArg) {
   Handle<PromiseObject*> promise = promiseArg.as<PromiseObject>();
 
+  auto releasePrivate = mozilla::MakeScopeExit([&] {
+    cx->runtime()->releaseScriptPrivate(referencingPrivate);
+  });
+
   if (cx->isExceptionPending()) {
     return RejectPromiseWithPendingError(cx, promise);
   }
 
   RootedObject result(cx,
                       CallModuleResolveHook(cx, referencingPrivate, specifier));
   if (!result) {
     return RejectPromiseWithPendingError(cx, promise);
--- a/js/src/gc/Barrier.cpp
+++ b/js/src/gc/Barrier.cpp
@@ -77,44 +77,27 @@ AutoTouchingGrayThings::AutoTouchingGray
 AutoTouchingGrayThings::~AutoTouchingGrayThings() {
   JSContext* cx = TlsContext.get();
   MOZ_ASSERT(cx->isTouchingGrayThings);
   cx->isTouchingGrayThings--;
 }
 
 #endif  // DEBUG
 
-template <typename S>
-template <typename T>
-void ReadBarrierFunctor<S>::operator()(T* t) {
-  InternalBarrierMethods<T*>::readBarrier(t);
+/* static */ void InternalBarrierMethods<Value>::readBarrier(const Value& v) {
+  ApplyGCThingTyped(v, [](auto t) { t->readBarrier(t); });
 }
 
-// All GC things may be held in a Value, either publicly or as a private GC
-// thing.
-#define JS_EXPAND_DEF(name, type, _) \
-  template void ReadBarrierFunctor<JS::Value>::operator()<type>(type*);
-JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF);
-#undef JS_EXPAND_DEF
-
-template <typename S>
-template <typename T>
-void PreBarrierFunctor<S>::operator()(T* t) {
-  InternalBarrierMethods<T*>::preBarrier(t);
+/* static */ void InternalBarrierMethods<Value>::preBarrier(const Value& v) {
+  ApplyGCThingTyped(v, [](auto t) { t->writeBarrierPre(t); });
 }
 
-// All GC things may be held in a Value, either publicly or as a private GC
-// thing.
-#define JS_EXPAND_DEF(name, type, _) \
-  template void PreBarrierFunctor<JS::Value>::operator()<type>(type*);
-JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF);
-#undef JS_EXPAND_DEF
-
-template void PreBarrierFunctor<jsid>::operator()<JS::Symbol>(JS::Symbol*);
-template void PreBarrierFunctor<jsid>::operator()<JSString>(JSString*);
+/* static */ void InternalBarrierMethods<jsid>::preBarrier(jsid id) {
+  ApplyGCThingTyped(id, [](auto t) { t->writeBarrierPre(t); });
+}
 
 template <typename T>
 /* static */ bool MovableCellHasher<T>::hasHash(const Lookup& l) {
   if (!l) {
     return true;
   }
 
   return l->zoneFromAnyThread()->hasUniqueId(l);
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -267,35 +267,21 @@ struct InternalBarrierMethods<T*> {
 
   static void readBarrier(T* v) { T::readBarrier(v); }
 
 #ifdef DEBUG
   static void assertThingIsNotGray(T* v) { return T::assertThingIsNotGray(v); }
 #endif
 };
 
-template <typename S>
-struct PreBarrierFunctor : public VoidDefaultAdaptor<S> {
-  template <typename T>
-  void operator()(T* t);
-};
-
-template <typename S>
-struct ReadBarrierFunctor : public VoidDefaultAdaptor<S> {
-  template <typename T>
-  void operator()(T* t);
-};
-
 template <>
 struct InternalBarrierMethods<Value> {
   static bool isMarkable(const Value& v) { return v.isGCThing(); }
 
-  static void preBarrier(const Value& v) {
-    DispatchTyped(PreBarrierFunctor<Value>(), v);
-  }
+  static void preBarrier(const Value& v);
 
   static MOZ_ALWAYS_INLINE void postBarrier(Value* vp, const Value& prev,
                                             const Value& next) {
     MOZ_ASSERT(!CurrentThreadIsIonCompiling());
     MOZ_ASSERT(vp);
 
     // If the target needs an entry, add it.
     js::gc::StoreBuffer* sb;
@@ -314,33 +300,29 @@ struct InternalBarrierMethods<Value> {
     }
     // Remove the prev entry if the new value does not need it.
     if ((prev.isObject() || prev.isString()) &&
         (sb = prev.toGCThing()->storeBuffer())) {
       sb->unputValue(vp);
     }
   }
 
-  static void readBarrier(const Value& v) {
-    DispatchTyped(ReadBarrierFunctor<Value>(), v);
-  }
+  static void readBarrier(const Value& v);
 
 #ifdef DEBUG
   static void assertThingIsNotGray(const Value& v) {
     JS::AssertValueIsNotGray(v);
   }
 #endif
 };
 
 template <>
 struct InternalBarrierMethods<jsid> {
   static bool isMarkable(jsid id) { return JSID_IS_GCTHING(id); }
-  static void preBarrier(jsid id) {
-    DispatchTyped(PreBarrierFunctor<jsid>(), id);
-  }
+  static void preBarrier(jsid id);
   static void postBarrier(jsid* idp, jsid prev, jsid next) {}
 #ifdef DEBUG
   static void assertThingIsNotGray(jsid id) { JS::AssertIdIsNotGray(id); }
 #endif
 };
 
 template <typename T>
 static inline void AssertTargetIsNotGray(const T& v) {
--- a/js/src/gc/FindSCCs.h
+++ b/js/src/gc/FindSCCs.h
@@ -7,84 +7,72 @@
 #ifndef gc_FindSCCs_h
 #define gc_FindSCCs_h
 
 #include "mozilla/Move.h"
 
 #include "jsfriendapi.h"
 #include "jsutil.h"
 
+#include "js/HashTable.h"
+
 namespace js {
 namespace gc {
 
-template <class Node>
+template <typename Node>
 struct GraphNodeBase {
-  Node* gcNextGraphNode;
-  Node* gcNextGraphComponent;
-  unsigned gcDiscoveryTime;
-  unsigned gcLowLink;
+  using NodeSet =
+      js::HashSet<Node*, js::DefaultHasher<Node*>, js::SystemAllocPolicy>;
 
-  GraphNodeBase()
-      : gcNextGraphNode(nullptr),
-        gcNextGraphComponent(nullptr),
-        gcDiscoveryTime(0),
-        gcLowLink(0) {}
-
-  ~GraphNodeBase() {}
+  NodeSet gcGraphEdges;
+  Node* gcNextGraphNode = nullptr;
+  Node* gcNextGraphComponent = nullptr;
+  unsigned gcDiscoveryTime = 0;
+  unsigned gcLowLink = 0;
 
   Node* nextNodeInGroup() const {
     if (gcNextGraphNode &&
         gcNextGraphNode->gcNextGraphComponent == gcNextGraphComponent) {
       return gcNextGraphNode;
     }
     return nullptr;
   }
 
   Node* nextGroup() const { return gcNextGraphComponent; }
 };
 
 /*
  * Find the strongly connected components of a graph using Tarjan's algorithm,
  * and return them in topological order.
  *
- * Nodes derive from GraphNodeBase and implement findGraphEdges, which calls
- * finder.addEdgeTo to describe the outgoing edges from that node:
+ * Nodes derive from GraphNodeBase and add target edge pointers to
+ * sourceNode.gcGraphEdges to describe the graph:
  *
- * struct MyComponentFinder;
- *
- * struct MyGraphNode : public GraphNodeBase
+ * struct MyGraphNode : public GraphNodeBase<MyGraphNode>
  * {
- *     void findOutgoingEdges(MyComponentFinder& finder)
- *     {
- *         for edge in my_outgoing_edges:
- *             if is_relevant(edge):
- *                 finder.addEdgeTo(edge.destination)
- *     }
+ *   ...
  * }
  *
- * struct MyComponentFinder : public ComponentFinder<MyGraphNode,
- *                                                   MyComponentFinder>
- * {
- *     ...
- * };
+ * MyGraphNode node1, node2, node3;
+ * node1.gcGraphEdges.put(node2); // Error checking elided.
+ * node2.gcGraphEdges.put(node3);
+ * node3.gcGraphEdges.put(node2);
  *
- * MyComponentFinder finder;
- * finder.addNode(v);
+ * ComponentFinder<MyGraphNode> finder;
+ * finder.addNode(node1);
+ * finder.addNode(node2);
+ * finder.addNode(node3);
+ * MyGraphNode* result = finder.getResultsList();
  */
 
-template <typename Node, typename Derived>
+template <typename Node>
 class ComponentFinder {
  public:
   explicit ComponentFinder(uintptr_t sl)
-      : clock(1),
-        stack(nullptr),
-        firstComponent(nullptr),
-        cur(nullptr),
-        stackLimit(sl),
-        stackFull(false) {}
+      : stackLimit(sl) {}
 
   ~ComponentFinder() {
     MOZ_ASSERT(!stack);
     MOZ_ASSERT(!firstComponent);
   }
 
   /* Forces all nodes to be added to a single component. */
   void useOneComponent() { stackFull = true; }
@@ -126,52 +114,52 @@ class ComponentFinder {
   }
 
   static void mergeGroups(Node* first) {
     for (Node* v = first; v; v = v->gcNextGraphNode) {
       v->gcNextGraphComponent = nullptr;
     }
   }
 
- public:
-  /* Call from implementation of GraphNodeBase::findOutgoingEdges(). */
+ private:
+  // Constant used to indicate an unprocessed vertex.
+  static const unsigned Undefined = 0;
+
+  // Constant used to indicate a processed vertex that is no longer on the
+  // stack.
+  static const unsigned Finished = (unsigned)-1;
+
   void addEdgeTo(Node* w) {
     if (w->gcDiscoveryTime == Undefined) {
       processNode(w);
       cur->gcLowLink = Min(cur->gcLowLink, w->gcLowLink);
     } else if (w->gcDiscoveryTime != Finished) {
       cur->gcLowLink = Min(cur->gcLowLink, w->gcDiscoveryTime);
     }
   }
 
- private:
-  /* Constant used to indicate an unprocessed vertex. */
-  static const unsigned Undefined = 0;
-
-  // Constant used to indicate an processed vertex that is no longer on the
-  // stack.
-  static const unsigned Finished = (unsigned)-1;
-
   void processNode(Node* v) {
     v->gcDiscoveryTime = clock;
     v->gcLowLink = clock;
     ++clock;
 
     v->gcNextGraphNode = stack;
     stack = v;
 
     int stackDummy;
     if (stackFull || !JS_CHECK_STACK_SIZE(stackLimit, &stackDummy)) {
       stackFull = true;
       return;
     }
 
     Node* old = cur;
     cur = v;
-    cur->findOutgoingEdges(*static_cast<Derived*>(this));
+    for (auto r = cur->gcGraphEdges.all(); !r.empty(); r.popFront()) {
+      addEdgeTo(r.front());
+    }
     cur = old;
 
     if (stackFull) {
       return;
     }
 
     if (v->gcLowLink == v->gcDiscoveryTime) {
       Node* nextComponent = firstComponent;
@@ -196,20 +184,20 @@ class ComponentFinder {
          */
         w->gcNextGraphNode = firstComponent;
         firstComponent = w;
       } while (w != v);
     }
   }
 
  private:
-  unsigned clock;
-  Node* stack;
-  Node* firstComponent;
-  Node* cur;
+  unsigned clock = 1;
+  Node* stack = nullptr;
+  Node* firstComponent = nullptr;
+  Node* cur = nullptr;
   uintptr_t stackLimit;
-  bool stackFull;
+  bool stackFull = false;
 };
 
 } /* namespace gc */
 } /* namespace js */
 
 #endif /* gc_FindSCCs_h */
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -3669,31 +3669,26 @@ void GCRuntime::freeFromBackgroundThread
       rt->defaultFreeOp()->free_(r.front());
     }
   } while (!lifoBlocksToFree.ref().isEmpty() ||
            !buffersToFreeAfterMinorGC.ref().empty());
 }
 
 void GCRuntime::waitBackgroundFreeEnd() { freeTask.join(); }
 
-struct IsAboutToBeFinalizedFunctor {
-  template <typename T>
-  bool operator()(Cell** t) {
-    mozilla::DebugOnly<const Cell*> prior = *t;
-    bool result = IsAboutToBeFinalizedUnbarriered(reinterpret_cast<T**>(t));
+/* static */ bool UniqueIdGCPolicy::needsSweep(Cell** cellp, uint64_t*) {
+  Cell* cell = *cellp;
+  return MapGCThingTyped(cell, cell->getTraceKind(), [](auto t) {
+    mozilla::DebugOnly<const Cell*> prior = t;
+    bool result = IsAboutToBeFinalizedUnbarriered(&t);
     // Sweep should not have to deal with moved pointers, since moving GC
     // handles updating the UID table manually.
-    MOZ_ASSERT(*t == prior);
+    MOZ_ASSERT(t == prior);
     return result;
-  }
-};
-
-/* static */ bool UniqueIdGCPolicy::needsSweep(Cell** cell, uint64_t*) {
-  return DispatchTraceKindTyped(IsAboutToBeFinalizedFunctor(),
-                                (*cell)->getTraceKind(), cell);
+  });
 }
 
 void JS::Zone::sweepUniqueIds() { uniqueIds().sweep(); }
 
 void Realm::destroy(FreeOp* fop) {
   JSRuntime* rt = fop->runtime();
   if (auto callback = rt->destroyRealmCallback) {
     callback(fop, this);
@@ -4032,25 +4027,20 @@ static bool InCrossCompartmentMap(JSObje
         ToMarkable(value.unbarrieredGet()) == src) {
       return true;
     }
   }
 
   return false;
 }
 
-struct MaybeCompartmentFunctor {
-  template <typename T>
-  JS::Compartment* operator()(T* t) {
+void CompartmentCheckTracer::onChild(const JS::GCCellPtr& thing) {
+  Compartment* comp = MapGCThingTyped(thing, [](auto t) {
     return t->maybeCompartment();
-  }
-};
-
-void CompartmentCheckTracer::onChild(const JS::GCCellPtr& thing) {
-  Compartment* comp = DispatchTyped(MaybeCompartmentFunctor(), thing);
+  });
   if (comp && compartment) {
     MOZ_ASSERT(comp == compartment ||
                (srcKind == JS::TraceKind::Object &&
                 InCrossCompartmentMap(static_cast<JSObject*>(src), thing)));
   } else {
     TenuredCell* tenured = TenuredCell::fromPointer(thing.asCell());
     Zone* thingZone = tenured->zoneFromAnyThread();
     MOZ_ASSERT(thingZone == zone || thingZone->isAtomsZone());
@@ -4067,18 +4057,19 @@ void GCRuntime::checkForCompartmentMisma
   AutoAssertEmptyNursery empty(cx);
   for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
     trc.zone = zone;
     for (auto thingKind : AllAllocKinds()) {
       for (auto i = zone->cellIter<TenuredCell>(thingKind, empty); !i.done();
            i.next()) {
         trc.src = i.getCell();
         trc.srcKind = MapAllocToTraceKind(thingKind);
-        trc.compartment = DispatchTraceKindTyped(MaybeCompartmentFunctor(),
-                                                 trc.src, trc.srcKind);
+        trc.compartment = MapGCThingTyped(trc.src, trc.srcKind, [](auto t) {
+          return t->maybeCompartment();
+        });
         js::TraceChildren(&trc, trc.src, trc.srcKind);
       }
     }
   }
 }
 #endif
 
 static void RelazifyFunctions(Zone* zone, AllocKind kind) {
@@ -4823,108 +4814,99 @@ static void DropStringWrappers(JSRuntime
       e.removeFront();
     }
   }
 }
 
 /*
  * Group zones that must be swept at the same time.
  *
- * If compartment A has an edge to an unmarked object in compartment B, then we
- * must not sweep A in a later slice than we sweep B. That's because a write
- * barrier in A could lead to the unmarked object in B becoming marked.
- * However, if we had already swept that object, we would be in trouble.
+ * From the point of view of the mutator, groups of zones transition atomically
+ * from marking to sweeping. If compartment A has an edge to an unmarked object
+ * in compartment B, then we must not start sweeping A in a later slice than we
+ * start sweeping B. That's because a write barrier in A could lead to the
+ * unmarked object in B becoming marked. However, if we had already swept that
+ * object, we would be in trouble.
  *
  * If we consider these dependencies as a graph, then all the compartments in
- * any strongly-connected component of this graph must be swept in the same
- * slice.
+ * any strongly-connected component of this graph must start sweeping in the
+ * same slice.
  *
  * Tarjan's algorithm is used to calculate the components.
  */
 
-void Compartment::findOutgoingEdges(ZoneComponentFinder& finder) {
+bool Compartment::findSweepGroupEdges() {
+  Zone* source = zone();
   for (js::WrapperMap::Enum e(crossCompartmentWrappers); !e.empty();
        e.popFront()) {
     CrossCompartmentKey& key = e.front().mutableKey();
     MOZ_ASSERT(!key.is<JSString*>());
+
+    // Add edge to wrapped object zone if wrapped object is not marked black to
+    // ensure that wrapper zone not finish marking after we start sweeping the
+    // wrapped zone.
+
     if (key.is<JSObject*>() &&
         key.as<JSObject*>()->asTenured().isMarkedBlack()) {
       // CCW target is already marked, so we don't need to watch out for
       // later marking of the CCW.
       continue;
     }
-    key.applyToWrapped([&finder](auto tp) {
-      /*
-       * Add edge to wrapped object compartment if wrapped object is not
-       * marked black to indicate that wrapper compartment not be swept
-       * after wrapped compartment.
-       */
-      JS::Zone* zone = (*tp)->asTenured().zone();
-      if (zone->isGCMarking()) {
-        finder.addEdgeTo(zone);
-      }
+
+    Zone* target = key.applyToWrapped([](auto tp) {
+      return (*tp)->asTenured().zone();
     });
-  }
-}
-
-void Zone::findOutgoingEdges(ZoneComponentFinder& finder) {
-  /*
-   * Any compartment may have a pointer to an atom in the atoms
-   * compartment, and these aren't in the cross compartment map.
-   */
-  if (Zone* zone = finder.maybeAtomsZone) {
-    MOZ_ASSERT(zone->isCollecting());
-    finder.addEdgeTo(zone);
+    if (!target->isGCMarking()) {
+      continue;
+    }
+
+    if (!source->addSweepGroupEdgeTo(target)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool Zone::findSweepGroupEdges(Zone* atomsZone) {
+  // Any zone may have a pointer to an atom in the atoms zone, and these aren't
+  // in the cross compartment map.
+  if (atomsZone->wasGCStarted() && !addSweepGroupEdgeTo(atomsZone)) {
+    return false;
   }
 
   for (CompartmentsInZoneIter comp(this); !comp.done(); comp.next()) {
-    comp->findOutgoingEdges(finder);
-  }
-
-  for (ZoneSet::Range r = gcSweepGroupEdges().all(); !r.empty(); r.popFront()) {
-    if (r.front()->isGCMarking()) {
-      finder.addEdgeTo(r.front());
-    }
-  }
-
-  Debugger::findZoneEdges(this, finder);
-}
-
-bool GCRuntime::findInterZoneEdges() {
-  /*
-   * Weakmaps which have keys with delegates in a different zone introduce the
-   * need for zone edges from the delegate's zone to the weakmap zone.
-   *
-   * Since the edges point into and not away from the zone the weakmap is in
-   * we must find these edges in advance and store them in a set on the Zone.
-   * If we run out of memory, we fall back to sweeping everything in one
-   * group.
-   */
-
+    if (!comp->findSweepGroupEdges()) {
+      return false;
+    }
+  }
+
+  return WeakMapBase::findSweepGroupEdges(this) &&
+         Debugger::findSweepGroupEdges(this);
+}
+
+bool GCRuntime::findSweepGroupEdges() {
   for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
-    if (!WeakMapBase::findInterZoneEdges(zone)) {
+    if (!zone->findSweepGroupEdges(atomsZone)) {
       return false;
     }
   }
-
   return true;
 }
 
 void GCRuntime::groupZonesForSweeping(JS::GCReason reason) {
 #ifdef DEBUG
   for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
     MOZ_ASSERT(zone->gcSweepGroupEdges().empty());
   }
 #endif
 
   JSContext* cx = rt->mainContextFromOwnThread();
-  Zone* maybeAtomsZone = atomsZone->wasGCStarted() ? atomsZone.ref() : nullptr;
-  ZoneComponentFinder finder(cx->nativeStackLimit[JS::StackForSystemCode],
-                             maybeAtomsZone);
-  if (!isIncremental || !findInterZoneEdges()) {
+  ZoneComponentFinder finder(cx->nativeStackLimit[JS::StackForSystemCode]);
+  if (!isIncremental || !findSweepGroupEdges()) {
     finder.useOneComponent();
   }
 
 #ifdef JS_GC_ZEAL
   // Use one component for two-slice zeal modes.
   if (useZeal && hasIncrementalTwoSliceZealMode()) {
     finder.useOneComponent();
   }
@@ -4934,17 +4916,17 @@ void GCRuntime::groupZonesForSweeping(JS
     MOZ_ASSERT(zone->isGCMarking());
     finder.addNode(zone);
   }
   sweepGroups = finder.getResultsList();
   currentSweepGroup = sweepGroups;
   sweepGroupIndex = 1;
 
   for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
-    zone->gcSweepGroupEdges().clear();
+    zone->clearSweepGroupEdges();
   }
 
 #ifdef DEBUG
   for (Zone* head = currentSweepGroup; head; head = head->nextGroup()) {
     for (Zone* zone = head; zone; zone = zone->nextNodeInGroup()) {
       MOZ_ASSERT(zone->isGCMarking());
     }
   }
@@ -8647,30 +8629,23 @@ JS_PUBLIC_API void JS::IncrementalPreWri
   if (!obj) {
     return;
   }
 
   MOZ_ASSERT(!JS::RuntimeHeapIsMajorCollecting());
   JSObject::writeBarrierPre(obj);
 }
 
-struct IncrementalReadBarrierFunctor {
-  template <typename T>
-  void operator()(T* t) {
-    T::readBarrier(t);
-  }
-};
-
 JS_PUBLIC_API void JS::IncrementalReadBarrier(GCCellPtr thing) {
   if (!thing) {
     return;
   }
 
   MOZ_ASSERT(!JS::RuntimeHeapIsMajorCollecting());
-  DispatchTyped(IncrementalReadBarrierFunctor(), thing);
+  ApplyGCThingTyped(thing, [](auto t) { t->readBarrier(t); });
 }
 
 JS_PUBLIC_API bool JS::WasIncrementalGC(JSRuntime* rt) {
   return rt->gc.isIncrementalGc();
 }
 
 uint64_t js::gc::NextCellUniqueId(JSRuntime* rt) {
   return rt->gc.nextCellUniqueId();
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -610,17 +610,17 @@ class GCRuntime {
   template <class ZoneIterT>
   void markGrayRoots(gcstats::PhaseKind phase);
   void markBufferedGrayRoots(JS::Zone* zone);
   void markAllWeakReferences(gcstats::PhaseKind phase);
   void markAllGrayReferences(gcstats::PhaseKind phase);
 
   void beginSweepPhase(JS::GCReason reason, AutoGCSession& session);
   void groupZonesForSweeping(JS::GCReason reason);
-  MOZ_MUST_USE bool findInterZoneEdges();
+  MOZ_MUST_USE bool findSweepGroupEdges();
   void getNextSweepGroup();
   IncrementalProgress markGrayReferencesInCurrentGroup(FreeOp* fop,
                                                        SliceBudget& budget);
   IncrementalProgress endMarkingSweepGroup(FreeOp* fop, SliceBudget& budget);
   void markIncomingCrossCompartmentPointers(MarkColor color);
   IncrementalProgress beginSweepingSweepGroup(FreeOp* fop, SliceBudget& budget);
   void sweepDebuggerOnMainThread(FreeOp* fop);
   void sweepJitDataOnMainThread(FreeOp* fop);
--- a/js/src/gc/Marking-inl.h
+++ b/js/src/gc/Marking-inl.h
@@ -4,25 +4,63 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef gc_Marking_inl_h
 #define gc_Marking_inl_h
 
 #include "gc/Marking.h"
 
+#include "mozilla/Maybe.h"
+
 #include "gc/RelocationOverlay.h"
 
 #ifdef ENABLE_BIGINT
 #  include "vm/BigIntType.h"
 #endif
 
+#include "vm/RegExpShared.h"
+
 namespace js {
 namespace gc {
 
+// An abstraction to re-wrap any kind of typed pointer back to the tagged pointer
+// it came from with |TaggedPtr<TargetType>::wrap(sourcePtr)|.
+template <typename T>
+struct TaggedPtr {};
+
+template <>
+struct TaggedPtr<JS::Value> {
+  static JS::Value wrap(JSObject* obj) { return JS::ObjectOrNullValue(obj); }
+  static JS::Value wrap(JSString* str) { return JS::StringValue(str); }
+  static JS::Value wrap(JS::Symbol* sym) { return JS::SymbolValue(sym); }
+#ifdef ENABLE_BIGINT
+  static JS::Value wrap(JS::BigInt* bi) { return JS::BigIntValue(bi); }
+#endif
+  template <typename T>
+  static JS::Value wrap(T* priv) {
+    static_assert(mozilla::IsBaseOf<Cell, T>::value,
+                  "Type must be a GC thing derived from js::gc::Cell");
+    return JS::PrivateGCThingValue(priv);
+  }
+};
+
+template <>
+struct TaggedPtr<jsid> {
+  static jsid wrap(JSString* str) {
+    return NON_INTEGER_ATOM_TO_JSID(&str->asAtom());
+  }
+  static jsid wrap(JS::Symbol* sym) { return SYMBOL_TO_JSID(sym); }
+};
+
+template <>
+struct TaggedPtr<TaggedProto> {
+  static TaggedProto wrap(JSObject* obj) { return TaggedProto(obj); }
+};
+
 template <typename T>
 struct MightBeForwarded {
   static_assert(mozilla::IsBaseOf<Cell, T>::value, "T must derive from Cell");
   static_assert(!mozilla::IsSame<Cell, T>::value &&
                     !mozilla::IsSame<TenuredCell, T>::value,
                 "T must not be Cell or TenuredCell");
 
   static const bool value = mozilla::IsBaseOf<JSObject, T>::value ||
@@ -40,43 +78,31 @@ inline bool IsForwarded(const T* t) {
   if (!MightBeForwarded<T>::value) {
     MOZ_ASSERT(!t->isForwarded());
     return false;
   }
 
   return t->isForwarded();
 }
 
-struct IsForwardedFunctor : public BoolDefaultAdaptor<Value, false> {
-  template <typename T>
-  bool operator()(const T* t) {
-    return IsForwarded(t);
-  }
-};
-
 inline bool IsForwarded(const JS::Value& value) {
-  return DispatchTyped(IsForwardedFunctor(), value);
+  auto isForwarded = [](auto t) { return IsForwarded(t); };
+  return MapGCThingTyped(value, isForwarded).valueOr(false);
 }
 
 template <typename T>
 inline T* Forwarded(const T* t) {
   const RelocationOverlay* overlay = RelocationOverlay::fromCell(t);
   MOZ_ASSERT(overlay->isForwarded());
   return reinterpret_cast<T*>(overlay->forwardingAddress());
 }
 
-struct ForwardedFunctor : public IdentityDefaultAdaptor<Value> {
-  template <typename T>
-  inline Value operator()(const T* t) {
-    return js::gc::RewrapTaggedPointer<Value, T>::wrap(Forwarded(t));
-  }
-};
-
 inline Value Forwarded(const JS::Value& value) {
-  return DispatchTyped(ForwardedFunctor(), value);
+  auto forward = [](auto t) { return TaggedPtr<Value>::wrap(Forwarded(t)); };
+  return MapGCThingTyped(value, forward).valueOr(value);
 }
 
 template <typename T>
 inline T MaybeForwarded(T t) {
   if (IsForwarded(t)) {
     t = Forwarded(t);
   }
   return t;
@@ -108,25 +134,18 @@ inline void CheckGCThingAfterMovingGC(T*
   }
 }
 
 template <typename T>
 inline void CheckGCThingAfterMovingGC(const ReadBarriered<T*>& t) {
   CheckGCThingAfterMovingGC(t.unbarrieredGet());
 }
 
-struct CheckValueAfterMovingGCFunctor : public VoidDefaultAdaptor<Value> {
-  template <typename T>
-  void operator()(T* t) {
-    CheckGCThingAfterMovingGC(t);
-  }
-};
-
 inline void CheckValueAfterMovingGC(const JS::Value& value) {
-  DispatchTyped(CheckValueAfterMovingGCFunctor(), value);
+  ApplyGCThingTyped(value, [](auto t) { CheckGCThingAfterMovingGC(t); });
 }
 
 #endif  // JSGC_HASH_TABLE_CHECKS
 
 } /* namespace gc */
 } /* namespace js */
 
 #endif  // gc_Marking_inl_h
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "gc/Marking-inl.h"
 
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/IntegerRange.h"
 #include "mozilla/ReentrancyGuard.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/TypeTraits.h"
 
 #include "jsfriendapi.h"
 
@@ -272,27 +273,19 @@ void js::CheckTracedThing(JSTracer* trc,
    */
   MOZ_ASSERT_IF(JS::RuntimeHeapIsBusy() && !zone->isGCSweeping() &&
                     !zone->isGCFinished() && !zone->isGCCompacting(),
                 !IsThingPoisoned(thing) ||
                     !InFreeList(thing->asTenured().arena(), thing));
 #endif
 }
 
-template <typename S>
-struct CheckTracedFunctor : public VoidDefaultAdaptor<S> {
-  template <typename T>
-  void operator()(T* t, JSTracer* trc) {
-    CheckTracedThing(trc, t);
-  }
-};
-
 template <typename T>
 void js::CheckTracedThing(JSTracer* trc, T thing) {
-  DispatchTyped(CheckTracedFunctor<T>(), thing, trc);
+  ApplyGCThingTyped(thing, [](auto t) { CheckTracedThing(t); });
 }
 
 namespace js {
 #define IMPL_CHECK_TRACED_THING(_, type, __) \
   template void CheckTracedThing<type>(JSTracer*, type*);
 JS_FOR_EACH_TRACEKIND(IMPL_CHECK_TRACED_THING);
 #undef IMPL_CHECK_TRACED_THING
 }  // namespace js
@@ -403,16 +396,18 @@ void js::gc::AssertRootMarkingPhase(JSTr
                 trc->runtime()->gc.state() == State::NotActive ||
                     trc->runtime()->gc.state() == State::MarkRoots);
 }
 #endif
 
 /*** Tracing Interface ******************************************************/
 
 template <typename T>
+T* DoCallback(JS::CallbackTracer* trc, T** thingp, const char* name);
+template <typename T>
 T DoCallback(JS::CallbackTracer* trc, T* thingp, const char* name);
 template <typename T>
 void DoMarking(GCMarker* gcmarker, T* thing);
 template <typename T>
 void DoMarking(GCMarker* gcmarker, const T& thing);
 template <typename T>
 void NoteWeakEdge(GCMarker* gcmarker, T** thingp);
 template <typename T>
@@ -513,50 +508,50 @@ void js::TraceProcessGlobalRoot(JSTracer
     DoCallback(trc->asCallbackTracer(), ConvertToBase(&thing), name);
   }
 }
 template void js::TraceProcessGlobalRoot<JSAtom>(JSTracer*, JSAtom*,
                                                  const char*);
 template void js::TraceProcessGlobalRoot<JS::Symbol>(JSTracer*, JS::Symbol*,
                                                      const char*);
 
-// A typed functor adaptor for TraceRoot.
-struct TraceRootFunctor {
-  template <typename T>
-  void operator()(JSTracer* trc, Cell** thingp, const char* name) {
-    TraceRoot(trc, reinterpret_cast<T**>(thingp), name);
-  }
-};
-
 void js::TraceGenericPointerRoot(JSTracer* trc, Cell** thingp,
                                  const char* name) {
   MOZ_ASSERT(thingp);
-  if (!*thingp) {
+  Cell* thing = *thingp;
+  if (!thing) {
     return;
   }
-  TraceRootFunctor f;
-  DispatchTraceKindTyped(f, (*thingp)->getTraceKind(), trc, thingp, name);
+
+  auto traced = MapGCThingTyped(thing, thing->getTraceKind(),
+                                [trc, name](auto t) -> Cell* {
+    TraceRoot(trc, &t, name);
+    return t;
+  });
+  if (traced != thing) {
+    *thingp = traced;
+  }
 }
 
-// A typed functor adaptor for TraceManuallyBarrieredEdge.
-struct TraceManuallyBarrieredEdgeFunctor {
-  template <typename T>
-  void operator()(JSTracer* trc, Cell** thingp, const char* name) {
-    TraceManuallyBarrieredEdge(trc, reinterpret_cast<T**>(thingp), name);
-  }
-};
-
 void js::TraceManuallyBarrieredGenericPointerEdge(JSTracer* trc, Cell** thingp,
                                                   const char* name) {
   MOZ_ASSERT(thingp);
+  Cell* thing = *thingp;
   if (!*thingp) {
     return;
   }
-  TraceManuallyBarrieredEdgeFunctor f;
-  DispatchTraceKindTyped(f, (*thingp)->getTraceKind(), trc, thingp, name);
+
+  auto traced = MapGCThingTyped(thing, thing->getTraceKind(),
+                                [trc, name](auto t) -> Cell* {
+    TraceManuallyBarrieredEdge(trc, &t, name);
+    return t;
+  });
+  if (traced != thing) {
+    *thingp = traced;
+  }
 }
 
 // This method is responsible for dynamic dispatch to the real tracer
 // implementation. Consider replacing this choke point with virtual dispatch:
 // a sufficiently smart C++ compiler may be able to devirtualize some paths.
 template <typename T>
 void js::gc::TraceEdgeInternal(JSTracer* trc, T* thingp, const char* name) {
 #define IS_SAME_TYPE_OR(name, type, _) mozilla::IsSame<type*, T>::value ||
@@ -737,27 +732,19 @@ void DoMarking(GCMarker* gcmarker, T* th
 
   CheckTracedThing(gcmarker, thing);
   gcmarker->traverse(thing);
 
   // Mark the compartment as live.
   SetMaybeAliveFlag(thing);
 }
 
-template <typename S>
-struct DoMarkingFunctor : public VoidDefaultAdaptor<S> {
-  template <typename T>
-  void operator()(T* t, GCMarker* gcmarker) {
-    DoMarking(gcmarker, t);
-  }
-};
-
 template <typename T>
 void DoMarking(GCMarker* gcmarker, const T& thing) {
-  DispatchTyped(DoMarkingFunctor<T>(), thing, gcmarker);
+  ApplyGCThingTyped(thing, [gcmarker](auto t) { DoMarking(gcmarker, t); });
 }
 
 template <typename T>
 void NoteWeakEdge(GCMarker* gcmarker, T** thingp) {
   // Do per-type marking precondition checks.
   if (!ShouldMark(gcmarker, *thingp)) {
     return;
   }
@@ -929,27 +916,21 @@ static void CheckTraversedEdge(S source,
 }
 
 template <typename S, typename T>
 void js::GCMarker::traverseEdge(S source, T* target) {
   CheckTraversedEdge(source, target);
   traverse(target);
 }
 
-template <typename V, typename S>
-struct TraverseEdgeFunctor : public VoidDefaultAdaptor<V> {
-  template <typename T>
-  void operator()(T t, GCMarker* gcmarker, S s) {
-    return gcmarker->traverseEdge(s, t);
-  }
-};
-
 template <typename S, typename T>
 void js::GCMarker::traverseEdge(S source, const T& thing) {
-  DispatchTyped(TraverseEdgeFunctor<T, S>(), thing, this, source);
+  ApplyGCThingTyped(thing, [this, source](auto t) {
+                             this->traverseEdge(source, t);
+                           });
 }
 
 namespace {
 
 template <typename T>
 struct TypeParticipatesInCC {};
 #define EXPAND_PARTICIPATES_IN_CC(_, type, addToCCKind) \
   template <>                                           \
@@ -1514,97 +1495,90 @@ void js::GCMarker::lazilyMarkChildren(Ob
     traverseEdge(group, static_cast<JSObject*>(fun));
   }
 }
 
 #ifdef ENABLE_BIGINT
 void JS::BigInt::traceChildren(JSTracer* trc) { return; }
 #endif
 
-struct TraverseObjectFunctor {
-  template <typename T>
-  void operator()(T* thing, GCMarker* gcmarker, JSObject* src) {
-    gcmarker->traverseEdge(src, *thing);
-  }
-};
+template <typename Functor>
+static void VisitTraceList(const Functor& f, const int32_t* traceList,
+                           uint8_t* memory);
 
 // Call the trace hook set on the object, if present. If further tracing of
 // NativeObject fields is required, this will return the native object.
 enum class CheckGeneration { DoChecks, NoChecks };
-template <typename Functor, typename... Args>
-static inline NativeObject* CallTraceHook(Functor f, JSTracer* trc,
-                                          JSObject* obj, CheckGeneration check,
-                                          Args&&... args) {
+template <typename Functor>
+static inline NativeObject* CallTraceHook(Functor&& f, JSTracer* trc,
+                                          JSObject* obj, CheckGeneration check) {
   const Class* clasp = obj->getClass();
   MOZ_ASSERT(clasp);
   MOZ_ASSERT(obj->isNative() == clasp->isNative());
 
   if (!clasp->hasTrace()) {
     return &obj->as<NativeObject>();
   }
 
   if (clasp->isTrace(InlineTypedObject::obj_trace)) {
     Shape** pshape = obj->as<InlineTypedObject>().addressOfShapeFromGC();
-    f(pshape, std::forward<Args>(args)...);
+    f(pshape);
 
     InlineTypedObject& tobj = obj->as<InlineTypedObject>();
     if (tobj.typeDescr().hasTraceList()) {
       VisitTraceList(f, tobj.typeDescr().traceList(),
-                     tobj.inlineTypedMemForGC(), std::forward<Args>(args)...);
+                     tobj.inlineTypedMemForGC());
     }
 
     return nullptr;
   }
 
   if (clasp == &UnboxedPlainObject::class_) {
     JSObject** pexpando = obj->as<UnboxedPlainObject>().addressOfExpando();
     if (*pexpando) {
-      f(pexpando, std::forward<Args>(args)...);
+      f(pexpando);
     }
 
     UnboxedPlainObject& unboxed = obj->as<UnboxedPlainObject>();
     const UnboxedLayout& layout = check == CheckGeneration::DoChecks
                                       ? unboxed.layout()
                                       : unboxed.layoutDontCheckGeneration();
     if (layout.traceList()) {
-      VisitTraceList(f, layout.traceList(), unboxed.data(),
-                     std::forward<Args>(args)...);
+      VisitTraceList(f, layout.traceList(), unboxed.data());
     }
 
     return nullptr;
   }
 
   clasp->doTrace(trc, obj);
 
   if (!clasp->isNative()) {
     return nullptr;
   }
   return &obj->as<NativeObject>();
 }
 
-template <typename F, typename... Args>
-static void VisitTraceList(F f, const int32_t* traceList, uint8_t* memory,
-                           Args&&... args) {
+template <typename Functor>
+static void VisitTraceList(const Functor& f, const int32_t* traceList,
+                           uint8_t* memory) {
   while (*traceList != -1) {
-    f(reinterpret_cast<JSString**>(memory + *traceList),
-      std::forward<Args>(args)...);
+    f(reinterpret_cast<JSString**>(memory + *traceList));
     traceList++;
   }
   traceList++;
   while (*traceList != -1) {
     JSObject** objp = reinterpret_cast<JSObject**>(memory + *traceList);
     if (*objp) {
-      f(objp, std::forward<Args>(args)...);
+      f(objp);
     }
     traceList++;
   }
   traceList++;
   while (*traceList != -1) {
-    f(reinterpret_cast<Value*>(memory + *traceList),
-      std::forward<Args>(args)...);
+    f(reinterpret_cast<Value*>(memory + *traceList));
     traceList++;
   }
 }
 
 /*** Mark-stack Marking *****************************************************/
 
 bool GCMarker::markUntilBudgetExhausted(SliceBudget& budget) {
 #ifdef DEBUG
@@ -1824,18 +1798,19 @@ scan_obj : {
     repush(obj);
     return;
   }
 
   markImplicitEdges(obj);
   ObjectGroup* group = obj->groupFromGC();
   traverseEdge(obj, group);
 
-  NativeObject* nobj = CallTraceHook(TraverseObjectFunctor(), this, obj,
-                                     CheckGeneration::DoChecks, this, obj);
+  NativeObject* nobj = CallTraceHook([this, obj](auto thingp) {
+                                       this->traverseEdge(obj, *thingp);
+                                     }, this, obj, CheckGeneration::DoChecks);
   if (!nobj) {
     return;
   }
 
   Shape* shape = nobj->lastProperty();
   traverseEdge(obj, shape);
 
   unsigned nslots = nobj->slotSpan();
@@ -2755,29 +2730,27 @@ void TenuringTracer::traverse(JSString**
   MOZ_ASSERT(!nursery().isInside(strp));
 
   Cell** cellp = reinterpret_cast<Cell**>(strp);
   if (IsInsideNursery(*cellp) && !nursery().getForwardedPointer(cellp)) {
     *strp = moveToTenured(*strp);
   }
 }
 
-template <typename S>
-struct TenuringTraversalFunctor : public IdentityDefaultAdaptor<S> {
-  template <typename T>
-  S operator()(T* t, TenuringTracer* trc) {
-    trc->traverse(&t);
-    return js::gc::RewrapTaggedPointer<S, T>::wrap(t);
-  }
-};
-
 template <typename T>
 void TenuringTracer::traverse(T* thingp) {
-  *thingp = DispatchTyped(TenuringTraversalFunctor<T>(), *thingp, this);
+  auto tenured = MapGCThingTyped(*thingp, [this](auto t) {
+    this->traverse(&t);
+    return TaggedPtr<T>::wrap(t);
+  });
+  if (tenured.isSome() && tenured.value() != *thingp) {
+    *thingp = tenured.value();
+  }
 }
+
 }  // namespace js
 
 template <typename T>
 void js::gc::StoreBuffer::MonoTypeBuffer<T>::trace(TenuringTracer& mover) {
   mozilla::ReentrancyGuard g(*owner_);
   MOZ_ASSERT(owner_->isEnabled());
   if (last_) {
     last_.trace(mover);
@@ -2933,27 +2906,21 @@ void js::gc::StoreBuffer::CellPtrEdge::t
 }
 
 void js::gc::StoreBuffer::ValueEdge::trace(TenuringTracer& mover) const {
   if (deref()) {
     mover.traverse(edge);
   }
 }
 
-struct TenuringFunctor {
-  template <typename T>
-  void operator()(T* thing, TenuringTracer& mover) {
-    mover.traverse(thing);
-  }
-};
-
 // Visit all object children of the object and trace them.
 void js::TenuringTracer::traceObject(JSObject* obj) {
-  NativeObject* nobj = CallTraceHook(TenuringFunctor(), this, obj,
-                                     CheckGeneration::NoChecks, *this);
+  NativeObject* nobj = CallTraceHook([this](auto thingp) {
+                                       this->traverse(thingp);
+                                     }, this, obj, CheckGeneration::NoChecks);
   if (!nobj) {
     return;
   }
 
   // Note: the contents of copy on write elements pointers are filled in
   // during parsing and cannot contain nursery pointers.
   if (!nobj->hasEmptyElements() && !nobj->denseElementsAreCopyOnWrite() &&
       ObjectDenseElementsMayBeMarkable(nobj)) {
@@ -3355,46 +3322,40 @@ bool js::gc::IsMarkedBlackInternal(JSRun
 
   if (!ShouldCheckMarkState(rt, thingp)) {
     return true;
   }
 
   return (*thingp)->asTenured().isMarkedBlack();
 }
 
-template <typename S>
-struct IsMarkedFunctor : public IdentityDefaultAdaptor<S> {
-  template <typename T>
-  S operator()(T* t, JSRuntime* rt, bool* rv) {
-    *rv = IsMarkedInternal(rt, &t);
-    return js::gc::RewrapTaggedPointer<S, T>::wrap(t);
-  }
-};
-
 template <typename T>
 bool js::gc::IsMarkedInternal(JSRuntime* rt, T* thingp) {
-  bool rv = true;
-  *thingp = DispatchTyped(IsMarkedFunctor<T>(), *thingp, rt, &rv);
-  return rv;
+  bool marked = true;
+  auto thing = MapGCThingTyped(*thingp, [rt, &marked](auto t) {
+    marked = IsMarkedInternal(rt, &t);
+    return TaggedPtr<T>::wrap(t);
+  });
+  if (thing.isSome() && thing.value() != *thingp) {
+    *thingp = thing.value();
+  }
+  return marked;
 }
 
-template <typename S>
-struct IsMarkedBlackFunctor : public IdentityDefaultAdaptor<S> {
-  template <typename T>
-  S operator()(T* t, JSRuntime* rt, bool* rv) {
-    *rv = IsMarkedBlackInternal(rt, &t);
-    return js::gc::RewrapTaggedPointer<S, T>::wrap(t);
-  }
-};
-
 template <typename T>
 bool js::gc::IsMarkedBlackInternal(JSRuntime* rt, T* thingp) {
-  bool rv = true;
-  *thingp = DispatchTyped(IsMarkedBlackFunctor<T>(), *thingp, rt, &rv);
-  return rv;
+  bool marked = true;
+  auto thing = MapGCThingTyped(*thingp, [rt, &marked](auto t) {
+    marked = IsMarkedBlackInternal(rt, &t);
+    return TaggedPtr<T>::wrap(t);
+  });
+  if (thing.isSome() && thing.value() != *thingp) {
+    *thingp = thing.value();
+  }
+  return marked;
 }
 
 bool js::gc::IsAboutToBeFinalizedDuringSweep(TenuredCell& tenured) {
   MOZ_ASSERT(!IsInsideNursery(&tenured));
   MOZ_ASSERT(tenured.zoneFromAnyThread()->isGCSweeping());
   return !tenured.isMarkedAny();
 }
 
@@ -3421,31 +3382,27 @@ bool js::gc::IsAboutToBeFinalizedInterna
   } else if (zone->isGCCompacting() && IsForwarded(thing)) {
     *thingp = Forwarded(thing);
     return false;
   }
 
   return false;
 }
 
-template <typename S>
-struct IsAboutToBeFinalizedInternalFunctor : public IdentityDefaultAdaptor<S> {
-  template <typename T>
-  S operator()(T* t, bool* rv) {
-    *rv = IsAboutToBeFinalizedInternal(&t);
-    return js::gc::RewrapTaggedPointer<S, T>::wrap(t);
-  }
-};
-
 template <typename T>
 bool js::gc::IsAboutToBeFinalizedInternal(T* thingp) {
-  bool rv = false;
-  *thingp =
-      DispatchTyped(IsAboutToBeFinalizedInternalFunctor<T>(), *thingp, &rv);
-  return rv;
+  bool dying = false;
+  auto thing = MapGCThingTyped(*thingp, [&dying](auto t) {
+    dying = IsAboutToBeFinalizedInternal(&t);
+    return TaggedPtr<T>::wrap(t);
+  });
+  if (thing.isSome() && thing.value() != *thingp) {
+    *thingp = thing.value();
+  }
+  return dying;
 }
 
 namespace js {
 namespace gc {
 
 template <typename T>
 JS_PUBLIC_API bool EdgeNeedsSweep(JS::Heap<T>* thingp) {
   return IsAboutToBeFinalizedInternal(ConvertToBase(thingp->unsafeGet()));
--- a/js/src/gc/Marking.h
+++ b/js/src/gc/Marking.h
@@ -130,59 +130,16 @@ inline Cell* ToMarkable(const Value& v) 
   if (v.isGCThing()) {
     return (Cell*)v.toGCThing();
   }
   return nullptr;
 }
 
 inline Cell* ToMarkable(Cell* cell) { return cell; }
 
-// Wrap a GC thing pointer into a new Value or jsid. The type system enforces
-// that the thing pointer is a wrappable type.
-template <typename S, typename T>
-struct RewrapTaggedPointer {};
-#define DECLARE_REWRAP(S, T, method, prefix)                 \
-  template <>                                                \
-  struct RewrapTaggedPointer<S, T> {                         \
-    static S wrap(T* thing) { return method(prefix thing); } \
-  }
-DECLARE_REWRAP(JS::Value, JSObject, JS::ObjectOrNullValue, );
-DECLARE_REWRAP(JS::Value, JSString, JS::StringValue, );
-DECLARE_REWRAP(JS::Value, JS::Symbol, JS::SymbolValue, );
-#ifdef ENABLE_BIGINT
-DECLARE_REWRAP(JS::Value, JS::BigInt, JS::BigIntValue, );
-#endif
-DECLARE_REWRAP(jsid, JSString, NON_INTEGER_ATOM_TO_JSID, (JSAtom*));
-DECLARE_REWRAP(jsid, JS::Symbol, SYMBOL_TO_JSID, );
-DECLARE_REWRAP(js::TaggedProto, JSObject, js::TaggedProto, );
-#undef DECLARE_REWRAP
-
-template <typename T>
-struct IsPrivateGCThingInValue
-    : public mozilla::EnableIf<mozilla::IsBaseOf<Cell, T>::value &&
-                                   !mozilla::IsBaseOf<JSObject, T>::value &&
-                                   !mozilla::IsBaseOf<JSString, T>::value &&
-                                   !mozilla::IsBaseOf<JS::Symbol, T>::value
-#ifdef ENABLE_BIGINT
-                                   && !mozilla::IsBaseOf<JS::BigInt, T>::value
-#endif
-                               ,
-                               T> {
-  static_assert(!mozilla::IsSame<Cell, T>::value &&
-                    !mozilla::IsSame<TenuredCell, T>::value,
-                "T must not be Cell or TenuredCell");
-};
-
-template <typename T>
-struct RewrapTaggedPointer<Value, T> {
-  static Value wrap(typename IsPrivateGCThingInValue<T>::Type* thing) {
-    return JS::PrivateGCThingValue(thing);
-  }
-};
-
 } /* namespace gc */
 
 // The return value indicates if anything was unmarked.
 bool UnmarkGrayShapeRecursively(Shape* shape);
 
 template <typename T>
 void CheckTracedThing(JSTracer* trc, T* thing);
 
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -7,95 +7,72 @@
 #include "gc/Tracer.h"
 
 #include "mozilla/DebugOnly.h"
 
 #include "jsutil.h"
 #include "NamespaceImports.h"
 
 #include "gc/GCInternals.h"
-#include "gc/Marking.h"
 #include "gc/PublicIterators.h"
 #include "gc/Zone.h"
 #include "util/Text.h"
 #ifdef ENABLE_BIGINT
 #  include "vm/BigIntType.h"
 #endif
 #include "vm/JSFunction.h"
 #include "vm/JSScript.h"
 #include "vm/Shape.h"
 #include "vm/SymbolType.h"
 
 #include "gc/GC-inl.h"
+#include "gc/Marking-inl.h"
 #include "vm/ObjectGroup-inl.h"
 #include "vm/Realm-inl.h"
 #include "vm/TypeInference-inl.h"
 
 using namespace js;
 using namespace js::gc;
 using mozilla::DebugOnly;
 
 namespace js {
 template <typename T>
 void CheckTracedThing(JSTracer* trc, T thing);
 }  // namespace js
 
 /*** Callback Tracer Dispatch ***********************************************/
 
 template <typename T>
-T DoCallback(JS::CallbackTracer* trc, T* thingp, const char* name) {
+T* DoCallback(JS::CallbackTracer* trc, T** thingp, const char* name) {
   CheckTracedThing(trc, *thingp);
   JS::AutoTracingName ctx(trc, name);
   trc->dispatchToOnEdge(thingp);
   return *thingp;
 }
 #define INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS(name, type, _) \
-  template type* DoCallback<type*>(JS::CallbackTracer*, type**, const char*);
+  template type* DoCallback<type>(JS::CallbackTracer*, type**, const char*);
 JS_FOR_EACH_TRACEKIND(INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS);
 #undef INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS
 
-template <typename S>
-struct DoCallbackFunctor : public IdentityDefaultAdaptor<S> {
-  template <typename T>
-  S operator()(T* t, JS::CallbackTracer* trc, const char* name) {
-    return js::gc::RewrapTaggedPointer<S, T>::wrap(DoCallback(trc, &t, name));
-  }
-};
-
-template <>
-Value DoCallback<Value>(JS::CallbackTracer* trc, Value* vp, const char* name) {
-  // Only update *vp if the value changed, to avoid TSan false positives for
+template <typename T>
+T DoCallback(JS::CallbackTracer* trc, T* thingp, const char* name) {
+  auto thing = MapGCThingTyped(*thingp, [trc, name](auto t) {
+    return TaggedPtr<T>::wrap(DoCallback(trc, &t, name));
+  });
+  // Only update *thingp if the value changed, to avoid TSan false positives for
   // template objects when using DumpHeapTracer or UbiNode tracers while Ion
   // compiling off-thread.
-  Value v = DispatchTyped(DoCallbackFunctor<Value>(), *vp, trc, name);
-  if (*vp != v) {
-    *vp = v;
-  }
-  return v;
-}
-
-template <>
-jsid DoCallback<jsid>(JS::CallbackTracer* trc, jsid* idp, const char* name) {
-  jsid id = DispatchTyped(DoCallbackFunctor<jsid>(), *idp, trc, name);
-  if (*idp != id) {
-    *idp = id;
+  if (thing.isSome() && thing.value() != *thingp) {
+    *thingp = thing.value();
   }
-  return id;
+  return *thingp;
 }
-
-template <>
-TaggedProto DoCallback<TaggedProto>(JS::CallbackTracer* trc,
-                                    TaggedProto* protop, const char* name) {
-  TaggedProto proto =
-      DispatchTyped(DoCallbackFunctor<TaggedProto>(), *protop, trc, name);
-  if (*protop != proto) {
-    *protop = proto;
-  }
-  return proto;
-}
+template JS::Value DoCallback<JS::Value>(JS::CallbackTracer*, JS::Value*, const char*);
+template JS::PropertyKey DoCallback<JS::PropertyKey>(JS::CallbackTracer*, JS::PropertyKey*, const char*);
+template TaggedProto DoCallback<TaggedProto>(JS::CallbackTracer*, TaggedProto*, const char*);
 
 void JS::CallbackTracer::getTracingEdgeName(char* buffer, size_t bufferSize) {
   MOZ_ASSERT(bufferSize > 0);
   if (contextFunctor_) {
     (*contextFunctor_)(this, buffer, bufferSize);
     return;
   }
   if (contextIndex_ != InvalidIndex) {
@@ -106,32 +83,24 @@ void JS::CallbackTracer::getTracingEdgeN
 }
 
 /*** Public Tracing API *****************************************************/
 
 JS_PUBLIC_API void JS::TraceChildren(JSTracer* trc, GCCellPtr thing) {
   js::TraceChildren(trc, thing.asCell(), thing.kind());
 }
 
-struct TraceChildrenFunctor {
-  template <typename T>
-  void operator()(JSTracer* trc, void* thingArg) {
-    T* thing = static_cast<T*>(thingArg);
-    MOZ_ASSERT_IF(thing->runtimeFromAnyThread() != trc->runtime(),
-                  ThingIsPermanentAtomOrWellKnownSymbol(thing) ||
-                      thing->zoneFromAnyThread()->isSelfHostingZone());
-
-    thing->traceChildren(trc);
-  }
-};
-
 void js::TraceChildren(JSTracer* trc, void* thing, JS::TraceKind kind) {
   MOZ_ASSERT(thing);
-  TraceChildrenFunctor f;
-  DispatchTraceKindTyped(f, kind, trc, thing);
+  ApplyGCThingTyped(thing, kind, [trc](auto t) {
+    MOZ_ASSERT_IF(t->runtimeFromAnyThread() != trc->runtime(),
+                  ThingIsPermanentAtomOrWellKnownSymbol(t) ||
+                  t->zoneFromAnyThread()->isSelfHostingZone());
+    t->traceChildren(trc);
+  });
 }
 
 JS_PUBLIC_API void JS::TraceIncomingCCWs(
     JSTracer* trc, const JS::CompartmentSet& compartments) {
   for (js::CompartmentsIter comp(trc->runtime()); !comp.done(); comp.next()) {
     if (compartments.has(comp)) {
       continue;
     }
--- a/js/src/gc/WeakMap.cpp
+++ b/js/src/gc/WeakMap.cpp
@@ -66,17 +66,17 @@ bool WeakMapBase::markZoneIteratively(JS
   for (WeakMapBase* m : zone->gcWeakMapList()) {
     if (m->marked && m->markIteratively(marker)) {
       markedAny = true;
     }
   }
   return markedAny;
 }
 
-bool WeakMapBase::findInterZoneEdges(JS::Zone* zone) {
+bool WeakMapBase::findSweepGroupEdges(JS::Zone* zone) {
   for (WeakMapBase* m : zone->gcWeakMapList()) {
     if (!m->findZoneEdges()) {
       return false;
     }
   }
   return true;
 }
 
@@ -144,17 +144,17 @@ bool ObjectValueMap::findZoneEdges() {
     JSObject* delegate = getDelegate(key);
     if (!delegate) {
       continue;
     }
     Zone* delegateZone = delegate->zone();
     if (delegateZone == zone() || !delegateZone->isGCMarking()) {
       continue;
     }
-    if (!delegateZone->gcSweepGroupEdges().put(key->zone())) {
+    if (!delegateZone->addSweepGroupEdgeTo(key->zone())) {
       return false;
     }
   }
   return true;
 }
 
 ObjectWeakMap::ObjectWeakMap(JSContext* cx) : map(cx, nullptr) {}
 
--- a/js/src/gc/WeakMap.h
+++ b/js/src/gc/WeakMap.h
@@ -75,17 +75,17 @@ class WeakMapBase : public mozilla::Link
   // Check all weak maps in a zone that have been marked as live in this garbage
   // collection, and mark the values of all entries that have become strong
   // references to them. Return true if we marked any new values, indicating
   // that we need to make another pass. In other words, mark my marked maps'
   // marked members' mid-collection.
   static bool markZoneIteratively(JS::Zone* zone, GCMarker* marker);
 
   // Add zone edges for weakmaps with key delegates in a different zone.
-  static bool findInterZoneEdges(JS::Zone* zone);
+  static MOZ_MUST_USE bool findSweepGroupEdges(JS::Zone* zone);
 
   // Sweep the weak maps in a zone, removing dead weak maps and removing
   // entries of live weak maps whose keys are dead.
   static void sweepZone(JS::Zone* zone);
 
   // Trace all weak map bindings. Used by the cycle collector.
   static void traceAllMappings(WeakMapTracer* tracer);
 
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -23,24 +23,17 @@ class Debugger;
 class RegExpZone;
 
 namespace jit {
 class JitZone;
 }  // namespace jit
 
 namespace gc {
 
-struct ZoneComponentFinder
-    : public ComponentFinder<JS::Zone, ZoneComponentFinder> {
-  ZoneComponentFinder(uintptr_t sl, JS::Zone* maybeAtomsZone)
-      : ComponentFinder<JS::Zone, ZoneComponentFinder>(sl),
-        maybeAtomsZone(maybeAtomsZone) {}
-
-  JS::Zone* maybeAtomsZone;
-};
+using ZoneComponentFinder = ComponentFinder<JS::Zone>;
 
 struct UniqueIdGCPolicy {
   static bool needsSweep(Cell** cell, uint64_t* value);
 };
 
 // Maps a Cell* to a unique, 64bit id.
 using UniqueIdMap = GCHashMap<Cell*, uint64_t, PointerHasher<Cell*>,
                               SystemAllocPolicy, UniqueIdGCPolicy>;
@@ -180,17 +173,17 @@ class Zone : public JS::shadow::Zone,
     MOZ_ASSERT(helperThreadUse_ == HelperThreadUse::Pending);
     helperThreadUse_ = HelperThreadUse::Active;
   }
   void clearUsedByHelperThread() {
     MOZ_ASSERT(helperThreadUse_ != HelperThreadUse::None);
     helperThreadUse_ = HelperThreadUse::None;
   }
 
-  void findOutgoingEdges(js::gc::ZoneComponentFinder& finder);
+  MOZ_MUST_USE bool findSweepGroupEdges(Zone* atomsZone);
 
   enum ShouldDiscardBaselineCode : bool {
     KeepBaselineCode = false,
     DiscardBaselineCode
   };
 
   enum ShouldReleaseTypes : bool { KeepTypes = false, ReleaseTypes };
 
@@ -414,25 +407,28 @@ class Zone : public JS::shadow::Zone,
    * Mapping from not yet marked keys to a vector of all values that the key
    * maps to in any live weak map.
    */
   js::ZoneOrGCTaskData<js::gc::WeakKeyTable> gcWeakKeys_;
 
  public:
   js::gc::WeakKeyTable& gcWeakKeys() { return gcWeakKeys_.ref(); }
 
- private:
-  // A set of edges from this zone to other zones.
-  //
-  // This is used during GC while calculating sweep groups to record edges
-  // that can't be determined by examining this zone by itself.
-  js::MainThreadData<ZoneSet> gcSweepGroupEdges_;
-
- public:
-  ZoneSet& gcSweepGroupEdges() { return gcSweepGroupEdges_.ref(); }
+  // A set of edges from this zone to other zones used during GC to calculate
+  // sweep groups.
+  NodeSet& gcSweepGroupEdges() {
+    return gcGraphEdges; // Defined in GraphNodeBase base class.
+  }
+  MOZ_MUST_USE bool addSweepGroupEdgeTo(Zone* otherZone) {
+    MOZ_ASSERT(otherZone->isGCMarking());
+    return gcSweepGroupEdges().put(otherZone);
+  }
+  void clearSweepGroupEdges() {
+    gcSweepGroupEdges().clear();
+  }
 
   // Keep track of all TypeDescr and related objects in this compartment.
   // This is used by the GC to trace them all first when compacting, since the
   // TypedObject trace hook may access these objects.
   //
   // There are no barriers here - the set contains only tenured objects so no
   // post-barrier is required, and these are weak references so no pre-barrier
   // is required.
--- a/js/src/jit-test/lib/wasm-binary.js
+++ b/js/src/jit-test/lib/wasm-binary.js
@@ -94,16 +94,17 @@ const I64TruncSF32Code = 0xae;
 const I64TruncUF32Code = 0xaf;
 const I64TruncSF64Code = 0xb0;
 const I64TruncUF64Code = 0xb1;
 const I64DivSCode      = 0x7f;
 const I64DivUCode      = 0x80;
 const I64RemSCode      = 0x81;
 const I64RemUCode      = 0x82;
 const RefNull          = 0xd0;
+const PlaceholderRefFunc = 0xd2;
 
 const FirstInvalidOpcode = 0xc5;
 const LastInvalidOpcode = 0xfb;
 const MiscPrefix = 0xfc;
 const SimdPrefix = 0xfd;
 const ThreadPrefix = 0xfe;
 const MozPrefix = 0xff;
 
--- a/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
@@ -156,17 +156,16 @@ tab_test("(table.init 1 (i32.const 7) (i
          "elem.drop 3 \n" +
          "(table.copy (i32.const 20) (i32.const 15) (i32.const 5)) \n" +
          "(table.copy (i32.const 21) (i32.const 29) (i32.const 1)) \n" +
          "(table.copy (i32.const 24) (i32.const 10) (i32.const 1)) \n" +
          "(table.copy (i32.const 13) (i32.const 11) (i32.const 4)) \n" +
          "(table.copy (i32.const 19) (i32.const 20) (i32.const 5))",
          [e,e,3,1,4, 1,e,2,7,1, 8,e,7,e,7, 5,2,7,e,9, e,7,e,8,8, e,e,e,e,e]);
 
-
 // And now a simplified version of the above, for memory.{init,drop,copy}.
 
 function gen_mem_mod_t(insn)
 {
   let t =
   `(module
      ;; -------- Memories --------
      (memory (export "memory0") 1 1)
@@ -293,16 +292,54 @@ checkNoDataCount([I32ConstCode, 0,
                   I32ConstCode, 0,
                   I32ConstCode, 0,
                   MiscPrefix, MemoryInitCode, 0, 0],
                 /memory.init requires a DataCount section/);
 
 checkNoDataCount([MiscPrefix, DataDropCode, 0],
                  /data.drop requires a DataCount section/);
 
+// Verification that we can handle encoding errors for passive element segments
+// properly.
+
+function checkPassiveElemSegment(mangle, err) {
+    let bin = moduleWithSections(
+        [v2vSigSection, declSection([0]), // One function
+         tableSection(1),                 // One table
+         { name: elemId,                  // One passive segment
+           body: (function () {
+               let body = [];
+               body.push(1);           // 1 element segment
+               body.push(1);           // Flag: Passive
+               body.push(AnyFuncCode + (mangle == "type" ? 1 : 0)); // always anyfunc
+               body.push(1);           // Element count
+               body.push(PlaceholderRefFunc + (mangle == "ref.func" ? 1 : 0)); // always ref.func
+               body.push(0);           // func index
+               body.push(EndCode + (mangle == "end" ? 1 : 0));
+               return body;
+           })() },
+         bodySection(                   // Empty function
+             [funcBody(
+                 {locals:[],
+                  body:[]})])
+        ]);
+    if (err) {
+        assertErrorMessage(() => new WebAssembly.Module(bin),
+                           WebAssembly.CompileError,
+                           err);
+    } else {
+        new WebAssembly.Module(bin);
+    }
+}
+
+checkPassiveElemSegment("");
+checkPassiveElemSegment("type", /passive segments can only contain function references/);
+checkPassiveElemSegment("ref.func", /failed to read ref.func operation/);
+checkPassiveElemSegment("end", /failed to read end of ref.func expression/);
+
 //---------------------------------------------------------------------//
 //---------------------------------------------------------------------//
 // Some further tests for memory.copy and memory.fill.  First, validation
 // tests.
 
 // Prefixed opcodes
 
 function checkMiscPrefixed(opcode, expect_failure) {
--- a/js/src/jsapi-tests/testFindSCCs.cpp
+++ b/js/src/jsapi-tests/testFindSCCs.cpp
@@ -11,41 +11,25 @@
 #include "gc/FindSCCs.h"
 #include "jsapi-tests/tests.h"
 
 static const unsigned MaxVertices = 10;
 
 using js::gc::ComponentFinder;
 using js::gc::GraphNodeBase;
 
-struct TestComponentFinder;
-
 struct TestNode : public GraphNodeBase<TestNode> {
   unsigned index;
-  bool hasEdge[MaxVertices];
 
-  void findOutgoingEdges(TestComponentFinder& finder);
 };
 
-struct TestComponentFinder
-    : public ComponentFinder<TestNode, TestComponentFinder> {
-  explicit TestComponentFinder(uintptr_t sl)
-      : ComponentFinder<TestNode, TestComponentFinder>(sl) {}
-};
+using TestComponentFinder = ComponentFinder<TestNode>;
 
 static TestNode Vertex[MaxVertices];
 
-void TestNode::findOutgoingEdges(TestComponentFinder& finder) {
-  for (unsigned i = 0; i < MaxVertices; ++i) {
-    if (hasEdge[i]) {
-      finder.addEdgeTo(&Vertex[i]);
-    }
-  }
-}
-
 BEGIN_TEST(testFindSCCs) {
   // no vertices
 
   setup(0);
   run();
   CHECK(end());
 
   // no edges
@@ -60,73 +44,78 @@ BEGIN_TEST(testFindSCCs) {
   CHECK(group(2, -1));
   CHECK(group(1, -1));
   CHECK(group(0, -1));
   CHECK(end());
 
   // linear
 
   setup(3);
-  edge(0, 1);
-  edge(1, 2);
+  CHECK(edge(0, 1));
+  CHECK(edge(1, 2));
   run();
   CHECK(group(0, -1));
   CHECK(group(1, -1));
   CHECK(group(2, -1));
   CHECK(end());
 
   // tree
 
   setup(3);
-  edge(0, 1);
-  edge(0, 2);
+  CHECK(edge(0, 1));
+  CHECK(edge(0, 2));
   run();
   CHECK(group(0, -1));
-  CHECK(group(2, -1));
-  CHECK(group(1, -1));
+  if (resultsList && resultsList->index == 1) {
+    CHECK(group(1, -1));
+    CHECK(group(2, -1));
+  } else {
+    CHECK(group(2, -1));
+    CHECK(group(1, -1));
+  }
   CHECK(end());
 
   // cycles
 
   setup(3);
-  edge(0, 1);
-  edge(1, 2);
-  edge(2, 0);
+  CHECK(edge(0, 1));
+  CHECK(edge(1, 2));
+  CHECK(edge(2, 0));
   run();
   CHECK(group(0, 1, 2, -1));
   CHECK(end());
 
   setup(4);
-  edge(0, 1);
-  edge(1, 2);
-  edge(2, 1);
-  edge(2, 3);
+  CHECK(edge(0, 1));
+  CHECK(edge(1, 2));
+  CHECK(edge(2, 1));
+  CHECK(edge(2, 3));
   run();
   CHECK(group(0, -1));
   CHECK(group(1, 2, -1));
   CHECK(group(3, -1));
   CHECK(end());
 
   // remaining
 
   setup(2);
-  edge(0, 1);
+  CHECK(edge(0, 1));
   run();
   CHECK(remaining(0, 1, -1));
   CHECK(end());
 
   setup(2);
-  edge(0, 1);
+  CHECK(edge(0, 1));
   run();
   CHECK(group(0, -1));
   CHECK(remaining(1, -1));
   CHECK(end());
 
   setup(2);
-  edge(0, 1);
+  CHECK(edge(0, 1));
   run();
   CHECK(group(0, -1));
   CHECK(group(1, -1));
   CHECK(remaining(-1));
   CHECK(end());
 
   return true;
 }
@@ -134,24 +123,24 @@ BEGIN_TEST(testFindSCCs) {
 unsigned vertex_count;
 TestComponentFinder* finder;
 TestNode* resultsList;
 
 void setup(unsigned count) {
   vertex_count = count;
   for (unsigned i = 0; i < MaxVertices; ++i) {
     TestNode& v = Vertex[i];
+    v.gcGraphEdges.clear();
     v.gcNextGraphNode = nullptr;
     v.index = i;
-    memset(&v.hasEdge, 0, sizeof(v.hasEdge));
   }
 }
 
-void edge(unsigned src_index, unsigned dest_index) {
-  Vertex[src_index].hasEdge[dest_index] = true;
+bool edge(unsigned src_index, unsigned dest_index) {
+  return Vertex[src_index].gcGraphEdges.put(&Vertex[dest_index]);
 }
 
 void run() {
   finder =
       new TestComponentFinder(cx->nativeStackLimit[JS::StackForSystemCode]);
   for (unsigned i = 0; i < vertex_count; ++i) {
     finder->addNode(&Vertex[i]);
   }
@@ -198,66 +187,45 @@ bool end() {
   CHECK(resultsList == nullptr);
 
   delete finder;
   finder = nullptr;
   return true;
 }
 END_TEST(testFindSCCs)
 
-struct TestComponentFinder2;
-
-struct TestNode2 : public GraphNodeBase<TestNode2> {
-  TestNode2* edge;
-
-  TestNode2() : edge(nullptr) {}
-  void findOutgoingEdges(TestComponentFinder2& finder);
-};
-
-struct TestComponentFinder2
-    : public ComponentFinder<TestNode2, TestComponentFinder2> {
-  explicit TestComponentFinder2(uintptr_t sl)
-      : ComponentFinder<TestNode2, TestComponentFinder2>(sl) {}
-};
-
-void TestNode2::findOutgoingEdges(TestComponentFinder2& finder) {
-  if (edge) {
-    finder.addEdgeTo(edge);
-  }
-}
-
 BEGIN_TEST(testFindSCCsStackLimit) {
   /*
    * Test what happens if recusion causes the stack to become full while
    * traversing the graph.
    *
    * The test case is a large number of vertices, almost all of which are
    * arranged in a linear chain.  The last few are left unlinked to exercise
    * adding vertices after the stack full condition has already been detected.
    *
    * Such an arrangement with no cycles would normally result in one group for
    * each vertex, but since the stack is exhasted in processing a single group
    * is returned containing all the vertices.
    */
   const unsigned max = 1000000;
   const unsigned initial = 10;
 
-  TestNode2* vertices = new TestNode2[max]();
+  TestNode* vertices = new TestNode[max]();
   for (unsigned i = initial; i < (max - 10); ++i) {
-    vertices[i].edge = &vertices[i + 1];
+    CHECK(vertices[i].gcGraphEdges.put(&vertices[i + 1]));
   }
 
-  TestComponentFinder2 finder(cx->nativeStackLimit[JS::StackForSystemCode]);
+  TestComponentFinder finder(cx->nativeStackLimit[JS::StackForSystemCode]);
   for (unsigned i = 0; i < max; ++i) {
     finder.addNode(&vertices[i]);
   }
 
-  TestNode2* r = finder.getResultsList();
+  TestNode* r = finder.getResultsList();
   CHECK(r);
-  TestNode2* v = r;
+  TestNode* v = r;
 
   unsigned count = 0;
   while (v) {
     ++count;
     v = v->nextNodeInGroup();
   }
   CHECK(count == max - initial);
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -680,16 +680,17 @@ static void ReleaseAssertObjectHasNoWrap
 JS_PUBLIC_API JSObject* JS_TransplantObject(JSContext* cx, HandleObject origobj,
                                             HandleObject target) {
   AssertHeapIsIdle();
   MOZ_ASSERT(origobj != target);
   MOZ_ASSERT(!origobj->is<CrossCompartmentWrapperObject>());
   MOZ_ASSERT(!target->is<CrossCompartmentWrapperObject>());
   MOZ_ASSERT(origobj->getClass() == target->getClass());
   ReleaseAssertObjectHasNoWrappers(cx, target);
+  JS::AssertCellIsNotGray(origobj);
   JS::AssertCellIsNotGray(target);
 
   RootedValue origv(cx, ObjectValue(*origobj));
   RootedObject newIdentity(cx);
 
   // Don't allow a compacting GC to observe any intermediate state.
   AutoDisableCompactingGC nocgc(cx);
 
@@ -697,23 +698,19 @@ JS_PUBLIC_API JSObject* JS_TransplantObj
 
   JS::Compartment* destination = target->compartment();
 
   if (origobj->compartment() == destination) {
     // If the original object is in the same compartment as the
     // destination, then we know that we won't find a wrapper in the
     // destination's cross compartment map and that the same
     // object will continue to work.
-    AutoRealmUnchecked ar(cx, origobj->nonCCWRealm());
+    AutoRealm ar(cx, origobj);
     JSObject::swap(cx, origobj, target);
     newIdentity = origobj;
-
-    // |origobj| might be gray so unmark it to avoid returning a possibly-gray
-    // object.
-    JS::ExposeObjectToActiveJS(newIdentity);
   } else if (WrapperMap::Ptr p = destination->lookupWrapper(origv)) {
     // There might already be a wrapper for the original object in
     // the new compartment. If there is, we use its identity and swap
     // in the contents of |target|.
     newIdentity = &p->value().get().toObject();
 
     // When we remove origv from the wrapper map, its wrapper, newIdentity,
     // must immediately cease to be a cross-compartment wrapper. Nuke it.
@@ -734,17 +731,17 @@ JS_PUBLIC_API JSObject* JS_TransplantObj
   // cached wrapper state.
   if (!RemapAllWrappersForObject(cx, origobj, newIdentity)) {
     MOZ_CRASH();
   }
 
   // Lastly, update the original object to point to the new one.
   if (origobj->compartment() != destination) {
     RootedObject newIdentityWrapper(cx, newIdentity);
-    AutoRealmUnchecked ar(cx, origobj->nonCCWRealm());
+    AutoRealm ar(cx, origobj);
     if (!JS_WrapObject(cx, &newIdentityWrapper)) {
       MOZ_CRASH();
     }
     MOZ_ASSERT(Wrapper::wrappedObject(newIdentityWrapper) == newIdentity);
     JSObject::swap(cx, origobj, newIdentityWrapper);
     if (!origobj->compartment()->putWrapper(
             cx, CrossCompartmentKey(newIdentity), origv)) {
       MOZ_CRASH();
--- a/js/src/vm/Compartment.h
+++ b/js/src/vm/Compartment.h
@@ -18,20 +18,16 @@
 #include "gc/Barrier.h"
 #include "gc/NurseryAwareHashMap.h"
 #include "js/UniquePtr.h"
 #include "vm/JSObject.h"
 #include "vm/JSScript.h"
 
 namespace js {
 
-namespace gc {
-struct ZoneComponentFinder;
-}  // namespace gc
-
 class CrossCompartmentKey {
  public:
   enum DebuggerObjectKind : uint8_t {
     DebuggerSource,
     DebuggerEnvironment,
     DebuggerObject,
     DebuggerWasmScript,
     DebuggerWasmSource
@@ -553,17 +549,17 @@ class JS::Compartment {
   void sweepRealms(js::FreeOp* fop, bool keepAtleastOne,
                    bool destroyingRuntime);
   void sweepAfterMinorGC(JSTracer* trc);
   void sweepCrossCompartmentWrappers();
 
   static void fixupCrossCompartmentWrappersAfterMovingGC(JSTracer* trc);
   void fixupAfterMovingGC();
 
-  void findOutgoingEdges(js::gc::ZoneComponentFinder& finder);
+  MOZ_MUST_USE bool findSweepGroupEdges();
 };
 
 namespace js {
 
 // We only set the maybeAlive flag for objects and scripts. It's assumed that,
 // if a compartment is alive, then it will have at least some live object or
 // script it in. Even if we get this wrong, the worst that will happen is that
 // scheduledForDestruction will be set on the compartment, which will cause
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -3361,51 +3361,55 @@ void Debugger::trace(JSTracer* trc) {
                                                          GlobalObject* global) {
   const GlobalObject::DebuggerVector* debuggers = global->getDebuggers();
   MOZ_ASSERT(!debuggers->empty());
   while (!debuggers->empty()) {
     debuggers->back()->removeDebuggeeGlobal(fop, global, nullptr);
   }
 }
 
-/* static */ void Debugger::findZoneEdges(Zone* zone,
-                                          js::gc::ZoneComponentFinder& finder) {
+/* static */ bool Debugger::findSweepGroupEdges(Zone* zone) {
   JSRuntime* rt = zone->runtimeFromMainThread();
   for (Debugger* dbg : rt->debuggerList()) {
     Zone* debuggerZone = dbg->object->zone();
     if (!debuggerZone->isGCMarking()) {
       continue;
     }
 
     if (debuggerZone == zone) {
       // Add edges to debuggee zones. These are weak references that are
       // not in the cross compartment wrapper map.
       for (auto e = dbg->debuggeeZones.all(); !e.empty(); e.popFront()) {
         Zone* debuggeeZone = e.front();
         if (debuggeeZone->isGCMarking()) {
-          finder.addEdgeTo(debuggeeZone);
+          if (!zone->addSweepGroupEdgeTo(debuggeeZone)) {
+            return false;
+          }
         }
       }
     } else {
       // For debugger cross compartment wrappers, add edges in the
       // opposite direction to those already added by
       // Compartment::findOutgoingEdges and above. This ensure that
       // debuggers and their debuggees are finalized in the same group.
       if (dbg->debuggeeZones.has(zone) ||
           dbg->generatorFrames.hasKeyInZone(zone) ||
           dbg->scripts.hasKeyInZone(zone) ||
           dbg->lazyScripts.hasKeyInZone(zone) ||
           dbg->sources.hasKeyInZone(zone) || dbg->objects.hasKeyInZone(zone) ||
           dbg->environments.hasKeyInZone(zone) ||
           dbg->wasmInstanceScripts.hasKeyInZone(zone) ||
           dbg->wasmInstanceSources.hasKeyInZone(zone)) {
-        finder.addEdgeTo(debuggerZone);
-      }
-    }
-  }
+        if (!zone->addSweepGroupEdgeTo(debuggerZone)) {
+          return false;
+        }
+      }
+    }
+  }
+  return true;
 }
 
 const ClassOps Debugger::classOps_ = {nullptr, /* addProperty */
                                       nullptr, /* delProperty */
                                       nullptr, /* enumerate   */
                                       nullptr, /* newEnumerate */
                                       nullptr, /* resolve     */
                                       nullptr, /* mayResolve  */
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -983,17 +983,17 @@ class Debugger : private mozilla::Linked
    * objects that are definitely live but not yet marked, it marks them and
    * returns true. If not, it returns false.
    */
   static void traceIncomingCrossCompartmentEdges(JSTracer* tracer);
   static MOZ_MUST_USE bool markIteratively(GCMarker* marker);
   static void traceAllForMovingGC(JSTracer* trc);
   static void sweepAll(FreeOp* fop);
   static void detachAllDebuggersFromGlobal(FreeOp* fop, GlobalObject* global);
-  static void findZoneEdges(JS::Zone* v, gc::ZoneComponentFinder& finder);
+  static MOZ_MUST_USE bool findSweepGroupEdges(JS::Zone* zone);
 #ifdef DEBUG
   static bool isDebuggerCrossCompartmentEdge(JSObject* obj,
                                              const js::gc::Cell* cell);
 #endif
 
   // Checks it the current compartment is allowed to execute code.
   static inline MOZ_MUST_USE bool checkNoExecute(JSContext* cx,
                                                  HandleScript script);
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -1463,27 +1463,19 @@ ScriptSourceObject* ScriptSourceObject::
 }
 
 void ScriptSourceObject::setPrivate(JSRuntime* rt, const Value& value) {
   // Update the private value, calling addRef/release hooks if necessary
   // to allow the embedding to maintain a reference count for the
   // private data.
   JS::AutoSuppressGCAnalysis nogc;
   Value prevValue = getReservedSlot(PRIVATE_SLOT);
-  if (!prevValue.isUndefined()) {
-    if (auto releaseHook = rt->scriptPrivateReleaseHook) {
-      releaseHook(prevValue);
-    }
-  }
+  rt->releaseScriptPrivate(prevValue);
   setReservedSlot(PRIVATE_SLOT, value);
-  if (!value.isUndefined()) {
-    if (auto addRefHook = rt->scriptPrivateAddRefHook) {
-      addRefHook(value);
-    }
-  }
+  rt->addRefScriptPrivate(value);
 }
 
 /* static */ bool JSScript::loadSource(JSContext* cx, ScriptSource* ss,
                                        bool* worked) {
   MOZ_ASSERT(!ss->hasSourceText());
   *worked = false;
   if (!cx->runtime()->sourceHook.ref() || !ss->sourceRetrievable()) {
     return true;
--- a/js/src/vm/Realm.cpp
+++ b/js/src/vm/Realm.cpp
@@ -178,25 +178,16 @@ bool Realm::ensureJitRealmExists(JSConte
 }
 
 #ifdef JSGC_HASH_TABLE_CHECKS
 
 void js::DtoaCache::checkCacheAfterMovingGC() {
   MOZ_ASSERT(!s || !IsForwarded(s));
 }
 
-namespace {
-struct CheckGCThingAfterMovingGCFunctor {
-  template <class T>
-  void operator()(T* t) {
-    CheckGCThingAfterMovingGC(*t);
-  }
-};
-}  // namespace
-
 #endif  // JSGC_HASH_TABLE_CHECKS
 
 LexicalEnvironmentObject*
 ObjectRealm::getOrCreateNonSyntacticLexicalEnvironment(JSContext* cx,
                                                        HandleObject enclosing,
                                                        HandleObject key,
                                                        HandleObject thisv) {
   MOZ_ASSERT(&ObjectRealm::get(enclosing) == this);
@@ -459,34 +450,16 @@ void ObjectRealm::sweepNativeIterators()
     ni = next;
   }
 }
 
 void Realm::sweepObjectRealm() { objects_.sweepNativeIterators(); }
 
 void Realm::sweepVarNames() { varNames_.sweep(); }
 
-namespace {
-struct TraceRootFunctor {
-  JSTracer* trc;
-  const char* name;
-  TraceRootFunctor(JSTracer* trc, const char* name) : trc(trc), name(name) {}
-  template <class T>
-  void operator()(T* t) {
-    return TraceRoot(trc, t, name);
-  }
-};
-struct NeedsSweepUnbarrieredFunctor {
-  template <class T>
-  bool operator()(T* t) const {
-    return IsAboutToBeFinalizedUnbarriered(t);
-  }
-};
-}  // namespace
-
 void Realm::sweepTemplateObjects() {
   if (mappedArgumentsTemplate_ &&
       IsAboutToBeFinalized(&mappedArgumentsTemplate_)) {
     mappedArgumentsTemplate_.set(nullptr);
   }
 
   if (unmappedArgumentsTemplate_ &&
       IsAboutToBeFinalized(&unmappedArgumentsTemplate_)) {
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -967,16 +967,28 @@ struct JSRuntime : public js::MallocProv
   // HostImportModuleDynamically. This is also used to enable/disable dynamic
   // module import and can accessed by off-thread parsing.
   mozilla::Atomic<JS::ModuleDynamicImportHook> moduleDynamicImportHook;
 
   // Hooks called when script private references are created and destroyed.
   js::MainThreadData<JS::ScriptPrivateReferenceHook> scriptPrivateAddRefHook;
   js::MainThreadData<JS::ScriptPrivateReferenceHook> scriptPrivateReleaseHook;
 
+  void addRefScriptPrivate(const JS::Value& value) {
+    if (!value.isUndefined() && scriptPrivateAddRefHook) {
+      scriptPrivateAddRefHook(value);
+    }
+  }
+
+  void releaseScriptPrivate(const JS::Value& value) {
+    if (!value.isUndefined() && scriptPrivateReleaseHook) {
+      scriptPrivateReleaseHook(value);
+    }
+  }
+
  public:
 #if defined(JS_BUILD_BINAST)
   js::BinaryASTSupport& binast() { return binast_; }
 
  private:
   js::BinaryASTSupport binast_;
 #endif  // defined(JS_BUILD_BINAST)
 
--- a/js/src/vm/TaggedProto.h
+++ b/js/src/vm/TaggedProto.h
@@ -2,16 +2,18 @@
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef vm_TaggedProto_h
 #define vm_TaggedProto_h
 
+#include "mozilla/Maybe.h"
+
 #include "gc/Tracer.h"
 
 namespace js {
 
 // Information about an object prototype, which can be either a particular
 // object, null, or a lazily generated object. The latter is only used by
 // certain kinds of proxies.
 class TaggedProto {
@@ -124,23 +126,29 @@ class WrappedPtrOperations<TaggedProto, 
   inline JSObject* toObject() const { return value().toObject(); }
   inline JSObject* toObjectOrNull() const { return value().toObjectOrNull(); }
   JSObject* raw() const { return value().raw(); }
   HashNumber hashCode() const { return value().hashCode(); }
   uint64_t uniqueId() const { return value().uniqueId(); }
 };
 
 // If the TaggedProto is a JSObject pointer, convert to that type and call |f|
-// with the pointer. If the TaggedProto is lazy, calls F::defaultValue.
-template <typename F, typename... Args>
-auto DispatchTyped(F f, const TaggedProto& proto, Args&&... args) {
+// with the pointer. If the TaggedProto is lazy, returns None().
+template <typename F>
+auto MapGCThingTyped(const TaggedProto& proto, F&& f) {
   if (proto.isObject()) {
-    return f(proto.toObject(), std::forward<Args>(args)...);
+    return mozilla::Some(f(proto.toObject()));
   }
-  return F::defaultValue(proto);
+  using ReturnType = decltype(f(static_cast<JSObject*>(nullptr)));
+  return mozilla::Maybe<ReturnType>();
+}
+
+template <typename F>
+bool ApplyGCThingTyped(const TaggedProto& proto, F&& f) {
+  return MapGCThingTyped(proto, [&f](auto t) { f(t); return true; }).isSome();
 }
 
 // Since JSObject pointers are either nullptr or a valid object and since the
 // object layout of TaggedProto is identical to a bare object pointer, we can
 // safely treat a pointer to an already-rooted object (e.g. HandleObject) as a
 // pointer to a TaggedProto.
 inline Handle<TaggedProto> AsTaggedProto(HandleObject obj) {
   static_assert(sizeof(JSObject*) == sizeof(TaggedProto),
--- a/js/src/vm/UbiNode.cpp
+++ b/js/src/vm/UbiNode.cpp
@@ -37,17 +37,17 @@
 #include "vm/StringType.h"
 #include "vm/SymbolType.h"
 
 #include "vm/Debugger-inl.h"
 #include "vm/JSObject-inl.h"
 
 using namespace js;
 
-using JS::DispatchTyped;
+using JS::ApplyGCThingTyped;
 using JS::HandleValue;
 using JS::Value;
 using JS::ZoneSet;
 using JS::ubi::AtomOrTwoByteChars;
 using JS::ubi::CoarseType;
 using JS::ubi::Concrete;
 using JS::ubi::Edge;
 using JS::ubi::EdgeRange;
@@ -148,30 +148,22 @@ JS::Realm* Concrete<void>::realm() const
 UniquePtr<EdgeRange> Concrete<void>::edges(JSContext*, bool) const {
   MOZ_CRASH("null ubi::Node");
 }
 
 Node::Size Concrete<void>::size(mozilla::MallocSizeOf mallocSizeof) const {
   MOZ_CRASH("null ubi::Node");
 }
 
-struct Node::ConstructFunctor : public js::BoolDefaultAdaptor<Value, false> {
-  template <typename T>
-  bool operator()(T* t, Node* node) {
-    node->construct(t);
-    return true;
-  }
-};
-
 Node::Node(const JS::GCCellPtr& thing) {
-  DispatchTyped(ConstructFunctor(), thing, this);
+  ApplyGCThingTyped(thing, [this](auto t) { this->construct(t); });
 }
 
 Node::Node(HandleValue value) {
-  if (!DispatchTyped(ConstructFunctor(), value, this)) {
+  if (!ApplyGCThingTyped(value, [this](auto t) { this->construct(t); })) {
     construct<void>(nullptr);
   }
 }
 
 Value Node::exposeToJS() const {
   Value v;
 
   if (is<JSObject>()) {
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -1245,16 +1245,17 @@ class AstElemSegment : public AstNode {
   AstElemSegment(AstRef targetTable, AstExpr* offsetIfActive,
                  AstRefVector&& elems)
       : targetTable_(targetTable),
         offsetIfActive_(offsetIfActive),
         elems_(std::move(elems)) {}
 
   AstRef targetTable() const { return targetTable_; }
   AstRef& targetTableRef() { return targetTable_; }
+  bool isPassive() const { return offsetIfActive_ == nullptr; }
   AstExpr* offsetIfActive() const { return offsetIfActive_; }
   AstRefVector& elems() { return elems_; }
   const AstRefVector& elems() const { return elems_; }
 };
 
 typedef AstVector<AstElemSegment*> AstElemSegmentVector;
 
 class AstStartFunc : public AstNode {
--- a/js/src/wasm/WasmConstants.h
+++ b/js/src/wasm/WasmConstants.h
@@ -352,16 +352,21 @@ enum class Op {
   FirstPrefix = 0xfc,
   MiscPrefix = 0xfc,
   ThreadPrefix = 0xfe,
   MozPrefix = 0xff,
 
   Limit = 0x100
 };
 
+// TODO: RefFunc can't be incorporated into the opcode table until we're willing
+// to handle it generally and we've renumbered RefEq, but we need it to express
+// passive element segments.
+constexpr uint16_t PlaceholderRefFunc = 0xd2;
+
 inline bool IsPrefixByte(uint8_t b) { return b >= uint8_t(Op::FirstPrefix); }
 
 // Opcodes in the "miscellaneous" opcode space.
 enum class MiscOp {
   // Saturating float-to-int conversions
   I32TruncSSatF32 = 0x00,
   I32TruncUSatF32 = 0x01,
   I32TruncSSatF64 = 0x02,
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -7167,24 +7167,38 @@ static bool EncodeDataCountSection(Encod
 #endif
 
 static bool EncodeElemSegment(Encoder& e, AstElemSegment& segment) {
   if (!EncodeDestinationOffsetOrFlags(e, segment.targetTable().index(),
                                       segment.offsetIfActive())) {
     return false;
   }
 
+  if (segment.isPassive()) {
+    if (!e.writeFixedU8(uint8_t(TypeCode::AnyFunc))) {
+      return false;
+    }
+  }
+
   if (!e.writeVarU32(segment.elems().length())) {
     return false;
   }
 
   for (const AstRef& elem : segment.elems()) {
+    // Passive segments have an initializer expression, for now restricted to a
+    // function index.
+    if (segment.isPassive() && !e.writeFixedU8(PlaceholderRefFunc)) {
+      return false;
+    }
     if (!e.writeVarU32(elem.index())) {
       return false;
     }
+    if (segment.isPassive() && !e.writeFixedU8(uint8_t(Op::End))) {
+      return false;
+    }
   }
 
   return true;
 }
 
 static bool EncodeElemSection(Encoder& e, AstModule& module) {
   if (module.elemSegments().empty()) {
     return true;
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -2319,23 +2319,36 @@ static bool DecodeElemSection(Decoder& d
       // touch the field.
       tableIndex = (uint32_t)-1;
     } else if (env->tables[tableIndex].kind != TableKind::AnyFunction) {
       return d.fail("only tables of 'anyfunc' may have element segments");
     }
 
     seg->tableIndex = tableIndex;
 
-    if (initializerKind == InitializerKind::Active ||
-        initializerKind == InitializerKind::ActiveWithIndex) {
-      InitExpr offset;
-      if (!DecodeInitializerExpression(d, env, ValType::I32, &offset)) {
-        return false;
+    switch (initializerKind) {
+      case InitializerKind::Active:
+      case InitializerKind::ActiveWithIndex: {
+        InitExpr offset;
+        if (!DecodeInitializerExpression(d, env, ValType::I32, &offset)) {
+          return false;
+        }
+        seg->offsetIfActive.emplace(offset);
+        break;
       }
-      seg->offsetIfActive.emplace(offset);
+      case InitializerKind::Passive: {
+        uint8_t form;
+        if (!d.readFixedU8(&form)) {
+          return d.fail("expected type form");
+        }
+        if (form != uint8_t(TypeCode::AnyFunc)) {
+          return d.fail("passive segments can only contain function references");
+        }
+        break;
+      }
     }
 
     uint32_t numElems;
     if (!d.readVarU32(&numElems)) {
       return d.fail("expected segment size");
     }
 
     if (numElems > MaxTableInitialLength) {
@@ -2350,33 +2363,51 @@ static bool DecodeElemSection(Decoder& d
     // We assume that passive segments may be applied to external tables.
     // We can do slightly better: if there are no external tables in the
     // module then we don't need to worry about passive segments either.
     // But this is a temporary restriction.
     bool exportedTable = initializerKind == InitializerKind::Passive ||
                          env->tables[tableIndex].importedOrExported;
 #endif
 
+    // For passive segments we should use DecodeInitializerExpression() but we
+    // don't really want to generalize that function yet, so instead read the
+    // required Ref.Func and End here.
+
     for (uint32_t i = 0; i < numElems; i++) {
+      if (initializerKind == InitializerKind::Passive) {
+        OpBytes op;
+        if (!d.readOp(&op) || op.b0 != PlaceholderRefFunc) {
+          return d.fail("failed to read ref.func operation");
+        }
+      }
+
       uint32_t funcIndex;
       if (!d.readVarU32(&funcIndex)) {
         return d.fail("failed to read element function index");
       }
 
       if (funcIndex >= env->numFuncs()) {
         return d.fail("table element out of range");
       }
 
 #ifdef WASM_PRIVATE_REFTYPES
       if (exportedTable &&
           !FuncTypeIsJSCompatible(d, *env->funcTypes[funcIndex])) {
         return false;
       }
 #endif
 
+      if (initializerKind == InitializerKind::Passive) {
+        OpBytes end;
+        if (!d.readOp(&end) || end.b0 != uint16_t(Op::End)) {
+          return d.fail("failed to read end of ref.func expression");
+        }
+      }
+
       seg->elemFuncIndices.infallibleAppend(funcIndex);
     }
 
     env->elemSegments.infallibleAppend(std::move(seg));
   }
 
   return d.finishSection(*range, "elem");
 }
--- a/nsprpub/TAG-INFO
+++ b/nsprpub/TAG-INFO
@@ -1,1 +1,1 @@
-NSPR_4_20_RTM
+753fe0f7964c
--- a/nsprpub/config/prdepend.h
+++ b/nsprpub/config/prdepend.h
@@ -5,9 +5,8 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSPR in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
-
--- a/nsprpub/configure
+++ b/nsprpub/configure
@@ -778,17 +778,16 @@ PATH_SEPARATOR
 SHELL'
 ac_subst_files=''
 ac_user_opts='
 enable_option_checking
 with_android_ndk
 with_android_toolchain
 with_android_version
 with_android_platform
-with_gonk
 with_dist_prefix
 with_dist_bindir
 with_dist_includedir
 with_dist_libdir
 with_mozilla
 enable_optimize
 enable_debug
 enable_debug_symbols
@@ -868,16 +867,17 @@ x_libraries=NONE
 bindir='${exec_prefix}/bin'
 sbindir='${exec_prefix}/sbin'
 libexecdir='${exec_prefix}/libexec'
 datarootdir='${prefix}/share'
 datadir='${datarootdir}'
 sysconfdir='${prefix}/etc'
 sharedstatedir='${prefix}/com'
 localstatedir='${prefix}/var'
+
 includedir='${prefix}/include'
 oldincludedir='/usr/include'
 docdir='${datarootdir}/doc/${PACKAGE}'
 infodir='${datarootdir}/info'
 htmldir='${docdir}'
 dvidir='${docdir}'
 pdfdir='${docdir}'
 psdir='${docdir}'
@@ -1479,17 +1479,16 @@ Optional Packages:
   --with-android-ndk=DIR
                           location where the Android NDK can be found
   --with-android-toolchain=DIR
                           location of the Android toolchain
   --with-android-version=VER
                           Android platform version, default 5 for arm, 9 for x86/mips
   --with-android-platform=DIR
                           location of platform dir
-  --with-gonk=DIR         location of gonk dir
   --with-dist-prefix=DIST_PREFIX
                           place build files in DIST_PREFIX dist
   --with-dist-bindir=DIR  build execuatables in DIR DIST_PREFIX/bin
   --with-dist-includedir=DIR
                           build include files in DIR DIST_PREFIX/include/nspr
   --with-dist-libdir=DIR  build library files in DIR DIST_PREFIX/lib
   --with-mozilla          Compile NSPR with Mozilla support
   --with-arm-kuser        Use kuser helpers (Linux/ARM only)
@@ -2483,17 +2482,17 @@ case $target_os in *\ *) target_os=`echo
 # The aliases save the names the user supplied, while $host etc.
 # will get canonicalized.
 test -n "$target_alias" &&
   test "$program_prefix$program_suffix$program_transform_name" = \
     NONENONEs,x,x, &&
   program_prefix=${target_alias}-
 
 MOD_MAJOR_VERSION=4
-MOD_MINOR_VERSION=20
+MOD_MINOR_VERSION=21
 MOD_PATCH_VERSION=0
 NSPR_MODNAME=nspr20
 _HAVE_PTHREADS=
 USE_PTHREADS=
 USE_USER_PTHREADS=
 USE_NSPR_THREADS=
 USE_N32=
 USE_X32=
@@ -2651,29 +2650,16 @@ i?86-*android*)
 mipsel-*android*)
     android_tool_prefix="mipsel-linux-android"
     ;;
 *)
     android_tool_prefix="$target_os"
     ;;
 esac
 
-
-
-# Check whether --with-gonk was given.
-if test "${with_gonk+set}" = set; then :
-  withval=$with_gonk; gonkdir=$withval
-fi
-
-
-if test -n "$gonkdir" ; then
-
-    $as_echo "#define ANDROID 1" >>confdefs.h
-
-else
 case "$target" in
 *-android*|*-linuxandroid*)
     if test -z "$android_ndk" ; then
        as_fn_error $? "You must specify --with-android-ndk=/path/to/ndk when targeting Android." "$LINENO" 5
     fi
 
     if test -z "$android_toolchain" ; then
         { $as_echo "$as_me:${as_lineno-$LINENO}: checking for android toolchain directory" >&5
@@ -2749,17 +2735,16 @@ case "$target" in
     CFLAGS="-mandroid -I$android_platform/usr/include -fno-short-enums -fno-exceptions $CFLAGS"
     CXXFLAGS="-mandroid -I$android_platform/usr/include -fpic -fno-short-enums -fno-exceptions $CXXFLAGS"
     LDFLAGS="-mandroid -L$android_platform/usr/lib -Wl,-rpath-link=$android_platform/usr/lib --sysroot=$android_platform $LDFLAGS"
 
     $as_echo "#define ANDROID 1" >>confdefs.h
 
     ;;
 esac
-fi
 
 dist_prefix='${MOD_DEPTH}/dist'
 dist_bindir='${dist_prefix}/bin'
 dist_includedir='${dist_prefix}/include/nspr'
 dist_libdir='${dist_prefix}/lib'
 if test "${includedir}" = '${prefix}/include'; then
     includedir='${prefix}/include/nspr'
 fi
--- a/nsprpub/configure.in
+++ b/nsprpub/configure.in
@@ -10,17 +10,17 @@ AC_CONFIG_SRCDIR([pr/include/nspr.h])
 
 AC_CONFIG_AUX_DIR(${srcdir}/build/autoconf)
 AC_CANONICAL_TARGET
 
 dnl ========================================================
 dnl = Defaults
 dnl ========================================================
 MOD_MAJOR_VERSION=4
-MOD_MINOR_VERSION=20
+MOD_MINOR_VERSION=21
 MOD_PATCH_VERSION=0
 NSPR_MODNAME=nspr20
 _HAVE_PTHREADS=
 USE_PTHREADS=
 USE_USER_PTHREADS=
 USE_NSPR_THREADS=
 USE_N32=
 USE_X32=
@@ -142,31 +142,16 @@ i?86-*android*)
 mipsel-*android*)
     android_tool_prefix="mipsel-linux-android"
     ;;
 *)
     android_tool_prefix="$target_os"
     ;;
 esac
 
-dnl ========================================================
-dnl = Gonk is a fork of Android used for Mozilla's B2G project.
-dnl = Configuration is done largely by the top level config
-dnl = and the specified gonk directory doesn't matter here.
-dnl ========================================================
-
-AC_ARG_WITH(gonk,
-[  --with-gonk=DIR         location of gonk dir],
-    gonkdir=$withval)
-
-if test -n "$gonkdir" ; then
-    dnl Most things are directly configured by env vars when building for gonk
-
-    AC_DEFINE(ANDROID)
-else
 case "$target" in
 *-android*|*-linuxandroid*)
     if test -z "$android_ndk" ; then
        AC_MSG_ERROR([You must specify --with-android-ndk=/path/to/ndk when targeting Android.])
     fi
 
     if test -z "$android_toolchain" ; then
         AC_MSG_CHECKING([for android toolchain directory])
@@ -241,17 +226,16 @@ case "$target" in
     CPPFLAGS="-I$android_platform/usr/include $CPPFLAGS"
     CFLAGS="-mandroid -I$android_platform/usr/include -fno-short-enums -fno-exceptions $CFLAGS"
     CXXFLAGS="-mandroid -I$android_platform/usr/include -fpic -fno-short-enums -fno-exceptions $CXXFLAGS"
     LDFLAGS="-mandroid -L$android_platform/usr/lib -Wl,-rpath-link=$android_platform/usr/lib --sysroot=$android_platform $LDFLAGS"
 
     AC_DEFINE(ANDROID)
     ;;
 esac
-fi
 
 dnl ========================================================
 dnl =
 dnl = Check options that may affect the compiler
 dnl =
 dnl ========================================================
 dist_prefix='${MOD_DEPTH}/dist'
 dist_bindir='${dist_prefix}/bin'
--- a/nsprpub/lib/ds/plvrsion.c
+++ b/nsprpub/lib/ds/plvrsion.c
@@ -49,17 +49,17 @@ PRVersionDescription VERSION_DESC_NAME =
     /* debug            */  PR_TRUE,            /* a debug build */
 #else
     /* debug            */  PR_FALSE,           /* an optomized build */
 #endif
     /* special          */  PR_FALSE,           /* they're all special, but ... */
     /* filename         */  _PRODUCTION,        /* the produced library name */
     /* description      */ "Portable runtime",  /* what we are */
     /* security         */ "N/A",               /* not applicable here */
-    /* copywrite        */  "Copyright (c) 1998 Netscape Communications Corporation. All Rights Reserved",
+    /* copywrite        */ "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/.",
     /* comment          */  "http://www.mozilla.org/MPL/",
     /* specialString    */ ""
 };
 
 #ifdef XP_UNIX
 
 /*
  * Version information for the 'ident' and 'what commands
--- a/nsprpub/lib/libc/src/plvrsion.c
+++ b/nsprpub/lib/libc/src/plvrsion.c
@@ -49,17 +49,17 @@ PRVersionDescription VERSION_DESC_NAME =
     /* debug            */  PR_TRUE,            /* a debug build */
 #else
     /* debug            */  PR_FALSE,           /* an optomized build */
 #endif
     /* special          */  PR_FALSE,           /* they're all special, but ... */
     /* filename         */  _PRODUCTION,        /* the produced library name */
     /* description      */ "Portable runtime",  /* what we are */
     /* security         */ "N/A",               /* not applicable here */
-    /* copywrite        */  "Copyright (c) 1998 Netscape Communications Corporation. All Rights Reserved",
+    /* copywrite        */ "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/.",
     /* comment          */  "http://www.mozilla.org/MPL/",
     /* specialString    */ ""
 };
 
 #ifdef XP_UNIX
 
 /*
  * Version information for the 'ident' and 'what commands
--- a/nsprpub/lib/prstreams/plvrsion.c
+++ b/nsprpub/lib/prstreams/plvrsion.c
@@ -49,17 +49,17 @@ PRVersionDescription VERSION_DESC_NAME =
     /* debug            */  PR_TRUE,            /* a debug build */
 #else
     /* debug            */  PR_FALSE,           /* an optomized build */
 #endif
     /* special          */  PR_FALSE,           /* they're all special, but ... */
     /* filename         */  _PRODUCTION,        /* the produced library name */
     /* description      */ "Portable runtime",  /* what we are */
     /* security         */ "N/A",               /* not applicable here */
-    /* copywrite        */  "Copyright (c) 1998 Netscape Communications Corporation. All Rights Reserved",
+    /* copywrite        */ "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/.",
     /* comment          */  "http://www.mozilla.org/MPL/",
     /* specialString    */ ""
 };
 
 #ifdef XP_UNIX
 
 /*
  * Version information for the 'ident' and 'what commands
--- a/nsprpub/pr/include/md/_win32_errors.h
+++ b/nsprpub/pr/include/md/_win32_errors.h
@@ -78,17 +78,17 @@ extern void _MD_win32_map_sendto_error(P
 #define	_PR_MD_MAP_SENDTO_ERROR	_MD_win32_map_sendto_error
 
 extern void _MD_win32_map_accept_error(PRInt32 err);
 #define	_PR_MD_MAP_ACCEPT_ERROR	_MD_win32_map_accept_error
 
 extern void _MD_win32_map_acceptex_error(PRInt32 err);
 #define	_PR_MD_MAP_ACCEPTEX_ERROR	_MD_win32_map_acceptex_error
 
-extern PRInt32 _MD_win32_map_connect_error(PRInt32 err);
+extern void _MD_win32_map_connect_error(PRInt32 err);
 #define	_PR_MD_MAP_CONNECT_ERROR	_MD_win32_map_connect_error
 
 extern void _MD_win32_map_bind_error(PRInt32 err);
 #define	_PR_MD_MAP_BIND_ERROR	_MD_win32_map_bind_error
 
 extern void _MD_win32_map_listen_error(PRInt32 err);
 #define	_PR_MD_MAP_LISTEN_ERROR	_MD_win32_map_listen_error
 
--- a/nsprpub/pr/include/prbit.h
+++ b/nsprpub/pr/include/prbit.h
@@ -9,17 +9,18 @@
 #include "prtypes.h"
 PR_BEGIN_EXTERN_C
 
 /*
 ** Replace compare/jump/add/shift sequence with compiler built-in/intrinsic
 ** functions.
 */
 #if defined(_WIN32) && (_MSC_VER >= 1300) && \
-    (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_ARM))
+    (defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM) || \
+     defined(_M_ARM64))
 # include <intrin.h>
 # pragma  intrinsic(_BitScanForward,_BitScanReverse)
   __forceinline static int __prBitScanForward32(unsigned int val)
   { 
     unsigned long idx;
     _BitScanForward(&idx, (unsigned long)val);
     return( (int)idx );
   }
@@ -28,17 +29,18 @@ PR_BEGIN_EXTERN_C
     unsigned long idx;
     _BitScanReverse(&idx, (unsigned long)val);
     return( (int)(31-idx) );
   }
 # define pr_bitscan_ctz32(val)  __prBitScanForward32(val)
 # define pr_bitscan_clz32(val)  __prBitScanReverse32(val)
 # define  PR_HAVE_BUILTIN_BITSCAN32
 #elif ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) && \
-       (defined(__i386__) || defined(__x86_64__) || defined(__arm__))
+       (defined(__i386__) || defined(__x86_64__) || defined(__arm__) || \
+        defined(__aarch64__))
 # define pr_bitscan_ctz32(val)  __builtin_ctz(val)
 # define pr_bitscan_clz32(val)  __builtin_clz(val)
 # define  PR_HAVE_BUILTIN_BITSCAN32
 #endif /* MSVC || GCC */
 
 /*
 ** A prbitmap_t is a long integer that can be used for bitmaps
 */
@@ -131,17 +133,17 @@ NSPR_API(PRIntn) PR_FloorLog2(PRUint32 i
 ** To get MSVC to generate a rotate instruction, we have to use the _rotl
 ** or _rotr intrinsic and use a pragma to make it inline.
 **
 ** Note: MSVC in VS2005 will do an inline rotate instruction on the above
 ** construct.
 */
 
 #if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64) || \
-    defined(_M_X64) || defined(_M_ARM))
+    defined(_M_X64) || defined(_M_ARM) || defined(_M_ARM64))
 #include <stdlib.h>
 #pragma intrinsic(_rotl, _rotr)
 #define PR_ROTATE_LEFT32(a, bits) _rotl(a, bits)
 #define PR_ROTATE_RIGHT32(a, bits) _rotr(a, bits)
 #else
 #define PR_ROTATE_LEFT32(a, bits) (((a) << (bits)) | ((a) >> (32 - (bits))))
 #define PR_ROTATE_RIGHT32(a, bits) (((a) >> (bits)) | ((a) << (32 - (bits))))
 #endif
--- a/nsprpub/pr/include/prinit.h
+++ b/nsprpub/pr/include/prinit.h
@@ -26,21 +26,21 @@ PR_BEGIN_EXTERN_C
 /*
 ** NSPR's version is used to determine the likelihood that the version you
 ** used to build your component is anywhere close to being compatible with
 ** what is in the underlying library.
 **
 ** The format of the version string is
 **     "<major version>.<minor version>[.<patch level>] [<Beta>]"
 */
-#define PR_VERSION  "4.20"
+#define PR_VERSION  "4.21 Beta"
 #define PR_VMAJOR   4
-#define PR_VMINOR   20
+#define PR_VMINOR   21
 #define PR_VPATCH   0
-#define PR_BETA     PR_FALSE
+#define PR_BETA     PR_TRUE
 
 /*
 ** PRVersionCheck
 **
 ** The basic signature of the function that is called to provide version
 ** checking. The result will be a boolean that indicates the likelihood
 ** that the underling library will perform as the caller expects.
 **
--- a/nsprpub/pr/src/io/pripv6.c
+++ b/nsprpub/pr/src/io/pripv6.c
@@ -242,17 +242,17 @@ static PRInt32 PR_CALLBACK Ipv6ToIpv4Soc
 		_PR_ConvertToIpv6NetAddr(&tmp_ipv4addr, ipv6addr);
 		PR_ASSERT(IsValidNetAddr(ipv6addr) == PR_TRUE);
 	}
 	return result;
 }
 
 #if defined(_PR_INET6_PROBE)
 static PRBool ipv6_is_present;
-extern PRBool _pr_test_ipv6_socket(void);
+PR_EXTERN(PRBool) _pr_test_ipv6_socket(void);
 
 #if !defined(_PR_INET6) && defined(_PR_HAVE_GETIPNODEBYNAME)
 extern PRStatus _pr_find_getipnodebyname(void);
 #endif
 
 #if !defined(_PR_INET6) && defined(_PR_HAVE_GETADDRINFO)
 extern PRStatus _pr_find_getaddrinfo(void);
 #endif
--- a/nsprpub/pr/src/prvrsion.c
+++ b/nsprpub/pr/src/prvrsion.c
@@ -49,17 +49,17 @@ PRVersionDescription VERSION_DESC_NAME =
     /* debug            */  PR_TRUE,            /* a debug build */
 #else
     /* debug            */  PR_FALSE,           /* an optomized build */
 #endif
     /* special          */  PR_FALSE,           /* they're all special, but ... */
     /* filename         */  _PRODUCTION,        /* the produced library name */
     /* description      */ "Portable runtime",  /* what we are */
     /* security         */ "N/A",               /* not applicable here */
-    /* copywrite        */  "Copyright (c) 1998 Netscape Communications Corporation. All Rights Reserved",
+    /* copywrite        */ "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/.",
     /* comment          */  "License information: http://www.mozilla.org/MPL/",
     /* specialString    */ ""
 };
 
 #ifdef XP_UNIX
 
 /*
  * Version information for the 'ident' and 'what commands
--- a/nsprpub/pr/tests/abstract.c
+++ b/nsprpub/pr/tests/abstract.c
@@ -146,12 +146,12 @@ main()
   printf("PASS\n");
   return 0;
 }
 
 #else
 int
 main()
 {
-  prinf("PASS\n");
+  printf("PASS\n");
   return 0;
 }
 #endif
--- a/nsprpub/pr/tests/vercheck.c
+++ b/nsprpub/pr/tests/vercheck.c
@@ -35,34 +35,34 @@ static char *compatible_version[] = {
     "4.7.6",
     "4.8", "4.8.1", "4.8.2", "4.8.3", "4.8.4", "4.8.5",
     "4.8.6", "4.8.7", "4.8.8", "4.8.9",
     "4.9", "4.9.1", "4.9.2", "4.9.3", "4.9.4", "4.9.5",
     "4.9.6",
     "4.10", "4.10.1", "4.10.2", "4.10.3", "4.10.4",
     "4.10.5", "4.10.6", "4.10.7", "4.10.8", "4.10.9",
     "4.10.10", "4.11", "4.12", "4.13", "4.14", "4.15",
-    "4.16", "4.17", "4.18", "4.19",
+    "4.16", "4.17", "4.18", "4.19", "4.20",
     PR_VERSION
 };
 
 /*
  * This release is not backward compatible with the old
  * NSPR 2.1 and 3.x releases.
  *
  * Any release is incompatible with future releases and
  * patches.
  */
 static char *incompatible_version[] = {
     "2.1 19980529",
     "3.0", "3.0.1",
     "3.1", "3.1.1", "3.1.2", "3.1.3",
     "3.5", "3.5.1",
-    "4.20.1",
-    "4.21", "4.21.1",
+    "4.21.1",
+    "4.22", "4.22.1",
     "10.0", "11.1", "12.14.20"
 };
 
 int main(int argc, char **argv)
 {
     int idx;
     int num_compatible = sizeof(compatible_version) / sizeof(char *);
     int num_incompatible = sizeof(incompatible_version) / sizeof(char *);
--- a/security/nss.symbols
+++ b/security/nss.symbols
@@ -208,17 +208,19 @@ NSS_CMSMessage_IsSigned
 NSS_CMSRecipientInfo_Create
 NSS_CMSSignedData_AddCertificate
 NSS_CMSSignedData_AddCertList
 NSS_CMSSignedData_AddSignerInfo
 NSS_CMSSignedData_Create
 NSS_CMSSignedData_CreateCertsOnly
 NSS_CMSSignedData_Destroy
 NSS_CMSSignedData_GetContentInfo
+NSS_CMSSignedData_GetDigestAlgs
 NSS_CMSSignedData_GetSignerInfo
+NSS_CMSSignedData_HasDigests
 NSS_CMSSignedData_ImportCerts
 NSS_CMSSignedData_SetDigestValue
 NSS_CMSSignedData_SignerInfoCount
 NSS_CMSSignedData_VerifySignerInfo
 NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs
 NSS_CMSSignerInfo_AddSigningTime
 NSS_CMSSignerInfo_AddSMIMECaps
 NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs