Merge mozilla-central to autoland. a=merge CLOSED TREE
authorBrindusan Cristian <cbrindusan@mozilla.com>
Thu, 23 Aug 2018 01:02:39 +0300
changeset 481279 867a77e99ab0d656c7e8f10f7d1d5950501e7535
parent 481278 ffe0fc382b2f0c8aa80f562e6a52993f19d48162 (current diff)
parent 481248 f8d52bf9ffdedf8d9197690ee848c3d88f360b53 (diff)
child 481280 d77ca862636da2f413aa0b3a6e98c11c45ecae15
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersmerge
milestone63.0a1
Merge mozilla-central to autoland. a=merge CLOSED TREE
js/src/jit-test/tests/debug/Frame-onPop-star-generators-01.js
js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js
toolkit/components/privatebrowsing/PrivateBrowsing.manifest
toolkit/components/privatebrowsing/PrivateBrowsingTrackingProtectionWhitelist.js
toolkit/components/privatebrowsing/moz.build
toolkit/components/privatebrowsing/nsIPrivateBrowsingTrackingProtectionWhitelist.idl
--- a/browser/base/content/browser-contentblocking.js
+++ b/browser/base/content/browser-contentblocking.js
@@ -413,23 +413,21 @@ var ContentBlocking = {
     // This state will be overriden later if there's an exception set for this site.
     let active = this.enabled && detected;
 
     for (let blocker of this.blockers) {
       blocker.categoryItem.classList.toggle("blocked", this.enabled && blocker.enabled);
     }
 
     // Check whether the user has added an exception for this site.
-    let hasException = false;
-    if (PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser)) {
-      hasException = PrivateBrowsingUtils.existsInTrackingAllowlist(baseURI);
-    } else {
-      hasException = Services.perms.testExactPermission(baseURI,
-        "trackingprotection") == Services.perms.ALLOW_ACTION;
-    }
+    let type = PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser) ?
+                 "trackingprotection-pb" :
+                 "trackingprotection";
+    let hasException = Services.perms.testExactPermission(baseURI, type) ==
+      Services.perms.ALLOW_ACTION;
 
     this.content.toggleAttribute("detected", detected);
     this.content.toggleAttribute("hasException", hasException);
 
     this.iconBox.toggleAttribute("active", active);
     this.iconBox.toggleAttribute("hasException", this.enabled && hasException);
 
     if (isSimulated) {
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -548,17 +548,18 @@
                 scrollButtonWidth: arrowScrollbox._scrollButtonDown.getBoundingClientRect().width
               };
             }
 
             let width = 0;
             for (let i = numPinned - 1; i >= 0; i--) {
               let tab = this.children[i];
               width += layoutData.pinnedTabWidth;
-              tab.style.marginInlineStart = -(width + layoutData.scrollButtonWidth) + "px";
+              tab.style.setProperty("margin-inline-start",
+                -(width + layoutData.scrollButtonWidth) + "px", "important");
               tab._pinnedUnscrollable = true;
             }
             this.style.paddingInlineStart = width + "px";
           } else {
             this.removeAttribute("positionpinnedtabs");
 
             for (let i = 0; i < numPinned; i++) {
               let tab = this.children[i];
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -281,8 +281,9 @@ skip-if = debug
 skip-if = !crashreporter || !e10s # Tabs can't crash without e10s
 
 [browser_cookies.js]
 [browser_cookies_legacy.js]
 [browser_cookies_privacy.js]
 [browser_speculative_connect.js]
 [browser_1446343-windowsize.js]
 [browser_restore_reversed_z_order.js]
+skip-if = true #Bug 1455602
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -383,20 +383,16 @@
 @RESPATH@/actors/*
 
 ; Safe Browsing
 @RESPATH@/components/nsURLClassifier.manifest
 @RESPATH@/components/nsUrlClassifierHashCompleter.js
 @RESPATH@/components/nsUrlClassifierListManager.js
 @RESPATH@/components/nsUrlClassifierLib.js
 
-; Private Browsing
-@RESPATH@/components/PrivateBrowsing.manifest
-@RESPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js
-
 ; Security Reports
 @RESPATH@/components/SecurityReporter.manifest
 @RESPATH@/components/SecurityReporter.js
 
 ; ANGLE GLES-on-D3D rendering library
 #ifdef MOZ_ANGLE_RENDERER
 @BINPATH@/libEGL.dll
 @BINPATH@/libGLESv2.dll
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -65,18 +65,18 @@
   }
 }
 
 .tabbrowser-tab {
   -moz-appearance: none;
   background-color: transparent;
   border-radius: 0;
   border-width: 0;
-  margin: 0;
-  padding: 0;
+  margin: 0 !important /* override tabbox.css */;
+  padding: 0 !important /* override tabbox.css */;
   -moz-box-align: stretch;
 }
 
 /* The selected tab should appear above the border between the tabs toolbar and
    the navigation toolbar. */
 .tabbrowser-tab[visuallyselected=true] {
   position: relative;
   z-index: 2;
--- a/devtools/client/inspector/boxmodel/actions/box-model.js
+++ b/devtools/client/inspector/boxmodel/actions/box-model.js
@@ -8,40 +8,40 @@ const {
   UPDATE_GEOMETRY_EDITOR_ENABLED,
   UPDATE_LAYOUT,
   UPDATE_OFFSET_PARENT,
 } = require("./index");
 
 module.exports = {
 
   /**
-   * Update the geometry editor's enabled state.
+   * Updates the geometry editor's enabled state.
    *
    * @param  {Boolean} enabled
    *         Whether or not the geometry editor is enabled or not.
    */
   updateGeometryEditorEnabled(enabled) {
     return {
       type: UPDATE_GEOMETRY_EDITOR_ENABLED,
       enabled,
     };
   },
 
   /**
-   * Update the layout state with the new layout properties.
+   * Updates the layout state with the new layout properties.
    */
   updateLayout(layout) {
     return {
       type: UPDATE_LAYOUT,
       layout,
     };
   },
 
   /**
-   * Update the offset parent state with the new DOM node.
+   * Updates the offset parent state with the new DOM node.
    */
   updateOffsetParent(offsetParent) {
     return {
       type: UPDATE_OFFSET_PARENT,
       offsetParent,
     };
   }
 
--- a/devtools/client/inspector/boxmodel/actions/index.js
+++ b/devtools/client/inspector/boxmodel/actions/index.js
@@ -3,18 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { createEnum } = require("devtools/client/shared/enum");
 
 createEnum([
 
-  // Update the geometry editor's enabled state.
+  // Updates the geometry editor's enabled state.
   "UPDATE_GEOMETRY_EDITOR_ENABLED",
 
-  // Update the layout state with the latest layout properties.
+  // Updates the layout state with the latest layout properties.
   "UPDATE_LAYOUT",
 
-  // Update the offset parent state with the new DOM node.
+  // Updates the offset parent state with the new DOM node.
   "UPDATE_OFFSET_PARENT",
 
 ], module.exports);
--- a/devtools/client/inspector/boxmodel/components/BoxModel.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModel.js
@@ -13,23 +13,23 @@ const BoxModelMain = createFactory(requi
 const BoxModelProperties = createFactory(require("./BoxModelProperties"));
 
 const Types = require("../types");
 
 class BoxModel extends PureComponent {
   static get propTypes() {
     return {
       boxModel: PropTypes.shape(Types.boxModel).isRequired,
-      setSelectedNode: PropTypes.func.isRequired,
-      showBoxModelProperties: PropTypes.bool.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelEditor: PropTypes.func.isRequired,
       onShowBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
       onToggleGeometryEditor: PropTypes.func.isRequired,
+      showBoxModelProperties: PropTypes.bool.isRequired,
+      setSelectedNode: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.onKeyDown = this.onKeyDown.bind(this);
   }
 
@@ -39,54 +39,56 @@ class BoxModel extends PureComponent {
     if (target == this.boxModelContainer) {
       this.boxModelMain.onKeyDown(event);
     }
   }
 
   render() {
     const {
       boxModel,
-      setSelectedNode,
-      showBoxModelProperties,
       onHideBoxModelHighlighter,
       onShowBoxModelEditor,
       onShowBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
       onToggleGeometryEditor,
+      setSelectedNode,
+      showBoxModelProperties,
     } = this.props;
 
-    return dom.div(
-      {
-        className: "boxmodel-container",
-        tabIndex: 0,
-        ref: div => {
-          this.boxModelContainer = div;
-        },
-        onKeyDown: this.onKeyDown,
-      },
-      BoxModelMain({
-        boxModel,
-        boxModelContainer: this.boxModelContainer,
-        ref: boxModelMain => {
-          this.boxModelMain = boxModelMain;
+    return (
+      dom.div(
+        {
+          className: "boxmodel-container",
+          tabIndex: 0,
+          ref: div => {
+            this.boxModelContainer = div;
+          },
+          onKeyDown: this.onKeyDown,
         },
-        onHideBoxModelHighlighter,
-        onShowBoxModelEditor,
-        onShowBoxModelHighlighter,
-      }),
-      BoxModelInfo({
-        boxModel,
-        onToggleGeometryEditor,
-      }),
-      showBoxModelProperties ?
-        BoxModelProperties({
+        BoxModelMain({
+          boxModel,
+          boxModelContainer: this.boxModelContainer,
+          ref: boxModelMain => {
+            this.boxModelMain = boxModelMain;
+          },
+          onHideBoxModelHighlighter,
+          onShowBoxModelEditor,
+          onShowBoxModelHighlighter,
+        }),
+        BoxModelInfo({
           boxModel,
-          setSelectedNode,
-          onHideBoxModelHighlighter,
-          onShowBoxModelHighlighterForNode,
-        })
-        :
-        null
+          onToggleGeometryEditor,
+        }),
+        showBoxModelProperties ?
+          BoxModelProperties({
+            boxModel,
+            setSelectedNode,
+            onHideBoxModelHighlighter,
+            onShowBoxModelHighlighterForNode,
+          })
+          :
+          null
+      )
     );
   }
 }
 
 module.exports = BoxModel;
--- a/devtools/client/inspector/boxmodel/components/BoxModelEditable.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModelEditable.js
@@ -13,19 +13,19 @@ const LONG_TEXT_ROTATE_LIMIT = 3;
 
 class BoxModelEditable extends PureComponent {
   static get propTypes() {
     return {
       box: PropTypes.string.isRequired,
       direction: PropTypes.string,
       focusable: PropTypes.bool.isRequired,
       level: PropTypes.string,
+      onShowBoxModelEditor: PropTypes.func.isRequired,
       property: PropTypes.string.isRequired,
       textContent: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
-      onShowBoxModelEditor: PropTypes.func.isRequired,
     };
   }
 
   componentDidMount() {
     const { property, onShowBoxModelEditor } = this.props;
 
     editableItem({
       element: this.boxModelEditable,
@@ -44,31 +44,33 @@ class BoxModelEditable extends PureCompo
       textContent,
     } = this.props;
 
     const rotate = direction &&
                  (direction == "left" || direction == "right") &&
                  box !== "position" &&
                  textContent.toString().length > LONG_TEXT_ROTATE_LIMIT;
 
-    return dom.p(
-      {
-        className: `boxmodel-${box}
-                    ${direction ? " boxmodel-" + direction : "boxmodel-" + property}
-                    ${rotate ? " boxmodel-rotate" : ""}`,
-      },
-      dom.span(
+    return (
+      dom.p(
         {
-          className: "boxmodel-editable",
-          "data-box": box,
-          tabIndex: box === level && focusable ? 0 : -1,
-          title: property,
-          ref: span => {
-            this.boxModelEditable = span;
+          className: `boxmodel-${box}
+                      ${direction ? " boxmodel-" + direction : "boxmodel-" + property}
+                      ${rotate ? " boxmodel-rotate" : ""}`,
+        },
+        dom.span(
+          {
+            className: "boxmodel-editable",
+            "data-box": box,
+            tabIndex: box === level && focusable ? 0 : -1,
+            title: property,
+            ref: span => {
+              this.boxModelEditable = span;
+            },
           },
-        },
-        textContent
+          textContent
+        )
       )
     );
   }
 }
 
 module.exports = BoxModelEditable;
--- a/devtools/client/inspector/boxmodel/components/BoxModelInfo.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModelInfo.js
@@ -44,42 +44,30 @@ class BoxModelInfo extends PureComponent
       width = "-",
     } = layout;
 
     let buttonClass = "layout-geometry-editor devtools-button";
     if (geometryEditorEnabled) {
       buttonClass += " checked";
     }
 
-    return dom.div(
-      {
-        className: "boxmodel-info",
-      },
-      dom.span(
-        {
-          className: "boxmodel-element-size",
-        },
-        SHARED_L10N.getFormatStr("dimensions", width, height)
-      ),
-      dom.section(
-        {
-          className: "boxmodel-position-group",
-        },
-        isPositionEditable ?
-          dom.button({
-            className: buttonClass,
-            title: BOXMODEL_L10N.getStr("boxmodel.geometryButton.tooltip"),
-            onClick: this.onToggleGeometryEditor,
-          })
-          :
-          null,
-        dom.span(
-          {
-            className: "boxmodel-element-position",
-          },
-          position
+    return (
+      dom.div({ className: "boxmodel-info" },
+        dom.span({ className: "boxmodel-element-size" },
+          SHARED_L10N.getFormatStr("dimensions", width, height)
+        ),
+        dom.section({ className: "boxmodel-position-group" },
+          isPositionEditable ?
+            dom.button({
+              className: buttonClass,
+              title: BOXMODEL_L10N.getStr("boxmodel.geometryButton.tooltip"),
+              onClick: this.onToggleGeometryEditor,
+            })
+            :
+            null,
+          dom.span({ className: "boxmodel-element-position" }, position)
         )
       )
     );
   }
 }
 
 module.exports = BoxModelInfo;
--- a/devtools/client/inspector/boxmodel/components/BoxModelMain.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModelMain.js
@@ -41,20 +41,20 @@ class BoxModelMain extends PureComponent
     this.getBorderOrPaddingValue = this.getBorderOrPaddingValue.bind(this);
     this.getContextBox = this.getContextBox.bind(this);
     this.getDisplayPosition = this.getDisplayPosition.bind(this);
     this.getHeightValue = this.getHeightValue.bind(this);
     this.getMarginValue = this.getMarginValue.bind(this);
     this.getPositionValue = this.getPositionValue.bind(this);
     this.getWidthValue = this.getWidthValue.bind(this);
     this.moveFocus = this.moveFocus.bind(this);
-    this.setAriaActive = this.setAriaActive.bind(this);
     this.onHighlightMouseOver = this.onHighlightMouseOver.bind(this);
     this.onKeyDown = this.onKeyDown.bind(this);
     this.onLevelClick = this.onLevelClick.bind(this);
+    this.setAriaActive = this.setAriaActive.bind(this);
   }
 
   componentDidUpdate() {
     const displayPosition = this.getDisplayPosition();
     const isContentBox = this.getContextBox();
 
     this.layouts = {
       "position": new Map([
@@ -409,313 +409,300 @@ class BoxModelMain extends PureComponent
     const marginRight = this.getMarginValue("margin-right", "right");
     const marginBottom = this.getMarginValue("margin-bottom", "bottom");
     const marginLeft = this.getMarginValue("margin-left", "left");
 
     height = this.getHeightValue(height);
     width = this.getWidthValue(width);
 
     const contentBox = layout["box-sizing"] == "content-box" ?
-      dom.div(
-        {
-          className: "boxmodel-size",
-        },
+      dom.div({ className: "boxmodel-size" },
         BoxModelEditable({
           box: "content",
           focusable,
           level,
           property: "width",
           ref: editable => {
             this.contentEditable = editable;
           },
           textContent: width,
           onShowBoxModelEditor
         }),
-        dom.span(
-          {},
-          "\u00D7"
-        ),
+        dom.span({}, "\u00D7"),
         BoxModelEditable({
           box: "content",
           focusable,
           level,
           property: "height",
           textContent: height,
           onShowBoxModelEditor
         })
       )
       :
-      dom.p(
-        {
-          className: "boxmodel-size",
-        },
-        dom.span(
-          {
-            title: "content",
-          },
+      dom.p({ className: "boxmodel-size" },
+        dom.span({ title: "content" },
           SHARED_L10N.getFormatStr("dimensions", width, height)
         )
       );
 
-    return dom.div(
-      {
-        className: "boxmodel-main devtools-monospace",
-        "data-box": "position",
-        ref: div => {
-          this.positionLayout = div;
-        },
-        onClick: this.onLevelClick,
-        onKeyDown: this.onKeyDown,
-        onMouseOver: this.onHighlightMouseOver,
-        onMouseOut: this.props.onHideBoxModelHighlighter,
-      },
-      displayPosition ?
-        dom.span(
-          {
-            className: "boxmodel-legend",
-            "data-box": "position",
-            title: "position",
-          },
-          "position"
-        )
-        :
-        null,
+    return (
       dom.div(
         {
-          className: "boxmodel-box"
-        },
-        dom.span(
-          {
-            className: "boxmodel-legend",
-            "data-box": "margin",
-            title: "margin",
+          className: "boxmodel-main devtools-monospace",
+          "data-box": "position",
+          ref: div => {
+            this.positionLayout = div;
           },
-          "margin"
-        ),
-        dom.div(
-          {
-            className: "boxmodel-margins",
-            "data-box": "margin",
-            title: "margin",
-            ref: div => {
-              this.marginLayout = div;
-            },
-          },
+          onClick: this.onLevelClick,
+          onKeyDown: this.onKeyDown,
+          onMouseOver: this.onHighlightMouseOver,
+          onMouseOut: this.props.onHideBoxModelHighlighter,
+        },
+        displayPosition ?
           dom.span(
             {
               className: "boxmodel-legend",
-              "data-box": "border",
-              title: "border",
+              "data-box": "position",
+              title: "position",
             },
-            "border"
+            "position"
+          )
+          :
+          null,
+        dom.div({ className: "boxmodel-box" },
+          dom.span(
+            {
+              className: "boxmodel-legend",
+              "data-box": "margin",
+              title: "margin",
+            },
+            "margin"
           ),
           dom.div(
             {
-              className: "boxmodel-borders",
-              "data-box": "border",
-              title: "border",
+              className: "boxmodel-margins",
+              "data-box": "margin",
+              title: "margin",
               ref: div => {
-                this.borderLayout = div;
+                this.marginLayout = div;
               },
             },
             dom.span(
               {
                 className: "boxmodel-legend",
-                "data-box": "padding",
-                title: "padding",
+                "data-box": "border",
+                title: "border",
               },
-              "padding"
+              "border"
             ),
             dom.div(
               {
-                className: "boxmodel-paddings",
-                "data-box": "padding",
-                title: "padding",
+                className: "boxmodel-borders",
+                "data-box": "border",
+                title: "border",
                 ref: div => {
-                  this.paddingLayout = div;
+                  this.borderLayout = div;
                 },
               },
-              dom.div({
-                className: "boxmodel-contents",
-                "data-box": "content",
-                title: "content",
-                ref: div => {
-                  this.contentLayout = div;
+              dom.span(
+                {
+                  className: "boxmodel-legend",
+                  "data-box": "padding",
+                  title: "padding",
                 },
-              })
+                "padding"
+              ),
+              dom.div(
+                {
+                  className: "boxmodel-paddings",
+                  "data-box": "padding",
+                  title: "padding",
+                  ref: div => {
+                    this.paddingLayout = div;
+                  },
+                },
+                dom.div({
+                  className: "boxmodel-contents",
+                  "data-box": "content",
+                  title: "content",
+                  ref: div => {
+                    this.contentLayout = div;
+                  },
+                })
+              )
             )
           )
-        )
-      ),
-      displayPosition ?
+        ),
+        displayPosition ?
+          BoxModelEditable({
+            box: "position",
+            direction: "top",
+            focusable,
+            level,
+            property: "position-top",
+            ref: editable => {
+              this.positionEditable = editable;
+            },
+            textContent: positionTop,
+            onShowBoxModelEditor,
+          })
+          :
+          null,
+        displayPosition ?
+          BoxModelEditable({
+            box: "position",
+            direction: "right",
+            focusable,
+            level,
+            property: "position-right",
+            textContent: positionRight,
+            onShowBoxModelEditor,
+          })
+          :
+          null,
+        displayPosition ?
+          BoxModelEditable({
+            box: "position",
+            direction: "bottom",
+            focusable,
+            level,
+            property: "position-bottom",
+            textContent: positionBottom,
+            onShowBoxModelEditor,
+          })
+          :
+          null,
+        displayPosition ?
+          BoxModelEditable({
+            box: "position",
+            direction: "left",
+            focusable,
+            level,
+            property: "position-left",
+            textContent: positionLeft,
+            onShowBoxModelEditor,
+          })
+          :
+          null,
         BoxModelEditable({
-          box: "position",
+          box: "margin",
           direction: "top",
           focusable,
           level,
-          property: "position-top",
+          property: "margin-top",
           ref: editable => {
-            this.positionEditable = editable;
+            this.marginEditable = editable;
           },
-          textContent: positionTop,
+          textContent: marginTop,
           onShowBoxModelEditor,
-        })
-        :
-        null,
-      displayPosition ?
+        }),
         BoxModelEditable({
-          box: "position",
+          box: "margin",
           direction: "right",
           focusable,
           level,
-          property: "position-right",
-          textContent: positionRight,
+          property: "margin-right",
+          textContent: marginRight,
           onShowBoxModelEditor,
-        })
-        :
-        null,
-      displayPosition ?
+        }),
         BoxModelEditable({
-          box: "position",
+          box: "margin",
           direction: "bottom",
           focusable,
           level,
-          property: "position-bottom",
-          textContent: positionBottom,
+          property: "margin-bottom",
+          textContent: marginBottom,
           onShowBoxModelEditor,
-        })
-        :
-        null,
-      displayPosition ?
+        }),
         BoxModelEditable({
-          box: "position",
+          box: "margin",
           direction: "left",
           focusable,
           level,
-          property: "position-left",
-          textContent: positionLeft,
+          property: "margin-left",
+          textContent: marginLeft,
+          onShowBoxModelEditor,
+        }),
+        BoxModelEditable({
+          box: "border",
+          direction: "top",
+          focusable,
+          level,
+          property: "border-top-width",
+          ref: editable => {
+            this.borderEditable = editable;
+          },
+          textContent: borderTop,
+          onShowBoxModelEditor,
+        }),
+        BoxModelEditable({
+          box: "border",
+          direction: "right",
+          focusable,
+          level,
+          property: "border-right-width",
+          textContent: borderRight,
+          onShowBoxModelEditor,
+        }),
+        BoxModelEditable({
+          box: "border",
+          direction: "bottom",
+          focusable,
+          level,
+          property: "border-bottom-width",
+          textContent: borderBottom,
+          onShowBoxModelEditor,
+        }),
+        BoxModelEditable({
+          box: "border",
+          direction: "left",
+          focusable,
+          level,
+          property: "border-left-width",
+          textContent: borderLeft,
           onShowBoxModelEditor,
-        })
-        :
-        null,
-      BoxModelEditable({
-        box: "margin",
-        direction: "top",
-        focusable,
-        level,
-        property: "margin-top",
-        ref: editable => {
-          this.marginEditable = editable;
-        },
-        textContent: marginTop,
-        onShowBoxModelEditor,
-      }),
-      BoxModelEditable({
-        box: "margin",
-        direction: "right",
-        focusable,
-        level,
-        property: "margin-right",
-        textContent: marginRight,
-        onShowBoxModelEditor,
-      }),
-      BoxModelEditable({
-        box: "margin",
-        direction: "bottom",
-        focusable,
-        level,
-        property: "margin-bottom",
-        textContent: marginBottom,
-        onShowBoxModelEditor,
-      }),
-      BoxModelEditable({
-        box: "margin",
-        direction: "left",
-        focusable,
-        level,
-        property: "margin-left",
-        textContent: marginLeft,
-        onShowBoxModelEditor,
-      }),
-      BoxModelEditable({
-        box: "border",
-        direction: "top",
-        focusable,
-        level,
-        property: "border-top-width",
-        ref: editable => {
-          this.borderEditable = editable;
-        },
-        textContent: borderTop,
-        onShowBoxModelEditor,
-      }),
-      BoxModelEditable({
-        box: "border",
-        direction: "right",
-        focusable,
-        level,
-        property: "border-right-width",
-        textContent: borderRight,
-        onShowBoxModelEditor,
-      }),
-      BoxModelEditable({
-        box: "border",
-        direction: "bottom",
-        focusable,
-        level,
-        property: "border-bottom-width",
-        textContent: borderBottom,
-        onShowBoxModelEditor,
-      }),
-      BoxModelEditable({
-        box: "border",
-        direction: "left",
-        focusable,
-        level,
-        property: "border-left-width",
-        textContent: borderLeft,
-        onShowBoxModelEditor,
-      }),
-      BoxModelEditable({
-        box: "padding",
-        direction: "top",
-        focusable,
-        level,
-        property: "padding-top",
-        ref: editable => {
-          this.paddingEditable = editable;
-        },
-        textContent: paddingTop,
-        onShowBoxModelEditor,
-      }),
-      BoxModelEditable({
-        box: "padding",
-        direction: "right",
-        focusable,
-        level,
-        property: "padding-right",
-        textContent: paddingRight,
-        onShowBoxModelEditor,
-      }),
-      BoxModelEditable({
-        box: "padding",
-        direction: "bottom",
-        focusable,
-        level,
-        property: "padding-bottom",
-        textContent: paddingBottom,
-        onShowBoxModelEditor,
-      }),
-      BoxModelEditable({
-        box: "padding",
-        direction: "left",
-        focusable,
-        level,
-        property: "padding-left",
-        textContent: paddingLeft,
-        onShowBoxModelEditor,
-      }),
-      contentBox
+        }),
+        BoxModelEditable({
+          box: "padding",
+          direction: "top",
+          focusable,
+          level,
+          property: "padding-top",
+          ref: editable => {
+            this.paddingEditable = editable;
+          },
+          textContent: paddingTop,
+          onShowBoxModelEditor,
+        }),
+        BoxModelEditable({
+          box: "padding",
+          direction: "right",
+          focusable,
+          level,
+          property: "padding-right",
+          textContent: paddingRight,
+          onShowBoxModelEditor,
+        }),
+        BoxModelEditable({
+          box: "padding",
+          direction: "bottom",
+          focusable,
+          level,
+          property: "padding-bottom",
+          textContent: paddingBottom,
+          onShowBoxModelEditor,
+        }),
+        BoxModelEditable({
+          box: "padding",
+          direction: "left",
+          focusable,
+          level,
+          property: "padding-left",
+          textContent: paddingLeft,
+          onShowBoxModelEditor,
+        }),
+        contentBox
+      )
     );
   }
 }
 
 module.exports = BoxModelMain;
--- a/devtools/client/inspector/boxmodel/components/BoxModelProperties.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModelProperties.js
@@ -15,19 +15,19 @@ const Types = require("../types");
 
 const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
 const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
 
 class BoxModelProperties extends PureComponent {
   static get propTypes() {
     return {
       boxModel: PropTypes.shape(Types.boxModel).isRequired,
-      setSelectedNode: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+      setSelectedNode: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {
       isOpen: true,
@@ -68,44 +68,41 @@ class BoxModelProperties extends PureCom
       isOpen: !this.state.isOpen,
     });
     event.stopPropagation();
   }
 
   render() {
     const {
       boxModel,
-      setSelectedNode,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
+      setSelectedNode,
     } = this.props;
     const { layout } = boxModel;
 
     const layoutInfo = ["box-sizing", "display", "float",
                         "line-height", "position", "z-index"];
 
     const properties = layoutInfo.map(info => {
       const { referenceElement, referenceElementType } = this.getReferenceElement(info);
 
       return ComputedProperty({
         name: info,
         key: info,
-        value: layout[info],
+        onHideBoxModelHighlighter,
+        onShowBoxModelHighlighterForNode,
         referenceElement,
         referenceElementType,
         setSelectedNode,
-        onHideBoxModelHighlighter,
-        onShowBoxModelHighlighterForNode,
+        value: layout[info],
       });
     });
 
-    return dom.div(
-      {
-        className: "boxmodel-properties",
-      },
+    return dom.div({ className: "boxmodel-properties" },
       dom.div(
         {
           className: "boxmodel-properties-header",
           onDoubleClick: this.onToggleExpander,
         },
         dom.span(
           {
             className: "boxmodel-properties-expander theme-twisty",
--- a/devtools/client/inspector/grids/actions/grids.js
+++ b/devtools/client/inspector/grids/actions/grids.js
@@ -8,49 +8,49 @@ const {
   UPDATE_GRID_COLOR,
   UPDATE_GRID_HIGHLIGHTED,
   UPDATE_GRIDS,
 } = require("./index");
 
 module.exports = {
 
   /**
-   * Update the color used for the grid's highlighter.
+   * Updates the color used for the grid's highlighter.
    *
    * @param  {NodeFront} nodeFront
    *         The NodeFront of the DOM node to toggle the grid highlighter.
    * @param  {String} color
    *         The color to use for this nodeFront's grid highlighter.
    */
   updateGridColor(nodeFront, color) {
     return {
       type: UPDATE_GRID_COLOR,
       color,
       nodeFront,
     };
   },
 
   /**
-   * Update the grid highlighted state.
+   * Updates the grid highlighted state.
    *
    * @param  {NodeFront} nodeFront
    *         The NodeFront of the DOM node to toggle the grid highlighter.
    * @param  {Boolean} highlighted
    *         Whether or not the grid highlighter is highlighting the grid.
    */
   updateGridHighlighted(nodeFront, highlighted) {
     return {
       type: UPDATE_GRID_HIGHLIGHTED,
       highlighted,
       nodeFront,
     };
   },
 
   /**
-   * Update the grid state with the new list of grids.
+   * Updates the grid state with the new list of grids.
    */
   updateGrids(grids) {
     return {
       type: UPDATE_GRIDS,
       grids,
     };
   },
 
--- a/devtools/client/inspector/grids/actions/highlighter-settings.js
+++ b/devtools/client/inspector/grids/actions/highlighter-settings.js
@@ -8,43 +8,43 @@ const {
   UPDATE_SHOW_GRID_AREAS,
   UPDATE_SHOW_GRID_LINE_NUMBERS,
   UPDATE_SHOW_INFINITE_LINES,
 } = require("./index");
 
 module.exports = {
 
   /**
-   * Update the grid highlighter's show grid areas preference.
+   * Updates the grid highlighter's show grid areas preference.
    *
    * @param  {Boolean} enabled
    *         Whether or not the grid highlighter should show the grid areas.
    */
   updateShowGridAreas(enabled) {
     return {
       type: UPDATE_SHOW_GRID_AREAS,
       enabled,
     };
   },
 
   /**
-   * Update the grid highlighter's show grid line numbers preference.
+   * Updates the grid highlighter's show grid line numbers preference.
    *
    * @param  {Boolean} enabled
    *         Whether or not the grid highlighter should show the grid line numbers.
    */
   updateShowGridLineNumbers(enabled) {
     return {
       type: UPDATE_SHOW_GRID_LINE_NUMBERS,
       enabled,
     };
   },
 
   /**
-   * Update the grid highlighter's show infinite lines preference.
+   * Updates the grid highlighter's show infinite lines preference.
    *
    * @param  {Boolean} enabled
    *         Whether or not the grid highlighter should extend grid lines infinitely.
    */
   updateShowInfiniteLines(enabled) {
     return {
       type: UPDATE_SHOW_INFINITE_LINES,
       enabled,
--- a/devtools/client/inspector/grids/actions/index.js
+++ b/devtools/client/inspector/grids/actions/index.js
@@ -3,27 +3,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { createEnum } = require("devtools/client/shared/enum");
 
 createEnum([
 
-  // Update the color used for the overlay of a grid.
+  // Updates the color used for the overlay of a grid.
   "UPDATE_GRID_COLOR",
 
-  // Update the grid highlighted state.
+  // Updates the grid highlighted state.
   "UPDATE_GRID_HIGHLIGHTED",
 
-  // Update the entire grids state with the new list of grids.
+  // Updates the entire grids state with the new list of grids.
   "UPDATE_GRIDS",
 
-  // Update the grid highlighter's show grid areas state.
+  // Updates the grid highlighter's show grid areas state.
   "UPDATE_SHOW_GRID_AREAS",
 
-  // Update the grid highlighter's show grid line numbers state.
+  // Updates the grid highlighter's show grid line numbers state.
   "UPDATE_SHOW_GRID_LINE_NUMBERS",
 
-  // Update the grid highlighter's show infinite lines state.
+  // Updates the grid highlighter's show infinite lines state.
   "UPDATE_SHOW_INFINITE_LINES",
 
 ], module.exports);
--- a/devtools/client/inspector/grids/components/Grid.js
+++ b/devtools/client/inspector/grids/components/Grid.js
@@ -16,77 +16,73 @@ const GridOutline = createFactory(requir
 const Types = require("../types");
 
 class Grid extends PureComponent {
   static get propTypes() {
     return {
       getSwatchColorPickerTooltip: PropTypes.func.isRequired,
       grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
       highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
-      setSelectedNode: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onSetGridOverlayColor: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
       onShowGridOutlineHighlight: PropTypes.func.isRequired,
       onToggleGridHighlighter: PropTypes.func.isRequired,
       onToggleShowGridAreas: PropTypes.func.isRequired,
       onToggleShowGridLineNumbers: PropTypes.func.isRequired,
       onToggleShowInfiniteLines: PropTypes.func.isRequired,
+      setSelectedNode: PropTypes.func.isRequired,
     };
   }
 
   render() {
     const {
       getSwatchColorPickerTooltip,
       grids,
       highlighterSettings,
-      setSelectedNode,
       onHideBoxModelHighlighter,
       onSetGridOverlayColor,
       onShowBoxModelHighlighterForNode,
       onShowGridOutlineHighlight,
       onToggleShowGridAreas,
       onToggleGridHighlighter,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
+      setSelectedNode,
     } = this.props;
 
-    return grids.length ?
-      dom.div(
-        {
-          id: "layout-grid-container",
-        },
-        dom.div(
-          {
-            className: "grid-content",
-          },
+    if (!grids.length) {
+      return (
+        dom.div({ className: "devtools-sidepanel-no-result" },
+          getStr("layout.noGridsOnThisPage")
+        )
+      );
+    }
+
+    return (
+      dom.div({ id: "layout-grid-container" },
+        dom.div({ className: "grid-content" },
           GridList({
             getSwatchColorPickerTooltip,
             grids,
-            setSelectedNode,
             onHideBoxModelHighlighter,
             onSetGridOverlayColor,
             onShowBoxModelHighlighterForNode,
             onToggleGridHighlighter,
+            setSelectedNode,
           }),
           GridDisplaySettings({
             highlighterSettings,
             onToggleShowGridAreas,
             onToggleShowGridLineNumbers,
             onToggleShowInfiniteLines,
           })
         ),
         GridOutline({
           grids,
           onShowGridOutlineHighlight,
         })
       )
-      :
-      dom.div(
-        {
-          className: "devtools-sidepanel-no-result",
-        },
-        getStr("layout.noGridsOnThisPage")
-      );
+    );
   }
 }
 
 module.exports = Grid;
--- a/devtools/client/inspector/grids/components/GridDisplaySettings.js
+++ b/devtools/client/inspector/grids/components/GridDisplaySettings.js
@@ -18,16 +18,17 @@ class GridDisplaySettings extends PureCo
       onToggleShowGridAreas: PropTypes.func.isRequired,
       onToggleShowGridLineNumbers: PropTypes.func.isRequired,
       onToggleShowInfiniteLines: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
+
     this.onShowGridAreasCheckboxClick = this.onShowGridAreasCheckboxClick.bind(this);
     this.onShowGridLineNumbersCheckboxClick =
       this.onShowGridLineNumbersCheckboxClick.bind(this);
     this.onShowInfiniteLinesCheckboxClick =
       this.onShowInfiniteLinesCheckboxClick.bind(this);
   }
 
   onShowGridAreasCheckboxClick() {
@@ -57,75 +58,58 @@ class GridDisplaySettings extends PureCo
     onToggleShowInfiniteLines(!highlighterSettings.showInfiniteLines);
   }
 
   render() {
     const {
       highlighterSettings,
     } = this.props;
 
-    return dom.div(
-      {
-        className: "grid-container",
-      },
-      dom.span(
-        {},
-        getStr("layout.gridDisplaySettings")
-      ),
-      dom.ul(
-        {},
-        dom.li(
-          {
-            className: "grid-settings-item",
-          },
-          dom.label(
-            {},
-            dom.input(
-              {
-                id: "grid-setting-show-grid-line-numbers",
-                type: "checkbox",
-                checked: highlighterSettings.showGridLineNumbers,
-                onChange: this.onShowGridLineNumbersCheckboxClick,
-              }
-            ),
-            getStr("layout.displayLineNumbers")
-          )
-        ),
-        dom.li(
-          {
-            className: "grid-settings-item",
-          },
-          dom.label(
-           {},
-           dom.input(
-             {
-               id: "grid-setting-show-grid-areas",
-               type: "checkbox",
-               checked: highlighterSettings.showGridAreasOverlay,
-               onChange: this.onShowGridAreasCheckboxClick,
-             }
-           ),
-           getStr("layout.displayAreaNames")
-          )
-        ),
-        dom.li(
-          {
-            className: "grid-settings-item",
-          },
-          dom.label(
-            {},
-            dom.input(
-              {
-                id: "grid-setting-extend-grid-lines",
-                type: "checkbox",
-                checked: highlighterSettings.showInfiniteLines,
-                onChange: this.onShowInfiniteLinesCheckboxClick,
-              }
-            ),
-            getStr("layout.extendLinesInfinitely")
+    return (
+      dom.div({ className: "grid-container" },
+        dom.span({}, getStr("layout.gridDisplaySettings")),
+        dom.ul({},
+          dom.li({ className: "grid-settings-item" },
+            dom.label({},
+              dom.input(
+                {
+                  id: "grid-setting-show-grid-line-numbers",
+                  type: "checkbox",
+                  checked: highlighterSettings.showGridLineNumbers,
+                  onChange: this.onShowGridLineNumbersCheckboxClick,
+                }
+              ),
+              getStr("layout.displayLineNumbers")
+            )
+          ),
+          dom.li({ className: "grid-settings-item" },
+            dom.label({},
+              dom.input(
+                {
+                  id: "grid-setting-show-grid-areas",
+                  type: "checkbox",
+                  checked: highlighterSettings.showGridAreasOverlay,
+                  onChange: this.onShowGridAreasCheckboxClick,
+                }
+              ),
+              getStr("layout.displayAreaNames")
+            )
+          ),
+          dom.li({ className: "grid-settings-item" },
+            dom.label({},
+              dom.input(
+                {
+                  id: "grid-setting-extend-grid-lines",
+                  type: "checkbox",
+                  checked: highlighterSettings.showInfiniteLines,
+                  onChange: this.onShowInfiniteLinesCheckboxClick,
+                }
+              ),
+              getStr("layout.extendLinesInfinitely")
+            )
           )
         )
       )
     );
   }
 }
 
 module.exports = GridDisplaySettings;
--- a/devtools/client/inspector/grids/components/GridItem.js
+++ b/devtools/client/inspector/grids/components/GridItem.js
@@ -17,29 +17,30 @@ const ElementNode = REPS.ElementNode;
 
 const Types = require("../types");
 
 class GridItem extends PureComponent {
   static get propTypes() {
     return {
       getSwatchColorPickerTooltip: PropTypes.func.isRequired,
       grid: PropTypes.shape(Types.grid).isRequired,
-      setSelectedNode: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onSetGridOverlayColor: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
       onToggleGridHighlighter: PropTypes.func.isRequired,
+      setSelectedNode: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
-    this.setGridColor = this.setGridColor.bind(this);
+
     this.onGridCheckboxClick = this.onGridCheckboxClick.bind(this);
     this.onGridInspectIconClick = this.onGridInspectIconClick.bind(this);
+    this.setGridColor = this.setGridColor.bind(this);
   }
 
   componentDidMount() {
     const swatchEl = findDOMNode(this).querySelector(".grid-color-swatch");
     const tooltip = this.props.getSwatchColorPickerTooltip();
 
     let previousColor;
     tooltip.addSwatch(swatchEl, {
@@ -92,55 +93,50 @@ class GridItem extends PureComponent {
   render() {
     const {
       grid,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
     } = this.props;
     const { nodeFront } = grid;
 
-    return dom.li(
-      {},
-      dom.label(
-        {},
-        dom.input(
+    return (
+      dom.li({},
+        dom.label({},
+          dom.input(
+            {
+              checked: grid.highlighted,
+              type: "checkbox",
+              value: grid.id,
+              onChange: this.onGridCheckboxClick,
+            }
+          ),
+          Rep(
+            {
+              defaultRep: ElementNode,
+              mode: MODE.TINY,
+              object: translateNodeFrontToGrip(nodeFront),
+              onDOMNodeMouseOut: () => onHideBoxModelHighlighter(),
+              onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(nodeFront),
+              onInspectIconClick: () => this.onGridInspectIconClick(nodeFront),
+            }
+          )
+        ),
+        dom.div(
           {
-            checked: grid.highlighted,
-            type: "checkbox",
-            value: grid.id,
-            onChange: this.onGridCheckboxClick,
+            className: "grid-color-swatch",
+            style: {
+              backgroundColor: grid.color,
+            },
+            title: grid.color,
           }
         ),
-        Rep(
-          {
-            defaultRep: ElementNode,
-            mode: MODE.TINY,
-            object: translateNodeFrontToGrip(nodeFront),
-            onDOMNodeMouseOut: () => onHideBoxModelHighlighter(),
-            onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(nodeFront),
-            onInspectIconClick: () => this.onGridInspectIconClick(nodeFront),
-          }
-        )
-      ),
-      dom.div(
-        {
-          className: "grid-color-swatch",
-          style: {
-            backgroundColor: grid.color,
-          },
-          title: grid.color,
-        }
-      ),
-      // The SwatchColorPicker relies on the nextSibling of the swatch element to apply
-      // the selected color. This is why we use a span in display: none for now.
-      // Ideally we should modify the SwatchColorPickerTooltip to bypass this requirement.
-      // See https://bugzilla.mozilla.org/show_bug.cgi?id=1341578
-      dom.span(
-        {
-          className: "grid-color-value"
-        },
-        grid.color
+        // The SwatchColorPicker relies on the nextSibling of the swatch element to apply
+        // the selected color. This is why we use a span in display: none for now.
+        // Ideally we should modify the SwatchColorPickerTooltip to bypass this
+        // requirement. See https://bugzilla.mozilla.org/show_bug.cgi?id=1341578
+        dom.span({ className: "grid-color-value" }, grid.color)
       )
     );
   }
 }
 
 module.exports = GridItem;
--- a/devtools/client/inspector/grids/components/GridList.js
+++ b/devtools/client/inspector/grids/components/GridList.js
@@ -13,56 +13,52 @@ const GridItem = createFactory(require("
 
 const Types = require("../types");
 
 class GridList extends PureComponent {
   static get propTypes() {
     return {
       getSwatchColorPickerTooltip: PropTypes.func.isRequired,
       grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
-      setSelectedNode: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onSetGridOverlayColor: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
       onToggleGridHighlighter: PropTypes.func.isRequired,
+      setSelectedNode: PropTypes.func.isRequired,
     };
   }
 
   render() {
     const {
       getSwatchColorPickerTooltip,
       grids,
-      setSelectedNode,
       onHideBoxModelHighlighter,
       onSetGridOverlayColor,
       onShowBoxModelHighlighterForNode,
       onToggleGridHighlighter,
+      setSelectedNode,
     } = this.props;
 
-    return dom.div(
-      {
-        className: "grid-container",
-      },
-      dom.span(
-        {},
-        getStr("layout.overlayGrid")
-      ),
-      dom.ul(
-        {
-          id: "grid-list",
-          className: "devtools-monospace",
-        },
-        grids.map(grid => GridItem({
-          key: grid.id,
-          getSwatchColorPickerTooltip,
-          grid,
-          setSelectedNode,
-          onHideBoxModelHighlighter,
-          onSetGridOverlayColor,
-          onShowBoxModelHighlighterForNode,
-          onToggleGridHighlighter,
-        }))
+    return (
+      dom.div({ className: "grid-container" },
+        dom.span({}, getStr("layout.overlayGrid")),
+        dom.ul(
+          {
+            id: "grid-list",
+            className: "devtools-monospace",
+          },
+          grids.map(grid => GridItem({
+            key: grid.id,
+            getSwatchColorPickerTooltip,
+            grid,
+            onHideBoxModelHighlighter,
+            onSetGridOverlayColor,
+            onShowBoxModelHighlighterForNode,
+            onToggleGridHighlighter,
+            setSelectedNode,
+          }))
+        )
       )
     );
   }
 }
 
 module.exports = GridList;
--- a/devtools/client/inspector/grids/components/GridOutline.js
+++ b/devtools/client/inspector/grids/components/GridOutline.js
@@ -53,23 +53,23 @@ class GridOutline extends PureComponent 
       showOutline: true,
       width: 0,
     };
 
     this.doHighlightCell = this.doHighlightCell.bind(this);
     this.getGridAreaName = this.getGridAreaName.bind(this);
     this.getHeight = this.getHeight.bind(this);
     this.getTotalWidthAndHeight = this.getTotalWidthAndHeight.bind(this);
+    this.onHighlightCell = this.onHighlightCell.bind(this);
     this.renderCannotShowOutlineText = this.renderCannotShowOutlineText.bind(this);
     this.renderGrid = this.renderGrid.bind(this);
     this.renderGridCell = this.renderGridCell.bind(this);
     this.renderGridOutline = this.renderGridOutline.bind(this);
     this.renderGridOutlineBorder = this.renderGridOutlineBorder.bind(this);
     this.renderOutline = this.renderOutline.bind(this);
-    this.onHighlightCell = this.onHighlightCell.bind(this);
   }
 
   componentWillReceiveProps({ grids }) {
     const selectedGrid = grids.find(grid => grid.highlighted);
 
     // Store the height of the grid container in the component state to prevent overflow
     // issues. We want to store the width of the grid container as well so that the
     // viewbox is only the calculated width of the grid outline.
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -51,29 +51,28 @@ const GRID_COLORS = [
 class GridInspector {
   constructor(inspector, window) {
     this.document = window.document;
     this.inspector = inspector;
     this.store = inspector.store;
     this.telemetry = inspector.telemetry;
     this.walker = this.inspector.walker;
 
-    this.updateGridPanel = this.updateGridPanel.bind(this);
-
     this.onHighlighterShown = this.onHighlighterShown.bind(this);
     this.onHighlighterHidden = this.onHighlighterHidden.bind(this);
     this.onNavigate = this.onNavigate.bind(this);
     this.onReflow = throttle(this.onReflow, 500, this);
     this.onSetGridOverlayColor = this.onSetGridOverlayColor.bind(this);
     this.onShowGridOutlineHighlight = this.onShowGridOutlineHighlight.bind(this);
     this.onSidebarSelect = this.onSidebarSelect.bind(this);
     this.onToggleGridHighlighter = this.onToggleGridHighlighter.bind(this);
     this.onToggleShowGridAreas = this.onToggleShowGridAreas.bind(this);
     this.onToggleShowGridLineNumbers = this.onToggleShowGridLineNumbers.bind(this);
     this.onToggleShowInfiniteLines = this.onToggleShowInfiniteLines.bind(this);
+    this.updateGridPanel = this.updateGridPanel.bind(this);
 
     this.init();
   }
 
   get highlighters() {
     if (!this._highlighters) {
       this._highlighters = this.inspector.highlighters;
     }
--- a/devtools/client/inspector/layout/components/LayoutApp.js
+++ b/devtools/client/inspector/layout/components/LayoutApp.js
@@ -22,39 +22,38 @@ const Accordion = createFactory(require(
 
 const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
 const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
 
 const LAYOUT_STRINGS_URI = "devtools/client/locales/layout.properties";
 const LAYOUT_L10N = new LocalizationHelper(LAYOUT_STRINGS_URI);
 
 const FLEXBOX_ENABLED_PREF = "devtools.flexboxinspector.enabled";
-
 const BOXMODEL_OPENED_PREF = "devtools.layout.boxmodel.opened";
 const FLEXBOX_OPENED_PREF = "devtools.layout.flexbox.opened";
 const GRID_OPENED_PREF = "devtools.layout.grid.opened";
 
 class LayoutApp extends PureComponent {
   static get propTypes() {
     return {
       boxModel: PropTypes.shape(BoxModelTypes.boxModel).isRequired,
       getSwatchColorPickerTooltip: PropTypes.func.isRequired,
       grids: PropTypes.arrayOf(PropTypes.shape(GridTypes.grid)).isRequired,
       highlighterSettings: PropTypes.shape(GridTypes.highlighterSettings).isRequired,
-      setSelectedNode: PropTypes.func.isRequired,
-      showBoxModelProperties: PropTypes.bool.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onSetFlexboxOverlayColor: PropTypes.func.isRequired,
       onSetGridOverlayColor: PropTypes.func.isRequired,
       onShowBoxModelEditor: PropTypes.func.isRequired,
       onShowBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
       onToggleGridHighlighter: PropTypes.func.isRequired,
       onToggleShowGridLineNumbers: PropTypes.func.isRequired,
       onToggleShowInfiniteLines: PropTypes.func.isRequired,
+      setSelectedNode: PropTypes.func.isRequired,
+      showBoxModelProperties: PropTypes.bool.isRequired,
     };
   }
 
   render() {
     let items = [
       {
         component: Grid,
         componentProps: this.props,
@@ -88,16 +87,17 @@ class LayoutApp extends PureComponent {
             const opened =  Services.prefs.getBoolPref(FLEXBOX_OPENED_PREF);
             Services.prefs.setBoolPref(FLEXBOX_OPENED_PREF, !opened);
           }
         },
         ...items
       ];
     }
 
-    return dom.div(
-      { id: "layout-container" },
-      Accordion({ items })
+    return (
+      dom.div({ id: "layout-container" },
+        Accordion({ items })
+      )
     );
   }
 }
 
 module.exports = connect(state => state)(LayoutApp);
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -29,18 +29,18 @@ class LayoutView {
   }
 
   init() {
     if (!this.inspector) {
       return;
     }
 
     const {
+      onShowBoxModelHighlighterForNode,
       setSelectedNode,
-      onShowBoxModelHighlighterForNode,
     } = this.inspector.getCommonComponentProps();
 
     const {
       onHideBoxModelHighlighter,
       onShowBoxModelEditor,
       onShowBoxModelHighlighter,
       onToggleGeometryEditor,
     } = this.inspector.getPanel("boxmodel").getComponentProps();
@@ -58,35 +58,35 @@ class LayoutView {
       onToggleGridHighlighter,
       onToggleShowGridAreas,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
     } = this.gridInspector.getComponentProps();
 
     const layoutApp = LayoutApp({
       getSwatchColorPickerTooltip: this.getSwatchColorPickerTooltip,
-      setSelectedNode,
-      /**
-       * Shows the box model properties under the box model if true, otherwise, hidden by
-       * default.
-       */
-      showBoxModelProperties: true,
       onHideBoxModelHighlighter,
       onSetFlexboxOverlayColor,
       onSetGridOverlayColor,
       onShowBoxModelEditor,
       onShowBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
       onShowGridOutlineHighlight,
       onToggleFlexboxHighlighter,
       onToggleGeometryEditor,
       onToggleGridHighlighter,
       onToggleShowGridAreas,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
+      setSelectedNode,
+      /**
+       * Shows the box model properties under the box model if true, otherwise, hidden by
+       * default.
+       */
+      showBoxModelProperties: true,
     });
 
     const provider = createElement(Provider, {
       id: "layoutview",
       key: "layoutview",
       store: this.store,
       title: INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle2"),
     }, layoutApp);
--- a/devtools/client/responsive.html/components/App.js
+++ b/devtools/client/responsive.html/components/App.js
@@ -12,24 +12,24 @@ const PropTypes = require("devtools/clie
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const Toolbar = createFactory(require("./Toolbar"));
 const Viewports = createFactory(require("./Viewports"));
 
 loader.lazyGetter(this, "DeviceModal",
   () => createFactory(require("./DeviceModal")));
 
+const { changeNetworkThrottling } = require("devtools/client/shared/components/throttling/actions");
 const {
   addCustomDevice,
   removeCustomDevice,
   updateDeviceDisplayed,
   updateDeviceModal,
   updatePreferredDevices,
 } = require("../actions/devices");
-const { changeNetworkThrottling } = require("devtools/client/shared/components/throttling/actions");
 const { changeReloadCondition } = require("../actions/reload-conditions");
 const { takeScreenshot } = require("../actions/screenshot");
 const { changeTouchSimulation } = require("../actions/touch-simulation");
 const { toggleLeftAlignment } = require("../actions/ui");
 const {
   changeDevice,
   changePixelRatio,
   removeDeviceAssociation,
@@ -217,60 +217,59 @@ class App extends Component {
     const selectedDevice = viewports[0].device;
     const selectedPixelRatio = viewports[0].pixelRatio;
 
     let deviceAdderViewportTemplate = {};
     if (devices.modalOpenedFromViewport !== null) {
       deviceAdderViewportTemplate = viewports[devices.modalOpenedFromViewport];
     }
 
-    return dom.div(
-      {
-        id: "app",
-      },
-      Toolbar({
-        devices,
-        displayPixelRatio,
-        networkThrottling,
-        reloadConditions,
-        screenshot,
-        selectedDevice,
-        selectedPixelRatio,
-        touchSimulation,
-        viewport: viewports[0],
-        onChangeDevice,
-        onChangeNetworkThrottling,
-        onChangePixelRatio,
-        onChangeReloadCondition,
-        onChangeTouchSimulation,
-        onExit,
-        onRemoveDeviceAssociation,
-        onResizeViewport,
-        onRotateViewport,
-        onScreenshot,
-        onToggleLeftAlignment,
-        onUpdateDeviceModal,
-      }),
-      Viewports({
-        screenshot,
-        viewports,
-        onBrowserMounted,
-        onContentResize,
-        onRemoveDeviceAssociation,
-        onResizeViewport,
-      }),
-      devices.isModalOpen ?
-        DeviceModal({
-          deviceAdderViewportTemplate,
+    return (
+      dom.div({ id: "app" },
+        Toolbar({
           devices,
-          onAddCustomDevice,
-          onDeviceListUpdate,
-          onRemoveCustomDevice,
-          onUpdateDeviceDisplayed,
+          displayPixelRatio,
+          networkThrottling,
+          reloadConditions,
+          screenshot,
+          selectedDevice,
+          selectedPixelRatio,
+          touchSimulation,
+          viewport: viewports[0],
+          onChangeDevice,
+          onChangeNetworkThrottling,
+          onChangePixelRatio,
+          onChangeReloadCondition,
+          onChangeTouchSimulation,
+          onExit,
+          onRemoveDeviceAssociation,
+          onResizeViewport,
+          onRotateViewport,
+          onScreenshot,
+          onToggleLeftAlignment,
           onUpdateDeviceModal,
-        })
-        :
-        null
+        }),
+        Viewports({
+          screenshot,
+          viewports,
+          onBrowserMounted,
+          onContentResize,
+          onRemoveDeviceAssociation,
+          onResizeViewport,
+        }),
+        devices.isModalOpen ?
+          DeviceModal({
+            deviceAdderViewportTemplate,
+            devices,
+            onAddCustomDevice,
+            onDeviceListUpdate,
+            onRemoveCustomDevice,
+            onUpdateDeviceDisplayed,
+            onUpdateDeviceModal,
+          })
+          :
+          null
+      )
     );
   }
 }
 
 module.exports = connect(state => state)(App);
--- a/devtools/client/responsive.html/components/Browser.js
+++ b/devtools/client/responsive.html/components/Browser.js
@@ -21,20 +21,20 @@ const FRAME_SCRIPT = "resource://devtool
 class Browser extends PureComponent {
   /**
    * This component is not allowed to depend directly on frequently changing data (width,
    * height). Any changes in props would cause the <iframe> to be removed and added again,
    * throwing away the current state of the page.
    */
   static get propTypes() {
     return {
+      onBrowserMounted: PropTypes.func.isRequired,
+      onContentResize: PropTypes.func.isRequired,
       swapAfterMount: PropTypes.bool.isRequired,
       userContextId: PropTypes.number.isRequired,
-      onBrowserMounted: PropTypes.func.isRequired,
-      onContentResize: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.onContentResize = this.onContentResize.bind(this);
   }
 
@@ -144,29 +144,31 @@ class Browser extends PureComponent {
 
     // In the case of @remote and @remoteType, the attribute must be set before the
     // element is added to the DOM to have any effect, which we are able to do with this
     // approach.
     //
     // @noisolation and @allowfullscreen are needed so that these frames have the same
     // access to browser features as regular browser tabs. The `swapFrameLoaders` platform
     // API we use compares such features before allowing the swap to proceed.
-    return dom.iframe(
-      {
-        allowFullScreen: "true",
-        className: "browser",
-        height: "100%",
-        mozbrowser: "true",
-        noisolation: "true",
-        remote: "true",
-        remotetype: "web",
-        src: "about:blank",
-        usercontextid: userContextId,
-        width: "100%",
-        ref: browser => {
-          this.browser = browser;
-        },
-      }
+    return (
+      dom.iframe(
+        {
+          allowFullScreen: "true",
+          className: "browser",
+          height: "100%",
+          mozbrowser: "true",
+          noisolation: "true",
+          remote: "true",
+          remotetype: "web",
+          src: "about:blank",
+          usercontextid: userContextId,
+          width: "100%",
+          ref: browser => {
+            this.browser = browser;
+          },
+        }
+      )
     );
   }
 }
 
 module.exports = Browser;
--- a/devtools/client/responsive.html/components/DeviceAdder.js
+++ b/devtools/client/responsive.html/components/DeviceAdder.js
@@ -5,27 +5,27 @@
 /* eslint-env browser */
 
 "use strict";
 
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
-const ViewportDimension = createFactory(require("./ViewportDimension.js"));
+const ViewportDimension = createFactory(require("./ViewportDimension"));
 
 const { getFormatStr, getStr } = require("../utils/l10n");
 const Types = require("../types");
 
 class DeviceAdder extends PureComponent {
   static get propTypes() {
     return {
       devices: PropTypes.shape(Types.devices).isRequired,
+      onAddCustomDevice: PropTypes.func.isRequired,
       viewportTemplate: PropTypes.shape(Types.viewport).isRequired,
-      onAddCustomDevice: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     const {
       height,
@@ -131,129 +131,89 @@ class DeviceAdder extends PureComponent 
       deviceName = getStr("responsive.customDeviceName");
       Object.assign(normalizedViewport, {
         pixelRatio: window.devicePixelRatio,
         userAgent: navigator.userAgent,
         touch: false,
       });
     }
 
-    return dom.div(
-      {
-        id: "device-adder"
-      },
-      dom.div(
-        {
-          id: "device-adder-content",
-        },
-        dom.div(
-          {
-            id: "device-adder-column-1",
-          },
-          dom.label(
-            {
-              id: "device-adder-name",
-            },
-            dom.span(
-              {
-                className: "device-adder-label",
-              },
-              getStr("responsive.deviceAdderName")
+    return (
+      dom.div({ id: "device-adder" },
+        dom.div({ id: "device-adder-content" },
+          dom.div({ id: "device-adder-column-1" },
+            dom.label({ id: "device-adder-name" },
+              dom.span({ className: "device-adder-label" },
+                getStr("responsive.deviceAdderName")
+              ),
+              dom.input({
+                defaultValue: deviceName,
+                ref: input => {
+                  this.nameInput = input;
+                },
+              })
             ),
-            dom.input({
-              defaultValue: deviceName,
-              ref: input => {
-                this.nameInput = input;
-              },
-            })
-          ),
-          dom.label(
-            {
-              id: "device-adder-size",
-            },
-            dom.span(
-              {
-                className: "device-adder-label"
-              },
-              getStr("responsive.deviceAdderSize")
-            ),
-            ViewportDimension({
-              viewport: {
-                width,
-                height,
-              },
-              onResizeViewport: this.onChangeSize,
-              onRemoveDeviceAssociation: () => {},
-            })
-          ),
-          dom.label(
-            {
-              id: "device-adder-pixel-ratio",
-            },
-            dom.span(
-              {
-                className: "device-adder-label"
-              },
-              getStr("responsive.deviceAdderPixelRatio")
+            dom.label({ id: "device-adder-size" },
+              dom.span({ className: "device-adder-label" },
+                getStr("responsive.deviceAdderSize")
+              ),
+              ViewportDimension({
+                viewport: {
+                  width,
+                  height,
+                },
+                onResizeViewport: this.onChangeSize,
+                onRemoveDeviceAssociation: () => {},
+              })
             ),
-            dom.input({
-              type: "number",
-              step: "any",
-              defaultValue: normalizedViewport.pixelRatio,
-              ref: input => {
-                this.pixelRatioInput = input;
-              },
-            })
-          )
-        ),
-        dom.div(
-          {
-            id: "device-adder-column-2",
-          },
-          dom.label(
-            {
-              id: "device-adder-user-agent",
-            },
-            dom.span(
-              {
-                className: "device-adder-label"
-              },
-              getStr("responsive.deviceAdderUserAgent")
+            dom.label({ id: "device-adder-pixel-ratio" },
+              dom.span({ className: "device-adder-label" },
+                getStr("responsive.deviceAdderPixelRatio")
+              ),
+              dom.input({
+                type: "number",
+                step: "any",
+                defaultValue: normalizedViewport.pixelRatio,
+                ref: input => {
+                  this.pixelRatioInput = input;
+                },
+              })
+            )
+          ),
+          dom.div({ id: "device-adder-column-2" },
+            dom.label({ id: "device-adder-user-agent" },
+              dom.span({ className: "device-adder-label" },
+                getStr("responsive.deviceAdderUserAgent")
+              ),
+              dom.input({
+                defaultValue: normalizedViewport.userAgent,
+                ref: input => {
+                  this.userAgentInput = input;
+                },
+              })
             ),
-            dom.input({
-              defaultValue: normalizedViewport.userAgent,
-              ref: input => {
-                this.userAgentInput = input;
-              },
-            })
+            dom.label({ id: "device-adder-touch" },
+              dom.span({ className: "device-adder-label" },
+                getStr("responsive.deviceAdderTouch")
+              ),
+              dom.input({
+                defaultChecked: normalizedViewport.touch,
+                type: "checkbox",
+                ref: input => {
+                  this.touchInput = input;
+                },
+              })
+            )
           ),
-          dom.label(
-            {
-              id: "device-adder-touch",
-            },
-            dom.span(
-              {
-                className: "device-adder-label"
-              },
-              getStr("responsive.deviceAdderTouch")
-            ),
-            dom.input({
-              defaultChecked: normalizedViewport.touch,
-              type: "checkbox",
-              ref: input => {
-                this.touchInput = input;
-              },
-            })
-          )
         ),
-      ),
-      dom.button(
-        {
-          id: "device-adder-save",
-          onClick: this.onDeviceAdderSave,
-        },
-        getStr("responsive.deviceAdderSave")
+        dom.button(
+          {
+            id: "device-adder-save",
+            onClick: this.onDeviceAdderSave,
+          },
+          getStr("responsive.deviceAdderSave")
+        )
       )
     );
   }
 }
 
 module.exports = DeviceAdder;
--- a/devtools/client/responsive.html/components/DeviceModal.js
+++ b/devtools/client/responsive.html/components/DeviceModal.js
@@ -128,101 +128,94 @@ class DeviceModal extends PureComponent 
     } = this;
 
     const sortedDevices = {};
     for (const type of devices.types) {
       sortedDevices[type] = Object.assign([], devices[type])
         .sort((a, b) => a.name.localeCompare(b.name));
     }
 
-    return dom.div(
-      {
-        id: "device-modal-wrapper",
-        className: this.props.devices.isModalOpen ? "opened" : "closed",
-      },
+    return (
       dom.div(
         {
-          className: "device-modal",
+          id: "device-modal-wrapper",
+          className: this.props.devices.isModalOpen ? "opened" : "closed",
         },
-        dom.button({
-          id: "device-close-button",
-          className: "devtools-button",
-          onClick: () => onUpdateDeviceModal(false),
-        }),
+        dom.div({ className: "device-modal" },
+          dom.button({
+            id: "device-close-button",
+            className: "devtools-button",
+            onClick: () => onUpdateDeviceModal(false),
+          }),
+          dom.div({ className: "device-modal-content" },
+            devices.types.map(type => {
+              return dom.div(
+                {
+                  className: `device-type device-type-${type}`,
+                  key: type,
+                },
+                dom.header({ className: "device-header" },
+                  type
+                ),
+                sortedDevices[type].map(device => {
+                  const details = getFormatStr(
+                    "responsive.deviceDetails", device.width, device.height,
+                    device.pixelRatio, device.userAgent, device.touch
+                  );
+
+                  let removeDeviceButton;
+                  if (type == "custom") {
+                    removeDeviceButton = dom.button({
+                      className: "device-remove-button devtools-button",
+                      onClick: () => onRemoveCustomDevice(device),
+                    });
+                  }
+
+                  return dom.label(
+                    {
+                      className: "device-label",
+                      key: device.name,
+                      title: details,
+                    },
+                    dom.input({
+                      className: "device-input-checkbox",
+                      type: "checkbox",
+                      value: device.name,
+                      checked: this.state[device.name],
+                      onChange: this.onDeviceCheckboxChange,
+                    }),
+                    dom.span(
+                      {
+                        className: "device-name",
+                      },
+                      device.name
+                    ),
+                    removeDeviceButton
+                  );
+                })
+              );
+            })
+          ),
+          DeviceAdder({
+            devices,
+            viewportTemplate: deviceAdderViewportTemplate,
+            onAddCustomDevice,
+          }),
+          dom.button(
+            {
+              id: "device-submit-button",
+              onClick: this.onDeviceModalSubmit,
+            },
+            getStr("responsive.done")
+          )
+        ),
         dom.div(
           {
-            className: "device-modal-content",
-          },
-          devices.types.map(type => {
-            return dom.div(
-              {
-                className: `device-type device-type-${type}`,
-                key: type,
-              },
-              dom.header(
-                {
-                  className: "device-header",
-                },
-                type
-              ),
-              sortedDevices[type].map(device => {
-                const details = getFormatStr(
-                  "responsive.deviceDetails", device.width, device.height,
-                  device.pixelRatio, device.userAgent, device.touch
-                );
-
-                let removeDeviceButton;
-                if (type == "custom") {
-                  removeDeviceButton = dom.button({
-                    className: "device-remove-button devtools-button",
-                    onClick: () => onRemoveCustomDevice(device),
-                  });
-                }
-
-                return dom.label(
-                  {
-                    className: "device-label",
-                    key: device.name,
-                    title: details,
-                  },
-                  dom.input({
-                    className: "device-input-checkbox",
-                    type: "checkbox",
-                    value: device.name,
-                    checked: this.state[device.name],
-                    onChange: this.onDeviceCheckboxChange,
-                  }),
-                  dom.span(
-                    {
-                      className: "device-name",
-                    },
-                    device.name
-                  ),
-                  removeDeviceButton
-                );
-              })
-            );
-          })
-        ),
-        DeviceAdder({
-          devices,
-          viewportTemplate: deviceAdderViewportTemplate,
-          onAddCustomDevice,
-        }),
-        dom.button(
-          {
-            id: "device-submit-button",
-            onClick: this.onDeviceModalSubmit,
-          },
-          getStr("responsive.done")
+            className: "modal-overlay",
+            onClick: () => onUpdateDeviceModal(false),
+          }
         )
-      ),
-      dom.div(
-        {
-          className: "modal-overlay",
-          onClick: () => onUpdateDeviceModal(false),
-        }
       )
     );
   }
 }
 
 module.exports = DeviceModal;
--- a/devtools/client/responsive.html/components/DevicePixelRatioMenu.js
+++ b/devtools/client/responsive.html/components/DevicePixelRatioMenu.js
@@ -17,19 +17,19 @@ loader.lazyRequireGetter(this, "showMenu
 
 const PIXEL_RATIO_PRESET = [1, 2, 3];
 
 class DevicePixelRatioMenu extends PureComponent {
   static get propTypes() {
     return {
       devices: PropTypes.shape(Types.devices).isRequired,
       displayPixelRatio: Types.pixelRatio.value.isRequired,
+      onChangePixelRatio: PropTypes.func.isRequired,
       selectedDevice: PropTypes.string.isRequired,
       selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired,
-      onChangePixelRatio: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.onShowDevicePixelMenu = this.onShowDevicePixelMenu.bind(this);
   }
 
--- a/devtools/client/responsive.html/components/DeviceSelector.js
+++ b/devtools/client/responsive.html/components/DeviceSelector.js
@@ -12,37 +12,37 @@ const { getStr } = require("../utils/l10
 const Types = require("../types");
 
 loader.lazyRequireGetter(this, "showMenu", "devtools/client/shared/components/menu/utils", true);
 
 class DeviceSelector extends PureComponent {
   static get propTypes() {
     return {
       devices: PropTypes.shape(Types.devices).isRequired,
-      selectedDevice: PropTypes.string.isRequired,
-      viewportId: PropTypes.number.isRequired,
       onChangeDevice: PropTypes.func.isRequired,
       onResizeViewport: PropTypes.func.isRequired,
       onUpdateDeviceModal: PropTypes.func.isRequired,
+      selectedDevice: PropTypes.string.isRequired,
+      viewportId: PropTypes.number.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.onShowDeviceMenu = this.onShowDeviceMenu.bind(this);
   }
 
   onShowDeviceMenu(event) {
     const {
       devices,
-      selectedDevice,
-      viewportId,
       onChangeDevice,
       onResizeViewport,
       onUpdateDeviceModal,
+      selectedDevice,
+      viewportId,
     } = this.props;
 
     const menuItems = [];
 
     for (const type of devices.types) {
       for (const device of devices[type]) {
         if (device.displayed) {
           menuItems.push({
--- a/devtools/client/responsive.html/components/ResizableViewport.js
+++ b/devtools/client/responsive.html/components/ResizableViewport.js
@@ -17,23 +17,23 @@ const Types = require("../types");
 
 const VIEWPORT_MIN_WIDTH = Constants.MIN_VIEWPORT_DIMENSION;
 const VIEWPORT_MIN_HEIGHT = Constants.MIN_VIEWPORT_DIMENSION;
 
 class ResizableViewport extends Component {
   static get propTypes() {
     return {
       leftAlignmentEnabled: PropTypes.bool.isRequired,
-      screenshot: PropTypes.shape(Types.screenshot).isRequired,
-      swapAfterMount: PropTypes.bool.isRequired,
-      viewport: PropTypes.shape(Types.viewport).isRequired,
       onBrowserMounted: PropTypes.func.isRequired,
       onContentResize: PropTypes.func.isRequired,
       onRemoveDeviceAssociation: PropTypes.func.isRequired,
       onResizeViewport: PropTypes.func.isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      swapAfterMount: PropTypes.bool.isRequired,
+      viewport: PropTypes.shape(Types.viewport).isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {
       isResizing: false,
--- a/devtools/client/responsive.html/components/SettingsMenu.js
+++ b/devtools/client/responsive.html/components/SettingsMenu.js
@@ -13,33 +13,33 @@ const { getStr } = require("../utils/l10
 const Types = require("../types");
 
 loader.lazyRequireGetter(this, "showMenu", "devtools/client/shared/components/menu/utils", true);
 
 class SettingsMenu extends PureComponent {
   static get propTypes() {
     return {
       leftAlignmentEnabled: PropTypes.bool.isRequired,
-      reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired,
       onChangeReloadCondition: PropTypes.func.isRequired,
       onToggleLeftAlignment: PropTypes.func.isRequired,
+      reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.onToggleSettingMenu = this.onToggleSettingMenu.bind(this);
   }
 
   onToggleSettingMenu(event) {
     const {
       leftAlignmentEnabled,
-      reloadConditions,
       onChangeReloadCondition,
       onToggleLeftAlignment,
+      reloadConditions,
     } = this.props;
 
     const menuItems = [
       {
         id: "toggleLeftAlignment",
         checked: leftAlignmentEnabled,
         label: getStr("responsive.leftAlignViewport"),
         type: "checkbox",
--- a/devtools/client/responsive.html/components/ViewportDimension.js
+++ b/devtools/client/responsive.html/components/ViewportDimension.js
@@ -10,19 +10,19 @@ const PropTypes = require("devtools/clie
 
 const { isKeyIn } = require("../utils/key");
 const { MIN_VIEWPORT_DIMENSION } = require("../constants");
 const Types = require("../types");
 
 class ViewportDimension extends Component {
   static get propTypes() {
     return {
-      viewport: PropTypes.shape(Types.viewport).isRequired,
       onResizeViewport: PropTypes.func.isRequired,
       onRemoveDeviceAssociation: PropTypes.func.isRequired,
+      viewport: PropTypes.shape(Types.viewport).isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     const { width, height } = props.viewport;
 
--- a/devtools/client/responsive.html/components/Viewports.js
+++ b/devtools/client/responsive.html/components/Viewports.js
@@ -12,34 +12,34 @@ const PropTypes = require("devtools/clie
 const ResizableViewport = createFactory(require("./ResizableViewport"));
 
 const Types = require("../types");
 
 class Viewports extends Component {
   static get propTypes() {
     return {
       leftAlignmentEnabled: PropTypes.bool.isRequired,
-      screenshot: PropTypes.shape(Types.screenshot).isRequired,
-      viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
       onBrowserMounted: PropTypes.func.isRequired,
       onContentResize: PropTypes.func.isRequired,
       onRemoveDeviceAssociation: PropTypes.func.isRequired,
       onResizeViewport: PropTypes.func.isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
     };
   }
 
   render() {
     const {
       leftAlignmentEnabled,
-      screenshot,
-      viewports,
       onBrowserMounted,
       onContentResize,
       onRemoveDeviceAssociation,
       onResizeViewport,
+      screenshot,
+      viewports,
     } = this.props;
 
     const viewportSize = window.getViewportSize();
     // The viewport may not have been created yet. Default to justify-content: center
     // for the container.
     let justifyContent = "center";
 
     // If the RDM viewport is bigger than the window's inner width, set the container's
@@ -63,23 +63,23 @@ class Viewports extends Component {
           {
             id: "viewports",
             className: leftAlignmentEnabled ? "left-aligned" : "",
           },
           viewports.map((viewport, i) => {
             return ResizableViewport({
               key: viewport.id,
               leftAlignmentEnabled,
-              screenshot,
-              swapAfterMount: i == 0,
-              viewport,
               onBrowserMounted,
               onContentResize,
               onRemoveDeviceAssociation,
               onResizeViewport,
+              screenshot,
+              swapAfterMount: i == 0,
+              viewport,
             });
           })
         )
       )
     );
   }
 }
 
--- a/dom/base/test/test_bug574596.html
+++ b/dom/base/test/test_bug574596.html
@@ -37,16 +37,17 @@ var dragLinkText = [[
   { type:"text/_moz_htmlcontext",   data:"", eqTest:ignoreFunc },
   { type:"text/_moz_htmlinfo",      data:"", eqTest:ignoreFunc },
   { type:"text/html",               data:'<div id="link1"><a href="http://www.mozilla.org/">link1</a></div>' },
   { type:"text/plain",              data:"http://www.mozilla.org/" }
 ]];
 
 
 function dumpTransfer(dataTransfer,expect) {
+  dataTransfer = SpecialPowers.wrap(dataTransfer);
   dtData = dataTransfer.mozItemCount + "items:\n";
   for (var i = 0; i < dataTransfer.mozItemCount; i++) {
     var dtTypes = dataTransfer.mozTypesAt(i);
     for (var j = 0; j < dtTypes.length; j++) {
       var actualData = dataTransfer.mozGetDataAt(dtTypes[j],i)
       if (expect && expect[i] && expect[i][j]) {
         if (expect[i][j].eqTest)
           dtData += expect[i][j].eqTest(actualData,expect[i][j].data) ? "ok" : "fail";
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -35,16 +35,18 @@
 #include "mozilla/dom/Directory.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/FileList.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/OSFileSystem.h"
 #include "mozilla/dom/Promise.h"
 #include "nsNetUtil.h"
 
+#define MOZ_CALLS_ENABLED_PREF "dom.datatransfer.mozAtAPIs"
+
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mItems)
@@ -1518,10 +1520,27 @@ DataTransfer::SetMode(DataTransfer::Mode
 {
   if (!PrefProtected() && aMode == Mode::Protected) {
     mMode = Mode::ReadOnly;
   } else {
     mMode = aMode;
   }
 }
 
+/* static */
+bool
+DataTransfer::MozAtAPIsEnabled(JSContext* aCx, JSObject* aObj /*unused*/)
+{
+  // Read the pref
+  static bool sPrefCached = false;
+  static bool sPrefCacheValue = false;
+
+  if (!sPrefCached) {
+    sPrefCached = true;
+    Preferences::AddBoolVarCache(&sPrefCacheValue, MOZ_CALLS_ENABLED_PREF);
+  }
+
+  // We can expose moz* APIs if we are chrome code or if pref is enabled
+  return nsContentUtils::IsSystemCaller(aCx) || sPrefCacheValue;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -423,16 +423,21 @@ public:
   //
   // If kFileMime is supported, then it will be placed either at
   // index 0 or at index 1 in aResult
   static void
   GetExternalClipboardFormats(const int32_t& aWhichClipboard,
                               const bool& aPlainTextOnly,
                               nsTArray<nsCString>* aResult);
 
+  // Returns true if moz* APIs should be exposed (true for chrome code or if
+  // dom.datatransfer.moz pref is enabled).
+  // The affected moz* APIs are mozItemCount, mozTypesAt, mozClearDataAt, mozSetDataAt, mozGetDataAt
+  static bool MozAtAPIsEnabled(JSContext* cx, JSObject* obj);
+
 protected:
 
   // caches text and uri-list data formats that exist in the drag service or
   // clipboard for retrieval later.
   nsresult CacheExternalData(const char* aFormat, uint32_t aIndex,
                              nsIPrincipal* aPrincipal, bool aHidden);
 
   // caches the formats that exist in the drag service that were added by an
--- a/dom/events/test/test_DataTransferItemList.html
+++ b/dom/events/test/test_DataTransferItemList.html
@@ -1,21 +1,21 @@
 <html>
 <head>
-  <title>Tests for the DatTransferItemList object</title>
+  <title>Tests for the DataTransferItemList object</title>
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/AddTask.js"></script>
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body style="height: 300px; overflow: auto;">
 <p id="display"> </p>
 <img id="image" draggable="true" src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82">
-<div id="over" "style="width: 100px; height: 100px; border: 2px black dashed;">
+<div id="over" style="width: 100px; height: 100px; border: 2px black dashed;">
   drag over here
 </div>
 
 <script>
   function spin() {
     // Defer to the event loop twice to wait for any events to be flushed out.
     return new Promise(function(a) {
       SimpleTest.executeSoon(function() {
--- a/dom/events/test/test_bug1264380.html
+++ b/dom/events/test/test_bug1264380.html
@@ -25,17 +25,17 @@ function runTests()
   target.href = "http://www.mozilla.org/";
   shadow.appendChild(target);
 
   let dataTransfer;
   let trapDrag = function(event) {
     ok(true, "Got dragstart event");
     dataTransfer = event.dataTransfer;
     ok(dataTransfer, "DataTransfer object is available.");
-    is(dataTransfer.mozItemCount, 1, "initial link item count");
+    is(SpecialPowers.wrap(dataTransfer).mozItemCount, 1, "initial link item count");
     is(dataTransfer.getData("text/uri-list"), "http://www.mozilla.org/", "link text/uri-list");
     is(dataTransfer.getData("text/plain"), "http://www.mozilla.org/", "link text/plain");
   }
 
   ok(!dragService.getCurrentSession(), "There shouldn't be a drag session!");
   iframeWin.addEventListener("dragstart", trapDrag, true);
   synthesizeMouse(target, 2, 2, { type: "mousedown" }, iframeWin);
   synthesizeMouse(target, 11, 11, { type: "mousemove" }, iframeWin);
--- a/dom/events/test/test_dragstart.html
+++ b/dom/events/test/test_dragstart.html
@@ -36,20 +36,16 @@ function afterDragTests()
   // be read only.
   ok(gDataTransfer instanceof DataTransfer, "DataTransfer after dragstart event");
   checkTypes(gDataTransfer, [], 0, "after dragstart event");
 
   expectError(() => gDataTransfer.setData("text/plain", "Some Text"),
               "NoModificationAllowedError", "setData when read only");
   expectError(() => gDataTransfer.clearData("text/plain"),
               "NoModificationAllowedError", "clearData when read only");
-  expectError(() => gDataTransfer.mozSetDataAt("text/plain", "Some Text", 0),
-              "NoModificationAllowedError", "setDataAt when read only");
-  expectError(() => gDataTransfer.mozClearDataAt("text/plain", 0),
-              "NoModificationAllowedError", "clearDataAt when read only");
   expectError(() => gDataTransfer.addElement(draggable),
               "NoModificationAllowedError", "addElement when read only");
 
   var evt = document.createEvent("dragevent");
   ok(evt instanceof DragEvent, "synthetic dragevent class")
   ok(evt instanceof MouseEvent, "synthetic event inherits from MouseEvent")
   evt.initDragEvent("dragstart", true, true, window, 1, 40, 35, 20, 15,
                     false, true, false, false, 0, null, null);
@@ -125,33 +121,37 @@ function doDragStartSelection(event)
 
   // text/unicode and Text are available for compatibility. They retrieve the
   // text/plain data
   is(dt.getData("text/unicode"), "This is a draggable bit of text.", "initial selection text/unicode");
   is(dt.getData("Text"), "This is a draggable bit of text.", "initial selection Text");
   is(dt.getData("TEXT"), "This is a draggable bit of text.", "initial selection TEXT");
   is(dt.getData("text/UNICODE"), "This is a draggable bit of text.", "initial selection text/UNICODE"); 
 
-  is(dt.mozItemCount, 1, "initial selection item count");
+  is(SpecialPowers.wrap(dt).mozItemCount, 1, "initial selection item count");
 
   dt.clearData("text/plain");
   dt.clearData("text/html");
   dt.clearData("text/_moz_htmlinfo");
   dt.clearData("text/_moz_htmlcontext");
 
   test_DataTransfer(dt);
   setTimeout(afterDragTests, 0);
 }
 
 function test_DataTransfer(dt)
 {
-  is(dt.mozItemCount, 0, "empty itemCount");
+  is(SpecialPowers.wrap(dt).mozItemCount, 0, "empty itemCount");
 
   var types = dt.types;
   ok(Array.isArray(types), "empty types is an Array");
+  // The above test fails if we have SpecialPowers.wrap(dt).types instead of dt.types
+  // because chrome consumers get the 'ReturnValueNeedsContainsHack'.
+  // So wrap with special powers after the test
+  dt = SpecialPowers.wrap(dt);
   checkTypes(dt, [], 0, "empty");
   is(dt.getData("text/plain"), "", "empty data is empty");
 
   // calling setDataAt requires an index that is 0 <= index <= dt.itemCount
   expectError(() => dt.mozSetDataAt("text/plain", "Some Text", 1),
               "IndexSizeError", "setDataAt index too high");
 
   is(dt.mozUserCancelled, false, "userCancelled");
@@ -311,30 +311,28 @@ function test_DataTransfer(dt)
   checkOneDataItem(dt, ["text/plain", "text/html"],
                    ["Changed Second Item", "<em>Second Item</em>"], 1, "changed with setData item at index 1");
 
   dt.mozSetDataAt("application/-moz-node", "draggable", 2);
   is(dt.mozItemCount, 3, "setDataAt node itemCount");
   checkOneDataItem(dt, ["application/-moz-node"], ["draggable"], 2, "setDataAt node item at index 2");
 
   // Try to add and then remove a non-string type to the DataTransfer and ensure
-  // that the type appears in DataTransfer.types. These calls need to be called
-  // with SpecialPowers.wrap(), as adding and removing non-string/file types to
-  // DataTransfer is chrome-only.
+  // that the type appears in DataTransfer.types.
   {
-    SpecialPowers.wrap(dt).mozSetDataAt("application/-x-body", document.body, 0);
+    dt.mozSetDataAt("application/-x-body", document.body, 0);
     let found = false;
     for (let i = 0; i < dt.types.length; ++i) {
       if (dt.types[i] == "application/-x-body") {
         found = true;
         break;
       }
     }
     ok(found, "Data should appear in datatransfer.types despite having a non-string type");
-    SpecialPowers.wrap(dt).mozClearDataAt("application/-x-body", 0);
+    dt.mozClearDataAt("application/-x-body", 0);
   }
 
   dt.mozClearDataAt("text/html", 1);
   is(dt.mozItemCount, 3, "clearDataAt itemCount");
   checkOneDataItem(dt, ["text/plain", "text/html"],
                    ["First Item", "Changed with setData"], 0, "clearDataAt item at index 0");
   checkOneDataItem(dt, ["text/plain"], ["Changed Second Item"], 1, "clearDataAt item at index 1");
 
@@ -414,48 +412,48 @@ function test_DataTransfer(dt)
 }
 
 function doDragStartLink(event)
 {
   var dt = event.dataTransfer;
   checkTypes(dt, ["text/x-moz-url", "text/x-moz-url-data", "text/x-moz-url-desc", "text/uri-list",
                   "text/_moz_htmlcontext", "text/_moz_htmlinfo", "text/html", "text/plain"], 0, "initial link");
 
-  is(dt.mozItemCount, 1, "initial link item count");
+  is(SpecialPowers.wrap(dt).mozItemCount, 1, "initial link item count");
   is(dt.getData("text/uri-list"), "http://www.mozilla.org/", "link text/uri-list");
   is(dt.getData("text/plain"), "http://www.mozilla.org/", "link text/plain");
 
   event.preventDefault();
 
   gExtraDragTests++;
 }
 
 function doDragStartImage(event)
 {
   var dataurl = $("image").src;
 
   var dt = event.dataTransfer;
   checkTypes(dt, ["text/x-moz-url", "text/x-moz-url-data", "text/x-moz-url-desc", "text/uri-list",
                   "text/_moz_htmlcontext", "text/_moz_htmlinfo", "text/html", "text/plain"], 0, "initial image");
 
-  is(dt.mozItemCount, 1, "initial image item count");
+  is(SpecialPowers.wrap(dt).mozItemCount, 1, "initial image item count");
   is(dt.getData("text/uri-list"), dataurl, "image text/uri-list");
   is(dt.getData("text/plain"), dataurl, "image text/plain");
 
   event.preventDefault();
 
   gExtraDragTests++;
 }
 
 function doDragStartInput(event)
 {
   var dt = event.dataTransfer;
   checkTypes(dt, ["text/plain"], 0, "initial input");
 
-  is(dt.mozItemCount, 1, "initial input item count");
+  is(SpecialPowers.wrap(dt).mozItemCount, 1, "initial input item count");
 //  is(dt.getData("text/plain"), "Text", "input text/plain");
 
 //  event.preventDefault();
 }
 
 function doDragStartSynthetic(event)
 {
   is(event.type, "dragstart", "synthetic dragstart event type");
@@ -506,21 +504,22 @@ function doDragOverSynthetic(event)
 //  Uncomment next two lines once the todo instanceof above is fixed.
 //  dt.setData("text/plain", "Text");
 //  is(dt.getData("text/plain"), "Text", "synthetic dragover data is set after adding");
 }
 
 function onDragStartDraggable(event)
 {
   var dt = event.dataTransfer;
-  ok(dt.mozItemCount == 0 && dt.types.length == 0 && event.originalTarget == gDragInfo.target, gDragInfo.testid);
+  ok(SpecialPowers.wrap(dt).mozItemCount == 0 && dt.types.length == 0 && event.originalTarget == gDragInfo.target, gDragInfo.testid);
 
   gExtraDragTests++;
 }
 
+// Expects dt wrapped in SpecialPowers
 function checkOneDataItem(dt, expectedtypes, expecteddata, index, testid)
 {
   checkTypes(dt, expectedtypes, index, testid);
   for (var f = 0; f < expectedtypes.length; f++) {
     if (index == 0)
       is(dt.getData(expectedtypes[f]), expecteddata[f], testid + " getData " + expectedtypes[f]);
     is(dt.mozGetDataAt(expectedtypes[f], index), expecteddata[f] ? expecteddata[f] : null,
        testid + " getDataAt " + expectedtypes[f]);
@@ -532,23 +531,24 @@ function checkTypes(dt, expectedtypes, i
   if (index == 0) {
     var types = dt.types;
     is(types.length, expectedtypes.length, testid + " types length");
     for (var f = 0; f < expectedtypes.length; f++) {
       is(types[f], expectedtypes[f], testid + " " + types[f] + " check");
     }
   }
 
-  types = dt.mozTypesAt(index);
+  types = SpecialPowers.wrap(dt).mozTypesAt(index);
   is(types.length, expectedtypes.length, testid + " typesAt length");
   for (var f = 0; f < expectedtypes.length; f++) {
     is(types[f], expectedtypes[f], testid + " " + types[f] + " at " + index + " check");
   }
 }
 
+// Expects dt wrapped in SpecialPowers
 function checkURL(dt, url, fullurllist, index, testid)
 {
   is(dt.getData("text/uri-list"), fullurllist, testid + " text/uri-list");
   is(dt.getData("URL"), url, testid + " URL");
   is(dt.mozGetDataAt("text/uri-list", 0), fullurllist, testid + " text/uri-list");
   is(dt.mozGetDataAt("URL", 0), fullurllist, testid + " URL");
 }
 
--- a/dom/media/mediasink/VideoSink.cpp
+++ b/dom/media/mediasink/VideoSink.cpp
@@ -42,16 +42,17 @@ VideoSink::VideoSink(AbstractThread* aTh
                      FrameStatistics& aFrameStats,
                      uint32_t aVQueueSentToCompositerSize)
   : mOwnerThread(aThread)
   , mAudioSink(aAudioSink)
   , mVideoQueue(aVideoQueue)
   , mContainer(aContainer)
   , mProducerID(ImageContainer::AllocateProducerID())
   , mFrameStats(aFrameStats)
+  , mOldDroppedCount(0)
   , mHasVideo(false)
   , mUpdateScheduler(aThread)
   , mVideoQueueSendToCompositorSize(aVQueueSentToCompositerSize)
   , mMinVideoQueueSize(StaticPrefs::MediaRuinAvSyncEnabled() ? 1 : 0)
 #ifdef XP_WIN
   , mHiResTimersRequested(false)
 #endif
 
@@ -459,16 +460,23 @@ VideoSink::RenderVideoFrames(int32_t aMa
 
     VSINK_LOG_V("playing video frame %" PRId64 " (id=%x) (vq-queued=%zu)",
                 frame->mTime.ToMicroseconds(), frame->mFrameID,
                 VideoQueue().GetSize());
   }
 
   if (images.Length() > 0) {
     mContainer->SetCurrentFrames(frames[0]->mDisplay, images);
+    uint32_t droppedCount = mContainer->GetDroppedImageCount();
+    uint32_t dropped = droppedCount - mOldDroppedCount;
+    if (dropped > 0) {
+      mFrameStats.NotifyDecodedFrames({0, 0, dropped});
+      mOldDroppedCount = droppedCount;
+      VSINK_LOG_V("%u video frame discarded by compositor", dropped);
+    }
   }
 }
 
 void
 VideoSink::UpdateRenderedVideoFrames()
 {
   AssertOwnerThread();
   MOZ_ASSERT(mAudioSink->IsPlaying(), "should be called while playing.");
--- a/dom/media/mediasink/VideoSink.h
+++ b/dom/media/mediasink/VideoSink.h
@@ -103,17 +103,18 @@ private:
 
   void MaybeResolveEndPromise();
 
   void AssertOwnerThread() const
   {
     MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   }
 
-  MediaQueue<VideoData>& VideoQueue() const {
+  MediaQueue<VideoData>& VideoQueue() const
+  {
     return mVideoQueue;
   }
 
   const RefPtr<AbstractThread> mOwnerThread;
   RefPtr<MediaSink> mAudioSink;
   MediaQueue<VideoData>& mVideoQueue;
   VideoFrameContainer* mContainer;
 
@@ -126,16 +127,18 @@ private:
 
   RefPtr<GenericPromise> mEndPromise;
   MozPromiseHolder<GenericPromise> mEndPromiseHolder;
   MozPromiseRequestHolder<GenericPromise> mVideoSinkEndRequest;
 
   // The presentation end time of the last video frame which has been displayed.
   TimeUnit mVideoFrameEndTime;
 
+  uint32_t mOldDroppedCount;
+
   // Event listeners for VideoQueue
   MediaEventListener mPushListener;
   MediaEventListener mFinishListener;
 
   // True if this sink is going to handle video track.
   bool mHasVideo;
 
   // Used to trigger another update of rendered frames in next round.
--- a/dom/media/tests/mochitest/test_peerConnection_stats.html
+++ b/dom/media/tests/mochitest/test_peerConnection_stats.html
@@ -8,30 +8,30 @@
 <script type="application/javascript">
   createHTML({
     bug: "1337525",
     title: "webRtc Stats composition and sanity"
   });
 var statsExpectedByType = {
   "inbound-rtp": {
     expected: ["id", "timestamp", "type", "ssrc", "isRemote", "mediaType",
-      "packetsReceived", "packetsLost", "bytesReceived", "jitter",],
+      "kind", "packetsReceived", "packetsLost", "bytesReceived", "jitter",],
     optional: ["roundTripTime", "remoteId", "nackCount",],
     localVideoOnly: ["discardedPackets", "framerateStdDev", "framerateMean",
       "bitrateMean", "bitrateStdDev", "firCount", "pliCount", "framesDecoded",],
     unimplemented: ["mediaTrackId", "transportId", "codecId",
       "packetsDiscarded", "associateStatsId",
       "sliCount", "qpSum", "packetsRepaired", "fractionLost",
       "burstPacketsLost", "burstLossCount", "burstDiscardCount",
       "gapDiscardRate", "gapLossRate",],
     deprecated: ["mozRtt"],
   },
   "outbound-rtp": {
     expected: ["id", "timestamp", "type", "ssrc", "isRemote", "mediaType",
-      "packetsSent", "bytesSent", "remoteId",],
+      "kind", "packetsSent", "bytesSent", "remoteId",],
     optional: ["remoteId", "nackCount",],
     localVideoOnly: ["droppedFrames", "bitrateMean", "bitrateStdDev",
       "framerateMean", "framerateStdDev", "framesEncoded", "firCount",
       "pliCount",],
     unimplemented: ["mediaTrackId", "transportId", "codecId",
       "sliCount", "qpSum", "targetBitrate",],
     deprecated: [],
   },
@@ -143,20 +143,26 @@ var pedanticChecks = report => {
       //
 
       // SSRC
       ok(stat.ssrc, stat.type + ".ssrc has a value");
 
       // isRemote
       ok(stat.isRemote !== undefined, stat.type + ".isRemote exists.");
 
-      // mediaType
+      // kind
+      ok(["audio", "video"].includes(stat.kind),
+        stat.type + ".kind is 'audio' or 'video'");
+
+      // mediaType, renamed to kind but remains for backward compability.
       ok(["audio", "video"].includes(stat.mediaType),
         stat.type + ".mediaType is 'audio' or 'video'");
 
+      ok(stat.kind == stat.mediaType, "kind equals legacy mediaType");
+
       // remote id
       if (stat.remoteId) {
         ok(report.has(stat.remoteId), "remoteId exists in report.");
         is(report.get(stat.remoteId).ssrc, stat.ssrc,
           "remote ssrc and local ssrc match.");
         is(report.get(stat.remoteId).remoteId, stat.id,
           "remote object has local object as it's own remote object.");
       }
@@ -164,17 +170,17 @@ var pedanticChecks = report => {
       // nackCount
       if (!stat.inner.isRemote) {
         ok(stat.nackCount >= 0, stat.type + ".nackCount is sane.");
       } else {
         is(stat.nackCount, undefined, stat.type
           + ".nackCount is only set when isRemote is false");
       }
 
-      if (!stat.inner.isRemote && stat.inner.mediaType == "video") {
+      if (!stat.inner.isRemote && stat.inner.kind == "video") {
         // firCount
         ok(stat.firCount >= 0 && stat.firCount < 100,
           stat.type + ".firCount is a sane number for a short test. value="
           + stat.firCount);
 
         // pliCount
         ok(stat.pliCount >= 0 && stat.pliCount < 100,
           stat.type + ".pliCount is a sane number for a short test. value="
@@ -200,17 +206,17 @@ var pedanticChecks = report => {
         + stat.bytesReceived);
 
       // packetsLost
       ok(stat.packetsLost < 100,
         stat.type + ".packetsLost is a sane number for a short test. value="
         + stat.packetsLost);
 
       // This should be much lower for audio, TODO: Bug 1330575
-      let expectedJitter = stat.mediaType == "video" ? 0.5 : 1;
+      let expectedJitter = stat.kind == "video" ? 0.5 : 1;
       // jitter
       ok(stat.jitter < expectedJitter,
         stat.type + ".jitter is sane number for a local only test. value="
         + stat.jitter);
 
       // packetsDiscarded
       // special exception for, TODO: Bug 1335967
       // if (!stat.inner.isRemote && stat.discardedPackets !== undefined) {
@@ -235,30 +241,30 @@ var pedanticChecks = report => {
       } else {
         is(stat.roundTripTime, undefined, stat.type
           + ".roundTripTime is only set when isRemote is true");
       }
 
       //
       // Local video only stats
       //
-      if (stat.inner.isRemote || stat.inner.mediaType != "video") {
+      if (stat.inner.isRemote || stat.inner.kind != "video") {
         expectations.localVideoOnly.forEach(field => {
           if (stat.inner.isRemote) {
             ok(stat[field] === undefined, stat.type + " does not have field "
               + field + " when isRemote is true");
-          } else { // mediaType != video
+          } else { // kind != video
             ok(stat[field] === undefined, stat.type + " does not have field "
-              + field + " when mediaType is not 'video'");
+              + field + " when kind is not 'video'");
           }
         });
       } else {
         expectations.localVideoOnly.forEach(field => {
           ok(stat.inner[field] !== undefined, stat.type + " has field " + field
-            + " when mediaType is video");
+            + " when kind is video");
         });
         // discardedPackets
         ok(stat.discardedPackets < 100, stat.type
           + ".discardedPackets is a sane number for a short test. value="
           + stat.discardedPackets);
         // framesDecoded
         ok(stat.framesDecoded > 0 && stat.framesDecoded < 1000000, stat.type
           + ".framesDecoded is a sane number for a short test. value="
@@ -316,30 +322,30 @@ var pedanticChecks = report => {
 
       //
       // Optional fields
       //
 
       //
       // Local video only stats
       //
-      if (stat.inner.isRemote || stat.inner.mediaType != "video") {
+      if (stat.inner.isRemote || stat.inner.kind != "video") {
         expectations.localVideoOnly.forEach(field => {
           if (stat.inner.isRemote) {
             ok(stat[field] === undefined, stat.type + " does not have field "
               + field + " when isRemote is true");
-          } else { // mediaType != video
+          } else { // kind != video
             ok(stat[field] === undefined, stat.type + " does not have field "
-              + field + " when mediaType is not 'video'");
+              + field + " when kind is not 'video'");
           }
         });
       } else {
         expectations.localVideoOnly.forEach(field => {
           ok(stat.inner[field] !== undefined, stat.type + " has field " + field
-            + " when mediaType is video and isRemote is false");
+            + " when kind is video and isRemote is false");
         });
 
         // bitrateMean
         if (stat.bitrateMean !== undefined) {
           // TODO: uncomment when Bug 1341533 lands
           // ok(stat.bitrateMean >= 0 && stat.bitrateMean < 2 ** 25,
           //   stat.type + ".bitrateMean is sane. value="
           //   + stat.bitrateMean);
--- a/dom/media/webrtc/WebrtcGlobal.h
+++ b/dom/media/webrtc/WebrtcGlobal.h
@@ -287,16 +287,17 @@ static void WriteRTCRtpStreamStats(
     WriteParam(aMsg, aParam.mBitrateMean);
     WriteParam(aMsg, aParam.mBitrateStdDev);
     WriteParam(aMsg, aParam.mCodecId);
     WriteParam(aMsg, aParam.mFramerateMean);
     WriteParam(aMsg, aParam.mFramerateStdDev);
     WriteParam(aMsg, aParam.mIsRemote);
     WriteParam(aMsg, aParam.mMediaTrackId);
     WriteParam(aMsg, aParam.mMediaType);
+    WriteParam(aMsg, aParam.mKind);
     WriteParam(aMsg, aParam.mRemoteId);
     WriteParam(aMsg, aParam.mSsrc);
     WriteParam(aMsg, aParam.mTransportId);
 }
 
 static bool ReadRTCRtpStreamStats(
               const Message* aMsg, PickleIterator* aIter,
               mozilla::dom::RTCRtpStreamStats* aResult)
@@ -304,16 +305,17 @@ static bool ReadRTCRtpStreamStats(
   if (!ReadParam(aMsg, aIter, &(aResult->mBitrateMean)) ||
       !ReadParam(aMsg, aIter, &(aResult->mBitrateStdDev)) ||
       !ReadParam(aMsg, aIter, &(aResult->mCodecId)) ||
       !ReadParam(aMsg, aIter, &(aResult->mFramerateMean)) ||
       !ReadParam(aMsg, aIter, &(aResult->mFramerateStdDev)) ||
       !ReadParam(aMsg, aIter, &(aResult->mIsRemote)) ||
       !ReadParam(aMsg, aIter, &(aResult->mMediaTrackId)) ||
       !ReadParam(aMsg, aIter, &(aResult->mMediaType)) ||
+      !ReadParam(aMsg, aIter, &(aResult->mKind)) ||
       !ReadParam(aMsg, aIter, &(aResult->mRemoteId)) ||
       !ReadParam(aMsg, aIter, &(aResult->mSsrc)) ||
       !ReadParam(aMsg, aIter, &(aResult->mTransportId))) {
     return false;
   }
 
   return true;
 }
--- a/dom/media/webrtc/moz.build
+++ b/dom/media/webrtc/moz.build
@@ -83,11 +83,8 @@ FINAL_LIBRARY = 'xul'
 
 if CONFIG['CC_TYPE'] in ('msvc', 'clang-cl'):
     CXXFLAGS += [
         '-wd4275', # non dll-interface class used as base for dll-interface class
         '-wd4312', # This is intended as a temporary hack to support building with VS2015
                    # 'reinterpret_cast': conversion from 'DWORD' to 'HANDLE' of greater size
     ]
     DEFINES['__PRETTY_FUNCTION__'] = '__FUNCSIG__'
-
-if CONFIG['MOZ_ASAN'] and CONFIG['CC_TYPE'] == 'clang-cl':
-    AllowCompilerWarnings()  # workaround for bug 1306642
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp
@@ -1936,17 +1936,17 @@ ServiceWorkerPrivate::TerminateWorker()
     if (DOMPrefs::ServiceWorkersTestingEnabled()) {
       nsCOMPtr<nsIObserverService> os = services::GetObserverService();
       if (os) {
         os->NotifyObservers(nullptr, "service-worker-shutdown", nullptr);
       }
     }
 
     Unused << NS_WARN_IF(!mWorkerPrivate->Cancel());
-    mWorkerPrivate = nullptr;
+    RefPtr<WorkerPrivate> workerPrivate(mWorkerPrivate.forget());
     mSupportsArray.Clear();
 
     // Any pending events are never going to fire on this worker.  Cancel
     // them so that intercepted channels can be reset and other resources
     // cleaned up.
     nsTArray<RefPtr<WorkerRunnable>> pendingEvents;
     mPendingFunctionalEvents.SwapElements(pendingEvents);
     for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
--- a/dom/tests/mochitest/general/mochitest.ini
+++ b/dom/tests/mochitest/general/mochitest.ini
@@ -85,16 +85,17 @@ subsuite = clipboard
 [test_bug1434273.html]
 [test_clientRects.html]
 [test_clipboard_disallowed.html]
 [test_clipboard_events.html]
 subsuite = clipboard
 [test_consoleAPI.html]
 [test_contentViewer_overrideDPPX.html]
 [test_CCW_optimization.html]
+[test_datatransfer_disallowed.html]
 [test_devicePixelRatio_with_zoom.html]
 [test_DOMMatrix.html]
 [test_domWindowUtils.html]
 [test_domWindowUtils_scrollbarSize.html]
 [test_domWindowUtils_scrollXY.html]
 [test_donottrack.html]
 [test_focus_scrollchildframe.html]
 [test_focus_legend_noparent.html]
--- a/dom/tests/mochitest/general/test_clipboard_disallowed.html
+++ b/dom/tests/mochitest/general/test_clipboard_disallowed.html
@@ -19,41 +19,33 @@ function doTest()
 }
 
 function checkAllowed(event)
 {
   let clipboardData = event.clipboardData;
 
   let exception;
   try {
-    clipboardData.mozSetDataAt("text/customdata", document.getElementById("input"), 0);
-  } catch(ex) {
-    exception = ex;
-  }
-  is(String(exception).indexOf("SecurityError"), 0, "Cannot set non-string");
-
-  exception = null;
-  try {
-    clipboardData.mozSetDataAt("application/x-moz-file", "Test", 0);
+    clipboardData.setData("application/x-moz-file", "Test");
   } catch(ex) {
     exception = ex;
   }
   is(String(exception).indexOf("SecurityError"), 0, "Cannot set file");
 
   exception = null;
   try {
-    clipboardData.mozSetDataAt("application/x-moz-file-promise", "Test", 0);
+    clipboardData.setData("application/x-moz-file-promise", "Test");
   } catch(ex) {
     exception = ex;
   }
   is(String(exception).indexOf("SecurityError"), 0, "Cannot set file promise");
 
   exception = null;
   try {
-    clipboardData.mozSetDataAt("application/something", "This is data", 0);
+    clipboardData.setData("application/something", "This is data");
   } catch(ex) {
     exception = ex;
   }
   is(exception, null, "Can set custom data to a string");
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
--- a/dom/tests/mochitest/general/test_clipboard_events.html
+++ b/dom/tests/mochitest/general/test_clipboard_events.html
@@ -297,17 +297,17 @@ add_task(async function test_input_cut_d
 
   // Cut using event.dataTransfer. The event is not cancelled so the default
   // cut should occur
   selectContentInput();
   contentInput.oncut = function(event) {
     ok(event instanceof ClipboardEvent, "cut event is a ClipboardEvent");
     ok(event.clipboardData instanceof DataTransfer, "cut event dataTransfer is a DataTransfer");
     is(event.target, contentInput, "cut event target");
-    is(event.clipboardData.mozItemCount, 0, "cut event mozItemCount");
+    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "cut event mozItemCount");
     is(event.clipboardData.getData("text/plain"), "", "cut event getData");
     event.clipboardData.setData("text/plain", "This is some dataTransfer text");
     cachedCutData = event.clipboardData;
   };
   try {
     await putOnClipboard("INPUT TEXT", () => {
       synthesizeKey("x", {accelKey: 1});
     }, "cut using dataTransfer on plaintext editor set clipboard correctly");
@@ -344,17 +344,17 @@ add_task(async function test_input_copy_
   await reset();
 
   // Copy using event.dataTransfer
   selectContentInput();
   contentInput.oncopy = function(event) {
     ok(event instanceof ClipboardEvent, "copy event is a ClipboardEvent");
     ok(event.clipboardData instanceof DataTransfer, "copy event dataTransfer is a DataTransfer");
     is(event.target, contentInput, "copy event target");
-    is(event.clipboardData.mozItemCount, 0, "copy event mozItemCount");
+    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "copy event mozItemCount");
     is(event.clipboardData.getData("text/plain"), "", "copy event getData");
     event.clipboardData.setData("text/plain", "Copied dataTransfer text");
     cachedCopyData = event.clipboardData;
   };
   try {
     await putOnClipboard("INPUT TEXT", () => {
       synthesizeKey("c", {accelKey: 1});
     }, "copy using dataTransfer on plaintext editor set clipboard correctly");
@@ -389,17 +389,17 @@ add_task(async function test_input_paste
   await reset();
 
   // Paste using event.dataTransfer
   selectContentInput();
   contentInput.onpaste = function(event) {
     ok(event instanceof ClipboardEvent, "paste event is an ClipboardEvent");
     ok(event.clipboardData instanceof DataTransfer, "paste event dataTransfer is a DataTransfer");
     is(event.target, contentInput, "paste event target");
-    is(event.clipboardData.mozItemCount, 1, "paste event mozItemCount");
+    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 1, "paste event mozItemCount");
     is(event.clipboardData.getData("text/plain"), clipboardInitialValue, "paste event getData");
     cachedPasteData = event.clipboardData;
   };
   try {
     synthesizeKey("v", {accelKey: 1});
     is(getClipboardText(), clipboardInitialValue,
       "paste using dataTransfer on plaintext editor did not modify clipboard contents");
     is(contentInput.value, clipboardInitialValue,
@@ -435,31 +435,31 @@ add_task(async function test_input_copyp
 
   // Cut several types of data and paste it again
   contentInput.value = "This is a line of text";
   contentInput.oncopy = function(event) {
     var cd = event.clipboardData;
     cd.setData("text/plain", "would be a phrase");
 
     var exh = false;
-    try { cd.mozSetDataAt("text/plain", "Text", 1); } catch (ex) { exh = true; }
+    try { SpecialPowers.wrap(cd).mozSetDataAt("text/plain", "Text", 1); } catch (ex) { exh = true; }
     ok(exh, "exception occured mozSetDataAt 1");
     exh = false;
-    try { cd.mozTypesAt(1); } catch (ex) { exh = true; }
+    try { SpecialPowers.wrap(cd).mozTypesAt(1); } catch (ex) { exh = true; }
     ok(exh, "exception occured mozTypesAt 1");
     exh = false;
-    try { cd.mozGetDataAt("text/plain", 1); } catch (ex) { exh = true; }
+    try { SpecialPowers.wrap(cd).mozGetDataAt("text/plain", 1); } catch (ex) { exh = true; }
     ok(exh, "exception occured mozGetDataAt 1");
     exh = false;
     try { cd.mozClearDataAt("text/plain", 1); } catch (ex) { exh = true; }
     ok(exh, "exception occured mozClearDataAt 1");
 
     cd.setData("text/x-moz-url", "http://www.mozilla.org");
-    cd.mozSetDataAt("text/x-custom", "Custom Text with \u0000 null", 0);
-    is(cd.mozItemCount, 1, "mozItemCount after set multiple types");
+    SpecialPowers.wrap(cd).mozSetDataAt("text/x-custom", "Custom Text with \u0000 null", 0);
+    is(SpecialPowers.wrap(cd).mozItemCount, 1, "mozItemCount after set multiple types");
     return false;
   };
 
   try {
     selectContentInput();
 
     await putOnClipboard("would be a phrase", () => {
       synthesizeKey("c", {accelKey: 1});
@@ -468,17 +468,17 @@ add_task(async function test_input_copyp
   finally {
     contentInput.oncopy = null;
   }
 
   contentInput.setSelectionRange(5, 14);
 
   contentInput.onpaste = function(event) {
     var cd = event.clipboardData;
-    is(cd.mozItemCount, 1, "paste after copy multiple types mozItemCount");
+    is(SpecialPowers.wrap(cd).mozItemCount, 1, "paste after copy multiple types mozItemCount");
     is(cd.getData("text/plain"), "would be a phrase", "paste text/plain multiple types");
 
     // Firefox for Android's clipboard code doesn't handle x-moz-url. Therefore
     // disabling the following test. Enable this once bug #840101 is fixed.
     if (!navigator.appVersion.includes("Android")) {
       is(cd.getData("text/x-moz-url"), "http://www.mozilla.org", "paste text/x-moz-url multiple types");
       is(cd.getData("text/x-custom"), "Custom Text with \u0000 null", "paste text/custom multiple types");
     } else {
@@ -635,20 +635,20 @@ function compareSynthetic(event, eventty
   if (step == "during") {
     is(eventtype, expectedData.type, "synthetic " + eventtype + " event fired");
   }
 
   ok(event.clipboardData instanceof DataTransfer, "clipboardData is assigned");
 
   is(event.type, expectedData.type, "synthetic " + eventtype + " event type");
   if (expectedData.data === null) {
-    is(event.clipboardData.mozItemCount, 0, "synthetic " + eventtype + " empty data");
+    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "synthetic " + eventtype + " empty data");
   }
   else {
-    is(event.clipboardData.mozItemCount, 1, "synthetic " + eventtype + " item count");
+    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 1, "synthetic " + eventtype + " item count");
     is(event.clipboardData.types.length, 1, "synthetic " + eventtype + " types length");
     is(event.clipboardData.getData(expectedData.dataType), expectedData.data,
        "synthetic " + eventtype + " data");
   }
 
   is(getClipboardText(), "empty", "event does not change the clipboard " + step + " dispatch");
 
   if (step == "during") {
@@ -661,17 +661,17 @@ async function checkCachedDataTransfer(c
 
   await putOnClipboard("Some Clipboard Text", () => { setClipboardText("Some Clipboard Text") },
                        "change clipboard outside of event");
 
   var oldtext = cd.getData("text/plain");
   ok(!oldtext, "clipboard get using " + testprefix);
 
   try {
-    cd.mozSetDataAt("text/plain", "Test Cache Data", 0);
+    SpecialPowers.wrap(cd).mozSetDataAt("text/plain", "Test Cache Data", 0);
   } catch (ex) {}
   ok(!cd.getData("text/plain"), "clipboard set using " + testprefix);
 
   is(getClipboardText(), "Some Clipboard Text", "clipboard not changed using " + testprefix);
 
   try {
     cd.mozClearDataAt("text/plain", 0);
   } catch (ex) {}
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_datatransfer_disallowed.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for DataTransfer moz* APIs</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script>
+function run_test()
+{
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.datatransfer.moz", false],
+  ]}, function() {
+    let hiddenMethods = ["mozTypesAt", "mozClearDataAt", "mozGetDataAt", "mozSetDataAt", "mozItemCount"];
+    let exposedMethods = Object.getOwnPropertyNames(DataTransfer.prototype);
+    for (var idx in hiddenMethods) {
+      if (exposedMethods.includes(hiddenMethods[idx])) {
+        ok(false, hiddenMethods[idx] + " should not be exposed");
+      } else {
+        ok(true, hiddenMethods[idx] + " was not exposed");
+      }
+    }
+    SimpleTest.finish();
+  });
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(run_test);
+</script>
--- a/dom/webidl/DataTransfer.webidl
+++ b/dom/webidl/DataTransfer.webidl
@@ -49,17 +49,17 @@ partial interface DataTransfer {
    * @throws NO_MODIFICATION_ALLOWED_ERR if the item cannot be modified
    */
   [Throws, UseCounter]
   void addElement(Element element);
 
   /**
    * The number of items being dragged.
    */
-  [UseCounter]
+  [Func="DataTransfer::MozAtAPIsEnabled"]
   readonly attribute unsigned long mozItemCount;
 
   /**
    * Sets the drag cursor state. Primarily used to control the cursor during
    * tab drags, but could be expanded to other uses. XXX Currently implemented
    * on Win32 only.
    *
    * Possible values:
@@ -72,34 +72,34 @@ partial interface DataTransfer {
   [UseCounter]
   attribute DOMString mozCursor;
 
   /**
    * Holds a list of the format types of the data that is stored for an item
    * at the specified index. If the index is not in the range from 0 to
    * itemCount - 1, an empty string list is returned.
    */
-  [Throws, NeedsCallerType, UseCounter]
+  [Throws, NeedsCallerType, Func="DataTransfer::MozAtAPIsEnabled"]
   DOMStringList mozTypesAt(unsigned long index);
 
   /**
    * Remove the data associated with the given format for an item at the
    * specified index. The index is in the range from zero to itemCount - 1.
    *
    * If the last format for the item is removed, the entire item is removed,
    * reducing the itemCount by one.
    *
    * If format is empty, then the data associated with all formats is removed.
    * If the format is not found, then this method has no effect.
    *
    * @param format the format to remove
    * @throws NS_ERROR_DOM_INDEX_SIZE_ERR if index is greater or equal than itemCount
    * @throws NO_MODIFICATION_ALLOWED_ERR if the item cannot be modified
    */
-  [Throws, NeedsSubjectPrincipal, UseCounter]
+  [Throws, NeedsSubjectPrincipal, Func="DataTransfer::MozAtAPIsEnabled"]
   void mozClearDataAt(DOMString format, unsigned long index);
 
   /*
    * A data transfer may store multiple items, each at a given zero-based
    * index. setDataAt may only be called with an index argument less than
    * itemCount in which case an existing item is modified, or equal to
    * itemCount in which case a new item is added, and the itemCount is
    * incremented by one.
@@ -113,29 +113,29 @@ partial interface DataTransfer {
    * (which will be converted into a string) or an nsISupports.
    *
    * @param format the format to add
    * @param data the data to add
    * @throws NS_ERROR_NULL_POINTER if the data is null
    * @throws NS_ERROR_DOM_INDEX_SIZE_ERR if index is greater than itemCount
    * @throws NO_MODIFICATION_ALLOWED_ERR if the item cannot be modified
    */
-  [Throws, NeedsSubjectPrincipal, UseCounter]
+  [Throws, NeedsSubjectPrincipal, Func="DataTransfer::MozAtAPIsEnabled"]
   void mozSetDataAt(DOMString format, any data, unsigned long index);
 
   /**
    * Retrieve the data associated with the given format for an item at the
    * specified index, or null if it does not exist. The index should be in the
    * range from zero to itemCount - 1.
    *
    * @param format the format of the data to look up
    * @returns the data of the given format, or null if it doesn't exist.
    * @throws NS_ERROR_DOM_INDEX_SIZE_ERR if index is greater or equal than itemCount
    */
-  [Throws, NeedsSubjectPrincipal, UseCounter]
+  [Throws, NeedsSubjectPrincipal, Func="DataTransfer::MozAtAPIsEnabled"]
   any mozGetDataAt(DOMString format, unsigned long index);
 
   /**
    * Update the drag image. Arguments are the same as setDragImage. This is only
    * valid within the parent chrome process.
    */
   [ChromeOnly]
   void updateDragImage(Element image, long x, long y);
--- a/dom/webidl/RTCStatsReport.webidl
+++ b/dom/webidl/RTCStatsReport.webidl
@@ -24,16 +24,17 @@ dictionary RTCStats {
   DOMHighResTimeStamp timestamp;
   RTCStatsType type;
   DOMString id;
 };
 
 dictionary RTCRtpStreamStats : RTCStats {
   unsigned long ssrc;
   DOMString mediaType;
+  DOMString kind;
   DOMString remoteId;
   boolean isRemote = false;
   DOMString mediaTrackId;
   DOMString transportId;
   DOMString codecId;
 
   // Video encoder/decoder measurements, not present in RTCP case
   double bitrateMean;
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -117,16 +117,25 @@ ImageContainerListener::NotifyComposite(
 {
   MutexAutoLock lock(mLock);
   if (mImageContainer) {
     mImageContainer->NotifyComposite(aNotification);
   }
 }
 
 void
+ImageContainerListener::NotifyDropped(uint32_t aDropped)
+{
+  MutexAutoLock lock(mLock);
+  if (mImageContainer) {
+    mImageContainer->NotifyDropped(aDropped);
+  }
+}
+
+void
 ImageContainerListener::ClearImageContainer()
 {
   MutexAutoLock lock(mLock);
   mImageContainer = nullptr;
 }
 
 void
 ImageContainerListener::DropImageClient()
@@ -253,39 +262,17 @@ ImageContainer::SetCurrentImageInternal(
   mGenerationCounter = ++sGenerationCounter;
 
   if (!aImages.IsEmpty()) {
     NS_ASSERTION(mCurrentImages.IsEmpty() ||
                  mCurrentImages[0].mProducerID != aImages[0].mProducerID ||
                  mCurrentImages[0].mFrameID <= aImages[0].mFrameID,
                  "frame IDs shouldn't go backwards");
     if (aImages[0].mProducerID != mCurrentProducerID) {
-      mFrameIDsNotYetComposited.Clear();
       mCurrentProducerID = aImages[0].mProducerID;
-    } else if (!aImages[0].mTimeStamp.IsNull()) {
-      // Check for expired frames
-      for (auto& img : mCurrentImages) {
-        if (img.mProducerID != aImages[0].mProducerID ||
-            img.mTimeStamp.IsNull() ||
-            img.mTimeStamp >= aImages[0].mTimeStamp) {
-          break;
-        }
-        if (!img.mComposited && !img.mTimeStamp.IsNull() &&
-            img.mFrameID != aImages[0].mFrameID) {
-          mFrameIDsNotYetComposited.AppendElement(img.mFrameID);
-        }
-      }
-
-      // Remove really old frames, assuming they'll never be composited.
-      const uint32_t maxFrames = 100;
-      if (mFrameIDsNotYetComposited.Length() > maxFrames) {
-        uint32_t dropFrames = mFrameIDsNotYetComposited.Length() - maxFrames;
-        mDroppedImageCount += dropFrames;
-        mFrameIDsNotYetComposited.RemoveElementsAt(0, dropFrames);
-      }
     }
   }
 
   nsTArray<OwningImage> newImages;
 
   for (uint32_t i = 0; i < aImages.Length(); ++i) {
     NS_ASSERTION(aImages[i].mImage, "image can't be null");
     NS_ASSERTION(!aImages[i].mTimeStamp.IsNull() || aImages.Length() == 1,
@@ -298,17 +285,17 @@ ImageContainer::SetCurrentImageInternal(
       NS_ASSERTION(aImages[i].mProducerID == aImages[i - 1].mProducerID,
                    "ProducerIDs must be the same");
     }
     OwningImage* img = newImages.AppendElement();
     img->mImage = aImages[i].mImage;
     img->mTimeStamp = aImages[i].mTimeStamp;
     img->mFrameID = aImages[i].mFrameID;
     img->mProducerID = aImages[i].mProducerID;
-    for (auto& oldImg : mCurrentImages) {
+    for (const auto& oldImg : mCurrentImages) {
       if (oldImg.mFrameID == img->mFrameID &&
           oldImg.mProducerID == img->mProducerID) {
         img->mComposited = oldImg.mComposited;
         break;
       }
     }
   }
 
@@ -434,40 +421,35 @@ ImageContainer::NotifyComposite(const Im
   RecursiveMutexAutoLock lock(mRecursiveMutex);
 
   // An image composition notification is sent the first time a particular
   // image is composited by an ImageHost. Thus, every time we receive such
   // a notification, a new image has been painted.
   ++mPaintCount;
 
   if (aNotification.producerID() == mCurrentProducerID) {
-    uint32_t i;
-    for (i = 0; i < mFrameIDsNotYetComposited.Length(); ++i) {
-      if (mFrameIDsNotYetComposited[i] <= aNotification.frameID()) {
-        if (mFrameIDsNotYetComposited[i] < aNotification.frameID()) {
-          ++mDroppedImageCount;
-        }
-      } else {
-        break;
-      }
-    }
-    mFrameIDsNotYetComposited.RemoveElementsAt(0, i);
     for (auto& img : mCurrentImages) {
       if (img.mFrameID == aNotification.frameID()) {
         img.mComposited = true;
       }
     }
   }
 
   if (!aNotification.imageTimeStamp().IsNull()) {
-    mPaintDelay = aNotification.firstCompositeTimeStamp() -
-        aNotification.imageTimeStamp();
+    mPaintDelay =
+      aNotification.firstCompositeTimeStamp() - aNotification.imageTimeStamp();
   }
 }
 
+void
+ImageContainer::NotifyDropped(uint32_t aDropped)
+{
+  mDroppedImageCount += aDropped;
+}
+
 #ifdef XP_WIN
 D3D11YCbCrRecycleAllocator*
 ImageContainer::GetD3D11YCbCrRecycleAllocator(KnowsCompositor* aAllocator)
 {
   if (mD3D11YCbCrRecycleAllocator &&
       aAllocator == mD3D11YCbCrRecycleAllocator->GetAllocator()) {
     return mD3D11YCbCrRecycleAllocator;
   }
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -351,16 +351,17 @@ protected:
 class ImageContainerListener final
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageContainerListener)
 
 public:
   explicit ImageContainerListener(ImageContainer* aImageContainer);
 
   void NotifyComposite(const ImageCompositeNotification& aNotification);
+  void NotifyDropped(uint32_t aDropped);
   void ClearImageContainer();
   void DropImageClient();
 private:
   typedef mozilla::Mutex Mutex;
 
   ~ImageContainerListener();
 
   Mutex mLock;
@@ -608,21 +609,21 @@ public:
    * non-null timestamp, and in a SetCurrentImages call the new image list is
    * non-empty, the timestamp of the first new image is non-null and greater
    * than the timestamp associated with the image, and the first new image's
    * frameID is not the same as the entry's.
    * Every expired image that is never composited is counted as dropped.
    */
   uint32_t GetDroppedImageCount()
   {
-    RecursiveMutexAutoLock lock(mRecursiveMutex);
     return mDroppedImageCount;
   }
 
   void NotifyComposite(const ImageCompositeNotification& aNotification);
+  void NotifyDropped(uint32_t aDropped);
 
   ImageContainerListener* GetImageContainerListener()
   {
     return mNotifyCompositeListener;
   }
 
   /**
    * Get the ImageClient associated with this container. Returns only after
@@ -670,18 +671,18 @@ private:
   // Number of contained images that have been painted at least once.  It's up
   // to the ImageContainer implementation to ensure accesses to this are
   // threadsafe.
   uint32_t mPaintCount;
 
   // See GetPaintDelay. Accessed only with mRecursiveMutex held.
   TimeDuration mPaintDelay;
 
-  // See GetDroppedImageCount. Accessed only with mRecursiveMutex held.
-  uint32_t mDroppedImageCount;
+  // See GetDroppedImageCount.
+  mozilla::Atomic<uint32_t> mDroppedImageCount;
 
   // This is the image factory used by this container, layer managers using
   // this container can set an alternative image factory that will be used to
   // create images for this container.
   RefPtr<ImageFactory> mImageFactory;
 
   gfx::IntSize mScaleHint;
 
@@ -696,19 +697,17 @@ private:
   // In this case the ImageContainer is perfectly usable, but it will forward
   // frames to the compositor through transactions in the main thread rather than
   // asynchronusly using the ImageBridge IPDL protocol.
   RefPtr<ImageClient> mImageClient;
 
   bool mIsAsync;
   CompositableHandle mAsyncContainerHandle;
 
-  nsTArray<FrameID> mFrameIDsNotYetComposited;
-  // ProducerID for last current image(s), including the frames in
-  // mFrameIDsNotYetComposited
+  // ProducerID for last current image(s)
   ProducerID mCurrentProducerID;
 
   RefPtr<ImageContainerListener> mNotifyCompositeListener;
 
   static mozilla::Atomic<uint32_t> sGenerationCounter;
 };
 
 class AutoLockImage
--- a/gfx/layers/composite/CompositableHost.h
+++ b/gfx/layers/composite/CompositableHost.h
@@ -122,21 +122,22 @@ public:
     return false;
   }
 
   /**
    * Returns the front buffer.
    * *aPictureRect (if non-null, and the returned TextureHost is non-null)
    * is set to the picture rect.
    */
-  virtual TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) {
+  virtual TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr)
+  {
     return nullptr;
   }
 
-  virtual gfx::IntSize GetImageSize() const
+  virtual gfx::IntSize GetImageSize()
   {
     MOZ_ASSERT(false, "Should have been overridden");
     return gfx::IntSize();
   }
 
   /**
    * Adds a mask effect using this texture as the mask, if possible.
    * @return true if the effect was added, false otherwise.
@@ -239,16 +240,18 @@ public:
 
   /// Called when shutting down the layer tree.
   /// This is a good place to clear all potential gpu resources before the widget
   /// is is destroyed.
   virtual void CleanupResources() {}
 
   virtual void BindTextureSource() {}
 
+  virtual uint32_t GetDroppedFrames() { return 0; }
+
 protected:
   HostLayerManager* GetLayerManager() const;
 
 protected:
   TextureInfo mTextureInfo;
   AsyncCompositableRef mAsyncRef;
   uint64_t mCompositorBridgeID;
   RefPtr<TextureSourceProvider> mTextureSourceProvider;
--- a/gfx/layers/composite/ImageComposite.cpp
+++ b/gfx/layers/composite/ImageComposite.cpp
@@ -13,79 +13,94 @@ using namespace gfx;
 namespace layers {
 
 /* static */ const float ImageComposite::BIAS_TIME_MS = 1.0f;
 
 ImageComposite::ImageComposite()
   : mLastFrameID(-1)
   , mLastProducerID(-1)
   , mBias(BIAS_NONE)
-{}
+  , mDroppedFrames(0)
+  , mLastChosenImageIndex(0)
+{
+}
 
 ImageComposite::~ImageComposite()
 {
 }
 
-/* static */ TimeStamp
-ImageComposite::GetBiasedTime(const TimeStamp& aInput, ImageComposite::Bias aBias)
+TimeStamp
+ImageComposite::GetBiasedTime(const TimeStamp& aInput) const
 {
-  switch (aBias) {
+  switch (mBias) {
   case ImageComposite::BIAS_NEGATIVE:
     return aInput - TimeDuration::FromMilliseconds(BIAS_TIME_MS);
   case ImageComposite::BIAS_POSITIVE:
     return aInput + TimeDuration::FromMilliseconds(BIAS_TIME_MS);
   default:
     return aInput;
   }
 }
 
-/* static */ ImageComposite::Bias
-ImageComposite::UpdateBias(const TimeStamp& aCompositionTime,
-                           const TimeStamp& aCompositedImageTime,
-                           const TimeStamp& aNextImageTime, // may be null
-                           ImageComposite::Bias aBias)
+void
+ImageComposite::UpdateBias(size_t aImageIndex)
 {
-  if (aCompositedImageTime.IsNull()) {
-    return ImageComposite::BIAS_NONE;
+  MOZ_ASSERT(aImageIndex < ImagesCount());
+
+  TimeStamp compositionTime = GetCompositionTime();
+  TimeStamp compositedImageTime = mImages[aImageIndex].mTimeStamp;
+  TimeStamp nextImageTime = aImageIndex + 1 < ImagesCount()
+                              ? mImages[aImageIndex + 1].mTimeStamp
+                              : TimeStamp();
+
+  if (compositedImageTime.IsNull()) {
+    mBias = ImageComposite::BIAS_NONE;
+    return;
   }
   TimeDuration threshold = TimeDuration::FromMilliseconds(1.0);
-  if (aCompositionTime - aCompositedImageTime < threshold &&
-      aCompositionTime - aCompositedImageTime > -threshold) {
+  if (compositionTime - compositedImageTime < threshold &&
+      compositionTime - compositedImageTime > -threshold) {
     // The chosen frame's time is very close to the composition time (probably
     // just before the current composition time, but due to previously set
     // negative bias, it could be just after the current composition time too).
     // If the inter-frame time is almost exactly equal to (a multiple of)
     // the inter-composition time, then we're in a dangerous situation because
     // jitter might cause frames to fall one side or the other of the
     // composition times, causing many frames to be skipped or duplicated.
     // Try to prevent that by adding a negative bias to the frame times during
     // the next composite; that should ensure the next frame's time is treated
     // as falling just before a composite time.
-    return ImageComposite::BIAS_NEGATIVE;
+    mBias = ImageComposite::BIAS_NEGATIVE;
+    return;
   }
-  if (!aNextImageTime.IsNull() &&
-      aNextImageTime - aCompositionTime < threshold &&
-      aNextImageTime - aCompositionTime > -threshold) {
+  if (!nextImageTime.IsNull() &&
+      nextImageTime - compositionTime < threshold &&
+      nextImageTime - compositionTime > -threshold) {
     // The next frame's time is very close to our composition time (probably
     // just after the current composition time, but due to previously set
     // positive bias, it could be just before the current composition time too).
     // We're in a dangerous situation because jitter might cause frames to
     // fall one side or the other of the composition times, causing many frames
     // to be skipped or duplicated.
     // Try to prevent that by adding a negative bias to the frame times during
     // the next composite; that should ensure the next frame's time is treated
     // as falling just before a composite time.
-    return ImageComposite::BIAS_POSITIVE;
+    mBias = ImageComposite::BIAS_POSITIVE;
+    return;
   }
-  return ImageComposite::BIAS_NONE;
+  mBias = ImageComposite::BIAS_NONE;
 }
 
 int
-ImageComposite::ChooseImageIndex() const
+ImageComposite::ChooseImageIndex()
 {
+  // ChooseImageIndex is called for all images in the layer when it is visible.
+  // Change to this behaviour would break dropped frames counting calculation:
+  // We rely on this assumption to determine if during successive runs an
+  // image is returned that isn't the one following immediately the previous one
   if (mImages.IsEmpty()) {
     return -1;
   }
   TimeStamp now = GetCompositionTime();
 
   if (now.IsNull()) {
     // Not in a composition, so just return the last image we composited
     // (if it's one of the current images).
@@ -93,30 +108,121 @@ ImageComposite::ChooseImageIndex() const
       if (mImages[i].mFrameID == mLastFrameID &&
           mImages[i].mProducerID == mLastProducerID) {
         return i;
       }
     }
     return -1;
   }
 
-  uint32_t result = 0;
+  uint32_t result = mLastChosenImageIndex;
   while (result + 1 < mImages.Length() &&
-      GetBiasedTime(mImages[result + 1].mTimeStamp, mBias) <= now) {
+         GetBiasedTime(mImages[result + 1].mTimeStamp) <= now) {
     ++result;
   }
+  if (result - mLastChosenImageIndex > 1) {
+    // We're not returning the same image as the last call to ChooseImageIndex
+    // or the immediately next one. We can assume that the frames not returned
+    // have been dropped as they were too late to be displayed
+    mDroppedFrames += result - mLastChosenImageIndex - 1;
+  }
+  mLastChosenImageIndex = result;
   return result;
 }
 
-const ImageComposite::TimedImage* ImageComposite::ChooseImage() const
+const ImageComposite::TimedImage* ImageComposite::ChooseImage()
 {
   int index = ChooseImageIndex();
   return index >= 0 ? &mImages[index] : nullptr;
 }
 
-ImageComposite::TimedImage* ImageComposite::ChooseImage()
+void
+ImageComposite::RemoveImagesWithTextureHost(TextureHost* aTexture)
+{
+  for (int32_t i = mImages.Length() - 1; i >= 0; --i) {
+    if (mImages[i].mTextureHost == aTexture) {
+      aTexture->UnbindTextureSource();
+      mImages.RemoveElementAt(i);
+    }
+  }
+}
+
+void
+ImageComposite::ClearImages()
+{
+  mImages.Clear();
+  mLastChosenImageIndex = 0;
+}
+
+uint32_t
+ImageComposite::ScanForLastFrameIndex(const nsTArray<TimedImage>& aNewImages)
 {
-  int index = ChooseImageIndex();
-  return index >= 0 ? &mImages[index] : nullptr;
+  if (mImages.IsEmpty()) {
+    return 0;
+  }
+  uint32_t i = mLastChosenImageIndex;
+  uint32_t newIndex = 0;
+  uint32_t dropped = 0;
+  // See if the new array of images have any images in common with the
+  // previous list that we haven't played yet.
+  uint32_t j = 0;
+  while (i < mImages.Length() && j < aNewImages.Length()) {
+    if (mImages[i].mProducerID != aNewImages[j].mProducerID) {
+      // This is new content, can stop.
+      newIndex = j;
+      break;
+    }
+    int32_t oldFrameID = mImages[i].mFrameID;
+    int32_t newFrameID = aNewImages[j].mFrameID;
+    if (oldFrameID > newFrameID) {
+      // This is an image we have already returned, we don't need to present
+      // it again and can start from this index next time.
+      newIndex = ++j;
+      continue;
+    }
+    if (oldFrameID < mLastFrameID) {
+      // we have already returned that frame previously, ignore.
+      i++;
+      continue;
+    }
+    if (oldFrameID < newFrameID) {
+      // This is a new image, all images prior the new one and not yet
+      // rendered can be considered as dropped. Those images have a FrameID
+      // inferior to the new image.
+      for (++i; i < mImages.Length() && mImages[i].mFrameID < newFrameID &&
+                mImages[i].mProducerID == aNewImages[j].mProducerID;
+           i++) {
+        dropped++;
+      }
+      break;
+    }
+    i++;
+    j++;
+  }
+  if (dropped > 0) {
+    mDroppedFrames += dropped;
+  }
+  if (newIndex >= aNewImages.Length()) {
+    // Somehow none of those images should be rendered (can this happen?)
+    // We will always return the last one for now.
+    newIndex = aNewImages.Length() - 1;
+  }
+  return newIndex;
+}
+
+void
+ImageComposite::SetImages(nsTArray<TimedImage>&& aNewImages)
+{
+  mLastChosenImageIndex = ScanForLastFrameIndex(aNewImages);
+  mImages = std::move(aNewImages);
+}
+
+const ImageComposite::TimedImage*
+ImageComposite::GetImage(size_t aIndex) const
+{
+  if (aIndex >= mImages.Length()) {
+    return nullptr;
+  }
+  return &mImages[aIndex];
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/composite/ImageComposite.h
+++ b/gfx/layers/composite/ImageComposite.h
@@ -35,33 +35,34 @@ public:
   int32_t GetProducerID()
   {
     const TimedImage* img = ChooseImage();
     return img ? img->mProducerID : -1;
   }
 
   int32_t GetLastFrameID() const { return mLastFrameID; }
   int32_t GetLastProducerID() const { return mLastProducerID; }
+  uint32_t GetDroppedFramesAndReset()
+  {
+    uint32_t dropped = mDroppedFrames;
+    mDroppedFrames = 0;
+    return dropped;
+  }
 
   enum Bias {
     // Don't apply bias to frame times
     BIAS_NONE,
     // Apply a negative bias to frame times to keep them before the vsync time
     BIAS_NEGATIVE,
     // Apply a positive bias to frame times to keep them after the vsync time
     BIAS_POSITIVE,
   };
 
-  static TimeStamp GetBiasedTime(const TimeStamp& aInput, ImageComposite::Bias aBias);
-
 protected:
-  static Bias UpdateBias(const TimeStamp& aCompositionTime,
-                         const TimeStamp& aCompositedImageTime,
-                         const TimeStamp& aNextImageTime, // may be null
-                         ImageComposite::Bias aBias);
+  void UpdateBias(size_t aImageIndex);
 
   virtual TimeStamp GetCompositionTime() const = 0;
 
   struct TimedImage {
     CompositableTextureHostRef mTextureHost;
     TimeStamp mTimeStamp;
     gfx::IntRect mPictureRect;
     int32_t mFrameID;
@@ -69,25 +70,42 @@ protected:
   };
 
   /**
    * ChooseImage is guaranteed to return the same TimedImage every time it's
    * called during the same composition, up to the end of Composite() ---
    * it depends only on mImages, mCompositor->GetCompositionTime(), and mBias.
    * mBias is updated at the end of Composite().
    */
-  const TimedImage* ChooseImage() const;
-  TimedImage* ChooseImage();
-  int ChooseImageIndex() const;
+  const TimedImage* ChooseImage();
+  int ChooseImageIndex();
+  const TimedImage* GetImage(size_t aIndex) const;
+  size_t ImagesCount() const { return mImages.Length(); }
+  const nsTArray<TimedImage>& Images() const { return mImages; }
 
-  nsTArray<TimedImage> mImages;
+  void RemoveImagesWithTextureHost(TextureHost* aTexture);
+  void ClearImages();
+  void SetImages(nsTArray<TimedImage>&& aNewImages);
+
   int32_t mLastFrameID;
   int32_t mLastProducerID;
+
+private:
+  nsTArray<TimedImage> mImages;
+  TimeStamp GetBiasedTime(const TimeStamp& aInput) const;
+  // Scan new images and look for common ones in the existing mImages array.
+  // Will determine if an image has been dropped through gaps between images and
+  // adjust mDroppedFrames accordingly.
+  // Return the index of what the last returned image would have been.
+  uint32_t ScanForLastFrameIndex(const nsTArray<TimedImage>& aNewImages);
+
   /**
    * Bias to apply to the next frame.
    */
   Bias mBias;
+  uint32_t mDroppedFrames;
+  uint32_t mLastChosenImageIndex;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // MOZILLA_GFX_IMAGECOMPOSITE_H
--- a/gfx/layers/composite/ImageHost.cpp
+++ b/gfx/layers/composite/ImageHost.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ImageHost.h"
 
 #include "LayersLogging.h"              // for AppendToString
 #include "composite/CompositableHost.h"  // for CompositableHost, etc
 #include "ipc/IPCMessageUtils.h"        // for null_t
+#include "mozilla/Move.h"
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/Effects.h"     // for TexturedEffect, Effect, etc
 #include "mozilla/layers/LayerManagerComposite.h"     // for TexturedEffect, Effect, etc
 #include "nsAString.h"
 #include "nsDebug.h"                    // for NS_WARNING, NS_ASSERTION
 #include "nsPrintfCString.h"            // for nsPrintfCString
 #include "nsString.h"                   // for nsAutoCString
 
@@ -61,37 +62,36 @@ ImageHost::UseTextureHost(const nsTArray
     img.mTimeStamp = t.mTimeStamp;
     img.mPictureRect = t.mPictureRect;
     img.mFrameID = t.mFrameID;
     img.mProducerID = t.mProducerID;
     img.mTextureHost->SetCropRect(img.mPictureRect);
     img.mTextureHost->Updated();
   }
 
-  mImages.SwapElements(newImages);
-  newImages.Clear();
+  SetImages(std::move(newImages));
 
   // If we only have one image we can upload it right away, otherwise we'll upload
   // on-demand during composition after we have picked the proper timestamp.
-  if (mImages.Length() == 1) {
-    SetCurrentTextureHost(mImages[0].mTextureHost);
+  if (ImagesCount() == 1) {
+    SetCurrentTextureHost(GetImage(0)->mTextureHost);
   }
 
   HostLayerManager* lm = GetLayerManager();
 
   // Video producers generally send replacement images with the same frameID but
   // slightly different timestamps in order to sync with the audio clock. This
   // means that any CompositeUntil() call we made in Composite() may no longer
   // guarantee that we'll composite until the next frame is ready. Fix that here.
   if (lm && mLastFrameID >= 0) {
-    for (size_t i = 0; i < mImages.Length(); ++i) {
-      bool frameComesAfter = mImages[i].mFrameID > mLastFrameID ||
-                             mImages[i].mProducerID != mLastProducerID;
-      if (frameComesAfter && !mImages[i].mTimeStamp.IsNull()) {
-        lm->CompositeUntil(mImages[i].mTimeStamp +
+    for (const auto& img : Images()) {
+      bool frameComesAfter =
+        img.mFrameID > mLastFrameID || img.mProducerID != mLastProducerID;
+      if (frameComesAfter && !img.mTimeStamp.IsNull()) {
+        lm->CompositeUntil(img.mTimeStamp +
                            TimeDuration::FromMilliseconds(BIAS_TIME_MS));
         break;
       }
     }
   }
 }
 
 void
@@ -137,54 +137,49 @@ ImageHost::CleanupResources()
 }
 
 void
 ImageHost::RemoveTextureHost(TextureHost* aTexture)
 {
   MOZ_ASSERT(!mLocked);
 
   CompositableHost::RemoveTextureHost(aTexture);
-
-  for (int32_t i = mImages.Length() - 1; i >= 0; --i) {
-    if (mImages[i].mTextureHost == aTexture) {
-      aTexture->UnbindTextureSource();
-      mImages.RemoveElementAt(i);
-    }
-  }
+  RemoveImagesWithTextureHost(aTexture);
 }
 
 TimeStamp
 ImageHost::GetCompositionTime() const
 {
   TimeStamp time;
   if (HostLayerManager* lm = GetLayerManager()) {
     time = lm->GetCompositionTime();
   }
   return time;
 }
 
 TextureHost*
 ImageHost::GetAsTextureHost(IntRect* aPictureRect)
 {
-  TimedImage* img = ChooseImage();
-  if (img) {
-    SetCurrentTextureHost(img->mTextureHost);
+  const TimedImage* img = ChooseImage();
+  if (!img) {
+    return nullptr;
   }
-  if (aPictureRect && img) {
+  SetCurrentTextureHost(img->mTextureHost);
+  if (aPictureRect) {
     *aPictureRect = img->mPictureRect;
   }
-  return img ? img->mTextureHost.get() : nullptr;
+  return img->mTextureHost;
 }
 
 void ImageHost::Attach(Layer* aLayer,
                        TextureSourceProvider* aProvider,
                        AttachFlags aFlags)
 {
   CompositableHost::Attach(aLayer, aProvider, aFlags);
-  for (auto& img : mImages) {
+  for (const auto& img : Images()) {
     img.mTextureHost->SetTextureSourceProvider(aProvider);
     img.mTextureHost->Updated();
   }
 }
 
 void
 ImageHost::Composite(Compositor* aCompositor,
                      LayerComposite* aLayer,
@@ -196,17 +191,17 @@ ImageHost::Composite(Compositor* aCompos
                      const nsIntRegion* aVisibleRegion,
                      const Maybe<gfx::Polygon>& aGeometry)
 {
   RenderInfo info;
   if (!PrepareToRender(aCompositor, &info)) {
     return;
   }
 
-  TimedImage* img = info.img;
+  const TimedImage* img = info.img;
 
   {
     AutoLockCompositableHost autoLock(this);
     if (autoLock.Failed()) {
       NS_WARNING("failed to lock front buffer");
       return;
     }
 
@@ -314,21 +309,22 @@ ImageHost::PrepareToRender(TextureSource
     return false;
   }
 
   int imageIndex = ChooseImageIndex();
   if (imageIndex < 0) {
     return false;
   }
 
-  if (uint32_t(imageIndex) + 1 < mImages.Length()) {
-    lm->CompositeUntil(mImages[imageIndex + 1].mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS));
+  if (uint32_t(imageIndex) + 1 < ImagesCount()) {
+    lm->CompositeUntil(GetImage(imageIndex + 1)->mTimeStamp +
+                       TimeDuration::FromMilliseconds(BIAS_TIME_MS));
   }
 
-  TimedImage* img = &mImages[imageIndex];
+  const TimedImage* img = GetImage(imageIndex);
   img->mTextureHost->SetTextureSourceProvider(aProvider);
   SetCurrentTextureHost(img->mTextureHost);
 
   aOutInfo->imageIndex = imageIndex;
   aOutInfo->img = img;
   aOutInfo->host = mCurrentTextureHost;
   return true;
 }
@@ -342,17 +338,17 @@ ImageHost::AcquireTextureSource(const Re
   }
   return mCurrentTextureSource.get();
 }
 
 void
 ImageHost::FinishRendering(const RenderInfo& aInfo)
 {
   HostLayerManager* lm = GetLayerManager();
-  TimedImage* img = aInfo.img;
+  const TimedImage* img = aInfo.img;
   int imageIndex = aInfo.imageIndex;
 
   if (mLastFrameID != img->mFrameID || mLastProducerID != img->mProducerID) {
     if (mAsyncRef) {
       ImageCompositeNotificationInfo info;
       info.mImageBridgeProcessId = mAsyncRef.mProcessId;
       info.mNotification = ImageCompositeNotification(
         mAsyncRef.mHandle,
@@ -364,78 +360,74 @@ ImageHost::FinishRendering(const RenderI
     mLastProducerID = img->mProducerID;
   }
 
   // Update mBias last. This can change which frame ChooseImage(Index) would
   // return, and we don't want to do that until we've finished compositing
   // since callers of ChooseImage(Index) assume the same image will be chosen
   // during a given composition. This must happen after autoLock's
   // destructor!
-  mBias = UpdateBias(
-      lm->GetCompositionTime(), mImages[imageIndex].mTimeStamp,
-      uint32_t(imageIndex + 1) < mImages.Length() ?
-          mImages[imageIndex + 1].mTimeStamp : TimeStamp(),
-      mBias);
+    UpdateBias(imageIndex);
 }
 
 void
 ImageHost::SetTextureSourceProvider(TextureSourceProvider* aProvider)
 {
   if (mTextureSourceProvider != aProvider) {
-    for (auto& img : mImages) {
+    for (const auto& img : Images()) {
       img.mTextureHost->SetTextureSourceProvider(aProvider);
     }
   }
   CompositableHost::SetTextureSourceProvider(aProvider);
 }
 
 void
 ImageHost::PrintInfo(std::stringstream& aStream, const char* aPrefix)
 {
   aStream << aPrefix;
   aStream << nsPrintfCString("ImageHost (0x%p)", this).get();
 
   nsAutoCString pfx(aPrefix);
   pfx += "  ";
-  for (auto& img : mImages) {
+  for (const auto& img : Images()) {
     aStream << "\n";
     img.mTextureHost->PrintInfo(aStream, pfx.get());
     AppendToString(aStream, img.mPictureRect, " [picture-rect=", "]");
   }
 }
 
 void
 ImageHost::Dump(std::stringstream& aStream,
                 const char* aPrefix,
                 bool aDumpHtml)
 {
-  for (auto& img : mImages) {
+  for (const auto& img : Images()) {
     aStream << aPrefix;
     aStream << (aDumpHtml ? "<ul><li>TextureHost: "
                              : "TextureHost: ");
     DumpTextureHost(aStream, img.mTextureHost);
     aStream << (aDumpHtml ? " </li></ul> " : " ");
   }
 }
 
 already_AddRefed<gfx::DataSourceSurface>
 ImageHost::GetAsSurface()
 {
-  TimedImage* img = ChooseImage();
+  const TimedImage* img = ChooseImage();
   if (img) {
     return img->mTextureHost->GetAsSurface();
   }
   return nullptr;
 }
 
 bool
 ImageHost::Lock()
 {
   MOZ_ASSERT(!mLocked);
-  TimedImage* img = ChooseImage();
+  const TimedImage* img = ChooseImage();
   if (!img) {
     return false;
   }
 
   SetCurrentTextureHost(img->mTextureHost);
 
   if (!mCurrentTextureHost->Lock()) {
     return false;
@@ -451,17 +443,17 @@ ImageHost::Unlock()
 
   if (mCurrentTextureHost) {
     mCurrentTextureHost->Unlock();
   }
   mLocked = false;
 }
 
 IntSize
-ImageHost::GetImageSize() const
+ImageHost::GetImageSize()
 {
   const TimedImage* img = ChooseImage();
   if (img) {
     return IntSize(img->mPictureRect.Width(), img->mPictureRect.Height());
   }
   return IntSize();
 }
 
@@ -484,17 +476,17 @@ ImageHost::IsOpaque()
     return true;
   }
   return false;
 }
 
 already_AddRefed<TexturedEffect>
 ImageHost::GenEffect(const gfx::SamplingFilter aSamplingFilter)
 {
-  TimedImage* img = ChooseImage();
+  const TimedImage* img = ChooseImage();
   if (!img) {
     return nullptr;
   }
   SetCurrentTextureHost(img->mTextureHost);
   if (!mCurrentTextureHost->BindTextureSource(mCurrentTextureSource)) {
     return nullptr;
   }
   bool isAlphaPremultiplied = true;
--- a/gfx/layers/composite/ImageHost.h
+++ b/gfx/layers/composite/ImageHost.h
@@ -64,17 +64,17 @@ public:
   virtual TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) override;
 
   virtual void Attach(Layer* aLayer,
                       TextureSourceProvider* aProvider,
                       AttachFlags aFlags = NO_FLAGS) override;
 
   virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider) override;
 
-  gfx::IntSize GetImageSize() const override;
+  gfx::IntSize GetImageSize() override;
 
   virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override;
 
   virtual void Dump(std::stringstream& aStream,
                     const char* aPrefix = "",
                     bool aDumpHtml = false) override;
 
   virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override;
@@ -86,19 +86,24 @@ public:
   virtual already_AddRefed<TexturedEffect> GenEffect(const gfx::SamplingFilter aSamplingFilter) override;
 
   void SetCurrentTextureHost(TextureHost* aTexture);
 
   virtual void CleanupResources() override;
 
   bool IsOpaque();
 
+  uint32_t GetDroppedFrames() override
+  {
+    return GetDroppedFramesAndReset();
+  }
+
   struct RenderInfo {
     int imageIndex;
-    TimedImage* img;
+    const TimedImage* img;
     RefPtr<TextureHost> host;
 
     RenderInfo() : imageIndex(-1), img(nullptr)
     {}
   };
 
   // Acquire rendering information for the current frame.
   bool PrepareToRender(TextureSourceProvider* aProvider, RenderInfo* aOutInfo);
--- a/gfx/layers/ipc/CompositableTransactionParent.cpp
+++ b/gfx/layers/ipc/CompositableTransactionParent.cpp
@@ -63,51 +63,58 @@ bool
 CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation& aEdit)
 {
   // Ignore all operations on compositables created on stale compositors. We
   // return true because the child is unable to handle errors.
   RefPtr<CompositableHost> compositable = FindCompositable(aEdit.compositable());
   if (!compositable) {
     return false;
   }
-  if (TextureSourceProvider* provider = compositable->GetTextureSourceProvider()) {
+  return ReceiveCompositableUpdate(aEdit.detail(), WrapNotNull(compositable));
+}
+
+bool
+CompositableParentManager::ReceiveCompositableUpdate(
+  const CompositableOperationDetail& aDetail,
+  NotNull<CompositableHost*> aCompositable)
+{
+  if (TextureSourceProvider* provider = aCompositable->GetTextureSourceProvider()) {
     if (!provider->IsValid()) {
       return false;
     }
   }
 
-  switch (aEdit.detail().type()) {
+  switch (aDetail.type()) {
     case CompositableOperationDetail::TOpPaintTextureRegion: {
       MOZ_LAYERS_LOG(("[ParentSide] Paint PaintedLayer"));
 
-      const OpPaintTextureRegion& op = aEdit.detail().get_OpPaintTextureRegion();
-      Layer* layer = compositable->GetLayer();
+      const OpPaintTextureRegion& op = aDetail.get_OpPaintTextureRegion();
+      Layer* layer = aCompositable->GetLayer();
       if (!layer || layer->GetType() != Layer::TYPE_PAINTED) {
         return false;
       }
       PaintedLayerComposite* thebes = static_cast<PaintedLayerComposite*>(layer);
 
       const ThebesBufferData& bufferData = op.bufferData();
 
       RenderTraceInvalidateStart(thebes, "FF00FF", op.updatedRegion().GetBounds());
 
-      if (!compositable->UpdateThebes(bufferData,
-                                      op.updatedRegion(),
-                                      thebes->GetValidRegion()))
-      {
+      if (!aCompositable->UpdateThebes(bufferData,
+                                       op.updatedRegion(),
+                                       thebes->GetValidRegion())) {
         return false;
       }
 
       RenderTraceInvalidateEnd(thebes, "FF00FF");
       break;
     }
     case CompositableOperationDetail::TOpUseTiledLayerBuffer: {
       MOZ_LAYERS_LOG(("[ParentSide] Paint TiledLayerBuffer"));
-      const OpUseTiledLayerBuffer& op = aEdit.detail().get_OpUseTiledLayerBuffer();
-      TiledContentHost* tiledHost = compositable->AsTiledContentHost();
+      const OpUseTiledLayerBuffer& op = aDetail.get_OpUseTiledLayerBuffer();
+      TiledContentHost* tiledHost = aCompositable->AsTiledContentHost();
 
       NS_ASSERTION(tiledHost, "The compositable is not tiled");
 
       const SurfaceDescriptorTiles& tileDesc = op.tileLayerDescriptor();
 
       bool success = tiledHost->UseTiledLayerBuffer(this, tileDesc);
 
       const InfallibleTArray<TileDescriptor>& tileDescriptors = tileDesc.tiles();
@@ -135,90 +142,90 @@ CompositableParentManager::ReceiveCompos
         }
       }
       if (!success) {
         return false;
       }
       break;
     }
     case CompositableOperationDetail::TOpRemoveTexture: {
-      const OpRemoveTexture& op = aEdit.detail().get_OpRemoveTexture();
+      const OpRemoveTexture& op = aDetail.get_OpRemoveTexture();
 
       RefPtr<TextureHost> tex = TextureHost::AsTextureHost(op.textureParent());
 
       MOZ_ASSERT(tex.get());
-      compositable->RemoveTextureHost(tex);
+      aCompositable->RemoveTextureHost(tex);
       break;
     }
     case CompositableOperationDetail::TOpUseTexture: {
-      const OpUseTexture& op = aEdit.detail().get_OpUseTexture();
+      const OpUseTexture& op = aDetail.get_OpUseTexture();
 
       AutoTArray<CompositableHost::TimedTexture,4> textures;
       for (auto& timedTexture : op.textures()) {
         CompositableHost::TimedTexture* t = textures.AppendElement();
         t->mTexture =
             TextureHost::AsTextureHost(timedTexture.textureParent());
         MOZ_ASSERT(t->mTexture);
         t->mTimeStamp = timedTexture.timeStamp();
         t->mPictureRect = timedTexture.picture();
         t->mFrameID = timedTexture.frameID();
         t->mProducerID = timedTexture.producerID();
         if (timedTexture.readLocked()) {
           t->mTexture->SetReadLocked();
         }
       }
       if (textures.Length() > 0) {
-        compositable->UseTextureHost(textures);
+        aCompositable->UseTextureHost(textures);
 
         for (auto& timedTexture : op.textures()) {
           RefPtr<TextureHost> texture = TextureHost::AsTextureHost(timedTexture.textureParent());
           if (texture) {
             texture->SetLastFwdTransactionId(mFwdTransactionId);
             // Make sure that each texture was handled by the compositable
             // because the recycling logic depends on it.
             MOZ_ASSERT(texture->NumCompositableRefs() > 0);
           }
         }
       }
 
-      if (UsesImageBridge() && compositable->GetLayer()) {
-        ScheduleComposition(compositable);
+      if (UsesImageBridge() && aCompositable->GetLayer()) {
+        ScheduleComposition(aCompositable);
       }
       break;
     }
     case CompositableOperationDetail::TOpUseComponentAlphaTextures: {
-      const OpUseComponentAlphaTextures& op = aEdit.detail().get_OpUseComponentAlphaTextures();
+      const OpUseComponentAlphaTextures& op = aDetail.get_OpUseComponentAlphaTextures();
       RefPtr<TextureHost> texOnBlack = TextureHost::AsTextureHost(op.textureOnBlackParent());
       RefPtr<TextureHost> texOnWhite = TextureHost::AsTextureHost(op.textureOnWhiteParent());
       if (op.readLockedBlack()) {
         texOnBlack->SetReadLocked();
       }
       if (op.readLockedWhite()) {
         texOnWhite->SetReadLocked();
       }
 
       MOZ_ASSERT(texOnBlack && texOnWhite);
-      compositable->UseComponentAlphaTextures(texOnBlack, texOnWhite);
+      aCompositable->UseComponentAlphaTextures(texOnBlack, texOnWhite);
 
       if (texOnBlack) {
         texOnBlack->SetLastFwdTransactionId(mFwdTransactionId);
         // Make sure that each texture was handled by the compositable
         // because the recycling logic depends on it.
         MOZ_ASSERT(texOnBlack->NumCompositableRefs() > 0);
       }
 
       if (texOnWhite) {
         texOnWhite->SetLastFwdTransactionId(mFwdTransactionId);
         // Make sure that each texture was handled by the compositable
         // because the recycling logic depends on it.
         MOZ_ASSERT(texOnWhite->NumCompositableRefs() > 0);
       }
 
       if (UsesImageBridge()) {
-        ScheduleComposition(compositable);
+        ScheduleComposition(aCompositable);
       }
       break;
     }
     default: {
       MOZ_ASSERT(false, "bad type");
     }
   }
 
--- a/gfx/layers/ipc/CompositableTransactionParent.h
+++ b/gfx/layers/ipc/CompositableTransactionParent.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_LAYERS_COMPOSITABLETRANSACTIONPARENT_H
 #define MOZILLA_LAYERS_COMPOSITABLETRANSACTIONPARENT_H
 
 #include <vector>                       // for vector
 #include "mozilla/Attributes.h"         // for override
+#include "mozilla/NotNull.h"
 #include "mozilla/layers/ISurfaceAllocator.h"  // for ISurfaceAllocator
 #include "mozilla/layers/LayersMessages.h"  // for EditReply, etc
 #include "mozilla/layers/TextureClient.h"
 #include "CompositableHost.h"
 
 namespace mozilla {
 namespace layers {
 
@@ -43,16 +44,18 @@ public:
     bool aUseWebRender);
   RefPtr<CompositableHost> FindCompositable(const CompositableHandle& aHandle);
 
 protected:
   /**
    * Handle the IPDL messages that affect PCompositable actors.
    */
   bool ReceiveCompositableUpdate(const CompositableOperation& aEdit);
+  bool ReceiveCompositableUpdate(const CompositableOperationDetail& aDetail,
+                                 NotNull<CompositableHost*> aCompositable);
 
   void ReleaseCompositable(const CompositableHandle& aHandle);
 
   uint64_t mFwdTransactionId = 0;
 
   /**
    * Mapping form IDs to CompositableHosts.
    */
--- a/gfx/layers/ipc/ImageBridgeChild.cpp
+++ b/gfx/layers/ipc/ImageBridgeChild.cpp
@@ -992,35 +992,51 @@ ImageBridgeChild::RecvParentAsyncMessage
       default:
         NS_ERROR("unknown AsyncParentMessageData type");
         return IPC_FAIL_NO_REASON(this);
     }
   }
   return IPC_OK();
 }
 
+RefPtr<ImageContainerListener>
+ImageBridgeChild::FindListener(const CompositableHandle& aHandle)
+{
+  RefPtr<ImageContainerListener> listener;
+  MutexAutoLock lock(mContainerMapLock);
+  auto it = mImageContainerListeners.find(aHandle.Value());
+  if (it != mImageContainerListeners.end()) {
+    listener = it->second;
+  }
+  return listener;
+}
+
 mozilla::ipc::IPCResult
 ImageBridgeChild::RecvDidComposite(InfallibleTArray<ImageCompositeNotification>&& aNotifications)
 {
   for (auto& n : aNotifications) {
-    RefPtr<ImageContainerListener> listener;
-    {
-      MutexAutoLock lock(mContainerMapLock);
-      auto it = mImageContainerListeners.find(n.compositable().Value());
-      if (it != mImageContainerListeners.end()) {
-        listener = it->second;
-      }
-    }
+    RefPtr<ImageContainerListener> listener = FindListener(n.compositable());
     if (listener) {
       listener->NotifyComposite(n);
     }
   }
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+ImageBridgeChild::RecvReportFramesDropped(const CompositableHandle& aHandle, const uint32_t& aFrames)
+{
+  RefPtr<ImageContainerListener> listener = FindListener(aHandle);
+  if (listener) {
+    listener->NotifyDropped(aFrames);
+  }
+
+  return IPC_OK();
+}
+
 PTextureChild*
 ImageBridgeChild::CreateTexture(const SurfaceDescriptor& aSharedData,
                                 const ReadLockDescriptor& aReadLock,
                                 LayersBackend aLayersBackend,
                                 TextureFlags aFlags,
                                 uint64_t aSerial,
                                 wr::MaybeExternalImageId& aExternalImageId,
                                 nsIEventTarget* aTarget)
--- a/gfx/layers/ipc/ImageBridgeChild.h
+++ b/gfx/layers/ipc/ImageBridgeChild.h
@@ -90,17 +90,17 @@ bool InImageBridgeChildThread();
  *   - (A) The ImageContainer uses ImageBridge. The image is already available to the
  *   compositor process because it has been sent with SetCurrentImage. Yet, the
  *   CompositableHost on the compositor side will needs the ID referring to the
  *   ImageContainer to access the Image. So during the Swap operation that happens
  *   in the transaction, we swap the container ID rather than the image data.
  *   - (B) Since the ImageContainer does not use ImageBridge, the image data is swaped.
  *
  * - During composition:
- *   - (A) The CompositableHost has an AsyncID, it looks up the ID in the 
+ *   - (A) The CompositableHost has an AsyncID, it looks up the ID in the
  *   global table to see if there is an image. If there is no image, nothing is rendered.
  *   - (B) The CompositableHost has image data rather than an ID (meaning it is not
  *   using ImageBridge), then it just composites the image data normally.
  *
  * This means that there might be a possibility for the ImageBridge to send the first
  * frame before the first layer transaction that will pass the container ID to the
  * CompositableHost happens. In this (unlikely) case the layer is not composited
  * until the layer transaction happens. This means this scenario is not harmful.
@@ -189,16 +189,19 @@ public:
   DeallocPMediaSystemResourceManagerChild(PMediaSystemResourceManagerChild* aActor) override;
 
   virtual mozilla::ipc::IPCResult
   RecvParentAsyncMessages(InfallibleTArray<AsyncParentMessageData>&& aMessages) override;
 
   virtual mozilla::ipc::IPCResult
   RecvDidComposite(InfallibleTArray<ImageCompositeNotification>&& aNotifications) override;
 
+  virtual mozilla::ipc::IPCResult
+  RecvReportFramesDropped(const CompositableHandle& aHandle, const uint32_t& aFrames) override;
+
   // Create an ImageClient from any thread.
   RefPtr<ImageClient> CreateImageClient(
     CompositableType aType,
     ImageContainer* aImageContainer);
 
   // Create an ImageClient from the ImageBridge thread.
   RefPtr<ImageClient> CreateImageClientNow(
     CompositableType aType,
@@ -398,16 +401,17 @@ private:
    */
   std::unordered_map<uint64_t, RefPtr<TextureClient>> mTexturesWaitingRecycled;
 
   /**
    * Mapping from async compositable IDs to image containers.
    */
   Mutex mContainerMapLock;
   std::unordered_map<uint64_t, RefPtr<ImageContainerListener>> mImageContainerListeners;
+  RefPtr<ImageContainerListener> FindListener(const CompositableHandle& aHandle);
 
 #if defined(XP_WIN)
   /**
    * Used for checking if D3D11Device is updated.
    */
   RefPtr<ID3D11Device> mImageDevice;
 #endif
 };
--- a/gfx/layers/ipc/ImageBridgeParent.cpp
+++ b/gfx/layers/ipc/ImageBridgeParent.cpp
@@ -193,20 +193,27 @@ mozilla::ipc::IPCResult
 ImageBridgeParent::RecvUpdate(EditArray&& aEdits, OpDestroyArray&& aToDestroy,
                               const uint64_t& aFwdTransactionId)
 {
   // This ensures that destroy operations are always processed. It is not safe
   // to early-return from RecvUpdate without doing so.
   AutoImageBridgeParentAsyncMessageSender autoAsyncMessageSender(this, &aToDestroy);
   UpdateFwdTransactionId(aFwdTransactionId);
 
-  for (EditArray::index_type i = 0; i < aEdits.Length(); ++i) {
-    if (!ReceiveCompositableUpdate(aEdits[i])) {
+  for (const auto& edit : aEdits) {
+    RefPtr<CompositableHost> compositable =
+      FindCompositable(edit.compositable());
+    if (!compositable ||
+        !ReceiveCompositableUpdate(edit.detail(), WrapNotNull(compositable))) {
       return IPC_FAIL_NO_REASON(this);
     }
+    uint32_t dropped = compositable->GetDroppedFrames();
+    if (dropped) {
+      Unused << SendReportFramesDropped(edit.compositable(), dropped);
+    }
   }
 
   if (!IsSameProcess()) {
     // Ensure that any pending operations involving back and front
     // buffers have completed, so that neither process stomps on the
     // other's buffer contents.
     LayerManagerComposite::PlatformSyncBeforeReplyUpdate();
   }
--- a/gfx/layers/ipc/PImageBridge.ipdl
+++ b/gfx/layers/ipc/PImageBridge.ipdl
@@ -31,16 +31,19 @@ sync protocol PImageBridge
   manages PTexture;
   manages PMediaSystemResourceManager;
 
 child:
   async ParentAsyncMessages(AsyncParentMessageData[] aMessages);
 
   async DidComposite(ImageCompositeNotification[] aNotifications);
 
+  // Report the number of frames dropped for the given CompositableHost.
+  async ReportFramesDropped(CompositableHandle aHandle, uint32_t aFrames);
+
 parent:
   async Update(CompositableOperation[] ops, OpDestroy[] toDestroy, uint64_t fwdTransactionId);
 
   // First step of the destruction sequence. This puts ImageBridge
   // in a state in which it can't send asynchronous messages
   // so as to not race with the channel getting closed.
   // In the child side, the Closing the channel does not happen right after WillClose,
   // it is scheduled in the ImageBridgeChild's message queue in order to ensure
--- a/gfx/layers/wr/WebRenderImageHost.cpp
+++ b/gfx/layers/wr/WebRenderImageHost.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 "WebRenderImageHost.h"
 
 #include "LayersLogging.h"
+#include "mozilla/Move.h"
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorVsyncScheduler.h"  // for CompositorVsyncScheduler
 #include "mozilla/layers/Effects.h"     // for TexturedEffect, Effect, etc
 #include "mozilla/layers/LayerManagerComposite.h"     // for TexturedEffect, Effect, etc
 #include "mozilla/layers/WebRenderBridgeParent.h"
 #include "mozilla/layers/AsyncImagePipelineManager.h"
 #include "nsAString.h"
 #include "nsDebug.h"                    // for NS_WARNING, NS_ASSERTION
@@ -63,86 +64,77 @@ WebRenderImageHost::UseTextureHost(const
     img.mTimeStamp = t.mTimeStamp;
     img.mPictureRect = t.mPictureRect;
     img.mFrameID = t.mFrameID;
     img.mProducerID = t.mProducerID;
     img.mTextureHost->SetCropRect(img.mPictureRect);
     img.mTextureHost->Updated();
   }
 
-  mImages.SwapElements(newImages);
-  newImages.Clear();
+  SetImages(std::move(newImages));
 
   if (mWrBridge && mWrBridge->CompositorScheduler() && GetAsyncRef()) {
     // Will check if we will generate frame.
     mWrBridge->CompositorScheduler()->ScheduleComposition();
   }
 
   // Video producers generally send replacement images with the same frameID but
   // slightly different timestamps in order to sync with the audio clock. This
   // means that any CompositeUntil() call we made in Composite() may no longer
   // guarantee that we'll composite until the next frame is ready. Fix that here.
   if (mWrBridge && mLastFrameID >= 0) {
     MOZ_ASSERT(mWrBridge->AsyncImageManager());
-    for (size_t i = 0; i < mImages.Length(); ++i) {
-      bool frameComesAfter = mImages[i].mFrameID > mLastFrameID ||
-                             mImages[i].mProducerID != mLastProducerID;
-      if (frameComesAfter && !mImages[i].mTimeStamp.IsNull()) {
-        mWrBridge->AsyncImageManager()->CompositeUntil(mImages[i].mTimeStamp +
-                           TimeDuration::FromMilliseconds(BIAS_TIME_MS));
+    for (const auto& img : Images()) {
+      bool frameComesAfter =
+        img.mFrameID > mLastFrameID || img.mProducerID != mLastProducerID;
+      if (frameComesAfter && !img.mTimeStamp.IsNull()) {
+        mWrBridge->AsyncImageManager()->CompositeUntil(
+          img.mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS));
         break;
       }
     }
   }
 }
 
 void
 WebRenderImageHost::UseComponentAlphaTextures(TextureHost* aTextureOnBlack,
                                               TextureHost* aTextureOnWhite)
 {
   MOZ_ASSERT_UNREACHABLE("unexpected to be called");
 }
 
 void
 WebRenderImageHost::CleanupResources()
 {
-  nsTArray<TimedImage> newImages;
-  mImages.SwapElements(newImages);
-  newImages.Clear();
+  ClearImages();
   SetCurrentTextureHost(nullptr);
 }
 
 void
 WebRenderImageHost::RemoveTextureHost(TextureHost* aTexture)
 {
   CompositableHost::RemoveTextureHost(aTexture);
-
-  for (int32_t i = mImages.Length() - 1; i >= 0; --i) {
-    if (mImages[i].mTextureHost == aTexture) {
-      aTexture->UnbindTextureSource();
-      mImages.RemoveElementAt(i);
-    }
-  }
+  RemoveImagesWithTextureHost(aTexture);
 }
 
 TimeStamp
 WebRenderImageHost::GetCompositionTime() const
 {
   TimeStamp time;
   if (mWrBridge) {
     MOZ_ASSERT(mWrBridge->AsyncImageManager());
     time = mWrBridge->AsyncImageManager()->GetCompositionTime();
   }
   return time;
 }
 
 TextureHost*
 WebRenderImageHost::GetAsTextureHost(IntRect* aPictureRect)
 {
-  TimedImage* img = ChooseImage();
+  const TimedImage* img = ChooseImage();
   if (img) {
     return img->mTextureHost;
   }
   return nullptr;
 }
 
 TextureHost*
 WebRenderImageHost::GetAsTextureHostForComposite()
@@ -152,44 +144,41 @@ WebRenderImageHost::GetAsTextureHostForC
   }
 
   int imageIndex = ChooseImageIndex();
   if (imageIndex < 0) {
     SetCurrentTextureHost(nullptr);
     return nullptr;
   }
 
-  if (uint32_t(imageIndex) + 1 < mImages.Length()) {
+  if (uint32_t(imageIndex) + 1 < ImagesCount()) {
     MOZ_ASSERT(mWrBridge->AsyncImageManager());
-    mWrBridge->AsyncImageManager()->CompositeUntil(mImages[imageIndex + 1].mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS));
+    mWrBridge->AsyncImageManager()->CompositeUntil(
+      GetImage(imageIndex + 1)->mTimeStamp +
+      TimeDuration::FromMilliseconds(BIAS_TIME_MS));
   }
 
-  TimedImage* img = &mImages[imageIndex];
+  const TimedImage* img = GetImage(imageIndex);
 
   if (mLastFrameID != img->mFrameID || mLastProducerID != img->mProducerID) {
     if (mAsyncRef) {
       ImageCompositeNotificationInfo info;
       info.mImageBridgeProcessId = mAsyncRef.mProcessId;
       info.mNotification = ImageCompositeNotification(
         mAsyncRef.mHandle,
         img->mTimeStamp, mWrBridge->AsyncImageManager()->GetCompositionTime(),
         img->mFrameID, img->mProducerID);
       mWrBridge->AsyncImageManager()->AppendImageCompositeNotification(info);
     }
     mLastFrameID = img->mFrameID;
     mLastProducerID = img->mProducerID;
   }
   SetCurrentTextureHost(img->mTextureHost);
 
-  mBias = UpdateBias(
-    mWrBridge->AsyncImageManager()->GetCompositionTime(),
-    mImages[imageIndex].mTimeStamp,
-    uint32_t(imageIndex + 1) < mImages.Length() ?
-      mImages[imageIndex + 1].mTimeStamp : TimeStamp(),
-    mBias);
+  UpdateBias(imageIndex);
 
   return mCurrentTextureHost;
 }
 
 void
 WebRenderImageHost::SetCurrentTextureHost(TextureHost* aTexture)
 {
   if (aTexture == mCurrentTextureHost.get()) {
@@ -218,56 +207,56 @@ WebRenderImageHost::Composite(Compositor
 {
   MOZ_ASSERT_UNREACHABLE("unexpected to be called");
 }
 
 void
 WebRenderImageHost::SetTextureSourceProvider(TextureSourceProvider* aProvider)
 {
   if (mTextureSourceProvider != aProvider) {
-    for (auto& img : mImages) {
+    for (const auto& img : Images()) {
       img.mTextureHost->SetTextureSourceProvider(aProvider);
     }
   }
   CompositableHost::SetTextureSourceProvider(aProvider);
 }
 
 void
 WebRenderImageHost::PrintInfo(std::stringstream& aStream, const char* aPrefix)
 {
   aStream << aPrefix;
   aStream << nsPrintfCString("WebRenderImageHost (0x%p)", this).get();
 
   nsAutoCString pfx(aPrefix);
   pfx += "  ";
-  for (auto& img : mImages) {
+  for (const auto& img : Images()) {
     aStream << "\n";
     img.mTextureHost->PrintInfo(aStream, pfx.get());
     AppendToString(aStream, img.mPictureRect, " [picture-rect=", "]");
   }
 }
 
 void
 WebRenderImageHost::Dump(std::stringstream& aStream,
-                const char* aPrefix,
-                bool aDumpHtml)
+                         const char* aPrefix,
+                         bool aDumpHtml)
 {
-  for (auto& img : mImages) {
+  for (const auto& img : Images()) {
     aStream << aPrefix;
     aStream << (aDumpHtml ? "<ul><li>TextureHost: "
                              : "TextureHost: ");
     DumpTextureHost(aStream, img.mTextureHost);
     aStream << (aDumpHtml ? " </li></ul> " : " ");
   }
 }
 
 already_AddRefed<gfx::DataSourceSurface>
 WebRenderImageHost::GetAsSurface()
 {
-  TimedImage* img = ChooseImage();
+  const TimedImage* img = ChooseImage();
   if (img) {
     return img->mTextureHost->GetAsSurface();
   }
   return nullptr;
 }
 
 bool
 WebRenderImageHost::Lock()
@@ -278,17 +267,17 @@ WebRenderImageHost::Lock()
 
 void
 WebRenderImageHost::Unlock()
 {
   MOZ_ASSERT_UNREACHABLE("unexpected to be called");
 }
 
 IntSize
-WebRenderImageHost::GetImageSize() const
+WebRenderImageHost::GetImageSize()
 {
   const TimedImage* img = ChooseImage();
   if (img) {
     return IntSize(img->mPictureRect.Width(), img->mPictureRect.Height());
   }
   return IntSize();
 }
 
--- a/gfx/layers/wr/WebRenderImageHost.h
+++ b/gfx/layers/wr/WebRenderImageHost.h
@@ -45,32 +45,37 @@ public:
   virtual TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) override;
 
   virtual void Attach(Layer* aLayer,
                       TextureSourceProvider* aProvider,
                       AttachFlags aFlags = NO_FLAGS) override;
 
   virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider) override;
 
-  gfx::IntSize GetImageSize() const override;
+  gfx::IntSize GetImageSize() override;
 
   virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override;
 
   virtual void Dump(std::stringstream& aStream,
                     const char* aPrefix = "",
                     bool aDumpHtml = false) override;
 
   virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override;
 
   virtual bool Lock() override;
 
   virtual void Unlock() override;
 
   virtual void CleanupResources() override;
 
+  uint32_t GetDroppedFrames() override
+  {
+    return GetDroppedFramesAndReset();
+  }
+
   virtual WebRenderImageHost* AsWebRenderImageHost() override { return this; }
 
   TextureHost* GetAsTextureHostForComposite();
 
   void SetWrBridge(WebRenderBridgeParent* aWrBridge);
 
   void ClearWrBridge();
 
--- a/gfx/skia/skia/src/utils/SkDashPath.cpp
+++ b/gfx/skia/skia/src/utils/SkDashPath.cpp
@@ -370,22 +370,23 @@ bool SkDashPath::InternalFilter(SkPath* 
     const SkPath* srcPtr = &src;
     if (cull_path(src, *rec, cullRect, intervalLength, &cullPathStorage)) {
         // if rect is closed, starts in a dash, and ends in a dash, add the initial join
         // potentially a better fix is described here: bug.skia.org/7445
         if (src.isRect(nullptr) && src.isLastContourClosed() && is_even(initialDashIndex)) {
             SkScalar pathLength = SkPathMeasure(src, false, rec->getResScale()).getLength();
             SkScalar endPhase = SkScalarMod(pathLength + initialDashLength, intervalLength);
             int index = 0;
-            while (endPhase > intervals[index]) {
-                endPhase -= intervals[index++];
+            SkScalar sum = 0;
+            while (endPhase > sum + intervals[index]) {
+                sum += intervals[index++];
                 SkASSERT(index <= count);
             }
             // if dash ends inside "on", or ends at beginning of "off"
-            if (is_even(index) == (endPhase > 0)) {
+            if (is_even(index) == (endPhase > sum)) {
                 SkPoint midPoint = src.getPoint(0);
                 // get vector at end of rect
                 int last = src.countPoints() - 1;
                 while (midPoint == src.getPoint(last)) {
                     --last;
                     SkASSERT(last >= 0);
                 }
                 // get vector at start of rect
--- a/gfx/tests/mochitest/mochitest.ini
+++ b/gfx/tests/mochitest/mochitest.ini
@@ -1,9 +1,9 @@
 [DEFAULT]
 
 [test_acceleration.html]
-skip-if = (os == 'win' && os_version == '10.0' && !debug) # Bug 1430530
+skip-if = (os == 'win') # Bug 1430530
 subsuite = gpu
 [test_bug509244.html]
 [test_bug513439.html]
 [test_font_whitelist.html]
 skip-if = (os == "win" && asan) # Bug 1458364
--- a/gfx/thebes/gfxFontMissingGlyphs.cpp
+++ b/gfx/thebes/gfxFontMissingGlyphs.cpp
@@ -152,17 +152,16 @@ public:
         if (!aManager->HasUserData(&sWRUserDataKey)) {
             aManager->SetUserData(&sWRUserDataKey, new WRUserData(aManager));
         }
     }
 
     void
     Remove()
     {
-        remove();
         mManager->RemoveUserData(&sWRUserDataKey);
     }
 
     layers::WebRenderLayerManager* mManager;
 
     static UserDataKey sWRUserDataKey;
 };
 
@@ -227,18 +226,21 @@ PurgeWRGlyphAtlas()
                     (uint32_t)(uintptr_t)gWRGlyphAtlas[i]->GetUserData(
                         reinterpret_cast<UserDataKey*>(manager));
                 if (handle) {
                     manager->AddImageKeyForDiscard(
                         wr::ImageKey{manager->WrBridge()->GetNamespace(), handle});
                 }
             }
         }
-        // Remove the layer manager's destroy notification.
-        user->Remove();
+    }
+    // Remove the layer managers' destroy notifications only after processing
+    // so as not to mess up gWRUsers iteration.
+    while (!gWRUsers.isEmpty()) {
+        gWRUsers.popFirst()->Remove();
     }
     // Finally, clear out the atlases.
     for (size_t i = 0; i < 8; i++) {
         gWRGlyphAtlas[i] = nullptr;
     }
 }
 
 WRUserData::WRUserData(layers::WebRenderLayerManager* aManager)
--- a/ipc/glue/BackgroundChildImpl.cpp
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -415,16 +415,17 @@ BackgroundChildImpl::AllocPCamerasChild(
 
 bool
 BackgroundChildImpl::DeallocPCamerasChild(camera::PCamerasChild *aActor)
 {
 #ifdef MOZ_WEBRTC
   RefPtr<camera::CamerasChild> child =
       dont_AddRef(static_cast<camera::CamerasChild*>(aActor));
   MOZ_ASSERT(aActor);
+  camera::Shutdown();
 #endif
   return true;
 }
 
 // -----------------------------------------------------------------------------
 // ServiceWorkerManager
 // -----------------------------------------------------------------------------
 
--- a/js/src/builtin/intl/NumberFormat.cpp
+++ b/js/src/builtin/intl/NumberFormat.cpp
@@ -30,16 +30,17 @@
 #include "vm/JSObject-inl.h"
 
 using namespace js;
 
 using mozilla::AssertedCast;
 using mozilla::IsFinite;
 using mozilla::IsNaN;
 using mozilla::IsNegative;
+using mozilla::SpecificNaN;
 
 using js::intl::CallICU;
 using js::intl::DateTimeFormatOptions;
 using js::intl::GetAvailableLocales;
 using js::intl::IcuLocale;
 
 using JS::AutoStableStringChars;
 
@@ -371,30 +372,36 @@ NewUNumberFormat(JSContext* cx, Handle<N
     }
     unum_setAttribute(nf, UNUM_GROUPING_USED, uUseGrouping);
     unum_setAttribute(nf, UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP);
 
     return toClose.forget();
 }
 
 static JSString*
-PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, double x,
+PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, double* x,
                        UFieldPositionIterator* fpositer)
 {
-    return CallICU(cx, [nf, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
-        return unum_formatDoubleForFields(nf, x, chars, size, fpositer, status);
+    // ICU incorrectly formats NaN values with the sign bit set, as if they
+    // were negative.  Replace all NaNs with a single pattern with sign bit
+    // unset ("positive", that is) until ICU is fixed.
+    if (MOZ_UNLIKELY(IsNaN(*x)))
+        *x = SpecificNaN<double>(0, 1);
+
+    return CallICU(cx, [nf, d = *x, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
+        return unum_formatDoubleForFields(nf, d, chars, size, fpositer, status);
     });
 }
 
 static bool
 intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result)
 {
     // Passing null for |fpositer| will just not compute partition information,
     // letting us common up all ICU number-formatting code.
-    JSString* str = PartitionNumberPattern(cx, nf, x, nullptr);
+    JSString* str = PartitionNumberPattern(cx, nf, &x, nullptr);
     if (!str)
         return false;
 
     result.setString(str);
     return true;
 }
 
 using FieldType = ImmutablePropertyNamePtr JSAtomState::*;
@@ -423,16 +430,21 @@ GetFieldTypeForNumberField(UNumberFormat
 
       case UNUM_FRACTION_FIELD:
         return &JSAtomState::fraction;
 
       case UNUM_SIGN_FIELD: {
         // Manual trawling through the ICU call graph appears to indicate that
         // the basic formatting we request will never include a positive sign.
         // But this analysis may be mistaken, so don't absolutely trust it.
+        MOZ_ASSERT(!IsNaN(d),
+                   "ICU appearing not to produce positive-sign among fields, "
+                   "plus our coercing all NaNs to one with sign bit unset "
+                   "(i.e. \"positive\"), means we shouldn't reach here with a "
+                   "NaN value");
         return IsNegative(d) ? &JSAtomState::minusSign : &JSAtomState::plusSign;
       }
 
       case UNUM_PERCENT_FIELD:
         return &JSAtomState::percentSign;
 
       case UNUM_CURRENCY_FIELD:
         return &JSAtomState::currency;
@@ -473,17 +485,17 @@ intl_FormatNumberToParts(JSContext* cx, 
     if (U_FAILURE(status)) {
         intl::ReportInternalError(cx);
         return false;
     }
 
     MOZ_ASSERT(fpositer);
     ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(fpositer);
 
-    RootedString overallResult(cx, PartitionNumberPattern(cx, nf, x, fpositer));
+    RootedString overallResult(cx, PartitionNumberPattern(cx, nf, &x, fpositer));
     if (!overallResult)
         return false;
 
     RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
     if (!partsArray)
         return false;
 
     // First, vacuum up fields in the overall formatted string.
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -146,18 +146,19 @@ GCRuntime::tryNewNurseryString(JSContext
 
     Cell* cell = cx->nursery().allocateString(cx->zone(), thingSize, kind);
     if (cell)
         return static_cast<JSString*>(cell);
 
     if (allowGC && !cx->suppressGC) {
         cx->runtime()->gc.minorGC(JS::gcreason::OUT_OF_NURSERY);
 
-        // Exceeding gcMaxBytes while tenuring can disable the Nursery.
-        if (cx->nursery().isEnabled())
+        // Exceeding gcMaxBytes while tenuring can disable the Nursery, and
+        // other heuristics can disable nursery strings for this zone.
+        if (cx->nursery().isEnabled() && cx->zone()->allocNurseryStrings)
             return static_cast<JSString*>(cx->nursery().allocateString(cx->zone(), thingSize, kind));
     }
     return nullptr;
 }
 
 template <typename StringAllocT, AllowGC allowGC /* = CanGC */>
 StringAllocT*
 js::AllocateString(JSContext* cx, InitialHeap heap)
--- a/js/src/gc/WeakMap-inl.h
+++ b/js/src/gc/WeakMap-inl.h
@@ -20,34 +20,34 @@ static T extractUnbarriered(const WriteB
     return v.get();
 }
 template <typename T>
 static T* extractUnbarriered(T* v)
 {
     return v;
 }
 
-template <class K, class V, class HP>
-WeakMap<K, V, HP>::WeakMap(JSContext* cx, JSObject* memOf)
+template <class K, class V>
+WeakMap<K, V>::WeakMap(JSContext* cx, JSObject* memOf)
   : Base(cx->zone()), WeakMapBase(memOf, cx->zone())
 {
     zone()->gcWeakMapList().insertFront(this);
     marked = JS::IsIncrementalGCInProgress(TlsContext.get());
 }
 
 // Trace a WeakMap entry based on 'markedCell' getting marked, where 'origKey'
 // is the key in the weakmap. These will probably be the same, but can be
 // different eg when markedCell is a delegate for origKey.
 //
 // This implementation does not use 'markedCell'; it looks up origKey and checks
 // the mark bits on everything it cares about, one of which will be
 // markedCell. But a subclass might use it to optimize the liveness check.
-template <class K, class V, class HP>
+template <class K, class V>
 void
-WeakMap<K, V, HP>::markEntry(GCMarker* marker, gc::Cell* markedCell, JS::GCCellPtr origKey)
+WeakMap<K, V>::markEntry(GCMarker* marker, gc::Cell* markedCell, JS::GCCellPtr origKey)
 {
     MOZ_ASSERT(marked);
 
     // If this cast fails, then you're instantiating the WeakMap with a
     // Lookup that can't be constructed from a Cell*. The WeakKeyTable
     // mechanism is indexed with a GCCellPtr, so that won't work.
     Ptr p = Base::lookup(static_cast<Lookup>(origKey.asCell()));
     MOZ_ASSERT(p.found());
@@ -59,19 +59,19 @@ WeakMap<K, V, HP>::markEntry(GCMarker* m
     } else if (keyNeedsMark(key)) {
         TraceEdge(marker, &p->value(), "WeakMap ephemeron value");
         TraceEdge(marker, &key, "proxy-preserved WeakMap ephemeron key");
         MOZ_ASSERT(key == p->key()); // No moving
     }
     key.unsafeSet(nullptr); // Prevent destructor from running barriers.
 }
 
-template <class K, class V, class HP>
+template <class K, class V>
 void
-WeakMap<K, V, HP>::trace(JSTracer* trc)
+WeakMap<K, V>::trace(JSTracer* trc)
 {
     MOZ_ASSERT_IF(JS::RuntimeHeapIsBusy(), isInList());
 
     TraceNullableEdge(trc, &memberOf, "WeakMap owner");
 
     if (trc->isMarkingTracer()) {
         MOZ_ASSERT(trc->weakMapAction() == ExpandWeakMaps);
         marked = true;
@@ -89,19 +89,19 @@ WeakMap<K, V, HP>::trace(JSTracer* trc)
     }
 
     // Always trace all values (unless weakMapAction() is
     // DoNotTraceWeakMaps).
     for (Range r = Base::all(); !r.empty(); r.popFront())
         TraceEdge(trc, &r.front().value(), "WeakMap entry value");
 }
 
-template <class K, class V, class HP>
+template <class K, class V>
 /* static */ void
-WeakMap<K, V, HP>::addWeakEntry(GCMarker* marker, JS::GCCellPtr key,
+WeakMap<K, V>::addWeakEntry(GCMarker* marker, JS::GCCellPtr key,
                                 const gc::WeakMarkable& markable)
 {
     Zone* zone = key.asCell()->asTenured().zone();
 
     auto p = zone->gcWeakKeys().get(key);
     if (p) {
         gc::WeakEntryVector& weakEntries = p->value;
         if (!weakEntries.append(markable))
@@ -109,19 +109,19 @@ WeakMap<K, V, HP>::addWeakEntry(GCMarker
     } else {
         gc::WeakEntryVector weakEntries;
         MOZ_ALWAYS_TRUE(weakEntries.append(markable));
         if (!zone->gcWeakKeys().put(JS::GCCellPtr(key), std::move(weakEntries)))
             marker->abortLinearWeakMarking();
     }
 }
 
-template <class K, class V, class HP>
+template <class K, class V>
 bool
-WeakMap<K, V, HP>::markIteratively(GCMarker* marker)
+WeakMap<K, V>::markIteratively(GCMarker* marker)
 {
     MOZ_ASSERT(marked);
 
     bool markedAny = false;
 
     for (Enum e(*this); !e.empty(); e.popFront()) {
         // If the entry is live, ensure its key and value are marked.
         bool keyIsMarked = gc::IsMarked(marker->runtime(), &e.front().mutableKey());
@@ -147,110 +147,112 @@ WeakMap<K, V, HP>::markIteratively(GCMar
             if (JSObject* delegate = getDelegate(e.front().key()))
                 addWeakEntry(marker, JS::GCCellPtr(delegate), markable);
         }
     }
 
     return markedAny;
 }
 
-template <class K, class V, class HP>
+template <class K, class V>
 inline JSObject*
-WeakMap<K, V, HP>::getDelegate(JSObject* key) const
+WeakMap<K, V>::getDelegate(JSObject* key) const
 {
+    JS::AutoSuppressGCAnalysis nogc;
+
     JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp();
     if (!op)
         return nullptr;
 
     JSObject* obj = op(key);
     if (!obj)
         return nullptr;
 
     MOZ_ASSERT(obj->runtimeFromMainThread() == zone()->runtimeFromMainThread());
     return obj;
 }
 
-template <class K, class V, class HP>
+template <class K, class V>
 inline JSObject*
-WeakMap<K, V, HP>::getDelegate(JSScript* script) const
+WeakMap<K, V>::getDelegate(JSScript* script) const
 {
     return nullptr;
 }
 
-template <class K, class V, class HP>
+template <class K, class V>
 inline JSObject*
-WeakMap<K, V, HP>::getDelegate(LazyScript* script) const
+WeakMap<K, V>::getDelegate(LazyScript* script) const
 {
     return nullptr;
 }
 
-template <class K, class V, class HP>
+template <class K, class V>
 inline bool
-WeakMap<K, V, HP>::keyNeedsMark(JSObject* key) const
+WeakMap<K, V>::keyNeedsMark(JSObject* key) const
 {
     JSObject* delegate = getDelegate(key);
     /*
      * Check if the delegate is marked with any color to properly handle
      * gray marking when the key's delegate is black and the map is gray.
      */
     return delegate && gc::IsMarkedUnbarriered(zone()->runtimeFromMainThread(), &delegate);
 }
 
-template <class K, class V, class HP>
+template <class K, class V>
 inline bool
-WeakMap<K, V, HP>::keyNeedsMark(JSScript* script) const
+WeakMap<K, V>::keyNeedsMark(JSScript* script) const
 {
     return false;
 }
 
-template <class K, class V, class HP>
+template <class K, class V>
 inline bool
-WeakMap<K, V, HP>::keyNeedsMark(LazyScript* script) const
+WeakMap<K, V>::keyNeedsMark(LazyScript* script) const
 {
     return false;
 }
 
 
-template <class K, class V, class HP>
+template <class K, class V>
 void
-WeakMap<K, V, HP>::sweep()
+WeakMap<K, V>::sweep()
 {
     /* Remove all entries whose keys remain unmarked. */
     for (Enum e(*this); !e.empty(); e.popFront()) {
         if (gc::IsAboutToBeFinalized(&e.front().mutableKey()))
             e.removeFront();
     }
 
 #if DEBUG
     // Once we've swept, all remaining edges should stay within the known-live
     // part of the graph.
     assertEntriesNotAboutToBeFinalized();
 #endif
 }
 
 /* memberOf can be nullptr, which means that the map is not part of a JSObject. */
-template <class K, class V, class HP>
+template <class K, class V>
 void
-WeakMap<K, V, HP>::traceMappings(WeakMapTracer* tracer)
+WeakMap<K, V>::traceMappings(WeakMapTracer* tracer)
 {
     for (Range r = Base::all(); !r.empty(); r.popFront()) {
         gc::Cell* key = gc::ToMarkable(r.front().key());
         gc::Cell* value = gc::ToMarkable(r.front().value());
         if (key && value) {
             tracer->trace(memberOf,
                           JS::GCCellPtr(r.front().key().get()),
                           JS::GCCellPtr(r.front().value().get()));
         }
     }
 }
 
 #if DEBUG
-template <class K, class V, class HP>
+template <class K, class V>
 void
-WeakMap<K, V, HP>::assertEntriesNotAboutToBeFinalized()
+WeakMap<K, V>::assertEntriesNotAboutToBeFinalized()
 {
     for (Range r = Base::all(); !r.empty(); r.popFront()) {
         K k(r.front().key());
         MOZ_ASSERT(!gc::IsAboutToBeFinalized(&k));
         MOZ_ASSERT(!gc::IsAboutToBeFinalized(&r.front().value()));
         MOZ_ASSERT(k == r.front().key());
     }
 }
--- a/js/src/gc/WeakMap.h
+++ b/js/src/gc/WeakMap.h
@@ -106,23 +106,22 @@ class WeakMapBase : public mozilla::Link
 
     // Zone containing this weak map.
     JS::Zone* zone_;
 
     // Whether this object has been traced during garbage collection.
     bool marked;
 };
 
-template <class Key, class Value,
-          class HashPolicy = DefaultHasher<Key> >
-class WeakMap : public HashMap<Key, Value, HashPolicy, ZoneAllocPolicy>,
+template <class Key, class Value>
+class WeakMap : public HashMap<Key, Value, MovableCellHasher<Key>, ZoneAllocPolicy>,
                 public WeakMapBase
 {
   public:
-    typedef HashMap<Key, Value, HashPolicy, ZoneAllocPolicy> Base;
+    typedef HashMap<Key, Value, MovableCellHasher<Key>, ZoneAllocPolicy> Base;
     typedef typename Base::Enum Enum;
     typedef typename Base::Lookup Lookup;
     typedef typename Base::Entry Entry;
     typedef typename Base::Range Range;
     typedef typename Base::Ptr Ptr;
     typedef typename Base::AddPtr AddPtr;
 
     explicit WeakMap(JSContext* cx, JSObject* memOf = nullptr);
@@ -186,23 +185,21 @@ class WeakMap : public HashMap<Key, Valu
 
   protected:
 #if DEBUG
     void assertEntriesNotAboutToBeFinalized();
 #endif
 };
 
 
-class ObjectValueMap : public WeakMap<HeapPtr<JSObject*>, HeapPtr<Value>,
-                                      MovableCellHasher<HeapPtr<JSObject*>>>
+class ObjectValueMap : public WeakMap<HeapPtr<JSObject*>, HeapPtr<Value>>
 {
   public:
     ObjectValueMap(JSContext* cx, JSObject* obj)
-      : WeakMap<HeapPtr<JSObject*>, HeapPtr<Value>,
-                MovableCellHasher<HeapPtr<JSObject*>>>(cx, obj)
+      : WeakMap(cx, obj)
     {}
 
     bool findZoneEdges() override;
 };
 
 
 // Generic weak map for mapping objects to other objects.
 class ObjectWeakMap
--- a/js/src/gc/WeakMapPtr.cpp
+++ b/js/src/gc/WeakMapPtr.cpp
@@ -36,19 +36,18 @@ struct DataType<JS::Value>
     using BarrieredType = HeapPtr<Value>;
     static JS::Value NullValue() { return JS::UndefinedValue(); }
 };
 
 template <typename K, typename V>
 struct Utils
 {
     typedef typename DataType<K>::BarrieredType KeyType;
-    typedef typename DataType<K>::HasherType HasherType;
     typedef typename DataType<V>::BarrieredType ValueType;
-    typedef WeakMap<KeyType, ValueType, HasherType> Type;
+    typedef WeakMap<KeyType, ValueType> Type;
     typedef Type* PtrType;
     static PtrType cast(void* ptr) { return static_cast<PtrType>(ptr); }
 };
 
 } /* WeakMapDetails */
 
 template <typename K, typename V>
 void
--- a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-03.js
+++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-03.js
@@ -1,24 +1,24 @@
-// If debugger.onEnterFrame returns {return:val}, the frame returns immediately.
+// If debugger.onEnterFrame returns null, the debuggee is terminated immediately.
 
 load(libdir + "asserts.js");
 
 var g = newGlobal();
 g.set = false;
 
 var dbg = Debugger(g);
 var savedFrame;
 dbg.onDebuggerStatement = function (frame) {
     var innerSavedFrame;
     dbg.onEnterFrame = function (frame) {
         innerSavedFrame = frame;
         return null;
     };
-    // Using frame.eval lets us catch termination.  
+    // Using frame.eval lets us catch termination.
     assertEq(frame.eval("set = true;"), null);
     assertEq(innerSavedFrame.live, false);
     savedFrame = frame;
     return { return: "pass" };
 };
 
 savedFrame = undefined;
 assertEq(g.eval("debugger;"), "pass");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-live-06.js
@@ -0,0 +1,26 @@
+// frame.live is false for generator frames after they return.
+
+let g = newGlobal();
+g.eval("function* f() { debugger; }");
+
+let dbg = Debugger(g);
+let savedFrame;
+
+dbg.onDebuggerStatement = frame => {
+    savedFrame = frame;
+    assertEq(frame.callee.name, "f");
+    assertEq(frame.live, true);
+    frame.onPop = function() {
+        assertEq(frame.live, true);
+    };
+};
+g.f().next();
+
+assertEq(savedFrame.live, false);
+try {
+    savedFrame.older;
+    throw new Error("expected exception, none thrown");
+} catch (exc) {
+    assertEq(exc.message, "Debugger.Frame is not live");
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-live-07.js
@@ -0,0 +1,53 @@
+// frame.live is false for generator frames popped due to exception or termination.
+
+load(libdir + "/asserts.js");
+
+function test(when, what) {
+    let g = newGlobal();
+    g.eval("function* f(x) { yield x; }");
+
+    let dbg = new Debugger;
+    let gw = dbg.addDebuggee(g);
+    let fw = gw.getOwnPropertyDescriptor("f").value;
+
+    let t = 0;
+    let poppedFrame;
+
+    function tick(frame) {
+        if (frame.callee == fw) {
+            if (t == when) {
+                poppedFrame = frame;
+                dbg.onEnterFrame = undefined;
+                frame.onPop = undefined;
+                return what;
+            }
+            t++;
+        }
+        return undefined;
+    }
+
+    dbg.onDebuggerStatement = frame => {
+        dbg.onEnterFrame = frame => {
+            frame.onPop = function() {
+                return tick(this);
+            };
+            return tick(frame);
+        };
+        let result = frame.eval("for (let _ of f(0)) {}");
+        assertDeepEq(result, what);
+    };
+    g.eval("debugger;");
+
+    assertEq(t, when);
+    assertEq(poppedFrame.live, false);
+    assertErrorMessage(() => poppedFrame.older,
+                       Error,
+                       "Debugger.Frame is not live");
+}
+
+for (let when = 0; when < 6; when++) {
+    for (let what of [null, {throw: "fit"}]) {
+        test(when, what);
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-04.js
@@ -0,0 +1,26 @@
+// Terminating a generator from the onPop callback for its initial yield
+// leaves the Frame in a sane but inactive state.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal();
+g.eval("function* f(x) { yield x; }");
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+let genFrame = null;
+dbg.onDebuggerStatement = frame => {
+    dbg.onEnterFrame = frame => {
+        if (frame.callee == gw.getOwnPropertyDescriptor("f").value) {
+            genFrame = frame;
+            frame.onPop = completion => null;
+        }
+    };
+    assertEq(frame.eval("f(0);"), null);
+};
+
+g.eval("debugger;");
+
+assertEq(genFrame instanceof Debugger.Frame, true);
+assertEq(genFrame.live, false);
+assertThrowsInstanceOf(() => genFrame.callee, Error);
deleted file mode 100644
--- a/js/src/jit-test/tests/debug/Frame-onPop-star-generators-01.js
+++ /dev/null
@@ -1,20 +0,0 @@
-// Returning {throw:} from an onPop handler when yielding works and
-// closes the generator-iterator.
-
-load(libdir + "iteration.js");
-
-var g = newGlobal();
-var dbg = new Debugger;
-var gw = dbg.addDebuggee(g);
-dbg.onDebuggerStatement = function handleDebugger(frame) {
-    frame.onPop = function (c) {
-        return {throw: "fit"};
-    };
-};
-g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
-g.eval("var it = g();");
-var rv = gw.executeInGlobal("it.next();");
-assertEq(rv.throw, "fit");
-
-dbg.enabled = false;
-assertIteratorDone(g.it);
deleted file mode 100644
--- a/js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// |jit-test| error: fit
-
-// Throwing an exception from an onPop handler when yielding terminates the debuggee
-// but does not close the generator-iterator.
-
-load(libdir + 'iteration.js')
-
-var g = newGlobal();
-var dbg = new Debugger;
-var gw = dbg.addDebuggee(g);
-dbg.onDebuggerStatement = function handleDebugger(frame) {
-    frame.onPop = function (c) {
-        throw "fit";
-    };
-};
-g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
-g.eval("var it = g();");
-assertEq(gw.executeInGlobal("it.next();"), null);
-
-dbg.enabled = false;
-assertIteratorNext(g.it, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-01.js
@@ -0,0 +1,31 @@
+// Stepping into the `.next()` method of a generator works as expected.
+
+let g = newGlobal();
+g.eval(`\
+function* nums() {      // line 1
+    yield 1;            //  2
+    yield 2;            //  3
+}                       //  4
+function f() {          //  5
+    let gen = nums();   //  6
+    gen.next();         //  7
+    gen.next();         //  8
+    gen.next();         //  9
+}                       // 10
+`);
+
+let log = [];
+let previousLine = -1;
+let dbg = new Debugger(g);
+dbg.onEnterFrame = frame => {
+    frame.onStep = () => {
+        let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
+        if (previousLine != line) { // We stepped to a new line.
+            log.push(line);
+            previousLine = line;
+        }
+    };
+};
+
+g.f();
+assertEq(log.join(" "), "5 6 1 6 7 1 2 7 8 2 3 8 9 3 9 10");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-02.js
@@ -0,0 +1,44 @@
+// Stepping into the `.throw()` method of a generator with no relevant catch block.
+//
+// The debugger fires onEnterFrame and then frame.onPop for the generator frame when
+// `gen.throw()` is called.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal();
+g.eval(`\
+function* z() {         // line 1
+    yield 1;            // 2
+    yield 2;            // 3
+}                       // 4
+function f() {          // 5
+    let gen = z();      // 6
+    gen.next();         // 7
+    gen.throw("fit");   // 8
+}                       // 9
+`);
+
+let log = "";
+let previousLine = -1;
+let dbg = new Debugger(g);
+dbg.onEnterFrame = frame => {
+    log += frame.callee.name + "{";
+    frame.onStep = () => {
+        let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
+        if (previousLine != line) { // We stepped to a new line.
+            log += line;
+            previousLine = line;
+        }
+    };
+    frame.onPop = completion => {
+        if ("throw" in completion)
+            log += "!";
+        log += "}";
+    }
+};
+
+assertThrowsValue(() => g.f(), "fit");
+// z{1} is the initial generator setup.
+// z{12} is the first .next() call, running to `yield 1` on line 2
+// The final `z{!}` is for the .throw() call.
+assertEq(log, "f{56z{1}67z{12}78z{!}!}");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-03.js
@@ -0,0 +1,45 @@
+// Stepping into the `.throw()` method of a generator with a relevant catch block.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal();
+g.eval(`\
+function* z() {         // line 1
+    try {               // 2
+        yield 1;        // 3
+    } catch (exc) {     // 4
+        yield 2;        // 5
+    }                   // 6
+}                       // 7
+function f() {          // 8
+    let gen = z();      // 9
+    gen.next();         // 10
+    gen.throw("fit");   // 11
+}                       // 12
+`);
+
+let log = [];
+let previousLine = -1;
+let dbg = new Debugger(g);
+dbg.onEnterFrame = frame => {
+    log.push(frame.callee.name + " in");
+    frame.onStep = () => {
+        let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
+        if (previousLine != line) { // We stepped to a new line.
+            log.push(line);
+            previousLine = line;
+        }
+    };
+    frame.onPop = completion => {
+        log.push(frame.callee.name + " out");
+    };
+};
+
+g.f();
+assertEq(
+    log.join(", "),
+    "f in, 8, 9, z in, 1, z out, " +
+    "9, 10, z in, 1, 2, 3, z out, " +
+    "10, 11, z in, 2, 4, 5, z out, " +  // not sure why we hit line 2 here, source notes bug maybe
+    "11, 12, f out"
+);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generators-defaults.js
@@ -0,0 +1,33 @@
+// onStep works during the evaluation of default parameter values in generators.
+//
+// (They're evaluated at a weird time in the generator life cycle, before the
+// generator object is created.)
+
+let g = newGlobal();
+g.eval(`\
+    function f1() {}        // line 1
+    function f2() {}        //  2
+    function f3() {}        //  3
+                            //  4
+    function* gen(          //  5
+        name,               //  6
+        schema = f1(),      //  7
+        timeToLive = f2(),  //  8
+        lucidity = f3()     //  9
+    ) {                     // 10
+    }                       // 11
+`);
+
+let dbg = Debugger(g);
+let log = [];
+dbg.onEnterFrame = frame => {
+    frame.onStep = () => {
+        let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
+        if (log.length == 0 || line != log[log.length - 1]) {
+            log.push(line);
+        }
+    };
+};
+
+g.gen(0);
+assertEq(log.toSource(), [5, 7, 1, 8, 2, 9, 3, 10].toSource());
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/breakpoint-oom-01.js
@@ -0,0 +1,42 @@
+// Test for OOM hitting a breakpoint in a generator.
+//
+// (The purpose is to test OOM-handling in the code that creates the
+// Debugger.Frame object and associates it with the generator object.)
+
+if (!('oomTest' in this))
+    quit();
+
+let g = newGlobal();
+g.eval(`\
+    function* gen(x) {  // line 1
+        x++;            // 2
+        yield x;        // 3
+    }                   // 4
+`);
+
+let dbg = new Debugger;
+
+// On OOM in the debugger, propagate it to the debuggee.
+dbg.uncaughtExceptionHook = exc => exc === "out of memory" ? {throw: exc} : null;
+
+let gw = dbg.addDebuggee(g);
+let script = gw.makeDebuggeeValue(g.gen).script;
+let hits = 0;
+let handler = {
+    hit(frame) {
+        hits++;
+        print("x=", frame.environment.getVariable("x"));
+    }
+};
+for (let offset of script.getLineOffsets(2))
+    script.setBreakpoint(offset, handler);
+
+let result;
+oomTest(() => {
+    hits = 0;
+    result = g.gen(1).next();
+}, false);
+assertEq(hits, 1);
+assertEq(result.done, false);
+assertEq(result.value, 2);
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1479429.js
@@ -0,0 +1,15 @@
+// Bug 1479429 - Methods throw on out-of-range bytecode offsets.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal();
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function(frame) {
+    assertThrowsInstanceOf(
+        () => frame.script.getPredecessorOffsets(0x400000),
+        TypeError);
+    assertThrowsInstanceOf(
+        () => frame.script.getSuccessorOffsets(-1),
+        TypeError);
+}
+g.eval("debugger;");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-async-generator-resumption-01.js
@@ -0,0 +1,60 @@
+// A Debugger can {return:} from onDebuggerStatement in an async generator.
+// A resolved promise for a {value: _, done: true} object is returned.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal();
+g.eval(`
+    async function* f(x) {
+        debugger;  // when==0 to force return here
+        await x;
+        yield 1;
+        debugger;  // when==1 to force return here
+    }
+`);
+
+let exc = null;
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+function test(when) {
+    let hits = 0;
+    let outcome = "FAIL";
+    dbg.onDebuggerStatement = frame => {
+        if (hits++ == when)
+            return {return: "ponies"};
+    };
+
+    let iter = g.f(0);
+
+    // At the initial suspend.
+    assertEq(hits, 0);
+    iter.next().then(result => {
+        // At the yield point, unless we already force-returned from the first
+        // debugger statement.
+        assertEq(hits, 1);
+        if (when == 0)
+            return result;
+        assertEq(result.value, 1);
+        assertEq(result.done, false);
+        return iter.next();
+    }).then(result => {
+        // After forced return.
+        assertEq(hits, when + 1);
+        assertEq(result.value, "ponies");
+        assertEq(result.done, true);
+        outcome = "pass";
+    }).catch(e => {
+        // An assertion failed.
+        exc = e;
+    });
+
+    assertEq(hits, 1);
+    drainJobQueue();
+    if (exc !== null)
+        throw exc;
+    assertEq(outcome, "pass");
+}
+
+for (let i = 0; i < 2; i++) {
+    test(i);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onDebuggerStatement-async-resumption-01.js
@@ -0,0 +1,34 @@
+// A Debugger can {return:} from onDebuggerStatement in an async function.
+// The async function's promise is resolved with the returned value.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal();
+g.eval(`
+    async function f(x) {
+        debugger;  // when==0 to force return here
+        await x;
+        debugger;  // when==1 to force return here
+    }
+`);
+
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+function test(when, what, expected) {
+    let hits = 0;
+    let result = "FAIL";
+    dbg.onDebuggerStatement = frame => {
+        if (hits++ == when)
+            return {return: gw.makeDebuggeeValue(what)};
+    };
+    g.f(0).then(x => { result = x; });
+    assertEq(hits, 1);
+    drainJobQueue();
+    assertEq(hits, when + 1);
+    assertEq(result, expected);
+}
+
+for (let i = 0; i < 2; i++) {
+    test(i, "ok", "ok");
+    test(i, g.Promise.resolve(37), 37);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-01.js
@@ -0,0 +1,33 @@
+// A Debugger can {return:} from the first onEnterFrame for an async function.
+// (The exact behavior is undocumented; we're testing that it doesn't crash.)
+
+let g = newGlobal();
+g.hit2 = false;
+g.eval(`async function f(x) { await x; return "ponies"; }`);
+
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+let hits = 0;
+let resumption = undefined;
+dbg.onEnterFrame = frame => {
+    if (frame.type == "call" && frame.callee.name === "f") {
+        frame.onPop = completion => {
+            assertEq(completion.return, resumption.return);
+            hits++;
+        };
+
+        // Don't tell anyone, but if we force-return a generator object here,
+        // the robots accept it as one of their own and plug it right into the
+        // async function machinery. This may be handy against Skynet someday.
+        resumption = frame.eval(`(function* f2() { hit2 = true; throw "fit"; })()`);
+        assertEq(resumption.return.class, "Generator");
+        return resumption;
+    }
+};
+
+let p = g.f(0);
+assertEq(hits, 1);
+let pw = gw.makeDebuggeeValue(p);
+assertEq(pw.isPromise, true);
+assertEq(pw.promiseState, "rejected");
+assertEq(pw.promiseReason, "fit");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-02.js
@@ -0,0 +1,36 @@
+// A Debugger can {throw:} from onEnterFrame in an async function.
+// The resulting promise (if any) is rejected with the thrown error value.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal();
+g.eval(`
+    async function f() { await 1; }
+    var err = new TypeError("object too hairy");
+`);
+
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+let errw = gw.makeDebuggeeValue(g.err);
+
+// Repeat the test for each onEnterFrame event.
+// It fires up to three times:
+// - when the async function g.f is called;
+// - when we enter it to run to `await 1`;
+// - when we resume after the await to run to the end.
+for (let when = 0; when < 3; when++) {
+    let hits = 0;
+    dbg.onEnterFrame = frame => {
+        return hits++ < when ? undefined : {throw: errw};
+    };
+
+    let result = undefined;
+    g.f()
+        .then(value => { result = {returned: value}; })
+        .catch(err => { result = {threw: err}; });
+
+    drainJobQueue();
+
+    assertEq(hits, when + 1);
+    assertDeepEq(result, {threw: g.err});
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-03.js
@@ -0,0 +1,30 @@
+// A Debugger can {return:} from onEnterFrame at any resume point in an async function.
+// The async function's promise is resolved with the returned value.
+
+let g = newGlobal();
+g.eval(`async function f(x) { await x; }`);
+
+let dbg = new Debugger(g);
+function test(when) {
+    let hits = 0;
+    dbg.onEnterFrame = frame => {
+        if (frame.type == "call" && frame.callee.name === "f") {
+            if (hits++ == when) {
+                return {return: "exit"};
+            }
+        }
+    };
+
+    let result = undefined;
+    let finished = false;
+    g.f("hello").then(value => { result = value; finished = true; });
+    drainJobQueue();
+    assertEq(finished, true);
+    assertEq(hits, when + 1);
+    assertEq(result, "exit");
+}
+
+// onEnterFrame with hits==0 is not a resume point; {return:} behaves differently there
+// (see onEnterFrame-async-resumption-02.js).
+test(1);  // force return from first resume point, immediately after the initial suspend
+test(2);  // force return from second resume point, immediately after the await instruction
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-01.js
@@ -0,0 +1,81 @@
+// Frame properties and methods work in generator-resuming onEnterFrame events.
+// Also tests onPop events, for good measure.
+
+let g = newGlobal();
+g.eval(`\
+    function* gen(lo, hi) {
+        var a = 1/2;
+        yield a;
+        yield a * a;
+    }
+`);
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+let hits = 0;
+let savedScript = null;
+let savedEnv = null;
+let savedOffsets = new Set;
+
+function check(frame) {
+    assertEq(frame.type, "call");
+    assertEq(frame.constructing, false);
+    assertEq(frame.callee, gw.makeDebuggeeValue(g.gen));
+
+    // `arguments` elements don't work in resumed generator frames,
+    // because generators don't keep the arguments around.
+    // The first onEnterFrame and onPop events can see them.
+    assertEq(frame.arguments.length, hits < 2 ? args.length : 0);
+    for (var i = 0; i < frame.arguments.length; i++) {
+        assertEq(frame.arguments.hasOwnProperty(i), true);
+
+        if (hits < 2)
+            assertEq(frame.arguments[i], gw.makeDebuggeeValue(args[i]), `arguments[${i}]`);
+        else
+            assertEq(frame.arguments[i], undefined);
+    }
+
+    if (savedEnv === null) {
+        savedEnv = frame.environment;
+        assertEq(savedScript, null);
+        savedScript = frame.script;
+    } else {
+        assertEq(frame.environment, savedEnv);
+        assertEq(frame.script, savedScript);
+    }
+    let a_expected = hits < 3 ? undefined : 1/2;
+    assertEq(savedEnv.getVariable("a"), a_expected);
+
+    assertEq(frame.generator, true);
+    assertEq(frame.live, true);
+
+    let pc = frame.offset;
+    assertEq(savedOffsets.has(pc), false);
+    savedOffsets.add(pc);
+
+    assertEq(frame.older, null);
+    assertEq(frame.this, gw);
+    assertEq(typeof frame.implementation, "string");
+
+    // And the moment of truth:
+    assertEq(frame.eval("2 + 2").return, 4);
+    assertEq(frame.eval("a").return, a_expected);
+    assertEq(frame.eval("if (a !== undefined) { assertEq(a < (lo + hi) / 2, true); } 7;").return, 7);
+}
+
+dbg.onEnterFrame = frame => {
+    if (frame.type === "eval")
+        return;
+    check(frame);
+    hits++;
+    frame.onPop = completion => {
+        check(frame);
+        hits++;
+    };
+};
+
+// g.gen ignores the arguments passed to it, but we use them to test
+// frame.arguments.
+let args = [0, 10, g, dbg];
+for (let v of g.gen(...args)) {}
+assertEq(hits, 8);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-02.js
@@ -0,0 +1,27 @@
+// onEnterFrame fires after the [[GeneratorState]] is set to "executing".
+//
+// This test checks that Debugger doesn't accidentally make it possible to
+// reenter a generator frame that's already on the stack. (Also tests a fun
+// corner case in baseline debug-mode OSR.)
+
+load(libdir + "asserts.js");
+
+let g = newGlobal();
+g.eval('function* f() { yield 1; yield 2; }');
+let dbg = Debugger(g);
+let genObj = null;
+let hits = 0;
+dbg.onEnterFrame = frame => {
+    // The first time onEnterFrame fires, there is no generator object, so
+    // there's nothing to test. The generator object doesn't exist until
+    // JSOP_GENERATOR is reached, right before the initial yield.
+    if (genObj !== null) {
+        dbg.removeDebuggee(g);  // avoid the DebuggeeWouldRun exception
+        assertThrowsInstanceOf(() => genObj.next(), g.TypeError);
+        dbg.addDebuggee(g);
+        hits++;
+    }
+};
+genObj = g.f();
+for (let x of genObj) {}
+assertEq(hits, 3);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-03.js
@@ -0,0 +1,25 @@
+// If onEnterFrame terminates a generator, the Frame is left in a sane but inactive state.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal();
+g.eval("function* f(x) { yield x; }");
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+let genFrame = null;
+dbg.onDebuggerStatement = frame => {
+    dbg.onEnterFrame = frame => {
+        if (frame.callee == gw.getOwnPropertyDescriptor("f").value) {
+            genFrame = frame;
+            return null;
+        }
+    };
+    assertEq(frame.eval("f(0);"), null);
+};
+
+g.eval("debugger;");
+
+assertEq(genFrame instanceof Debugger.Frame, true);
+assertEq(genFrame.live, false);
+assertThrowsInstanceOf(() => genFrame.callee, Error);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-01.js
@@ -0,0 +1,36 @@
+// A debugger can {throw:} from onEnterFrame at any resume point in a generator.
+// It closes the generator.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal();
+g.eval(`
+    function* f() { yield 1; }
+    var exn = new TypeError("object too hairy");
+`);
+
+let dbg = new Debugger;
+let gw = dbg.addDebuggee(g);
+
+// Repeat the test for each onEnterFrame event.
+// It fires up to three times:
+// - when the generator g.f is called;
+// - when we enter it to run to `yield 1`;
+// - when we resume after the yield to run to the end.
+for (let i = 0; i < 3; i++) {
+    let hits = 0;
+    dbg.onEnterFrame = frame => {
+        return hits++ < i ? undefined : {throw: gw.makeDebuggeeValue(g.exn)};
+    };
+    let genObj;
+    assertThrowsValue(
+        () => {
+            genObj = g.f();
+            for (let x of genObj) {}
+        },
+        g.exn
+    );
+    assertEq(hits, i + 1);
+    if (hits > 1)
+        assertEq(genObj.next().done, true);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-02.js
@@ -0,0 +1,39 @@
+// A Debugger can {return:} from onEnterFrame at any resume point in a generator.
+// Force-returning closes the generator.
+
+load(libdir + "asserts.js");
+
+let g = newGlobal();
+g.values = [1, 2, 3];
+g.eval(`function* f() { yield* values; }`);
+
+let dbg = Debugger(g);
+
+// onEnterFrame will fire up to 5 times.
+// - once for the initial call to g.f();
+// - four times at resume points:
+//   - initial resume at the top of the generator body
+//   - resume after yielding 1
+//   - resume after yielding 2
+//   - resume after yielding 3 (this resumption will run to the end).
+// This test ignores the initial call and focuses on resume points.
+for (let i = 1; i < 5; i++) {
+    let hits = 0;
+    dbg.onEnterFrame = frame => {
+        return hits++ < i ? undefined : {return: "we're done here"};
+    };
+
+    let genObj = g.f();
+    let actual = [];
+    while (true) {
+        let r = genObj.next();
+        if (r.done) {
+            assertDeepEq(r, {value: "we're done here", done: true});
+            break;
+        }
+        actual.push(r.value);
+    }
+    assertEq(hits, i + 1);
+    assertDeepEq(actual, g.values.slice(0, i - 1));
+    assertDeepEq(genObj.next(), {value: undefined, done: true});
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-generator-resumption-03.js
@@ -0,0 +1,35 @@
+// Returning {throw:} from onEnterFrame when resuming inside a try block in a
+// generator causes control to jump to the catch block.
+
+let g = newGlobal();
+g.eval(`
+    function* gen() {
+        try {
+            yield 0;
+            return "fail";
+        } catch (exc) {
+            assertEq(exc, "fit");
+            return "ok";
+        }
+    }
+`)
+
+let dbg = new Debugger(g);
+let hits = 0;
+dbg.onEnterFrame = frame => {
+    assertEq(frame.callee.name, "gen");
+    if (++hits == 3) {
+        // First hit is when calling gen();
+        // second hit is resuming at the implicit initial yield;
+        // third hit is resuming inside the try block.
+        return {throw: "fit"};
+    }
+};
+
+let it = g.gen();
+let result = it.next();
+assertEq(result.done, false);
+assertEq(result.value, 0);
+result = it.next();
+assertEq(result.done, true);
+assertEq(result.value, "ok");
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -4642,31 +4642,44 @@ BaselineCompiler::emit_JSOP_YIELD()
 }
 
 bool
 BaselineCompiler::emit_JSOP_AWAIT()
 {
     return emit_JSOP_YIELD();
 }
 
-typedef bool (*DebugAfterYieldFn)(JSContext*, BaselineFrame*);
+typedef bool (*DebugAfterYieldFn)(JSContext*, BaselineFrame*, jsbytecode*, bool*);
 static const VMFunction DebugAfterYieldInfo =
     FunctionInfo<DebugAfterYieldFn>(jit::DebugAfterYield, "DebugAfterYield");
 
 bool
 BaselineCompiler::emit_JSOP_DEBUGAFTERYIELD()
 {
     if (!compileDebugInstrumentation_)
         return true;
 
     frame.assertSyncedStack();
     masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
     prepareVMCall();
+    pushArg(ImmPtr(pc));
     pushArg(R0.scratchReg());
-    return callVM(DebugAfterYieldInfo);
+    if (!callVM(DebugAfterYieldInfo))
+        return false;
+
+    icEntries_.back().setFakeKind(ICEntry::Kind_DebugAfterYield);
+
+    Label done;
+    masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &done);
+    {
+        masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand);
+        masm.jump(&return_);
+    }
+    masm.bind(&done);
+    return true;
 }
 
 typedef bool (*FinalSuspendFn)(JSContext*, HandleObject, jsbytecode*);
 static const VMFunction FinalSuspendInfo =
     FunctionInfo<FinalSuspendFn>(jit::FinalSuspend, "FinalSuspend");
 
 bool
 BaselineCompiler::emit_JSOP_FINALYIELDRVAL()
@@ -4688,17 +4701,17 @@ BaselineCompiler::emit_JSOP_FINALYIELDRV
 
 typedef bool (*InterpretResumeFn)(JSContext*, HandleObject, HandleValue, HandlePropertyName,
                                   MutableHandleValue);
 static const VMFunction InterpretResumeInfo =
     FunctionInfo<InterpretResumeFn>(jit::InterpretResume, "InterpretResume");
 
 typedef bool (*GeneratorThrowFn)(JSContext*, BaselineFrame*, Handle<GeneratorObject*>,
                                  HandleValue, uint32_t);
-static const VMFunction GeneratorThrowInfo =
+static const VMFunction GeneratorThrowOrReturnInfo =
     FunctionInfo<GeneratorThrowFn>(jit::GeneratorThrowOrReturn, "GeneratorThrowOrReturn", TailCall);
 
 bool
 BaselineCompiler::emit_JSOP_RESUME()
 {
     GeneratorObject::ResumeKind resumeKind = GeneratorObject::getResumeKind(pc);
 
     frame.syncStack(0);
@@ -4873,17 +4886,17 @@ BaselineCompiler::emit_JSOP_RESUME()
         masm.loadBaselineFramePtr(BaselineFrameReg, scratch2);
 
         prepareVMCall();
         pushArg(Imm32(resumeKind));
         pushArg(retVal);
         pushArg(genObj);
         pushArg(scratch2);
 
-        TrampolinePtr code = cx->runtime()->jitRuntime()->getVMWrapper(GeneratorThrowInfo);
+        TrampolinePtr code = cx->runtime()->jitRuntime()->getVMWrapper(GeneratorThrowOrReturnInfo);
 
         // Create the frame descriptor.
         masm.subStackPtrFrom(scratch1);
         masm.makeFrameDescriptor(scratch1, JitFrame_BaselineJS, ExitFrameLayout::Size());
 
         // Push the frame descriptor and a dummy return address (it doesn't
         // matter what we push here, frame iterators will use the frame pc
         // set in jit::GeneratorThrowOrReturn).
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -98,16 +98,17 @@ struct DebugModeOSREntry
 
     bool needsRecompileInfo() const {
         return frameKind == ICEntry::Kind_CallVM ||
                frameKind == ICEntry::Kind_WarmupCounter ||
                frameKind == ICEntry::Kind_StackCheck ||
                frameKind == ICEntry::Kind_EarlyStackCheck ||
                frameKind == ICEntry::Kind_DebugTrap ||
                frameKind == ICEntry::Kind_DebugPrologue ||
+               frameKind == ICEntry::Kind_DebugAfterYield ||
                frameKind == ICEntry::Kind_DebugEpilogue;
     }
 
     bool recompiled() const {
         return oldBaselineScript != script->baselineScript();
     }
 
     BaselineDebugModeOSRInfo* takeRecompInfo() {
@@ -302,16 +303,18 @@ ICEntryKindToString(ICEntry::Kind kind)
       case ICEntry::Kind_StackCheck:
         return "stack check";
       case ICEntry::Kind_EarlyStackCheck:
         return "early stack check";
       case ICEntry::Kind_DebugTrap:
         return "debug trap";
       case ICEntry::Kind_DebugPrologue:
         return "debug prologue";
+      case ICEntry::Kind_DebugAfterYield:
+        return "debug after yield";
       case ICEntry::Kind_DebugEpilogue:
         return "debug epilogue";
       default:
         MOZ_CRASH("bad ICEntry kind");
     }
 }
 #endif // JS_JITSPEW
 
@@ -362,16 +365,17 @@ PatchBaselineFramesForDebugMode(JSContex
     //  H. From inside HandleExceptionBaseline.
     //  I. From inside the interrupt handler via the prologue stack check.
     //  J. From the warmup counter in the prologue.
     //
     // On to Off:
     //  - All the ways above.
     //  C. From the debug trap handler.
     //  D. From the debug prologue.
+    //  K. From a JSOP_DEBUGAFTERYIELD instruction.
     //  E. From the debug epilogue.
     //
     // Cycles (On to Off to On)+ or (Off to On to Off)+:
     //  F. Undo cases B, C, D, E, I or J above on previously patched yet unpopped
     //     frames.
     //
     // In general, we patch the return address from the VM call to return to a
     // "continuation fixer" to fix up machine state (registers and stack
@@ -465,16 +469,17 @@ PatchBaselineFramesForDebugMode(JSContex
                 MOZ_ASSERT(info->pc == pc);
                 MOZ_ASSERT(info->frameKind == kind);
                 MOZ_ASSERT(kind == ICEntry::Kind_CallVM ||
                            kind == ICEntry::Kind_WarmupCounter ||
                            kind == ICEntry::Kind_StackCheck ||
                            kind == ICEntry::Kind_EarlyStackCheck ||
                            kind == ICEntry::Kind_DebugTrap ||
                            kind == ICEntry::Kind_DebugPrologue ||
+                           kind == ICEntry::Kind_DebugAfterYield ||
                            kind == ICEntry::Kind_DebugEpilogue);
 
                 // We will have allocated a new recompile info, so delete the
                 // existing one.
                 frame.baselineFrame()->deleteDebugModeOSRInfo();
             }
 
             // The RecompileInfo must already be allocated so that this
@@ -541,16 +546,27 @@ PatchBaselineFramesForDebugMode(JSContex
                 // Case D above.
                 //
                 // We patch a jump directly to the right place in the prologue
                 // after popping the frame reg and checking for forced return.
                 recompInfo->resumeAddr = bl->postDebugPrologueAddr();
                 popFrameReg = true;
                 break;
 
+              case ICEntry::Kind_DebugAfterYield:
+                // Case K above.
+                //
+                // Resume at the next instruction.
+                MOZ_ASSERT(*pc == JSOP_DEBUGAFTERYIELD);
+                recompInfo->resumeAddr = bl->nativeCodeForPC(script,
+                                                             pc + JSOP_DEBUGAFTERYIELD_LENGTH,
+                                                             &recompInfo->slotInfo);
+                popFrameReg = true;
+                break;
+
               default:
                 // Case E above.
                 //
                 // We patch a jump directly to the epilogue after popping the
                 // frame reg and checking for forced return.
                 MOZ_ASSERT(kind == ICEntry::Kind_DebugEpilogue);
                 recompInfo->resumeAddr = bl->epilogueEntryAddr();
                 popFrameReg = true;
@@ -940,19 +956,19 @@ HasForcedReturn(BaselineDebugModeOSRInfo
 {
     ICEntry::Kind kind = info->frameKind;
 
     // The debug epilogue always checks its resumption value, so we don't need
     // to check rv.
     if (kind == ICEntry::Kind_DebugEpilogue)
         return true;
 
-    // |rv| is the value in ReturnReg. If true, in the case of the prologue,
-    // it means a forced return.
-    if (kind == ICEntry::Kind_DebugPrologue)
+    // |rv| is the value in ReturnReg. If true, in the case of the prologue or
+    // after yield, it means a forced return.
+    if (kind == ICEntry::Kind_DebugPrologue || kind == ICEntry::Kind_DebugAfterYield)
         return rv;
 
     // N.B. The debug trap handler handles its own forced return, so no
     // need to deal with it here.
     return false;
 }
 
 static inline bool
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -8696,17 +8696,18 @@ JitRealm::generateStringConcatStub(JSCon
     }
     masm.bind(&notInline);
 
     // Keep AND'ed flags in temp1.
 
     // Ensure result length <= JSString::MAX_LENGTH.
     masm.branch32(Assembler::Above, temp2, Imm32(JSString::MAX_LENGTH), &failure);
 
-    // Allocate a new rope, guaranteed to be in the nursery.
+    // Allocate a new rope, guaranteed to be in the nursery if
+    // stringsCanBeInNursery. (As a result, no post barriers are needed below.)
     masm.newGCString(output, temp3, &failure, stringsCanBeInNursery);
 
     // Store rope length and flags. temp1 still holds the result of AND'ing the
     // lhs and rhs flags, so we just have to clear the other flags and set
     // NON_ATOM_BIT to get our rope flags (Latin1 if both lhs and rhs are
     // Latin1).
     static_assert(JSString::INIT_ROPE_FLAGS == JSString::NON_ATOM_BIT,
                   "Rope type flags must be NON_ATOM_BIT only");
--- a/js/src/jit/SharedIC.h
+++ b/js/src/jit/SharedIC.h
@@ -251,18 +251,19 @@ class ICEntry
 
         // As above, but for the early check. See emitStackCheck.
         Kind_EarlyStackCheck,
 
         // A fake IC entry for returning from DebugTrapHandler.
         Kind_DebugTrap,
 
         // A fake IC entry for returning from a callVM to
-        // Debug{Prologue,Epilogue}.
+        // Debug{Prologue,AfterYield,Epilogue}.
         Kind_DebugPrologue,
+        Kind_DebugAfterYield,
         Kind_DebugEpilogue,
 
         Kind_Invalid
     };
 
   private:
     // What this IC is for.
     Kind kind_ : 4;
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -952,37 +952,54 @@ InterpretResume(JSContext* cx, HandleObj
     args[1].set(val);
     args[2].setString(kind);
 
     return CallSelfHostedFunction(cx, cx->names().InterpretGeneratorResume, UndefinedHandleValue,
                                   args, rval);
 }
 
 bool
-DebugAfterYield(JSContext* cx, BaselineFrame* frame)
+DebugAfterYield(JSContext* cx, BaselineFrame* frame, jsbytecode* pc, bool* mustReturn)
 {
+    *mustReturn = false;
+
     // The BaselineFrame has just been constructed by JSOP_RESUME in the
     // caller. We need to set its debuggee flag as necessary.
-    if (frame->script()->isDebuggee())
+    //
+    // If a breakpoint is set on JSOP_DEBUGAFTERYIELD, or stepping is enabled,
+    // we may already have done this work. Don't fire onEnterFrame again.
+    if (frame->script()->isDebuggee() && !frame->isDebuggee()) {
         frame->setIsDebuggee();
+        return DebugPrologue(cx, frame, pc, mustReturn);
+    }
     return true;
 }
 
 bool
 GeneratorThrowOrReturn(JSContext* cx, BaselineFrame* frame, Handle<GeneratorObject*> genObj,
                        HandleValue arg, uint32_t resumeKind)
 {
     // Set the frame's pc to the current resume pc, so that frame iterators
     // work. This function always returns false, so we're guaranteed to enter
     // the exception handler where we will clear the pc.
     JSScript* script = frame->script();
     uint32_t offset = script->yieldAndAwaitOffsets()[genObj->yieldAndAwaitIndex()];
-    frame->setOverridePc(script->offsetToPC(offset));
+    jsbytecode* pc = script->offsetToPC(offset);
+    frame->setOverridePc(pc);
+
+    // In the interpreter, GeneratorObject::resume marks the generator as running,
+    // so we do the same.
+    genObj->setRunning();
 
-    MOZ_ALWAYS_TRUE(DebugAfterYield(cx, frame));
+    bool mustReturn = false;
+    if (!DebugAfterYield(cx, frame, pc, &mustReturn))
+        return false;
+    if (mustReturn)
+        resumeKind = GeneratorObject::RETURN;
+
     MOZ_ALWAYS_FALSE(js::GeneratorThrowOrReturn(cx, frame, genObj, arg, resumeKind));
     return false;
 }
 
 bool
 CheckGlobalOrEvalDeclarationConflicts(JSContext* cx, BaselineFrame* frame)
 {
     RootedScript script(cx, frame->script());
@@ -1082,21 +1099,25 @@ bool
 HandleDebugTrap(JSContext* cx, BaselineFrame* frame, uint8_t* retAddr, bool* mustReturn)
 {
     *mustReturn = false;
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = script->baselineScript()->icEntryFromReturnAddress(retAddr).pc(script);
 
     if (*pc == JSOP_DEBUGAFTERYIELD) {
-        // JSOP_DEBUGAFTERYIELD will set the frame's debuggee flag, but if we
-        // set a breakpoint there we have to do it now.
+        // JSOP_DEBUGAFTERYIELD will set the frame's debuggee flag and call the
+        // onEnterFrame handler, but if we set a breakpoint there we have to do
+        // it now.
         MOZ_ASSERT(!frame->isDebuggee());
-        if (!DebugAfterYield(cx, frame))
+
+        if (!DebugAfterYield(cx, frame, pc, mustReturn))
             return false;
+        if (*mustReturn)
+            return true;
     }
 
     MOZ_ASSERT(frame->isDebuggee());
     MOZ_ASSERT(script->stepModeEnabled() || script->hasBreakpointsAt(pc));
 
     RootedValue rval(cx);
     ResumeMode resumeMode = ResumeMode::Continue;
 
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -780,17 +780,17 @@ MOZ_MUST_USE bool
 NormalSuspend(JSContext* cx, HandleObject obj, BaselineFrame* frame, jsbytecode* pc,
               uint32_t stackDepth);
 MOZ_MUST_USE bool
 FinalSuspend(JSContext* cx, HandleObject obj, jsbytecode* pc);
 MOZ_MUST_USE bool
 InterpretResume(JSContext* cx, HandleObject obj, HandleValue val, HandlePropertyName kind,
                 MutableHandleValue rval);
 MOZ_MUST_USE bool
-DebugAfterYield(JSContext* cx, BaselineFrame* frame);
+DebugAfterYield(JSContext* cx, BaselineFrame* frame, jsbytecode* pc, bool* mustReturn);
 MOZ_MUST_USE bool
 GeneratorThrowOrReturn(JSContext* cx, BaselineFrame* frame, Handle<GeneratorObject*> genObj,
                        HandleValue arg, uint32_t resumeKind);
 
 MOZ_MUST_USE bool
 GlobalNameConflictsCheckFromIon(JSContext* cx, HandleScript script);
 MOZ_MUST_USE bool
 CheckGlobalOrEvalDeclarationConflicts(JSContext* cx, BaselineFrame* frame);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4472,17 +4472,20 @@ BinParse(JSContext* cx, unsigned argc, V
             JS::Rooted<JSLinearString*> linearFormat(cx);
             linearFormat = stringFormat->ensureLinear(cx);
             if (StringEqualsAscii(linearFormat, "multipart")) {
                 useMultipart = true;
             } else if (StringEqualsAscii(linearFormat, "simple")) {
                 useMultipart = false;
             } else {
                 JSAutoByteString printable;
-                JS_ReportErrorASCII(cx, "Unknown value for option `format`, expected 'multipart' or 'simple', got %s", ValueToPrintableUTF8(cx, optionFormat, &printable));
+                JS_ReportErrorUTF8(cx,
+                                   "Unknown value for option `format`, expected 'multipart' or "
+                                   "'simple', got %s",
+                                   ValueToPrintableUTF8(cx, optionFormat, &printable));
                 return false;
             }
         } else {
             const char* typeName = InformalValueTypeName(optionFormat);
             JS_ReportErrorASCII(cx, "option `format` should be a string, got %s", typeName);
             return false;
         }
     }
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Intl/NumberFormat/formatting-NaN.js
@@ -0,0 +1,35 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1484943;
+var summary = "Don't crash doing format/formatToParts on -NaN";
+
+print(BUGNUMBER + ": " + summary);
+
+//-----------------------------------------------------------------------------
+
+assertEq("formatToParts" in Intl.NumberFormat(), true);
+
+var nf = new Intl.NumberFormat("en-US");
+var parts;
+
+var values = [NaN, -NaN];
+
+for (var v of values)
+{
+  assertEq(nf.format(v), "NaN");
+
+  parts = nf.formatToParts(v);
+  assertEq(parts.length, 1);
+  assertEq(parts[0].type, "nan");
+  assertEq(parts[0].value, "NaN");
+}
+
+//-----------------------------------------------------------------------------
+
+if (typeof reportCompare === "function")
+  reportCompare(0, 0, 'ok');
+
+print("Tests complete");
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -1812,16 +1812,25 @@ Debugger::fireEnterFrame(JSContext* cx, 
     MOZ_ASSERT(hook->isCallable());
 
     Maybe<AutoRealm> ar;
     ar.emplace(cx, object);
 
     RootedValue scriptFrame(cx);
 
     FrameIter iter(cx);
+
+#if DEBUG
+    // Assert that the hook won't be able to re-enter the generator.
+    if (iter.hasScript() && *iter.pc() == JSOP_DEBUGAFTERYIELD) {
+        GeneratorObject* genObj = GetGeneratorObjectForFrame(cx, iter.abstractFramePtr());
+        MOZ_ASSERT(genObj->isRunning() || genObj->isClosing());
+    }
+#endif
+
     if (!getFrame(cx, iter, &scriptFrame))
         return reportUncaughtException(ar);
 
     RootedValue fval(cx, ObjectValue(*hook));
     RootedValue rv(cx);
     bool ok = js::Call(cx, fval, object, scriptFrame, &rv);
 
     return processHandlerResult(ar, ok, rv, iter.abstractFramePtr(), iter.pc(), vp);
@@ -6090,18 +6099,23 @@ class DebuggerScriptGetSuccessorOrPredec
     bool successor_;
     MutableHandleObject result_;
 
   public:
     DebuggerScriptGetSuccessorOrPredecessorOffsetsMatcher(JSContext* cx, size_t offset,
                                                           bool successor,
                                                           MutableHandleObject result)
       : cx_(cx), offset_(offset), successor_(successor), result_(result) { }
+
     using ReturnType = bool;
+
     ReturnType match(HandleScript script) {
+        if (!EnsureScriptOffsetIsValid(cx_, script, offset_))
+            return false;
+
         PcVector adjacent;
         if (successor_) {
             if (!GetSuccessorBytecodes(script->code() + offset_, adjacent)) {
                 ReportOutOfMemory(cx_);
                 return false;
             }
         } else {
             if (!GetPredecessorBytecodes(script, script->code() + offset_, adjacent)) {
@@ -6115,22 +6129,24 @@ class DebuggerScriptGetSuccessorOrPredec
             return false;
 
         for (jsbytecode* pc : adjacent) {
             if (!NewbornArrayPush(cx_, result_, NumberValue(pc - script->code())))
                 return false;
         }
         return true;
     }
+
     ReturnType match(Handle<LazyScript*> lazyScript) {
         RootedScript script(cx_, DelazifyScript(cx_, lazyScript));
         if (!script)
             return false;
         return match(script);
     }
+
     ReturnType match(Handle<WasmInstanceObject*> instance) {
         JS_ReportErrorASCII(cx_, "getSuccessorOrPredecessorOffsets NYI on wasm instances");
         return false;
     }
 };
 
 static bool
 DebuggerScript_getSuccessorOrPredecessorOffsets(JSContext* cx, unsigned argc, Value* vp,
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -120,33 +120,32 @@ CheckDebuggeeThing(JSObject* obj, bool i
  * created.
  *
  * Also note that keys in these weakmaps can be in any compartment, debuggee or
  * not, because they cannot be deleted when a compartment is no longer a
  * debuggee: the values need to maintain object identity across add/remove/add
  * transitions.
  */
 template <class UnbarrieredKey, bool InvisibleKeysOk=false>
-class DebuggerWeakMap : private WeakMap<HeapPtr<UnbarrieredKey>, HeapPtr<JSObject*>,
-                                        MovableCellHasher<HeapPtr<UnbarrieredKey>>>
+class DebuggerWeakMap : private WeakMap<HeapPtr<UnbarrieredKey>, HeapPtr<JSObject*>>
 {
   private:
     typedef HeapPtr<UnbarrieredKey> Key;
     typedef HeapPtr<JSObject*> Value;
 
     typedef HashMap<JS::Zone*,
                     uintptr_t,
                     DefaultHasher<JS::Zone*>,
                     ZoneAllocPolicy> CountMap;
 
     CountMap zoneCounts;
     JS::Compartment* compartment;
 
   public:
-    typedef WeakMap<Key, Value, MovableCellHasher<Key>> Base;
+    typedef WeakMap<Key, Value> Base;
 
     explicit DebuggerWeakMap(JSContext* cx)
         : Base(cx),
           zoneCounts(cx->zone()),
           compartment(cx->compartment())
     { }
 
   public:
--- a/js/src/vm/GeneratorObject.cpp
+++ b/js/src/vm/GeneratorObject.cpp
@@ -129,35 +129,33 @@ js::SetGeneratorClosed(JSContext* cx, Ab
 }
 
 bool
 js::GeneratorThrowOrReturn(JSContext* cx, AbstractFramePtr frame, Handle<GeneratorObject*> genObj,
                            HandleValue arg, uint32_t resumeKind)
 {
     if (resumeKind == GeneratorObject::THROW) {
         cx->setPendingException(arg);
-        genObj->setRunning();
     } else {
         MOZ_ASSERT(resumeKind == GeneratorObject::RETURN);
 
         MOZ_ASSERT(arg.isObject());
         frame.setReturnValue(arg);
 
         RootedValue closing(cx, MagicValue(JS_GENERATOR_CLOSING));
         cx->setPendingException(closing);
         genObj->setClosing();
     }
     return false;
 }
 
 bool
 GeneratorObject::resume(JSContext* cx, InterpreterActivation& activation,
-                        HandleObject obj, HandleValue arg, GeneratorObject::ResumeKind resumeKind)
+                        Handle<GeneratorObject*> genObj, HandleValue arg)
 {
-    Rooted<GeneratorObject*> genObj(cx, &obj->as<GeneratorObject>());
     MOZ_ASSERT(genObj->isSuspended());
 
     RootedFunction callee(cx, &genObj->callee());
     RootedObject envChain(cx, &genObj->environmentChain());
     if (!activation.resumeGeneratorFrame(callee, envChain))
         return false;
     activation.regs().fp()->setResumedGenerator();
 
@@ -179,28 +177,18 @@ GeneratorObject::resume(JSContext* cx, I
 
     // Always push on a value, even if we are raising an exception. In the
     // exception case, the stack needs to have something on it so that exception
     // handling doesn't skip the catch blocks. See TryNoteIter::settle.
     activation.regs().sp++;
     MOZ_ASSERT(activation.regs().spForStackDepth(activation.regs().stackDepth()));
     activation.regs().sp[-1] = arg;
 
-    switch (resumeKind) {
-      case NEXT:
-        genObj->setRunning();
-        return true;
-
-      case THROW:
-      case RETURN:
-        return GeneratorThrowOrReturn(cx, activation.regs().fp(), genObj, arg, resumeKind);
-
-      default:
-        MOZ_CRASH("bad resumeKind");
-    }
+    genObj->setRunning();
+    return true;
 }
 
 const Class GeneratorObject::class_ = {
     "Generator",
     JSCLASS_HAS_RESERVED_SLOTS(GeneratorObject::RESERVED_SLOTS)
 };
 
 static const JSFunctionSpec generator_methods[] = {
--- a/js/src/vm/GeneratorObject.h
+++ b/js/src/vm/GeneratorObject.h
@@ -55,17 +55,17 @@ class GeneratorObject : public NativeObj
             return THROW;
         MOZ_ASSERT(atom == cx->names().return_);
         return RETURN;
     }
 
     static JSObject* create(JSContext* cx, AbstractFramePtr frame);
 
     static bool resume(JSContext* cx, InterpreterActivation& activation,
-                       HandleObject obj, HandleValue arg, ResumeKind resumeKind);
+                       Handle<GeneratorObject*> genObj, HandleValue arg);
 
     static bool initialSuspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame, jsbytecode* pc) {
         return suspend(cx, obj, frame, pc, nullptr, 0);
     }
 
     static bool normalSuspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame, jsbytecode* pc,
                               Value* vp, unsigned nvalues) {
         return suspend(cx, obj, frame, pc, vp, nvalues);
@@ -142,23 +142,27 @@ class GeneratorObject : public NativeObj
                       "test below should return false for YIELD_AND_AWAIT_INDEX_RUNNING");
         return getFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT).toInt32() < YIELD_AND_AWAIT_INDEX_CLOSING;
     }
     void setRunning() {
         MOZ_ASSERT(isSuspended());
         setFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT, Int32Value(YIELD_AND_AWAIT_INDEX_RUNNING));
     }
     void setClosing() {
-        MOZ_ASSERT(isSuspended());
+        MOZ_ASSERT(isRunning());
         setFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT, Int32Value(YIELD_AND_AWAIT_INDEX_CLOSING));
     }
     void setYieldAndAwaitIndex(uint32_t yieldAndAwaitIndex) {
         MOZ_ASSERT_IF(yieldAndAwaitIndex == 0,
                       getFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT).isUndefined());
         MOZ_ASSERT_IF(yieldAndAwaitIndex != 0, isRunning() || isClosing());
+        setYieldAndAwaitIndexNoAssert(yieldAndAwaitIndex);
+    }
+    // Debugger has to flout the state machine rules a bit.
+    void setYieldAndAwaitIndexNoAssert(uint32_t yieldAndAwaitIndex) {
         MOZ_ASSERT(yieldAndAwaitIndex < uint32_t(YIELD_AND_AWAIT_INDEX_CLOSING));
         setFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT, Int32Value(yieldAndAwaitIndex));
         MOZ_ASSERT(isSuspended());
     }
     uint32_t yieldAndAwaitIndex() const {
         MOZ_ASSERT(isSuspended());
         return getFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT).toInt32();
     }
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -4262,36 +4262,60 @@ CASE(JSOP_AWAIT)
     POP_RETURN_VALUE();
 
     goto successful_return_continuation;
 }
 
 CASE(JSOP_RESUME)
 {
     {
-        ReservedRooted<JSObject*> gen(&rootObject0, &REGS.sp[-2].toObject());
+        Rooted<GeneratorObject*> gen(cx, &REGS.sp[-2].toObject().as<GeneratorObject>());
         ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]);
         // popInlineFrame expects there to be an additional value on the stack
         // to pop off, so leave "gen" on the stack.
 
         GeneratorObject::ResumeKind resumeKind = GeneratorObject::getResumeKind(REGS.pc);
-        bool ok = GeneratorObject::resume(cx, activation, gen, val, resumeKind);
+        if (!GeneratorObject::resume(cx, activation, gen, val))
+            goto error;
 
         JSScript* generatorScript = REGS.fp()->script();
         if (cx->realm() != generatorScript->realm())
             cx->enterRealmOf(generatorScript);
         SET_SCRIPT(generatorScript);
 
         TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
         TraceLoggerEvent scriptEvent(TraceLogger_Scripts, script);
         TraceLogStartEvent(logger, scriptEvent);
         TraceLogStartEvent(logger, TraceLogger_Interpreter);
 
-        if (!ok)
+        switch (Debugger::onEnterFrame(cx, REGS.fp())) {
+          case ResumeMode::Continue:
+            break;
+          case ResumeMode::Throw:
+          case ResumeMode::Terminate:
             goto error;
+          case ResumeMode::Return:
+            MOZ_ASSERT_IF(REGS.fp()->callee().isGenerator(),  // as opposed to an async function
+                          gen->isClosed());
+            if (!ForcedReturn(cx, REGS))
+                goto error;
+            goto successful_return_continuation;
+        }
+
+        switch (resumeKind) {
+          case GeneratorObject::NEXT:
+            break;
+          case GeneratorObject::THROW:
+          case GeneratorObject::RETURN:
+            MOZ_ALWAYS_FALSE(GeneratorThrowOrReturn(cx, activation.regs().fp(), gen, val,
+                                                    resumeKind));
+            goto error;
+          default:
+            MOZ_CRASH("bad resumeKind");
+        }
     }
     ADVANCE_AND_DISPATCH(0);
 }
 
 CASE(JSOP_DEBUGAFTERYIELD)
 {
     // No-op in the interpreter, as GeneratorObject::resume takes care of
     // fixing up InterpreterFrames.
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -2154,17 +2154,17 @@ class FrameIter
     size_t      numFrameSlots() const;
     Value       frameSlotValue(size_t index) const;
 
     // Ensures that we have rematerialized the top frame and its associated
     // inline frames. Can only be called when isIon().
     bool ensureHasRematerializedFrame(JSContext* cx);
 
     // True when isInterp() or isBaseline(). True when isIon() if it
-    // has a rematerialized frame. False otherwise false otherwise.
+    // has a rematerialized frame. False otherwise.
     bool hasUsableAbstractFramePtr() const;
 
     // -----------------------------------------------------------
     // The following functions can only be called when isInterp(),
     // isBaseline(), isWasm() or isIon(). Further, abstractFramePtr() can
     // only be called when hasUsableAbstractFramePtr().
     // -----------------------------------------------------------
 
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -322,17 +322,16 @@ struct SCOutput {
     JS::StructuredCloneScope scope() const { return buf.scope(); }
 
     MOZ_MUST_USE bool write(uint64_t u);
     MOZ_MUST_USE bool writePair(uint32_t tag, uint32_t data);
     MOZ_MUST_USE bool writeDouble(double d);
     MOZ_MUST_USE bool writeBytes(const void* p, size_t nbytes);
     MOZ_MUST_USE bool writeChars(const Latin1Char* p, size_t nchars);
     MOZ_MUST_USE bool writeChars(const char16_t* p, size_t nchars);
-    MOZ_MUST_USE bool writePtr(const void*);
 
     template <class T>
     MOZ_MUST_USE bool writeArray(const T* p, size_t nbytes);
 
     void setCallbacks(const JSStructuredCloneCallbacks* callbacks,
                       void* closure,
                       OwnTransferablePolicy policy)
     {
@@ -834,20 +833,21 @@ SCInput::getPtr(uint64_t data, void** pt
     // No endianness conversion is used for pointers, since they are not sent
     // across address spaces anyway.
     *ptr = reinterpret_cast<void*>(data);
 }
 
 bool
 SCInput::readPtr(void** p)
 {
+    // See endianness comment in getPtr, above.
     uint64_t u;
     if (!readNativeEndian(&u))
         return false;
-    *p = reinterpret_cast<void*>(NativeEndian::swapFromLittleEndian(u));
+    *p = reinterpret_cast<void*>(u);
     return true;
 }
 
 SCOutput::SCOutput(JSContext* cx, JS::StructuredCloneScope scope)
   : cx(cx), buf(scope)
 {
 }
 
@@ -948,22 +948,16 @@ SCOutput::writeChars(const char16_t* p, 
 
 bool
 SCOutput::writeChars(const Latin1Char* p, size_t nchars)
 {
     static_assert(sizeof(Latin1Char) == sizeof(uint8_t), "Latin1Char must fit in 1 byte");
     return writeBytes(p, nchars);
 }
 
-bool
-SCOutput::writePtr(const void* p)
-{
-    return write(reinterpret_cast<uint64_t>(p));
-}
-
 void
 SCOutput::discardTransferables()
 {
     buf.discardTransferables();
 }
 
 } // namespace js
 
@@ -1788,17 +1782,17 @@ JSStructuredCloneWriter::writeTransferMa
             ReportOutOfMemory(context());
             return false;
         }
 
         // Emit a placeholder pointer.  We defer stealing the data until later
         // (and, if necessary, detaching this object if it's an ArrayBuffer).
         if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY, JS::SCTAG_TMO_UNFILLED))
             return false;
-        if (!out.writePtr(nullptr)) // Pointer to ArrayBuffer contents.
+        if (!out.write(0)) // Pointer to ArrayBuffer contents.
             return false;
         if (!out.write(0)) // extraData
             return false;
     }
 
     return true;
 }
 
@@ -1913,17 +1907,17 @@ JSStructuredCloneWriter::transferOwnersh
                 return reportDataCloneError(JS_SCERR_TRANSFERABLE);
             if (!out.buf.callbacks_->writeTransfer(cx, obj, out.buf.closure_, &tag, &ownership, &content, &extraData))
                 return false;
             MOZ_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY);
         }
 
         point.write(NativeEndian::swapToLittleEndian(PairToUInt64(tag, ownership)));
         point.next();
-        point.write(NativeEndian::swapToLittleEndian(reinterpret_cast<uint64_t>(content)));
+        point.write(reinterpret_cast<uint64_t>(content));
         point.next();
         point.write(NativeEndian::swapToLittleEndian(extraData));
         point.next();
     }
 
 #if DEBUG
     // Make sure there aren't any more transfer map entries after the expected
     // number we read out.
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -1082,35 +1082,40 @@ XPCJSContext::Initialize(XPCJSContext* a
                    kStackQuotaMin)
         : kStackQuotaMin;
 #  if defined(MOZ_ASAN)
     // See the standalone MOZ_ASAN branch below for the ASan case.
     const size_t kTrustedScriptBuffer = 450 * 1024;
 #  else
     const size_t kTrustedScriptBuffer = 180 * 1024;
 #  endif
+#elif defined(XP_WIN)
+    // 1MB is the default stack size on Windows. We use the -STACK linker flag
+    // (see WIN32_EXE_LDFLAGS in config/config.mk) to request a larger stack,
+    // so we determine the stack size at runtime.
+    const size_t kStackQuota = GetWindowsStackSize();
+#  if defined(MOZ_ASAN)
+    // See the standalone MOZ_ASAN branch below for the ASan case.
+    const size_t kTrustedScriptBuffer = 450 * 1024;
+#  else
+    const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8) ? 180 * 1024   //win64
+                                                              : 120 * 1024;  //win32
+#  endif
 #elif defined(MOZ_ASAN)
     // ASan requires more stack space due to red-zones, so give it double the
     // default (1MB on 32-bit, 2MB on 64-bit). ASAN stack frame measurements
     // were not taken at the time of this writing, so we hazard a guess that
     // ASAN builds have roughly thrice the stack overhead as normal builds.
     // On normal builds, the largest stack frame size we might encounter is
     // 9.0k (see above), so let's use a buffer of 9.0 * 5 * 10 = 450k.
     //
     // FIXME: Does this branch make sense for Windows and Android?
     // (See bug 1415195)
     const size_t kStackQuota =  2 * kDefaultStackQuota;
     const size_t kTrustedScriptBuffer = 450 * 1024;
-#elif defined(XP_WIN)
-    // 1MB is the default stack size on Windows. We use the -STACK linker flag
-    // (see WIN32_EXE_LDFLAGS in config/config.mk) to request a larger stack,
-    // so we determine the stack size at runtime.
-    const size_t kStackQuota = GetWindowsStackSize();
-    const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8) ? 180 * 1024   //win64
-                                                              : 120 * 1024;  //win32
 #elif defined(ANDROID)
     // Android appears to have 1MB stacks. Allow the use of 3/4 of that size
     // (768KB on 32-bit), since otherwise we can crash with a stack overflow
     // when nearing the 1MB limit.
     const size_t kStackQuota = kDefaultStackQuota + kDefaultStackQuota / 2;
     const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800;
 #else
     // Catch-all configuration for other environments.
--- a/js/xpconnect/src/moz.build
+++ b/js/xpconnect/src/moz.build
@@ -59,11 +59,8 @@ LOCAL_INCLUDES += [
     '/layout/style',
 ]
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-shadow', '-Werror=format']
 
 if CONFIG['NIGHTLY_BUILD']:
     DEFINES['ENABLE_WASM_GC'] = True
-
-if CONFIG['MOZ_ASAN'] and CONFIG['CC_TYPE'] == 'clang-cl':
-    AllowCompilerWarnings()  # workaround for bug 1090497
--- a/media/webrtc/signaling/gtest/moz.build
+++ b/media/webrtc/signaling/gtest/moz.build
@@ -43,11 +43,8 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'uiki
 if CONFIG['CC_TYPE'] in ('msvc', 'clang-cl'):
     # This is intended as a temporary workaround to enable warning free building
     # with VS2015.
     # reinterpret_cast': conversion from 'DWORD' to 'HANDLE' of greater size
     CXXFLAGS += ['-wd4312']
 
     # Disable warning for decorated name length exceeded, name was truncated
     CXXFLAGS += ['-wd4503']
-
-if CONFIG['MOZ_ASAN'] and CONFIG['CC_TYPE'] == 'clang-cl':
-    AllowCompilerWarnings()  # workaround for bug 1306642
--- a/media/webrtc/signaling/src/media-conduit/moz.build
+++ b/media/webrtc/signaling/src/media-conduit/moz.build
@@ -30,11 +30,8 @@ UNIFIED_SOURCES += [
 
 if CONFIG['OS_TARGET'] == 'Android':
     UNIFIED_SOURCES += [
         'MediaCodecVideoCodec.cpp',
         'WebrtcMediaCodecVP8VideoCodec.cpp',
     ]
 
 FINAL_LIBRARY = 'xul'
-
-if CONFIG['MOZ_ASAN'] and CONFIG['CC_TYPE'] == 'clang-cl':
-    AllowCompilerWarnings()  # workaround for bug 1306642
--- a/media/webrtc/signaling/src/mediapipeline/moz.build
+++ b/media/webrtc/signaling/src/mediapipeline/moz.build
@@ -24,11 +24,8 @@ UNIFIED_SOURCES += [
     'MediaPipelineFilter.cpp',
     'RtpLogger.cpp',
     'TransportLayerPacketDumper.cpp',
 ]
 
 DEFINES['TRACING'] = True
 
 FINAL_LIBRARY = 'xul'
-
-if CONFIG['MOZ_ASAN'] and CONFIG['CC_TYPE'] == 'clang-cl':
-    AllowCompilerWarnings()  # workaround for bug 1306642
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -3428,19 +3428,19 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
     MOZ_ASSERT(query->pipelines[p]);
     MOZ_ASSERT(query->pipelines[p]->Conduit());
     if (!query->pipelines[p] || !query->pipelines[p]->Conduit()) {
       // continue if we don't have a valid conduit
       continue;
     }
     const MediaPipeline& mp = *query->pipelines[p];
     bool isAudio = (mp.Conduit()->type() == MediaSessionConduit::AUDIO);
-    nsString mediaType = isAudio ?
+    nsString kind = isAudio ?
         NS_LITERAL_STRING("audio") : NS_LITERAL_STRING("video");
-    nsString idstr = mediaType;
+    nsString idstr = kind;
     idstr.AppendLiteral("_");
     idstr.AppendInt((uint32_t)p);
 
     // TODO(@@NG):ssrcs handle Conduits having multiple stats at the same level
     // This is pending spec work
     // Gather pipeline stats.
     switch (mp.Direction()) {
       case MediaPipeline::DirectionType::TRANSMIT: {
@@ -3467,17 +3467,18 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
                                                   &packetsLost,
                                                   &rtt)) {
             remoteId = NS_LITERAL_STRING("outbound_rtcp_") + idstr;
             RTCInboundRTPStreamStats s;
             s.mTimestamp.Construct(timestamp);
             s.mId.Construct(remoteId);
             s.mType.Construct(RTCStatsType::Inbound_rtp);
             ssrc.apply([&s](uint32_t aSsrc){s.mSsrc.Construct(aSsrc);});
-            s.mMediaType.Construct(mediaType);
+            s.mMediaType.Construct(kind); // mediaType is the old name for kind.
+            s.mKind.Construct(kind);
             s.mJitter.Construct(double(jitterMs)/1000);
             s.mRemoteId.Construct(localId);
             s.mIsRemote = true;
             s.mPacketsReceived.Construct(packetsReceived);
             s.mBytesReceived.Construct(bytesReceived);
             s.mPacketsLost.Construct(packetsLost);
             if (rtt > 0) {
               s.mRoundTripTime.Construct(rtt);
@@ -3488,17 +3489,18 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
         }
         // Then, fill in local side (with cross-link to remote only if present)
         {
           RTCOutboundRTPStreamStats s;
           s.mTimestamp.Construct(query->now);
           s.mId.Construct(localId);
           s.mType.Construct(RTCStatsType::Outbound_rtp);
           ssrc.apply([&s](uint32_t aSsrc){s.mSsrc.Construct(aSsrc);});
-          s.mMediaType.Construct(mediaType);
+          s.mMediaType.Construct(kind); // mediaType is the old name for kind.
+          s.mKind.Construct(kind);
           s.mRemoteId.Construct(remoteId);
           s.mIsRemote = false;
           s.mPacketsSent.Construct(mp.RtpPacketsSent());
           s.mBytesSent.Construct(mp.RtpBytesSent());
 
           // Fill in packet type statistics
           webrtc::RtcpPacketTypeCounter counters;
           if (mp.Conduit()->GetSendPacketTypeStats(&counters)) {
@@ -3553,32 +3555,34 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
           if (mp.Conduit()->GetRTCPSenderReport(&timestamp,
                                                 &packetsSent, &bytesSent)) {
             remoteId = NS_LITERAL_STRING("inbound_rtcp_") + idstr;
             RTCOutboundRTPStreamStats s;
             s.mTimestamp.Construct(timestamp);
             s.mId.Construct(remoteId);
             s.mType.Construct(RTCStatsType::Outbound_rtp);
             ssrc.apply([&s](uint32_t aSsrc){s.mSsrc.Construct(aSsrc);});
-            s.mMediaType.Construct(mediaType);
+            s.mMediaType.Construct(kind); // mediaType is the old name for kind.
+            s.mKind.Construct(kind);
             s.mRemoteId.Construct(localId);
             s.mIsRemote = true;
             s.mPacketsSent.Construct(packetsSent);
             s.mBytesSent.Construct(bytesSent);
             query->report->mOutboundRTPStreamStats.Value().AppendElement(s,
                                                                          fallible);
           }
         }
         // Then, fill in local side (with cross-link to remote only if present)
         RTCInboundRTPStreamStats s;
         s.mTimestamp.Construct(query->now);
         s.mId.Construct(localId);
         s.mType.Construct(RTCStatsType::Inbound_rtp);
         ssrc.apply([&s](uint32_t aSsrc){s.mSsrc.Construct(aSsrc);});
-        s.mMediaType.Construct(mediaType);
+        s.mMediaType.Construct(kind); // mediaType is the old name for kind.
+        s.mKind.Construct(kind);
         unsigned int jitterMs, packetsLost;
         if (mp.Conduit()->GetRTPStats(&jitterMs, &packetsLost)) {
           s.mJitter.Construct(double(jitterMs)/1000);
           s.mPacketsLost.Construct(packetsLost);
         }
         if (remoteId.Length()) {
           s.mRemoteId.Construct(remoteId);
         }
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -248,20 +248,16 @@
 @BINPATH@/actors/*
 
 ; Safe Browsing
 @BINPATH@/components/nsURLClassifier.manifest
 @BINPATH@/components/nsUrlClassifierHashCompleter.js
 @BINPATH@/components/nsUrlClassifierListManager.js
 @BINPATH@/components/nsUrlClassifierLib.js
 
-; Private Browsing
-@BINPATH@/components/PrivateBrowsing.manifest
-@BINPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js
-
 ; Security Reports
 @BINPATH@/components/SecurityReporter.manifest
 @BINPATH@/components/SecurityReporter.js
 
 ; [Browser Chrome Files]
 @BINPATH@/chrome/toolkit@JAREXT@
 @BINPATH@/chrome/toolkit.manifest
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4889,17 +4889,17 @@ pref("layers.child-process-shutdown", tr
 pref("layers.max-active", -1);
 
 // Compositor target frame rate. NOTE: If vsync is enabled the compositor
 // frame rate will still be capped.
 // -1 -> default (match layout.frame_rate or 60 FPS)
 // 0  -> full-tilt mode: Recomposite even if not transaction occured.
 pref("layers.offmainthreadcomposition.frame-rate", -1);
 
-#if defined(XP_MACOSX)
+#if defined(XP_MACOSX) || defined (OS_OPENBSD)
 pref("layers.enable-tiles", true);
 #else
 pref("layers.enable-tiles", false);
 #endif
 #if defined(XP_WIN)
 pref("layers.enable-tiles-if-skia-pomtp", true);
 #else
 pref("layers.enable-tiles-if-skia-pomtp", false);
@@ -5932,8 +5932,15 @@ pref("browser.fastblock.enabled", false)
 pref("browser.fastblock.timeout", 5000);
 
 // Enable clipboard readText() and writeText() by default
 pref("dom.events.asyncClipboard", true);
 // Disable clipboard read() and write() by default
 pref("dom.events.asyncClipboard.dataTransfer", false);
 // Should only be enabled in tests
 pref("dom.events.testing.asyncClipboard", false);
+
+#ifdef NIGHTLY_BUILD
+// Disable moz* APIs in DataTransfer
+pref("dom.datatransfer.mozAtAPIs", false);
+#else
+pref("dom.datatransfer.mozAtAPIs", true);
+#endif
--- a/netwerk/base/nsChannelClassifier.cpp
+++ b/netwerk/base/nsChannelClassifier.cpp
@@ -17,32 +17,32 @@
 #include "nsIClassOfService.h"
 #include "nsIDocShell.h"
 #include "nsIDocument.h"
 #include "nsIHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIIOService.h"
 #include "nsIParentChannel.h"
 #include "nsIPermissionManager.h"
-#include "nsIPrivateBrowsingTrackingProtectionWhitelist.h"
 #include "nsIProtocolHandler.h"
 #include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISecureBrowserUI.h"
 #include "nsISecurityEventSink.h"
 #include "nsISupportsPriority.h"
 #include "nsIURL.h"
 #include "nsIWebProgressListener.h"
 #include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
 #include "nsXULAppAPI.h"
 #include "nsQueryObject.h"
 #include "nsIUrlClassifierDBService.h"
 #include "nsIURLFormatter.h"
 
+#include "mozilla/AntiTrackingCommon.h"
 #include "mozilla/ErrorNames.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/net/HttpBaseChannel.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/Unused.h"
@@ -444,94 +444,49 @@ nsChannelClassifier::ShouldEnableTrackin
         return NS_OK;
       }
     }
 
     if (AddonMayLoad(aChannel, chanURI)) {
         return NS_OK;
     }
 
-    nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
+    nsCOMPtr<nsIIOService> ios = services::GetIOService();
+    NS_ENSURE_TRUE(ios, NS_ERROR_FAILURE);
 
     nsCOMPtr<nsIURI> topWinURI;
     rv = chan->GetTopWindowURI(getter_AddRefs(topWinURI));
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     if (!topWinURI && CachedPrefs::GetInstance()->IsAllowListExample()) {
       LOG(("nsChannelClassifier[%p]: Allowlisting test domain\n", this));
       rv = ios->NewURI(NS_LITERAL_CSTRING("http://allowlisted.example.com"),
                        nullptr, nullptr, getter_AddRefs(topWinURI));
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
-    // Take the host/port portion so we can allowlist by site. Also ignore the
-    // scheme, since users who put sites on the allowlist probably don't expect
-    // allowlisting to depend on scheme.
-    nsCOMPtr<nsIURL> url = do_QueryInterface(topWinURI, &rv);
+    rv = AntiTrackingCommon::IsOnContentBlockingAllowList(topWinURI, mIsAllowListed);
     if (NS_FAILED(rv)) {
       return rv; // normal for some loads, no need to print a warning
     }
 
-    nsCString escaped(NS_LITERAL_CSTRING("https://"));
-    nsAutoCString temp;
-    rv = url->GetHostPort(temp);
-    NS_ENSURE_SUCCESS(rv, rv);
-    escaped.Append(temp);
-
-    // Stuff the whole thing back into a URI for the permission manager.
-    rv = ios->NewURI(escaped, nullptr, nullptr, getter_AddRefs(topWinURI));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<nsIPermissionManager> permMgr =
-        do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION;
-    rv = permMgr->TestPermission(topWinURI, "trackingprotection", &permissions);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (permissions == nsIPermissionManager::ALLOW_ACTION) {
+    if (mIsAllowListed) {
+      *result = false;
       if (LOG_ENABLED()) {
         nsCString chanSpec = chanURI->GetSpecOrDefault();
         chanSpec.Truncate(std::min(chanSpec.Length(), sMaxSpecLength));
-        LOG(("nsChannelClassifier[%p]: User override on channel[%p] (%s) for %s",
-             this, aChannel, chanSpec.get(), escaped.get()));
+        LOG(("nsChannelClassifier[%p]: User override on channel[%p] (%s)",
+             this, aChannel, chanSpec.get()));
       }
-      mIsAllowListed = true;
-      *result = false;
     } else {
       *result = true;
     }
 
-    // In Private Browsing Mode we also check against an in-memory list.
-    if (NS_UsePrivateBrowsing(aChannel)) {
-      nsCOMPtr<nsIPrivateBrowsingTrackingProtectionWhitelist> pbmtpWhitelist =
-          do_GetService(NS_PBTRACKINGPROTECTIONWHITELIST_CONTRACTID, &rv);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      bool exists = false;
-      rv = pbmtpWhitelist->ExistsInAllowList(topWinURI, &exists);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      if (exists) {
-        mIsAllowListed = true;
-        if (LOG_ENABLED()) {
-          nsCString chanSpec = chanURI->GetSpecOrDefault();
-          chanSpec.Truncate(std::min(chanSpec.Length(), sMaxSpecLength));
-          LOG(("nsChannelClassifier[%p]: User override (PBM) on channel[%p] (%s) for %s",
-               this, aChannel, chanSpec.get(), escaped.get()));
-        }
-      }
-
-      *result = !exists;
-    }
-
     // Tracking protection will be enabled so return without updating
     // the security state. If any channels are subsequently cancelled
     // (page elements blocked) the state will be then updated.
     if (*result) {
       if (LOG_ENABLED()) {
         nsCString chanSpec = chanURI->GetSpecOrDefault();
         chanSpec.Truncate(std::min(chanSpec.Length(), sMaxSpecLength));
         nsCString topWinSpec = topWinURI->GetSpecOrDefault();
@@ -1227,17 +1182,22 @@ TrackingURICallback::OnTrackerFound(nsre
   nsCOMPtr<nsIChannel> channel = mChannelClassifier->GetChannel();
   MOZ_ASSERT(channel);
   if (aErrorCode == NS_ERROR_TRACKING_URI &&
       mChannelClassifier->ShouldEnableTrackingProtection()) {
     mChannelClassifier->SetBlockedContent(channel, aErrorCode,
                                           mList, mProvider, mFullHash);
     LOG(("TrackingURICallback[%p]::OnTrackerFound, cancelling channel[%p]",
          mChannelClassifier.get(), channel.get()));
-    channel->Cancel(aErrorCode);
+    nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(channel);
+    if (httpChannel) {
+      Unused << httpChannel->CancelForTrackingProtection();
+    } else {
+      Unused << channel->Cancel(aErrorCode);
+    }
   } else {
     MOZ_ASSERT(aErrorCode == NS_ERROR_TRACKING_ANNOTATION_URI);
     MOZ_ASSERT(mChannelClassifier->ShouldEnableTrackingAnnotation());
 
     bool isThirdPartyWithTopLevelWinURI = false;
     nsresult rv = IsThirdParty(channel, &isThirdPartyWithTopLevelWinURI);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       LOG(("TrackingURICallback[%p]::OnTrackerFound IsThirdParty() failed",
--- a/netwerk/base/nsIChannelEventSink.idl
+++ b/netwerk/base/nsIChannelEventSink.idl
@@ -48,16 +48,23 @@ interface nsIChannelEventSink : nsISuppo
     /**
      * This is a special-cased redirect coming from hitting HSTS upgrade
      * redirect from http to https only.  In some cases this type of redirect
      * may be considered as safe despite not being the-same-origin redirect.
      */
     const unsigned long REDIRECT_STS_UPGRADE = 1 << 3;
 
     /**
+     * This redirect has already been presented to the nsILoadURIDelegate
+     * for possible handling; if this flag is set we may safely skip checking
+     * if the nsILoadURIDelegate will handle the redirect.
+     */
+    const unsigned long REDIRECT_DELEGATES_CHECKED = 1 << 4;
+
+    /**
      * Called when a redirect occurs. This may happen due to an HTTP 3xx status
      * code. The purpose of this method is to notify the sink that a redirect
      * is about to happen, but also to give the sink the right to veto the
      * redirect by throwing or passing a failure-code in the callback.
      *
      * Note that vetoing the redirect simply means that |newChannel| will not
      * be opened. It is important to understand that |oldChannel| will continue
      * loading as if it received a HTTP 200, which includes notifying observers
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -4719,10 +4719,16 @@ HttpBaseChannel::GetNativeServerTiming(n
   if (NS_SUCCEEDED(mURI->SchemeIs("https", &isHTTPS)) && isHTTPS) {
     ParseServerTimingHeader(mResponseHead, aServerTiming);
     ParseServerTimingHeader(mResponseTrailers, aServerTiming);
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+HttpBaseChannel::CancelForTrackingProtection()
+{
+  return Cancel(NS_ERROR_TRACKING_URI);
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -266,16 +266,17 @@ public:
   virtual void SetAltDataForChild(bool aIsForChild) override;
   NS_IMETHOD GetConnectionInfoHashKey(nsACString& aConnectionInfoHashKey) override;
   NS_IMETHOD GetIntegrityMetadata(nsAString& aIntegrityMetadata) override;
   NS_IMETHOD SetIntegrityMetadata(const nsAString& aIntegrityMetadata) override;
   NS_IMETHOD GetLastRedirectFlags(uint32_t *aValue) override;
   NS_IMETHOD SetLastRedirectFlags(uint32_t aValue) override;
   NS_IMETHOD GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) override;
   NS_IMETHOD SetNavigationStartTimeStamp(TimeStamp aTimeStamp) override;
+  NS_IMETHOD CancelForTrackingProtection() override;
 
   inline void CleanRedirectCacheChainIfNecessary()
   {
       mRedirectedCachekeys = nullptr;
   }
   NS_IMETHOD HTTPUpgrade(const nsACString & aProtocolName,
                          nsIHttpUpgradeListener *aListener) override;
 
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -462,16 +462,31 @@ nsHttpChannel::PrepareToConnect()
         mCallOnResume = &nsHttpChannel::HandleOnBeforeConnect;
         return NS_OK;
     }
 
     return OnBeforeConnect();
 }
 
 void
+nsHttpChannel::HandleContinueCancelledByTrackingProtection()
+{
+    MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+    if (mSuspendCount) {
+        LOG(("Waiting until resume HandleContinueCancelledByTrackingProtection [this=%p]\n", this));
+        mCallOnResume = &nsHttpChannel::HandleContinueCancelledByTrackingProtection;
+        return;
+    }
+
+    LOG(("nsHttpChannel::HandleContinueCancelledByTrackingProtection [this=%p]\n", this));
+    ContinueCancelledByTrackingProtection();
+}
+
+void
 nsHttpChannel::HandleOnBeforeConnect()
 {
     MOZ_ASSERT(!mCallOnResume, "How did that happen?");
     nsresult rv;
 
     if (mSuspendCount) {
         LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
         mCallOnResume = &nsHttpChannel::HandleOnBeforeConnect;
@@ -5982,27 +5997,98 @@ NS_INTERFACE_MAP_END_INHERITING(HttpBase
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::Cancel(nsresult status)
 {
     MOZ_ASSERT(NS_IsMainThread());
     // We should never have a pump open while a CORS preflight is in progress.
     MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
+    MOZ_ASSERT(status != NS_ERROR_TRACKING_URI,
+               "NS_ERROR_TRACKING_URI needs to be handled by CancelForTrackingProtection()");
 
     LOG(("nsHttpChannel::Cancel [this=%p status=%" PRIx32 "]\n",
          this, static_cast<uint32_t>(status)));
     if (mCanceled) {
         LOG(("  ignoring; already canceled\n"));
         return NS_OK;
     }
 
     if (mWaitingForRedirectCallback) {
         LOG(("channel canceled during wait for redirect callback"));
     }
+
+    return CancelInternal(status);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::CancelForTrackingProtection()
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    // We should never have a pump open while a CORS preflight is in progress.
+    MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
+
+    LOG(("nsHttpChannel::CancelForTrackingProtection [this=%p]\n", this));
+
+    if (mCanceled) {
+        LOG(("  ignoring; already canceled\n"));
+        return NS_OK;
+    }
+
+    // We are being canceled by the channel classifier because of tracking
+    // protection, but we haven't yet had a chance to dispatch the
+    // "http-on-modify-request" notifications yet (this would normally be
+    // done in PrepareToConnect()).  So do that now, before proceeding to
+    // cancel.
+    //
+    // Note that running these observers can itself result in the channel
+    // being canceled.  In that case, we accept that cancelation code as
+    // the cause of the cancelation, as if the classification of the channel
+    // would have occurred past this point!
+
+    // notify "http-on-modify-request" observers
+    CallOnModifyRequestObservers();
+
+    SetLoadGroupUserAgentOverride();
+
+    // Check if request was cancelled during on-modify-request or on-useragent.
+    if (mCanceled) {
+        return mStatus;
+    }
+
+    if (mSuspendCount) {
+        LOG(("Waiting until resume in Cancel [this=%p]\n", this));
+        MOZ_ASSERT(!mCallOnResume);
+        mCallOnResume = &nsHttpChannel::HandleContinueCancelledByTrackingProtection;
+        return NS_OK;
+    }
+
+    return CancelInternal(NS_ERROR_TRACKING_URI);
+}
+
+void
+nsHttpChannel::ContinueCancelledByTrackingProtection()
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    // We should never have a pump open while a CORS preflight is in progress.
+    MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
+
+    LOG(("nsHttpChannel::ContinueCancelledByTrackingProtection [this=%p]\n",
+         this));
+    if (mCanceled) {
+        LOG(("  ignoring; already canceled\n"));
+        return;
+    }
+
+    Unused << CancelInternal(NS_ERROR_TRACKING_URI);
+}
+
+nsresult
+nsHttpChannel::CancelInternal(nsresult status)
+{
     mCanceled = true;
     mStatus = status;
     if (mProxyRequest)
         mProxyRequest->Cancel(status);
     CancelNetworkRequest(status);
     mCacheInputStream.CloseAndRelease();
     if (mCachePump)
         mCachePump->Cancel(status);
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -158,16 +158,17 @@ public:
     NS_IMETHOD AsyncOpen2(nsIStreamListener *aListener) override;
     // nsIHttpChannel
     NS_IMETHOD GetEncodedBodySize(uint64_t *aEncodedBodySize) override;
     // nsIHttpChannelInternal
     NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey) override;
     NS_IMETHOD SetChannelIsForDownload(bool aChannelIsForDownload) override;
     NS_IMETHOD GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) override;
     NS_IMETHOD SetNavigationStartTimeStamp(TimeStamp aTimeStamp) override;
+    NS_IMETHOD CancelForTrackingProtection() override;
     // nsISupportsPriority
     NS_IMETHOD SetPriority(int32_t value) override;
     // nsIClassOfService
     NS_IMETHOD SetClassFlags(uint32_t inFlags) override;
     NS_IMETHOD AddClassFlags(uint32_t inFlags) override;
     NS_IMETHOD ClearClassFlags(uint32_t inFlags) override;
 
     // nsIResumableChannel
@@ -279,16 +280,19 @@ public:
 
 protected:
     virtual ~nsHttpChannel();
 
 private:
     typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
 
     bool     RequestIsConditional();
+    void HandleContinueCancelledByTrackingProtection();
+    nsresult CancelInternal(nsresult status);
+    void ContinueCancelledByTrackingProtection();
 
     // Connections will only be established in this function.
     // (including DNS prefetch and speculative connection.)
     nsresult BeginConnectActual();
 
     // We might synchronously or asynchronously call BeginConnectActual,
     // which includes DNS prefetch and speculative connection, according to
     // whether an async tracker lookup is required. If the tracker lookup
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -328,9 +328,17 @@ interface nsIHttpChannelInternal : nsISu
      * SetupReplacementChannel() method.
      */
     [noscript, infallible]
     attribute unsigned long lastRedirectFlags;
 
       // This is use to determine the duration since navigation started.
     [noscript] attribute TimeStamp navigationStartTimeStamp;
 
+    /**
+     * Cancel a channel because we have determined that it needs to be blocked
+     * for tracking protection.  This is an internal API that is meant to be
+     * called by the channel classifier.  Please DO NOT use this API if you don't
+     * know whether you should be using it.
+     */
+    [noscript] void cancelForTrackingProtection();
+
 };
--- a/old-configure.in
+++ b/old-configure.in
@@ -2783,16 +2783,18 @@ if test -n "$MOZ_LIBJPEG_TURBO" -a -n "$
   Darwin:arm*)
   ;;
   WINNT:x86)
     LIBJPEG_TURBO_ASFLAGS="-DPIC -DWIN32"
   ;;
   WINNT:x86_64)
     LIBJPEG_TURBO_ASFLAGS="-D__x86_64__ -DPIC -DWIN64 -DMSVC"
   ;;
+  WINNT:*)
+  ;;
   *:arm)
     LIBJPEG_TURBO_ASFLAGS="-march=armv7-a -mfpu=neon"
   ;;
   *:aarch64)
     LIBJPEG_TURBO_ASFLAGS="-march=armv8-a"
   ;;
   *:mips32)
     LIBJPEG_TURBO_ASFLAGS="-mdspr2"
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -2579,17 +2579,17 @@ function synthesizeDragOver(aSrcElement,
 
   // This method runs before other callbacks, and acts as a way to inject the
   // initial drag data into the DataTransfer.
   function fillDrag(event) {
     if (aDragData) {
       for (var i = 0; i < aDragData.length; i++) {
         var item = aDragData[i];
         for (var j = 0; j < item.length; j++) {
-          event.dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
+          _EU_maybeWrap(event.dataTransfer).mozSetDataAt(item[j].type, item[j].data, i);
         }
       }
     }
     event.dataTransfer.dropEffect = aDropEffect || "move";
     event.preventDefault();
   }
 
   function trapDrag(subject, topic) {
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -11,36 +11,40 @@
 #include "mozilla/AbstractThread.h"
 #include "mozilla/Logging.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozIThirdPartyUtil.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindowInner.h"
 #include "nsICookiePermission.h"
 #include "nsICookieService.h"
+#include "nsIIOService.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIURI.h"
+#include "nsIURL.h"
 #include "nsPIDOMWindow.h"
 #include "nsScriptSecurityManager.h"
 #include "prtime.h"
 
 #define ANTITRACKING_PERM_KEY "3rdPartyStorage"
 
 using namespace mozilla;
 using mozilla::dom::ContentChild;
 
 static LazyLogModule gAntiTrackingLog("AntiTracking");
+static const nsCString::size_type sMaxSpecLength = 128;
 
 #define LOG(format) MOZ_LOG(gAntiTrackingLog, mozilla::LogLevel::Debug, format)
 
 #define LOG_SPEC(format, uri)                                                 \
   PR_BEGIN_MACRO                                                              \
     if (MOZ_LOG_TEST(gAntiTrackingLog, mozilla::LogLevel::Debug)) {           \
       nsAutoCString _specStr(NS_LITERAL_CSTRING("(null)"));                   \
+      _specStr.Truncate(std::min(_specStr.Length(), sMaxSpecLength));         \
       if (uri) {                                                              \
         _specStr = uri->GetSpecOrDefault();                                   \
       }                                                                       \
       const char* _spec = _specStr.get();                                     \
       LOG(format);                                                            \
     }                                                                         \
   PR_END_MACRO
 
@@ -637,8 +641,71 @@ AntiTrackingCommon::MaybeIsFirstPartySto
   Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
   LOG_SPEC(("Testing permission type %s for %s resulted in %d (%s)",
             type.get(), _spec, int(result),
             result == nsIPermissionManager::ALLOW_ACTION ?
               "success" : "failure"), parentPrincipalURI);
 
   return result == nsIPermissionManager::ALLOW_ACTION;
 }
+
+nsresult
+AntiTrackingCommon::IsOnContentBlockingAllowList(nsIURI* aTopWinURI,
+                                                 bool& aIsAllowListed)
+{
+  aIsAllowListed = false;
+
+  LOG_SPEC(("Deciding whether the user has overridden content blocking for %s",
+            _spec), aTopWinURI);
+
+  nsCOMPtr<nsIIOService> ios = services::GetIOService();
+  NS_ENSURE_TRUE(ios, NS_ERROR_FAILURE);
+
+  // Take the host/port portion so we can allowlist by site. Also ignore the
+  // scheme, since users who put sites on the allowlist probably don't expect
+  // allowlisting to depend on scheme.
+  nsresult rv = NS_ERROR_FAILURE;
+  nsCOMPtr<nsIURL> url = do_QueryInterface(aTopWinURI, &rv);
+  if (NS_FAILED(rv)) {
+    return rv; // normal for some loads, no need to print a warning
+  }
+
+  nsCString escaped(NS_LITERAL_CSTRING("https://"));
+  nsAutoCString temp;
+  rv = url->GetHostPort(temp);
+  NS_ENSURE_SUCCESS(rv, rv);
+  escaped.Append(temp);
+
+  // Stuff the whole thing back into a URI for the permission manager.
+  nsCOMPtr<nsIURI> topWinURI;
+  rv = ios->NewURI(escaped, nullptr, nullptr, getter_AddRefs(topWinURI));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIPermissionManager> permMgr =
+    do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Check both the normal mode and private browsing mode user override permissions.
+  const char* types[] = {
+    "trackingprotection",
+    "trackingprotection-pb"
+  };
+
+  for (size_t i = 0; i < ArrayLength(types); ++i) {
+    uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION;
+    rv = permMgr->TestPermission(topWinURI, types[i], &permissions);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (permissions == nsIPermissionManager::ALLOW_ACTION) {
+      aIsAllowListed = true;
+      LOG_SPEC(("Found user override type %s for %s", types[i], _spec),
+               topWinURI);
+      // Stop checking the next permisson type if we decided to override.
+      break;
+    }
+  }
+
+  if (!aIsAllowListed) {
+    LOG(("No user override found"));
+  }
+
+  return NS_OK;
+}
--- a/toolkit/components/antitracking/AntiTrackingCommon.h
+++ b/toolkit/components/antitracking/AntiTrackingCommon.h
@@ -78,13 +78,18 @@ public:
 
   // For IPC only.
   static void
   SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aPrincipal,
                                                              const nsCString& aParentOrigin,
                                                              const nsCString& aGrantedOrigin,
                                                              FirstPartyStorageAccessGrantedForOriginResolver&& aResolver);
 
+
+  // Check whether a top window URI is on the content blocking allow list.
+  static nsresult
+  IsOnContentBlockingAllowList(nsIURI* aTopWinURI, bool& aIsAllowListed);
+
 };
 
 } // namespace mozilla
 
 #endif // mozilla_antitrackingservice_h
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -44,17 +44,16 @@ DIRS += [
     'mozintl',
     'mozprotocol',
     'osfile',
     'parentalcontrols',
     'passwordmgr',
     'perf',
     'perfmonitoring',
     'places',
-    'privatebrowsing',
     'processsingleton',
     'promiseworker',
     'prompts',
     'protobuf',
     'reader',
     'remotebrowserutils',
     'remotepagemanager',
     'reflect',
deleted file mode 100644
--- a/toolkit/components/privatebrowsing/PrivateBrowsing.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-component {a319b616-c45d-4037-8d86-01c592b5a9af} PrivateBrowsingTrackingProtectionWhitelist.js
-contract @mozilla.org/pbm-tp-whitelist;1 {a319b616-c45d-4037-8d86-01c592b5a9af}
deleted file mode 100644
--- a/toolkit/components/privatebrowsing/PrivateBrowsingTrackingProtectionWhitelist.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/* 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/. */
-
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-function PrivateBrowsingTrackingProtectionWhitelist() {
-  // The list of URIs explicitly excluded from tracking protection.
-  this._allowlist = [];
-
-  Services.obs.addObserver(this, "last-pb-context-exited", true);
-}
-
-PrivateBrowsingTrackingProtectionWhitelist.prototype = {
-  classID: Components.ID("{a319b616-c45d-4037-8d86-01c592b5a9af}"),
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIPrivateBrowsingTrackingProtectionWhitelist, Ci.nsIObserver, Ci.nsISupportsWeakReference]),
-  _xpcom_factory: XPCOMUtils.generateSingletonFactory(PrivateBrowsingTrackingProtectionWhitelist),
-
-  /**
-   * Add the provided URI to the list of allowed tracking sites.
-   *
-   * @param uri nsIURI
-   *        The URI to add to the list.
-   */
-  addToAllowList(uri) {
-    if (!this._allowlist.includes(uri.spec)) {
-      this._allowlist.push(uri.spec);
-    }
-  },
-
-  /**
-   * Remove the provided URI from the list of allowed tracking sites.
-   *
-   * @param uri nsIURI
-   *        The URI to add to the list.
-   */
-  removeFromAllowList(uri) {
-    let index = this._allowlist.indexOf(uri.spec);
-    if (index !== -1) {
-      this._allowlist.splice(index, 1);
-    }
-  },
-
-  /**
-   * Check if the provided URI exists in the list of allowed tracking sites.
-   *
-   * @param uri nsIURI
-   *        The URI to add to the list.
-   */
-  existsInAllowList(uri) {
-    return this._allowlist.includes(uri.spec);
-  },
-
-  observe(subject, topic, data) {
-    if (topic == "last-pb-context-exited") {
-      this._allowlist = [];
-    }
-  }
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PrivateBrowsingTrackingProtectionWhitelist]);
deleted file mode 100644
--- a/toolkit/components/privatebrowsing/moz.build
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-with Files('**'):
-    BUG_COMPONENT = ('Core', 'DOM: Security')
-
-XPIDL_SOURCES += [
-    'nsIPrivateBrowsingTrackingProtectionWhitelist.idl',
-]
-
-XPIDL_MODULE = 'privatebrowsing'
-
-EXTRA_COMPONENTS += [
-    'PrivateBrowsing.manifest',
-    'PrivateBrowsingTrackingProtectionWhitelist.js',
-]
deleted file mode 100644
--- a/toolkit/components/privatebrowsing/nsIPrivateBrowsingTrackingProtectionWhitelist.idl
+++ /dev/null
@@ -1,46 +0,0 @@
-/* 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 "nsISupports.idl"
-
-interface nsIURI;
-
-/**
- * The Private Browsing Tracking Protection service checks a URI against an
- * in-memory list of tracking sites.
- */
-[scriptable, uuid(c77ddfac-6cd6-43a9-84e8-91682a1a7b18)]
-interface nsIPrivateBrowsingTrackingProtectionWhitelist : nsISupports
-{
-  /**
-   * Add a URI to the list of allowed tracking sites in Private Browsing mode
-   * (essentially a tracking whitelist). This operation will cause the URI to
-   * be registered if it does not currently exist. If it already exists, then
-   * the operation is essentially a no-op.
-   *
-   * @param uri         the uri to add to the list
-   */
-  void addToAllowList(in nsIURI uri);
-
-  /**
-   * Remove a URI from the list of allowed tracking sites in Private Browsing
-   * mode (the tracking whitelist). If the URI is not already in the list,
-   * then the operation is essentially a no-op.
-   *
-   * @param uri         the uri to remove from the list
-   */
-  void removeFromAllowList(in nsIURI uri);
-
-  /**
-   * Check if a URI exists in the list of allowed tracking sites in Private
-   * Browsing mode (the tracking whitelist).
-   *
-   * @param uri         the uri to look for in the list
-   */
-  bool existsInAllowList(in nsIURI uri);
-};
-
-%{ C++
-#define NS_PBTRACKINGPROTECTIONWHITELIST_CONTRACTID "@mozilla.org/pbm-tp-whitelist;1"
-%}
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -464,17 +464,17 @@
     "kind": "exponential",
     "high": 10000,
     "n_buckets": 50,
     "description": "Time spent on one asynchronous SnowWhite freeing (ms)"
   },
   "CYCLE_COLLECTOR_SLICE_DURING_IDLE": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["dev-telemetry-gc-alerts@mozilla.org"],
-    "expires_in_version": "62",
+    "expires_in_version": "never",
     "kind": "linear",
     "high": 100,
     "n_buckets": 50,
     "bug_numbers": [1372042],
     "description": "Percent of cycle collector slice done during idle time"
   },
   "DEFERRED_FINALIZE_ASYNC": {
     "record_in_processes": ["main", "content"],
@@ -516,17 +516,17 @@
     "kind": "exponential",
     "high": 10000,
     "n_buckets": 50,
     "description": "Max time spent on one forget skippable (ms)"
   },
   "FORGET_SKIPPABLE_DURING_IDLE": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["dev-telemetry-gc-alerts@mozilla.org"],
-    "expires_in_version": "62",
+    "expires_in_version": "never",
     "kind": "linear",
     "high": 100,
     "n_buckets": 50,
     "bug_numbers": [1372042],
     "description": "Percent of the cycle collector's forget skippable done during idle time"
   },
   "FULLSCREEN_TRANSITION_BLACK_MS": {
     "record_in_processes": ["main", "content"],
@@ -782,17 +782,17 @@
     "kind": "enumerated",
     "n_values": 32,
     "bug_numbers": [1293262],
     "description": "How many objects groups were selected for pretenuring by a minor GC"
   },
   "GC_SLICE_DURING_IDLE": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["dev-telemetry-gc-alerts@mozilla.org"],
-    "expires_in_version": "62",
+    "expires_in_version": "never",
     "kind": "linear",
     "high": 100,
     "n_buckets": 50,
     "bug_numbers": [1372042],
     "description": "Percent of GC slice done during idle time"
   },
   "GC_BUDGET_OVERRUN": {
     "record_in_processes": ["main", "content"],
--- a/toolkit/content/widgets.css
+++ b/toolkit/content/widgets.css
@@ -13,10 +13,11 @@
 @import url("chrome://global/skin/groupbox.css");
 @import url("chrome://global/skin/menu.css");
 @import url("chrome://global/skin/menulist.css");
 @import url("chrome://global/skin/notification.css");
 @import url("chrome://global/skin/popup.css");
 @import url("chrome://global/skin/progressmeter.css");
 @import url("chrome://global/skin/richlistbox.css");
 @import url("chrome://global/skin/splitter.css");
+@import url("chrome://global/skin/tabbox.css");
 @import url("chrome://global/skin/toolbar.css");
 @import url("chrome://global/skin/wizard.css");
--- a/toolkit/content/widgets/tabbox.xml
+++ b/toolkit/content/widgets/tabbox.xml
@@ -3,22 +3,17 @@
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 
 <bindings id="tabBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
-
   <binding id="tabbox">
-    <resources>
-      <stylesheet src="chrome://global/skin/tabbox.css"/>
-    </resources>
-
     <implementation>
       <property name="handleCtrlTab">
         <setter>
         <![CDATA[
           this.setAttribute("handleCtrlTab", val);
           return val;
         ]]>
         </setter>
@@ -212,20 +207,16 @@
                     .getService(Ci.nsIEventListenerService);
         els.removeSystemEventListener(this._eventNode, "keydown", this, false);
       </destructor>
     </implementation>
   </binding>
 
   <binding id="tabs"
            extends="chrome://global/content/bindings/general.xml#basecontrol">
-    <resources>
-      <stylesheet src="chrome://global/skin/tabbox.css"/>
-    </resources>
-
     <content>
       <xul:spacer class="tabs-left"/>
       <children/>
       <xul:spacer class="tabs-right" flex="1"/>
     </content>
 
     <implementation implements="nsIDOMXULSelectControlElement, nsIDOMXULRelatedElement">
       <constructor>
@@ -528,20 +519,16 @@
         event.stopPropagation();
       ]]>
       </handler>
     </handlers>
 #endif
   </binding>
 
   <binding id="tabpanels">
-    <resources>
-      <stylesheet src="chrome://global/skin/tabbox.css"/>
-    </resources>
-
     <implementation implements="nsIDOMXULRelatedElement">
       <!-- nsIDOMXULRelatedElement -->
       <method name="getRelatedElement">
         <parameter name="aTabPanelElm"/>
         <body>
         <![CDATA[
           if (!aTabPanelElm)
             return null;
@@ -644,20 +631,16 @@
           ]]>
         </setter>
       </property>
     </implementation>
   </binding>
 
   <binding id="tab" display="xul:button"
            extends="chrome://global/content/bindings/general.xml#basetext">
-    <resources>
-      <stylesheet src="chrome://global/skin/tabbox.css"/>
-    </resources>
-
     <content>
       <xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected,visuallyselected" flex="1">
         <xul:image class="tab-icon"
                    xbl:inherits="validate,src=image"
                    role="presentation"/>
         <xul:label class="tab-text"
                    xbl:inherits="value=label,accesskey,crop,disabled"
                    flex="1"
--- a/toolkit/modules/PrivateBrowsingUtils.jsm
+++ b/toolkit/modules/PrivateBrowsingUtils.jsm
@@ -1,20 +1,50 @@
 /* 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/. */
 
 var EXPORTED_SYMBOLS = ["PrivateBrowsingUtils"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function PrivateBrowsingContentBlockingAllowList() {
+  Services.obs.addObserver(this, "last-pb-context-exited", true);
+}
+
+PrivateBrowsingContentBlockingAllowList.prototype = {
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
 
-XPCOMUtils.defineLazyServiceGetter(this, "gPBMTPWhitelist",
-                                   "@mozilla.org/pbm-tp-whitelist;1",
-                                   "nsIPrivateBrowsingTrackingProtectionWhitelist");
+  /**
+   * Add the provided URI to the list of allowed tracking sites.
+   *
+   * @param uri nsIURI
+   *        The URI to add to the list.
+   */
+  addToAllowList(uri) {
+    Services.perms.add(uri, "trackingprotection-pb", Ci.nsIPermissionManager.ALLOW_ACTION,
+                       Ci.nsIPermissionManager.EXPIRE_SESSION);
+  },
+
+  /**
+   * Remove the provided URI from the list of allowed tracking sites.
+   *
+   * @param uri nsIURI
+   *        The URI to remove from the list.
+   */
+  removeFromAllowList(uri) {
+    Services.perms.remove(uri, "trackingprotection-pb");
+  },
+
+  observe(subject, topic, data) {
+    if (topic == "last-pb-context-exited") {
+      Services.perms.removeByType("trackingprotection-pb");
+    }
+  }
+};
 
 const kAutoStartPref = "browser.privatebrowsing.autostart";
 
 // This will be set to true when the PB mode is autostarted from the command
 // line for the current session.
 var gTemporaryAutoStartMode = false;
 
 var PrivateBrowsingUtils = {
@@ -51,26 +81,27 @@ var PrivateBrowsingUtils = {
     }
     return this.privacyContextFromWindow(aBrowser.contentWindow).usePrivateBrowsing;
   },
 
   privacyContextFromWindow: function pbu_privacyContextFromWindow(aWindow) {
     return aWindow.docShell.QueryInterface(Ci.nsILoadContext);
   },
 
-  addToTrackingAllowlist(aURI) {
-    gPBMTPWhitelist.addToAllowList(aURI);
+  get _pbCBAllowList() {
+    delete this._pbCBAllowList;
+    return this._pbCBAllowList = new PrivateBrowsingContentBlockingAllowList();
   },
 
-  existsInTrackingAllowlist(aURI) {
-    return gPBMTPWhitelist.existsInAllowList(aURI);
+  addToTrackingAllowlist(aURI) {
+    this._pbCBAllowList.addToAllowList(aURI);
   },
 
   removeFromTrackingAllowlist(aURI) {
-    gPBMTPWhitelist.removeFromAllowList(aURI);
+    this._pbCBAllowList.removeFromAllowList(aURI);
   },
 
   get permanentPrivateBrowsing() {
     try {
       return gTemporaryAutoStartMode ||
              Services.prefs.getBoolPref(kAutoStartPref);
     } catch (e) {
       // The pref does not exist
--- a/uriloader/base/nsDocLoader.cpp
+++ b/uriloader/base/nsDocLoader.cpp
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nspr.h"
 #include "mozilla/Logging.h"
 #include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
 
 #include "nsDocLoader.h"
 #include "nsCURILoader.h"
 #include "nsNetUtil.h"
 #include "nsIHttpChannel.h"
 #include "nsIWebProgressListener2.h"
 
 #include "nsIServiceManager.h"
@@ -29,16 +31,18 @@
 #include "nsIScriptSecurityManager.h"
 
 #include "nsITransport.h"
 #include "nsISocketTransport.h"
 #include "nsIDocShell.h"
 #include "nsIDocument.h"
 #include "nsPresContext.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsILoadURIDelegate.h"
+#include "nsIBrowserDOMWindow.h"
 
 using mozilla::DebugOnly;
 using mozilla::LogLevel;
 
 //
 // Log module for nsIDocumentLoader logging...
 //
 // To enable logging (see mozilla/Logging.h for full details):
@@ -1416,21 +1420,116 @@ int64_t nsDocLoader::CalculateMaxProgres
     if (info->mMaxProgress < info->mCurrentProgress) {
       return int64_t(-1);
     }
     max += info->mMaxProgress;
   }
   return max;
 }
 
+class LoadURIDelegateRedirectHandler final : public mozilla::dom::PromiseNativeHandler
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(LoadURIDelegateRedirectHandler)
+
+  LoadURIDelegateRedirectHandler(nsDocLoader* aDocLoader,
+                                 nsIChannel* aOldChannel,
+                                 nsIChannel* aNewChannel,
+                                 uint32_t aFlags,
+                                 nsIAsyncVerifyRedirectCallback* aCallback)
+  : mDocLoader(aDocLoader)
+  , mOldChannel(aOldChannel)
+  , mNewChannel(aNewChannel)
+  , mFlags(aFlags)
+  , mCallback(aCallback)
+  {}
+
+  void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    if (aValue.isBoolean() && aValue.toBoolean()) {
+      // The app handled the redirect, notify the callback
+      mCallback->OnRedirectVerifyCallback(NS_ERROR_ABORT);
+    } else {
+      UnhandledCallback();
+    }
+  }
+
+  void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    UnhandledCallback();
+  }
+
+private:
+  ~LoadURIDelegateRedirectHandler()
+  {}
+
+  void UnhandledCallback()
+  {
+    // If the redirect wasn't handled by the nsILoadURIDelegate, let Gecko
+    // handle it.
+    mFlags |= nsIChannelEventSink::REDIRECT_DELEGATES_CHECKED;
+    mDocLoader->AsyncOnChannelRedirect(mOldChannel, mNewChannel, mFlags,
+                                       mCallback);
+  }
+
+  RefPtr<nsDocLoader> mDocLoader;
+  nsCOMPtr<nsIChannel> mOldChannel;
+  nsCOMPtr<nsIChannel> mNewChannel;
+  uint32_t mFlags;
+  nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback;
+};
+
+NS_IMPL_CYCLE_COLLECTION(LoadURIDelegateRedirectHandler, mDocLoader, 
+                         mOldChannel, mNewChannel, mCallback)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LoadURIDelegateRedirectHandler)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(LoadURIDelegateRedirectHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(LoadURIDelegateRedirectHandler)
+
 NS_IMETHODIMP nsDocLoader::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
                                                   nsIChannel *aNewChannel,
                                                   uint32_t aFlags,
                                                   nsIAsyncVerifyRedirectCallback *cb)
 {
+  if ((aFlags &
+      (nsIChannelEventSink::REDIRECT_TEMPORARY |
+       nsIChannelEventSink::REDIRECT_PERMANENT)) &&
+      !(aFlags & nsIChannelEventSink::REDIRECT_DELEGATES_CHECKED)) {
+    nsCOMPtr<nsIDocShell> docShell =
+      do_QueryInterface(static_cast<nsIRequestObserver*>(this));
+
+    nsCOMPtr<nsILoadURIDelegate> delegate;
+    docShell->GetLoadURIDelegate(getter_AddRefs(delegate));
+
+    nsCOMPtr<nsIURI> newURI;
+    aNewChannel->GetURI(getter_AddRefs(newURI));
+
+    if (newURI && delegate) {
+      RefPtr<mozilla::dom::Promise> promise;
+      const int where = nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
+      nsresult rv = delegate->LoadURI(newURI, where, /* flags */ 0,
+                                      /* triggering principal */ nullptr,
+                                      getter_AddRefs(promise));
+      if (NS_SUCCEEDED(rv) && promise) {
+        RefPtr<LoadURIDelegateRedirectHandler> handler =
+          new LoadURIDelegateRedirectHandler(this, aOldChannel, aNewChannel,
+                                             aFlags, cb);
+
+        promise->AppendNativeHandler(handler);
+        return NS_OK;
+      }
+    }
+  }
+
   if (aOldChannel)
   {
     nsLoadFlags loadFlags = 0;
     int32_t stateFlags = nsIWebProgressListener::STATE_REDIRECTING |
                          nsIWebProgressListener::STATE_IS_REQUEST;
 
     aOldChannel->GetLoadFlags(&loadFlags);
     // If the document channel is being redirected, then indicate that the
--- a/xpcom/components/nsComponentManager.cpp
+++ b/xpcom/components/nsComponentManager.cpp
@@ -475,23 +475,42 @@ AssertNotMallocAllocated(T* aPtr)
   MOZ_ASSERT(info.tag == TagUnknown);
 #endif
 }
 
 template<typename T>
 static void
 AssertNotStackAllocated(T* aPtr)
 {
-  // The main thread's stack should be allocated at the top of our address
-  // space. Anything stack allocated should be above us on the stack, and
-  // therefore above our first argument pointer.
-  // Only this is apparently not the case on Windows.
-#if !(defined(XP_WIN) || defined(ANDROID))
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(uintptr_t(aPtr) < uintptr_t(&aPtr));
+  // On all of our supported platforms, the stack grows down. Any address
+  // located below the address of our argument is therefore guaranteed not to be
+  // stack-allocated by the caller.
+  //
+  // For addresses above our argument, things get trickier. The main thread
+  // stack is traditionally placed at the top of the program's address space,
+  // but that is becoming less reliable as more and more systems adopt address
+  // space layout randomization strategies, so we have to guess how much space
+  // above our argument pointer we need to care about.
+  //
+  // On most systems, we're guaranteed at least several KiB at the top of each
+  // stack for TLS. We'd probably be safe assuming at least 4KiB in the stack
+  // segment above our argument address, but safer is... well, safer.
+  //
+  // For threads with huge stacks, it's theoretically possible that we could
+  // wind up being passed a stack-allocated string from farther up the stack,
+  // but this is a best-effort thing, so we'll assume we only care about the
+  // immediate caller. For that case, max 2KiB per stack frame is probably a
+  // reasonable guess most of the time, and is less than the ~4KiB that we
+  // expect for TLS, so go with that to avoid the risk of bumping into heap
+  // data just above the stack.
+#ifdef DEBUG
+  static constexpr size_t kFuzz = 2048;
+
+  MOZ_ASSERT(uintptr_t(aPtr) < uintptr_t(&aPtr) ||
+             uintptr_t(aPtr) > uintptr_t(&aPtr) + kFuzz);
 #endif
 }
 
 static inline nsCString
 AsLiteralCString(const char* aStr)
 {
   AssertNotMallocAllocated(aStr);
   AssertNotStackAllocated(aStr);