Merge m-i to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Sun, 26 Feb 2017 10:48:26 -0800
changeset 489857 7ef1e9abd296a8edc39b7efc8d637767ba2f77ed
parent 489854 aa2b14b08dbb728ab6b9868e43a66842626f1833 (current diff)
parent 489849 712e84866cf557b5ed88c7b991dd508ec3d550ef (diff)
child 489858 ead57be1ab6a6fb5a7627dea16d6fec2261c950d
child 489866 bb906e721c81334a58c0da46ffe12f1dafba208e
child 489873 7e3dff19e15382214a692dd3a0569cacc21f7214
child 489874 59f0d08a88e10afec6e1fce6fc6fae9e70994d8b
child 489883 d57c8b074c6020f5e5fa63e8db7b1aac886054ed
child 489887 0df915f5c4cfc7613a3eb2fe6d378ff9e53080d2
child 489889 e2f6a5c6c3ae617a3d3378a2e4490b8fa42c3f70
child 489905 da86fa38df68be976f482e2a4033b2e668e97850
child 489913 ab13580f98cb78c8b0fa3d8204c0c93c138bde7a
child 489928 699dc9ca1d1fba666967f276fb76aacc265d7a96
child 489933 9ef954da40d3e87b53be30bd763cb1e13de64f09
child 489938 c305d66a42b8ecee5cac0143b723556fe4475e1a
child 489953 272fb9b3826891b308b2e988b4459e2946e2464f
child 489954 e9bb30c75664669cc6b7b38ec4bb6d9daf088db6
child 489955 6e611feb14b15db02f2f7c8430afd89a3903eb81
child 489959 3a71fe8b46fd0abf3168ad7fd032322a7f231114
child 489960 99239977e6c3a9428a037a9b1fd20f832adc5b09
child 489970 8e332422e57c39ee72bc7493875fad72f23dc640
child 490008 a933cbd4392ab0cd951714d292ff6e00f8c68b30
child 490020 d877c96b30b18a800a40e9b2de28e7419f9415de
child 490053 cf1f6dbed8c5ff887f43450d82296e90fd3a4432
child 490482 1010a36abac503473d216218dcaec462a9161e40
child 490651 44a2c8647e08f900f1c4246fa16173b947d5636b
child 490656 5e3e67d915c6aba2d491de98c7c7db6473e93df6
child 490725 cbdc06f80a83c4b3d8b4f811610f10a16eb1f417
child 490892 d56c558a6332df7fca0b40d1115954994c876aea
child 491664 a18510cb914956cbdd95cbec9404ca9d7ddcfb03
child 491756 3e79450491c996014d06cdf93d34d16ef68c4670
child 491758 f711d9bd1cd3daa9156fddd9fd2b55e39e20ef2c
child 491851 90aa4092fd0b526771d84adbb7700f022b061506
child 493814 11e84805d6f1d019af261aac2c7429576bf684e3
child 494007 6a875aebdeaa9767ffc1262d09b8ac7cd6d283e2
child 494563 18d17d517db3fe929068b09f35af034dfc4f70cb
child 494584 2b90ebe5492a93c83a3fad687b38d90e698be4b3
child 495115 e405ca9efe793f62b3029988b24f12ad8457e41a
child 496777 279b1b33cbf0fcb3a959e4ccf60457015b5bbf3f
child 496797 c1cdf401eeb7b1def4847e8cb08fdd3108a6a4f3
child 496798 3e511cc49a5d6e2a20f1eb4417fb84530383c26a
child 499685 a604cf7b218d3d56aedd2eae857c371596f43f4a
child 499708 84cf5dd3cae3b83ea037a4c90606a2f8b74c8ac0
child 499854 98247214b24b7178889e2ee45541a5de2f6329fb
child 502129 162a9cf4bd927b589381199f03994f6958c4fc67
push id46924
push usermaglione.k@gmail.com
push dateSun, 26 Feb 2017 21:23:01 +0000
reviewersmerge
milestone54.0a1
Merge m-i to m-c, a=merge
layout/reftests/css-grid/reftest-stylo.list
--- a/accessible/windows/msaa/DocAccessibleWrap.cpp
+++ b/accessible/windows/msaa/DocAccessibleWrap.cpp
@@ -122,16 +122,20 @@ DocAccessibleWrap::Shutdown()
 ////////////////////////////////////////////////////////////////////////////////
 // DocAccessible public
 
 void*
 DocAccessibleWrap::GetNativeWindow() const
 {
   if (XRE_IsContentProcess()) {
     DocAccessibleChild* ipcDoc = IPCDoc();
+    if (!ipcDoc) {
+      return nullptr;
+    }
+
     HWND hWnd = ipcDoc->GetEmulatedWindowHandle();
     if (hWnd) {
       return hWnd;
     }
 
     auto tab = static_cast<dom::TabChild*>(ipcDoc->Manager());
     MOZ_ASSERT(tab);
     if (!tab) {
--- a/devtools/client/inspector/layout/components/App.js
+++ b/devtools/client/inspector/layout/components/App.js
@@ -27,16 +27,17 @@ const App = createClass({
 
   propTypes: {
     boxModel: PropTypes.shape(Types.boxModel).isRequired,
     getSwatchColorPickerTooltip: PropTypes.func.isRequired,
     grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
     highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
     setSelectedNode: PropTypes.func.isRequired,
     showBoxModelProperties: PropTypes.bool.isRequired,
+    showGridOutline: PropTypes.bool.isRequired,
     onHideBoxModelHighlighter: PropTypes.func.isRequired,
     onSetGridOverlayColor: PropTypes.func.isRequired,
     onShowBoxModelEditor: PropTypes.func.isRequired,
     onShowBoxModelHighlighter: PropTypes.func.isRequired,
     onToggleGridHighlighter: PropTypes.func.isRequired,
     onToggleShowGridLineNumbers: PropTypes.func.isRequired,
     onToggleShowInfiniteLines: PropTypes.func.isRequired,
   },
--- a/devtools/client/inspector/layout/components/Grid.js
+++ b/devtools/client/inspector/layout/components/Grid.js
@@ -4,72 +4,86 @@
 
 "use strict";
 
 const { addons, createClass, createFactory, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 
 const GridDisplaySettings = createFactory(require("./GridDisplaySettings"));
 const GridList = createFactory(require("./GridList"));
+const GridOutline = createFactory(require("./GridOutline"));
 
 const Types = require("../types");
 const { getStr } = require("../utils/l10n");
 
 module.exports = createClass({
 
   displayName: "Grid",
 
   propTypes: {
     getSwatchColorPickerTooltip: PropTypes.func.isRequired,
     grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
     highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
     setSelectedNode: PropTypes.func.isRequired,
+    showGridOutline: PropTypes.bool.isRequired,
     onHideBoxModelHighlighter: PropTypes.func.isRequired,
     onSetGridOverlayColor: PropTypes.func.isRequired,
     onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
     onToggleGridHighlighter: PropTypes.func.isRequired,
     onToggleShowGridLineNumbers: PropTypes.func.isRequired,
     onToggleShowInfiniteLines: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   render() {
     let {
       getSwatchColorPickerTooltip,
       grids,
       highlighterSettings,
       setSelectedNode,
+      showGridOutline,
       onHideBoxModelHighlighter,
       onSetGridOverlayColor,
       onShowBoxModelHighlighterForNode,
       onToggleGridHighlighter,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
     } = this.props;
 
     return grids.length ?
       dom.div(
         {
           id: "layout-grid-container",
         },
-        GridList({
-          getSwatchColorPickerTooltip,
-          grids,
-          setSelectedNode,
-          onHideBoxModelHighlighter,
-          onSetGridOverlayColor,
-          onShowBoxModelHighlighterForNode,
-          onToggleGridHighlighter,
-        }),
-        GridDisplaySettings({
-          highlighterSettings,
-          onToggleShowGridLineNumbers,
-          onToggleShowInfiniteLines,
-        })
+        showGridOutline ?
+          GridOutline({
+            grids,
+          })
+          :
+          null,
+        dom.div(
+          {
+            className: "grid-content",
+          },
+          GridList({
+            getSwatchColorPickerTooltip,
+            grids,
+            setSelectedNode,
+            onHideBoxModelHighlighter,
+            onSetGridOverlayColor,
+            onShowBoxModelHighlighterForNode,
+            onToggleGridHighlighter,
+          }),
+          GridDisplaySettings({
+            highlighterSettings,
+            onToggleShowGridLineNumbers,
+            onToggleShowInfiniteLines,
+          })
+        )
       )
       :
       dom.div(
         {
           className: "layout-no-grids",
         },
         getStr("layout.noGrids")
       );
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/layout/components/GridOutline.js
@@ -0,0 +1,123 @@
+/* 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/. */
+
+"use strict";
+
+const { addons, createClass, DOM: dom, PropTypes } =
+  require("devtools/client/shared/vendor/react");
+
+const Types = require("../types");
+
+// Move SVG grid to the right 100 units, so that it is not flushed against the edge of
+// layout border
+const TRANSLATE_X = -100;
+const TRANSLATE_Y = 0;
+
+const VIEWPORT_HEIGHT = 100;
+const VIEWPORT_WIDTH = 450;
+
+module.exports = createClass({
+
+  displayName: "GridOutline",
+
+  propTypes: {
+    grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
+  },
+
+  mixins: [ addons.PureRenderMixin ],
+
+  getInitialState() {
+    return {
+      selectedGrids: [],
+    };
+  },
+
+  componentWillReceiveProps({ grids }) {
+    this.setState({
+      selectedGrids: grids.filter(grid => grid.highlighted),
+    });
+  },
+
+  renderGridCell(columnNumber, rowNumber, color) {
+    return dom.rect(
+      {
+        className: "grid-outline-cell",
+        x: columnNumber,
+        y: rowNumber,
+        width: 10,
+        height: 10,
+        stroke: color,
+      }
+    );
+  },
+
+  renderGridFragment({ color, gridFragments }) {
+    // TODO: We are drawing the first fragment since only one is currently being stored.
+    // In future we will need to iterate over all fragments of a grid.
+    const { rows, cols } = gridFragments[0];
+    const numOfColLines = cols.lines.length - 1;
+    const numOfRowLines = rows.lines.length - 1;
+    const rectangles = [];
+
+    // Draw a rectangle that acts as a border for the grid outline
+    const border = this.renderGridOutlineBorder(numOfRowLines, numOfColLines, color);
+    rectangles.push(border);
+
+    let x = 1;
+    let y = 1;
+    const width = 10;
+    const height = 10;
+
+    // Draw the cells within the grid outline border
+    for (let row = 0; row < numOfRowLines; row++) {
+      for (let col = 0; col < numOfColLines; col++) {
+        rectangles.push(this.renderGridCell(x, y, color));
+        x += width;
+      }
+
+      x = 1;
+      y += height;
+    }
+
+    return rectangles;
+  },
+
+  renderGridOutline(gridFragments) {
+    return dom.g(
+      {
+        className: "grid-cell-group",
+      },
+      gridFragments.map(gridFragment => this.renderGridFragment(gridFragment))
+    );
+  },
+
+  renderGridOutlineBorder(numberOfRows, numberOfCols, color) {
+    return dom.rect(
+      {
+        className: "grid-outline-border",
+        x: 1,
+        y: 1,
+        width: numberOfCols * 10,
+        height: numberOfRows * 10,
+        stroke: color,
+      }
+    );
+  },
+
+  render() {
+    return this.state.selectedGrids.length ?
+      dom.svg(
+        {
+          className: "grid-outline",
+          width: "100%",
+          height: 100,
+          viewBox: `${TRANSLATE_X} ${TRANSLATE_Y} ${VIEWPORT_WIDTH} ${VIEWPORT_HEIGHT}`,
+        },
+        this.renderGridOutline(this.state.selectedGrids)
+      )
+      :
+      null;
+  },
+
+});
--- a/devtools/client/inspector/layout/components/moz.build
+++ b/devtools/client/inspector/layout/components/moz.build
@@ -7,9 +7,10 @@
 DevToolsModules(
     'Accordion.css',
     'Accordion.js',
     'App.js',
     'Grid.js',
     'GridDisplaySettings.js',
     'GridItem.js',
     'GridList.js',
+    'GridOutline.js',
 )
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -24,16 +24,17 @@ const {
 
 const App = createFactory(require("./components/App"));
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
 
 const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers";
+const SHOW_GRID_OUTLINE_PREF = "devtools.gridinspector.showGridOutline";
 const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines";
 
 // Default grid colors.
 const GRID_COLORS = [
   "#05E4EE",
   "#BB9DFF",
   "#FFB53B",
   "#71F362",
@@ -109,16 +110,22 @@ LayoutView.prototype = {
       },
 
       /**
        * Shows the box model properties under the box model if true, otherwise, hidden by
        * default.
        */
       showBoxModelProperties: true,
 
+      /**
+       * Shows the grid outline if user preferences are set to true, otherwise, hidden by
+       * default.
+       */
+      showGridOutline: Services.prefs.getBoolPref(SHOW_GRID_OUTLINE_PREF),
+
       onHideBoxModelHighlighter,
 
       /**
        * Handler for a change in the grid overlay color picker for a grid container.
        *
        * @param  {NodeFront} node
        *         The NodeFront of the grid container element for which the grid color is
        *         being updated.
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -69,16 +69,17 @@ pref("devtools.inspector.colorWidget.ena
 // Enable the Font Inspector
 pref("devtools.fontinspector.enabled", true);
 
 // Enable the Layout View
 pref("devtools.layoutview.enabled", false);
 
 // Grid highlighter preferences
 pref("devtools.gridinspector.showGridLineNumbers", false);
+pref("devtools.gridinspector.showGridOutline", false);
 pref("devtools.gridinspector.showInfiniteLines", false);
 
 // By how many times eyedropper will magnify pixels
 pref("devtools.eyedropper.zoom", 6);
 
 // Enable to collapse attributes that are too long.
 pref("devtools.markup.collapseAttributes", true);
 
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -35,20 +35,49 @@
 }
 
 /**
  * Grid Container
  */
 
 #layout-grid-container {
   display: flex;
+  flex-direction: column;
   margin: 5px;
 }
 
 /**
+ * Grid Content
+ */
+
+.grid-content {
+  display: flex;
+  flex: 1;
+}
+
+/**
+ * Grid Outline
+ */
+
+#grid-outline {
+  margin: 5px auto;
+}
+
+.grid-outline-border {
+  stroke-width: 0.75;
+  fill: none;
+}
+
+.grid-outline-cell {
+  fill: none;
+  pointer-events: all;
+  stroke-dasharray: 0.5, 2;
+}
+
+/**
  * Container when no grids are present
  */
 
 .layout-no-grids {
   font-style: italic;
   text-align: center;
   padding: 0.5em;
 }
--- a/devtools/client/webconsole/test/browser.ini
+++ b/devtools/client/webconsole/test/browser.ini
@@ -176,16 +176,17 @@ skip-if = (os == 'linux' && bits == 32 &
 [browser_console_error_source_click.js]
 [browser_console_filters.js]
 [browser_console_iframe_messages.js]
 [browser_console_keyboard_accessibility.js]
 [browser_console_log_inspectable_object.js]
 [browser_console_native_getters.js]
 [browser_console_navigation_marker.js]
 [browser_console_netlogging.js]
+skip-if = true # Bug 1298364
 [browser_console_nsiconsolemessage.js]
 [browser_console_optimized_out_vars.js]
 [browser_console_private_browsing.js]
 skip-if = e10s # Bug 1042253 - webconsole e10s tests
 [browser_console_server_logging.js]
 [browser_console_variables_view.js]
 [browser_console_variables_view_filter.js]
 [browser_console_variables_view_dom_nodes.js]
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -2829,19 +2829,17 @@ nsGlobalWindow::SetNewDocument(nsIDocume
   AutoJSAPI jsapi;
   jsapi.Init();
   JSContext *cx = jsapi.cx();
 
   // Check if we're anywhere near the stack limit before we reach the
   // transplanting code, since it has no good way to handle errors. This uses
   // the untrusted script limit, which is not strictly necessary since no
   // actual script should run.
-  bool overrecursed = false;
-  JS_CHECK_RECURSION_CONSERVATIVE_DONT_REPORT(cx, overrecursed = true);
-  if (overrecursed) {
+  if (!js::CheckRecursionLimitConservativeDontReport(cx)) {
     NS_WARNING("Overrecursion in SetNewDocument");
     return NS_ERROR_FAILURE;
   }
 
   if (!mDoc) {
     // First document load.
 
     // Get our private root. If it is equal to us, then we need to
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -2114,17 +2114,19 @@ nsresult
 ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObjArg)
 {
   js::AssertSameCompartment(aCx, aObjArg);
 
   // Check if we're anywhere near the stack limit before we reach the
   // transplanting code, since it has no good way to handle errors. This uses
   // the untrusted script limit, which is not strictly necessary since no
   // actual script should run.
-  JS_CHECK_RECURSION_CONSERVATIVE(aCx, return NS_ERROR_FAILURE);
+  if (!js::CheckRecursionLimitConservative(aCx)) {
+    return NS_ERROR_FAILURE;
+  }
 
   JS::Rooted<JSObject*> aObj(aCx, aObjArg);
   const DOMJSClass* domClass = GetDOMClass(aObj);
 
   // DOM things are always parented to globals.
   JS::Rooted<JSObject*> oldParent(aCx,
                                   js::GetGlobalForObjectCrossCompartment(aObj));
   MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(oldParent) == oldParent);
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -109,17 +109,19 @@ js::obj_propertyIsEnumerable(JSContext* 
     return true;
 }
 
 #if JS_HAS_TOSOURCE
 static bool
 obj_toSource(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    JS_CHECK_RECURSION(cx, return false);
+
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     JSString* str = ObjectToSource(cx, obj);
     if (!str)
         return false;
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -2367,17 +2367,19 @@ ASTSerializer::classDefinition(ParseNode
     return optExpression(pn->pn_kid2, &heritage) &&
            statement(pn->pn_kid3, &classBody) &&
            builder.classDefinition(expr, className, heritage, classBody, &pn->pn_pos, dst);
 }
 
 bool
 ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
+
     switch (pn->getKind()) {
       case PNK_FUNCTION:
       case PNK_VAR:
         return declaration(pn, dst);
 
       case PNK_LET:
       case PNK_CONST:
         return declaration(pn, dst);
@@ -2806,17 +2808,19 @@ ASTSerializer::generatorExpression(Parse
 
     return expression(next->pn_kid->pn_left, &body) &&
            builder.generatorExpression(body, blocks, filter, isLegacy, &pn->pn_pos, dst);
 }
 
 bool
 ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
+
     switch (pn->getKind()) {
       case PNK_FUNCTION:
       {
         ASTType type = pn->pn_funbox->function()->isArrow() ? AST_ARROW_EXPR : AST_FUNC_EXPR;
         return function(pn, type, dst);
       }
 
       case PNK_COMMA:
@@ -3369,17 +3373,19 @@ ASTSerializer::objectPattern(ParseNode* 
     }
 
     return builder.objectPattern(elts, &pn->pn_pos, dst);
 }
 
 bool
 ASTSerializer::pattern(ParseNode* pn, MutableHandleValue dst)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
+
     switch (pn->getKind()) {
       case PNK_OBJECT:
         return objectPattern(pn, dst);
 
       case PNK_ARRAY:
         return arrayPattern(pn, dst);
 
       default:
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -239,19 +239,19 @@ class LoopControl : public BreakableCont
         MOZ_ASSERT(is<LoopControl>());
 
         LoopControl* enclosingLoop = findNearest<LoopControl>(enclosing());
 
         stackDepth_ = bce->stackDepth;
         loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1;
 
         int loopSlots;
-        if (loopKind == StatementKind::Spread)
+        if (loopKind == StatementKind::Spread || loopKind == StatementKind::ForOfLoop)
             loopSlots = 3;
-        else if (loopKind == StatementKind::ForInLoop || loopKind == StatementKind::ForOfLoop)
+        else if (loopKind == StatementKind::ForInLoop)
             loopSlots = 2;
         else
             loopSlots = 0;
 
         MOZ_ASSERT(loopSlots <= stackDepth_);
 
         if (enclosingLoop) {
             canIonOsr_ = (enclosingLoop->canIonOsr_ &&
@@ -273,74 +273,16 @@ class LoopControl : public BreakableCont
         MOZ_ASSERT(continueTarget.offset != -1);
         if (!patchBreaks(bce))
             return false;
         bce->patchJumpsToTarget(continues, continueTarget);
         return true;
     }
 };
 
-class ForOfLoopControl : public LoopControl
-{
-    // The stack depth of the iterator.
-    int32_t iterDepth_;
-
-    // for-of loops, when throwing from non-iterator code (i.e. from the body
-    // or from evaluating the LHS of the loop condition), need to call
-    // IteratorClose. If IteratorClose itself throws, we must not re-call
-    // IteratorClose. Since non-local jumps like break and return call
-    // IteratorClose, whenever a non-local jump is emitted, we must terminate
-    // the current JSTRY_ITERCLOSE note to skip the non-local jump code, then
-    // start a new one.
-    //
-    // Visually,
-    //
-    //   for (x of y) {
-    //     ...             instantiate ForOfLoopControl
-    //     ...         +   <-- iterCloseTryStart_ points to right before
-    //     ...                 assignment to loop variable
-    //     ...         ^
-    //     ...         |
-    //     if (...)    v
-    //                 +   call finishIterCloseTryNote before |break|
-    //                     above range is noted with JSTRY_ITERCLOSE
-    //
-    //       break;    <-- break and IteratorClose are not inside
-    //                     JSTRY_ITERCLOSE note
-    //
-    //                     call startNewIterCloseTryNote after |break|
-    //                 +   <-- next iterCloseTryStart_ points here
-    //     ...         |
-    //     ...         ~
-    //   }
-    ptrdiff_t iterCloseTryStart_;
-
-  public:
-    ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth)
-      : LoopControl(bce, StatementKind::ForOfLoop),
-        iterDepth_(iterDepth),
-        iterCloseTryStart_(-1)
-    {
-        MOZ_ASSERT(bce->stackDepth >= iterDepth);
-    }
-
-    MOZ_MUST_USE bool finishIterCloseTryNote(BytecodeEmitter* bce) {
-        ptrdiff_t end = bce->offset();
-        MOZ_ASSERT(end >= iterCloseTryStart_);
-        if (end != iterCloseTryStart_)
-            return bce->tryNoteList.append(JSTRY_ITERCLOSE, iterDepth_, iterCloseTryStart_, end);
-        return true;
-    }
-
-    void startNewIterCloseTryNote(BytecodeEmitter* bce) {
-        MOZ_ASSERT(bce->offset() > iterCloseTryStart_);
-        iterCloseTryStart_ = bce->offset();
-    }
-};
-
 class TryFinallyControl : public BytecodeEmitter::NestableControl
 {
     bool emittingSubroutine_;
 
   public:
     // The subroutine when emitting a finally block.
     JumpList gosubs;
 
@@ -2052,16 +1994,154 @@ class MOZ_STACK_CLASS IfThenElseEmitter
     }
 
     int32_t popped() const {
         return -pushed_;
     }
 #endif
 };
 
+class ForOfLoopControl : public LoopControl
+{
+    // The stack depth of the iterator.
+    int32_t iterDepth_;
+
+    // for-of loops, when throwing from non-iterator code (i.e. from the body
+    // or from evaluating the LHS of the loop condition), need to call
+    // IteratorClose.  This is done by enclosing non-iterator code with
+    // try-catch and call IteratorClose in `catch` block.
+    // If IteratorClose itself throws, we must not re-call IteratorClose. Since
+    // non-local jumps like break and return call IteratorClose, whenever a
+    // non-local jump is emitted, we must tell catch block not to perform
+    // IteratorClose.
+    //
+    //   for (x of y) {
+    //     // Operations for iterator (IteratorNext etc) are outside of
+    //     // try-block.
+    //     try {
+    //       ...
+    //       if (...) {
+    //         // Before non-local jump, clear iterator on the stack to tell
+    //         // catch block not to perform IteratorClose.
+    //         tmpIterator = iterator;
+    //         iterator = undefined;
+    //         IteratorClose(tmpIterator, { break });
+    //         break;
+    //       }
+    //       ...
+    //     } catch (e) {
+    //       // Just throw again when iterator is cleared by non-local jump.
+    //       if (iterator === undefined)
+    //         throw e;
+    //       IteratorClose(iterator, { throw, e });
+    //     }
+    //   }
+    Maybe<TryEmitter> tryCatch_;
+
+    bool allowSelfHosted_;
+
+  public:
+    ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted)
+      : LoopControl(bce, StatementKind::ForOfLoop),
+        iterDepth_(iterDepth),
+        allowSelfHosted_(allowSelfHosted)
+    {
+    }
+
+    bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) {
+        tryCatch_.emplace(bce, TryEmitter::TryCatch, TryEmitter::DontUseRetVal);
+
+        if (!tryCatch_->emitTry())
+            return false;
+        return true;
+    }
+
+    bool emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) {
+        if (!tryCatch_->emitCatch())              // ITER ...
+            return false;
+
+        if (!bce->emit1(JSOP_EXCEPTION))          // ITER ... EXCEPTION
+            return false;
+        unsigned slotFromTop = bce->stackDepth - iterDepth_;
+        if (!bce->emitDupAt(slotFromTop))         // ITER ... EXCEPTION ITER
+            return false;
+
+        // If ITER is undefined, it means the exception is thrown by
+        // IteratorClose for non-local jump, and we should't perform
+        // IteratorClose again here.
+        if (!bce->emit1(JSOP_UNDEFINED))          // ITER ... EXCEPTION ITER UNDEF
+            return false;
+        if (!bce->emit1(JSOP_STRICTNE))           // ITER ... EXCEPTION NE
+            return false;
+
+        IfThenElseEmitter ifIteratorIsNotClosed(bce);
+        if (!ifIteratorIsNotClosed.emitIf())      // ITER ... EXCEPTION
+            return false;
+
+        MOZ_ASSERT(slotFromTop == unsigned(bce->stackDepth - iterDepth_));
+        if (!bce->emitDupAt(slotFromTop))         // ITER ... EXCEPTION ITER
+            return false;
+        if (!emitIteratorClose(bce, CompletionKind::Throw)) // ITER ... EXCEPTION
+            return false;
+
+        if (!ifIteratorIsNotClosed.emitEnd())     // ITER ... EXCEPTION
+            return false;
+
+        if (!bce->emit1(JSOP_THROW))              // ITER ...
+            return false;
+
+        if (!tryCatch_->emitEnd())
+            return false;
+
+        tryCatch_.reset();
+        return true;
+    }
+
+    bool emitIteratorClose(BytecodeEmitter* bce,
+                           CompletionKind completionKind = CompletionKind::Normal) {
+        return bce->emitIteratorClose(completionKind, allowSelfHosted_);
+    }
+
+    bool emitPrepareForNonLocalJump(BytecodeEmitter* bce, bool isTarget) {
+        // Pop unnecessary values from the stack.  Effectively this means
+        // leaving try-catch block.  However, the performing IteratorClose can
+        // reach the depth for try-catch, and effectively re-enter the
+        // try-catch block.
+        if (!bce->emit1(JSOP_POP))                        // ITER RESULT
+            return false;
+        if (!bce->emit1(JSOP_POP))                        // ITER
+            return false;
+
+        // Clear ITER slot on the stack to tell catch block to avoid performing
+        // IteratorClose again.
+        if (!bce->emit1(JSOP_UNDEFINED))                  // ITER UNDEF
+            return false;
+        if (!bce->emit1(JSOP_SWAP))                       // UNDEF ITER
+            return false;
+
+        if (!emitIteratorClose(bce))                      // UNDEF
+            return false;
+
+        if (isTarget) {
+            // At the level of the target block, there's bytecode after the
+            // loop that will pop the iterator and the value, so push
+            // undefineds to balance the stack.
+            if (!bce->emit1(JSOP_UNDEFINED))              // UNDEF UNDEF
+                return false;
+            if (!bce->emit1(JSOP_UNDEFINED))              // UNDEF UNDEF UNDEF
+                return false;
+        } else {
+            if (!bce->emit1(JSOP_POP))                    //
+                return false;
+        }
+
+        return true;
+    }
+};
+
 BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
                                  Parser<FullParseHandler>* parser, SharedContext* sc,
                                  HandleScript script, Handle<LazyScript*> lazyScript,
                                  uint32_t lineNum, EmitterMode emitterMode)
   : sc(sc),
     cx(sc->context),
     parent(parent),
     script(cx, script),
@@ -2410,16 +2490,22 @@ BytecodeEmitter::emitDupAt(unsigned slot
 }
 
 bool
 BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind)
 {
     return emit2(JSOP_CHECKISOBJ, uint8_t(kind));
 }
 
+bool
+BytecodeEmitter::emitCheckIsCallable(CheckIsCallableKind kind)
+{
+    return emit2(JSOP_CHECKISCALLABLE, uint8_t(kind));
+}
+
 static inline unsigned
 LengthOfSetLine(unsigned line)
 {
     return 1 /* SN_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1);
 }
 
 /* Updates line number notes, not column notes. */
 bool
@@ -2651,20 +2737,18 @@ NonLocalExitControl::leaveScope(Bytecode
 bool
 NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* target)
 {
     using NestableControl = BytecodeEmitter::NestableControl;
     using EmitterScope = BytecodeEmitter::EmitterScope;
 
     EmitterScope* es = bce_->innermostEmitterScope;
     int npops = 0;
-    bool hasForOfLoopsWithIteratorClose = false;
-
-    // IteratorClose is handled specially in the exception unwinder. For
-    // 'continue', 'break', and 'return' statements, emit IteratorClose
+
+    // For 'continue', 'break', and 'return' statements, emit IteratorClose
     // bytecode inline. 'continue' statements do not call IteratorClose for
     // the loop they are continuing.
     bool emitIteratorClose = kind_ == Continue || kind_ == Break || kind_ == Return;
     bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue;
 
     auto flushPops = [&npops](BytecodeEmitter* bce) {
         if (npops && !bce->flushPops(&npops))
             return false;
@@ -2691,41 +2775,32 @@ NonLocalExitControl::prepareForNonLocalJ
                 /*
                  * There's a [exception or hole, retsub pc-index] pair and the
                  * possible return value on the stack that we need to pop.
                  */
                 npops += 3;
             } else {
                 if (!flushPops(bce_))
                     return false;
-                if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs))
+                if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) // ...
                     return false;
             }
             break;
           }
 
           case StatementKind::ForOfLoop:
-            if (!flushPops(bce_))
-                return false;
-
-            // The iterator and the current value are on the stack.
-            //
             if (emitIteratorClose) {
-                hasForOfLoopsWithIteratorClose = true;
-                if (!control->as<ForOfLoopControl>().finishIterCloseTryNote(bce_))
+                if (!flushPops(bce_))
                     return false;
-                if (!bce_->emit1(JSOP_POP))               // ... ITER
-                    return false;
-                if (!bce_->emitIteratorClose())           // ...
+
+                ForOfLoopControl& loopinfo = control->as<ForOfLoopControl>();
+                if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ false)) // ...
                     return false;
             } else {
-                if (!bce_->emit1(JSOP_POP))               // ... ITER
-                    return false;
-                if (!bce_->emit1(JSOP_POP))               // ...
-                    return false;
+                npops += 3;
             }
             break;
 
           case StatementKind::ForInLoop:
             if (!flushPops(bce_))
                 return false;
 
             // The iterator and the current value are on the stack.
@@ -2738,51 +2813,28 @@ NonLocalExitControl::prepareForNonLocalJ
           default:
             break;
         }
     }
 
     if (!flushPops(bce_))
         return false;
 
-    if (target && target->is<ForOfLoopControl>() && emitIteratorCloseAtTarget) {
-        hasForOfLoopsWithIteratorClose = true;
-        if (!target->as<ForOfLoopControl>().finishIterCloseTryNote(bce_))
-            return false;
-
-        // The iterator and the current value are on the stack. At the level
-        // of the target block, there's bytecode after the loop that will pop
-        // the iterator and the value, so duplicate the iterator and call
-        // IteratorClose.
-        if (!bce_->emitDupAt(1))                          // ... ITER RESULT ITER
-            return false;
-        if (!bce_->emitIteratorClose())                   // ... ITER RESULT
+    if (target && emitIteratorCloseAtTarget && target->is<ForOfLoopControl>()) {
+        ForOfLoopControl& loopinfo = target->as<ForOfLoopControl>();
+        if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ true)) // ... UNDEF UNDEF UNDEF
             return false;
     }
 
     EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope;
     for (; es != targetEmitterScope; es = es->enclosingInFrame()) {
         if (!leaveScope(es))
             return false;
     }
 
-    // See comment in ForOfLoopControl.
-    if (hasForOfLoopsWithIteratorClose) {
-        for (NestableControl* control = bce_->innermostNestableControl;
-             control != target;
-             control = control->enclosing())
-        {
-            if (control->is<ForOfLoopControl>())
-                control->as<ForOfLoopControl>().startNewIterCloseTryNote(bce_);
-        }
-
-        if (target && target->is<ForOfLoopControl>() && emitIteratorCloseAtTarget)
-            target->as<ForOfLoopControl>().startNewIterCloseTryNote(bce_);
-    }
-
     return true;
 }
 
 }  // anonymous namespace
 
 bool
 BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteType noteType)
 {
@@ -2986,17 +3038,18 @@ BytecodeEmitter::strictifySetNameOp(JSOp
         default:;
     }
     return op;
 }
 
 bool
 BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
  restart:
 
     switch (pn->getKind()) {
       // Trivial cases with no side effects.
       case PNK_NOP:
       case PNK_STRING:
       case PNK_TEMPLATE_STRING:
@@ -5176,17 +5229,17 @@ BytecodeEmitter::emitSetOrInitializeDest
         if (!emit1(JSOP_POP))
             return false;
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted)
+BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted /* = false */)
 {
     MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting,
                ".next() iteration is prohibited in self-hosted code because it "
                "can run user-modifiable iteration code");
 
     if (!emit1(JSOP_DUP))                                 // ... ITER ITER
         return false;
     if (!emitAtomOp(cx->names().next, JSOP_CALLPROP))     // ... ITER NEXT
@@ -5197,17 +5250,18 @@ BytecodeEmitter::emitIteratorNext(ParseN
         return false;
     if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ... RESULT
         return false;
     checkTypeSet(JSOP_CALL);
     return true;
 }
 
 bool
-BytecodeEmitter::emitIteratorClose(bool allowSelfHosted)
+BytecodeEmitter::emitIteratorClose(CompletionKind completionKind /* = CompletionKind::Normal */,
+                                   bool allowSelfHosted /* = false */)
 {
     MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting,
                ".close() on iterators is prohibited in self-hosted code because it "
                "can run user-modifiable iteration code");
 
     // Generate inline logic corresponding to IteratorClose (ES 7.4.6).
     //
     // Callers need to ensure that the iterator object is at the top of the
@@ -5230,27 +5284,96 @@ BytecodeEmitter::emitIteratorClose(bool 
         return false;
     if (!emit1(JSOP_UNDEFINED))                           // ... ITER RET RET UNDEFINED
         return false;
     if (!emit1(JSOP_NE))                                  // ... ITER RET ?NEQL
         return false;
     if (!ifReturnMethodIsDefined.emitIfElse())
         return false;
 
+    if (completionKind == CompletionKind::Throw) {
+        // 7.4.6 IteratorClose ( iterator, completion )
+        //   ...
+        //   3. Let return be ? GetMethod(iterator, "return").
+        //   4. If return is undefined, return Completion(completion).
+        //   5. Let innerResult be Call(return, iterator, « »).
+        //   6. If completion.[[Type]] is throw, return Completion(completion).
+        //   7. If innerResult.[[Type]] is throw, return
+        //      Completion(innerResult).
+        //
+        // For CompletionKind::Normal case, JSOP_CALL for step 5 checks if RET
+        // is callable, and throws if not.  Since step 6 doesn't match and
+        // error handling in step 3 and step 7 can be merged.
+        //
+        // For CompletionKind::Throw case, an error thrown by JSOP_CALL for
+        // step 5 is ignored by try-catch.  So we should check if RET is
+        // callable here, outside of try-catch, and the throw immediately if
+        // not.
+        CheckIsCallableKind kind = CheckIsCallableKind::IteratorReturn;
+        if (!emitCheckIsCallable(kind))                   // ... ITER RET
+            return false;
+    }
+
     // Steps 5, 8.
     //
     // Call "return" if it is not undefined or null, and check that it returns
     // an Object.
     if (!emit1(JSOP_SWAP))                                // ... RET ITER
         return false;
-    if (!emitCall(JSOP_CALL, 0))                          // ... RESULT
+
+    Maybe<TryEmitter> tryCatch;
+
+    if (completionKind == CompletionKind::Throw) {
+        tryCatch.emplace(this, TryEmitter::TryCatch, TryEmitter::DontUseRetVal,
+                         TryEmitter::DontUseControl);
+
+        // Mutate stack to balance stack for try-catch.
+        if (!emit1(JSOP_UNDEFINED))                       // ... RET ITER UNDEF
+            return false;
+        if (!tryCatch->emitTry())                         // ... RET ITER UNDEF
+            return false;
+        if (!emitDupAt(2))                                // ... RET ITER UNDEF RET
+            return false;
+        if (!emitDupAt(2))                                // ... RET ITER UNDEF RET ITER
+            return false;
+    }
+
+    if (!emitCall(JSOP_CALL, 0))                          // ... ... RESULT
         return false;
     checkTypeSet(JSOP_CALL);
-    if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT
-        return false;
+
+    if (completionKind == CompletionKind::Throw) {
+        if (!emit1(JSOP_SWAP))                            // ... RET ITER RESULT UNDEF
+            return false;
+        if (!emit1(JSOP_POP))                             // ... RET ITER RESULT
+            return false;
+
+        if (!tryCatch->emitCatch())                       // ... RET ITER RESULT
+            return false;
+
+        // Just ignore the exception thrown by call.
+        if (!emit1(JSOP_EXCEPTION))                       // ... RET ITER RESULT EXC
+            return false;
+        if (!emit1(JSOP_POP))                             // ... RET ITER RESULT
+            return false;
+
+        if (!tryCatch->emitEnd())                         // ... RET ITER RESULT
+            return false;
+
+        // Restore stack.
+        if (!emit2(JSOP_UNPICK, 2))                       // ... RESULT RET ITER
+            return false;
+        if (!emit1(JSOP_POP))                             // ... RESULT RET
+            return false;
+        if (!emit1(JSOP_POP))                             // ... RESULT
+            return false;
+    } else {
+        if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT
+            return false;
+    }
 
     if (!ifReturnMethodIsDefined.emitElse())
         return false;
     if (!emit1(JSOP_POP))                                 // ... ITER
         return false;
     if (!ifReturnMethodIsDefined.emitEnd())
         return false;
 
@@ -6797,60 +6920,73 @@ BytecodeEmitter::emitForOf(ParseNode* fo
 {
     MOZ_ASSERT(forOfLoop->isKind(PNK_FOR));
     MOZ_ASSERT(forOfLoop->isArity(PN_BINARY));
 
     ParseNode* forOfHead = forOfLoop->pn_left;
     MOZ_ASSERT(forOfHead->isKind(PNK_FOROF));
     MOZ_ASSERT(forOfHead->isArity(PN_TERNARY));
 
+    ParseNode* forHeadExpr = forOfHead->pn_kid3;
+
+    // Certain builtins (e.g. Array.from) are implemented in self-hosting
+    // as for-of loops.
+    bool allowSelfHostedIter = false;
+    if (emitterMode == BytecodeEmitter::SelfHosting &&
+        forHeadExpr->isKind(PNK_CALL) &&
+        forHeadExpr->pn_head->name() == cx->names().allowContentIter)
+    {
+        allowSelfHostedIter = true;
+    }
+
     // Evaluate the expression being iterated.
-    ParseNode* forHeadExpr = forOfHead->pn_kid3;
     if (!emitTree(forHeadExpr))                           // ITERABLE
         return false;
     if (!emitIterator())                                  // ITER
         return false;
 
     int32_t iterDepth = stackDepth;
 
-    // For-of loops have both the iterator and the value on the stack. Push
-    // undefined to balance the stack.
+    // For-of loops have both the iterator, the result, and the result.value
+    // on the stack. Push undefineds to balance the stack.
     if (!emit1(JSOP_UNDEFINED))                           // ITER RESULT
         return false;
-
-    ForOfLoopControl loopInfo(this, iterDepth);
+    if (!emit1(JSOP_UNDEFINED))                           // ITER RESULT UNDEF
+        return false;
+
+    ForOfLoopControl loopInfo(this, iterDepth, allowSelfHostedIter);
 
     // Annotate so IonMonkey can find the loop-closing jump.
     unsigned noteIndex;
     if (!newSrcNote(SRC_FOR_OF, &noteIndex))
         return false;
 
     JumpList initialJump;
-    if (!emitJump(JSOP_GOTO, &initialJump))               // ITER RESULT
+    if (!emitJump(JSOP_GOTO, &initialJump))               // ITER RESULT UNDEF
         return false;
 
     JumpTarget top{ -1 };
-    if (!emitLoopHead(nullptr, &top))                     // ITER RESULT
+    if (!emitLoopHead(nullptr, &top))                     // ITER RESULT UNDEF
         return false;
 
     // If the loop had an escaping lexical declaration, replace the current
     // environment with an dead zoned one to implement TDZ semantics.
     if (headLexicalEmitterScope) {
         // The environment chain only includes an environment for the for-of
         // loop head *if* a scope binding is captured, thereby requiring
         // recreation each iteration. If a lexical scope exists for the head,
         // it must be the innermost one. If that scope has closed-over
         // bindings inducing an environment, recreate the current environment.
         DebugOnly<ParseNode*> forOfTarget = forOfHead->pn_kid1;
         MOZ_ASSERT(forOfTarget->isKind(PNK_LET) || forOfTarget->isKind(PNK_CONST));
         MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope);
         MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical);
 
         if (headLexicalEmitterScope->hasEnvironment()) {
-            if (!emit1(JSOP_RECREATELEXICALENV))          // ITER RESULT
+            if (!emit1(JSOP_RECREATELEXICALENV))          // ITER RESULT UNDEF
                 return false;
         }
 
         // For uncaptured bindings, put them back in TDZ.
         if (!headLexicalEmitterScope->deadZoneFrameSlots(this))
             return false;
     }
 
@@ -6860,86 +6996,91 @@ BytecodeEmitter::emitForOf(ParseNode* fo
 #ifdef DEBUG
         auto loopDepth = this->stackDepth;
 #endif
 
         // Emit code to assign result.value to the iteration variable.
         //
         // Note that ES 13.7.5.13, step 5.c says getting result.value does not
         // call IteratorClose, so start JSTRY_ITERCLOSE after the GETPROP.
+        if (!emit1(JSOP_POP))                             // ITER RESULT
+            return false;
         if (!emit1(JSOP_DUP))                             // ITER RESULT RESULT
             return false;
         if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE
             return false;
 
-        loopInfo.startNewIterCloseTryNote(this);
+        if (!loopInfo.emitBeginCodeNeedingIteratorClose(this))
+            return false;
 
         if (!emitInitializeForInOrOfTarget(forOfHead))    // ITER RESULT VALUE
             return false;
 
-        if (!emit1(JSOP_POP))                             // ITER RESULT
-            return false;
-
         MOZ_ASSERT(stackDepth == loopDepth,
                    "the stack must be balanced around the initializing "
                    "operation");
 
+        // Remove VALUE from the stack to release it.
+        if (!emit1(JSOP_POP))                             // ITER RESULT
+            return false;
+        if (!emit1(JSOP_UNDEFINED))                       // ITER RESULT UNDEF
+            return false;
+
         // Perform the loop body.
         ParseNode* forBody = forOfLoop->pn_right;
-        if (!emitTree(forBody))                           // ITER RESULT
-            return false;
-
-        if (!loopInfo.finishIterCloseTryNote(this))
+        if (!emitTree(forBody))                           // ITER RESULT UNDEF
+            return false;
+
+        MOZ_ASSERT(stackDepth == loopDepth,
+                   "the stack must be balanced around the for-of body");
+
+        if (!loopInfo.emitEndCodeNeedingIteratorClose(this))
             return false;
 
         // Set offset for continues.
         loopInfo.continueTarget = { offset() };
 
-        if (!emitLoopEntry(forHeadExpr, initialJump))     // ITER RESULT
-            return false;
-
-        if (!emit1(JSOP_POP))                             // ITER
-            return false;
-        if (!emit1(JSOP_DUP))                             // ITER ITER
-            return false;
-
-        // Certain builtins (e.g. Array.from) are implemented in self-hosting
-        // as for-of loops.
-        bool allowSelfHostedIter = false;
-        if (emitterMode == BytecodeEmitter::SelfHosting &&
-            forHeadExpr->isKind(PNK_CALL) &&
-            forHeadExpr->pn_head->name() == cx->names().allowContentIter)
-        {
-            allowSelfHostedIter = true;
-        }
-
-        if (!emitIteratorNext(forOfHead, allowSelfHostedIter)) // ITER RESULT
-            return false;
-        if (!emit1(JSOP_DUP))                             // ITER RESULT RESULT
-            return false;
-        if (!emitAtomOp(cx->names().done, JSOP_GETPROP))  // ITER RESULT DONE
+        if (!emitLoopEntry(forHeadExpr, initialJump))     // ITER RESULT UNDEF
+            return false;
+
+        if (!emit1(JSOP_SWAP))                            // ITER UNDEF RESULT
+            return false;
+        if (!emit1(JSOP_POP))                             // ITER UNDEF
+            return false;
+        if (!emitDupAt(1))                                // ITER UNDEF ITER
+            return false;
+
+        if (!emitIteratorNext(forOfHead, allowSelfHostedIter)) // ITER UNDEF RESULT
+            return false;
+
+        if (!emit1(JSOP_SWAP))                            // ITER RESULT UNDEF
+            return false;
+
+        if (!emitDupAt(1))                                // ITER RESULT UNDEF RESULT
+            return false;
+        if (!emitAtomOp(cx->names().done, JSOP_GETPROP))  // ITER RESULT UNDEF DONE
             return false;
 
         if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget))
-            return false;                                 // ITER RESULT
+            return false;                                 // ITER RESULT UNDEF
 
         MOZ_ASSERT(this->stackDepth == loopDepth);
     }
 
     // Let Ion know where the closing jump of this loop is.
     if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset))
         return false;
 
     if (!loopInfo.patchBreaksAndContinues(this))
         return false;
 
     if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset))
         return false;
 
-    return emitUint16Operand(JSOP_POPN, 2);               //
+    return emitUint16Operand(JSOP_POPN, 3);               //
 }
 
 bool
 BytecodeEmitter::emitForIn(ParseNode* forInLoop, EmitterScope* headLexicalEmitterScope)
 {
     MOZ_ASSERT(forInLoop->isKind(PNK_FOR));
     MOZ_ASSERT(forInLoop->isArity(PN_BINARY));
     MOZ_ASSERT(forInLoop->isOp(JSOP_ITER));
@@ -7339,16 +7480,18 @@ BytecodeEmitter::emitComprehensionForOf(
     if (!emitTree(forHeadExpr))                // EXPR
         return false;
     if (!emitIterator())                       // ITER
         return false;
 
     // Push a dummy result so that we properly enter iteration midstream.
     if (!emit1(JSOP_UNDEFINED))                // ITER RESULT
         return false;
+    if (!emit1(JSOP_UNDEFINED))                // ITER RESULT VALUE
+        return false;
 
     // Enter the block before the loop body, after evaluating the obj.
     // Initialize let bindings with undefined when entering, as the name
     // assigned to is a plain assignment.
     TDZCheckCache tdzCache(this);
     Maybe<EmitterScope> emitterScope;
     ParseNode* loopVariableName;
     if (lexicalScope) {
@@ -7376,52 +7519,69 @@ BytecodeEmitter::emitComprehensionForOf(
     if (!emitLoopHead(nullptr, &top))
         return false;
 
 #ifdef DEBUG
     int loopDepth = this->stackDepth;
 #endif
 
     // Emit code to assign result.value to the iteration variable.
+    if (!emit1(JSOP_POP))                                 // ITER RESULT
+        return false;
     if (!emit1(JSOP_DUP))                                 // ITER RESULT RESULT
         return false;
     if (!emitAtomOp(cx->names().value, JSOP_GETPROP))     // ITER RESULT VALUE
         return false;
+
+    // Notice: Comprehension for-of doesn't perform IteratorClose, since it's
+    // not in the spec.
+
     if (!emitAssignment(loopVariableName, JSOP_NOP, nullptr)) // ITER RESULT VALUE
         return false;
+
+    // Remove VALUE from the stack to release it.
     if (!emit1(JSOP_POP))                                 // ITER RESULT
         return false;
+    if (!emit1(JSOP_UNDEFINED))                           // ITER RESULT UNDEF
+        return false;
 
     // The stack should be balanced around the assignment opcode sequence.
     MOZ_ASSERT(this->stackDepth == loopDepth);
 
     // Emit code for the loop body.
-    if (!emitTree(forBody))
-        return false;
+    if (!emitTree(forBody))                               // ITER RESULT UNDEF
+        return false;
+
+    // The stack should be balanced around the assignment opcode sequence.
+    MOZ_ASSERT(this->stackDepth == loopDepth);
 
     // Set offset for continues.
     loopInfo.continueTarget = { offset() };
 
     if (!emitLoopEntry(forHeadExpr, jmp))
         return false;
 
-    if (!emit1(JSOP_POP))                                 // ITER
-        return false;
-    if (!emit1(JSOP_DUP))                                 // ITER ITER
-        return false;
-    if (!emitIteratorNext(forHead))                       // ITER RESULT
-        return false;
-    if (!emit1(JSOP_DUP))                                 // ITER RESULT RESULT
-        return false;
-    if (!emitAtomOp(cx->names().done, JSOP_GETPROP))      // ITER RESULT DONE
+    if (!emit1(JSOP_SWAP))                                // ITER UNDEF RESULT
+        return false;
+    if (!emit1(JSOP_POP))                                 // ITER UNDEF
+        return false;
+    if (!emitDupAt(1))                                    // ITER UNDEF ITER
+        return false;
+    if (!emitIteratorNext(forHead))                       // ITER UNDEF RESULT
+        return false;
+    if (!emit1(JSOP_SWAP))                                // ITER RESULT UNDEF
+        return false;
+    if (!emitDupAt(1))                                    // ITER RESULT UNDEF RESULT
+        return false;
+    if (!emitAtomOp(cx->names().done, JSOP_GETPROP))      // ITER RESULT UNDEF DONE
         return false;
 
     JumpList beq;
     JumpTarget breakTarget{ -1 };
-    if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT
+    if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT UNDEF
         return false;
 
     MOZ_ASSERT(this->stackDepth == loopDepth);
 
     // Let Ion know where the closing jump of this loop is.
     if (!setSrcNoteOffset(noteIndex, 0, beq.offset - jmp.offset))
         return false;
 
@@ -7433,17 +7593,17 @@ BytecodeEmitter::emitComprehensionForOf(
 
     if (emitterScope) {
         if (!emitterScope->leave(this))
             return false;
         emitterScope.reset();
     }
 
     // Pop the result and the iter.
-    return emitUint16Operand(JSOP_POPN, 2);               //
+    return emitUint16Operand(JSOP_POPN, 3);               //
 }
 
 bool
 BytecodeEmitter::emitComprehensionForIn(ParseNode* pn)
 {
     MOZ_ASSERT(pn->isKind(PNK_COMPREHENSIONFOR));
 
     ParseNode* forHead = pn->pn_left;
@@ -10103,17 +10263,18 @@ BytecodeEmitter::emitClass(ParseNode* pn
     MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness));
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     EmitLevelManager elm(this);
 
     /* Emit notes to tell the current bytecode's source line number.
        However, a couple trees require special treatment; see the
        relevant emitter functions for details. */
     if (emitLineNote == EMIT_LINENOTE && !ParseNodeRequiresSpecialLineNumberNotes(pn)) {
         if (!updateLineNumberNotes(pn->pn_pos.begin))
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -468,16 +468,19 @@ struct MOZ_STACK_CLASS BytecodeEmitter
 
     // Helper to emit JSOP_DUPAT. The argument is the value's depth on the
     // JS stack, as measured from the top.
     MOZ_MUST_USE bool emitDupAt(unsigned slotFromTop);
 
     // Helper to emit JSOP_CHECKISOBJ.
     MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind);
 
+    // Helper to emit JSOP_CHECKISCALLABLE.
+    MOZ_MUST_USE bool emitCheckIsCallable(CheckIsCallableKind kind);
+
     // Emit a bytecode followed by an uint16 immediate operand stored in
     // big-endian order.
     MOZ_MUST_USE bool emitUint16Operand(JSOp op, uint32_t operand);
 
     // Emit a bytecode followed by an uint32 immediate operand.
     MOZ_MUST_USE bool emitUint32Operand(JSOp op, uint32_t operand);
 
     // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand.
@@ -676,17 +679,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter
 
     // emitIterator expects the iterable to already be on the stack.
     // It will replace that stack value with the corresponding iterator
     MOZ_MUST_USE bool emitIterator();
 
     // Pops iterator from the top of the stack. Pushes the result of |.next()|
     // onto the stack.
     MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, bool allowSelfHosted = false);
-    MOZ_MUST_USE bool emitIteratorClose(bool allowSelfHosted = false);
+    MOZ_MUST_USE bool emitIteratorClose(CompletionKind completionKind = CompletionKind::Normal,
+                                        bool allowSelfHosted = false);
 
     template <typename InnerEmitter>
     MOZ_MUST_USE bool wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth,
                                                                 InnerEmitter emitter);
 
     // Check if the value on top of the stack is "undefined". If so, replace
     // that value on the stack with the value defined by |defaultExpr|.
     // |pattern| is a lhs node of the default expression.  If it's an
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -51,17 +51,18 @@ ListContainsHoistedDeclaration(JSContext
 //
 // THIS IS NOT A GENERAL-PURPOSE FUNCTION.  It is only written to work in the
 // specific context of deciding that |node|, as one arm of a PNK_IF controlled
 // by a constant condition, contains a declaration that forbids |node| being
 // completely eliminated as dead.
 static bool
 ContainsHoistedDeclaration(JSContext* cx, ParseNode* node, bool* result)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
   restart:
 
     // With a better-typed AST, we would have distinct parse node classes for
     // expressions and for statements and would characterize expressions with
     // ExpressionKind and statements with StatementKind.  Perhaps someday.  In
     // the meantime we must characterize every ParseNodeKind, even the
     // expression/sub-expression ones that, if we handle all statement kinds
@@ -1630,17 +1631,18 @@ FoldName(JSContext* cx, ParseNode* node,
         return true;
 
     return Fold(cx, &node->pn_expr, parser, inGenexpLambda);
 }
 
 bool
 Fold(JSContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bool inGenexpLambda)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     ParseNode* pn = *pnp;
 
     switch (pn->getKind()) {
       case PNK_NOP:
       case PNK_REGEXP:
       case PNK_STRING:
       case PNK_TRUE:
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -3929,17 +3929,18 @@ Parser<ParseHandler>::maybeParseDirectiv
     }
     return true;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::statementList(YieldHandling yieldHandling)
 {
-    JS_CHECK_RECURSION(context, return null());
+    if (!CheckRecursionLimit(context))
+        return null();
 
     Node pn = handler.newStatementList(pos());
     if (!pn)
         return null();
 
     bool canHaveDirectives = pc->atBodyLevel();
     if (canHaveDirectives)
         tokenStream.clearSawOctalEscape();
@@ -7158,17 +7159,18 @@ Parser<ParseHandler>::variableStatement(
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::statement(YieldHandling yieldHandling)
 {
     MOZ_ASSERT(checkOptionsCalled);
 
-    JS_CHECK_RECURSION(context, return null());
+    if (!CheckRecursionLimit(context))
+        return null();
 
     TokenKind tt;
     if (!tokenStream.getToken(&tt, TokenStream::Operand))
         return null();
 
     switch (tt) {
       // BlockStatement[?Yield, ?Return]
       case TOK_LC:
@@ -7357,17 +7359,18 @@ Parser<ParseHandler>::statement(YieldHan
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling,
                                         bool canHaveDirectives /* = false */)
 {
     MOZ_ASSERT(checkOptionsCalled);
 
-    JS_CHECK_RECURSION(context, return null());
+    if (!CheckRecursionLimit(context))
+        return null();
 
     TokenKind tt;
     if (!tokenStream.getToken(&tt, TokenStream::Operand))
         return null();
 
     switch (tt) {
       // BlockStatement[?Yield, ?Return]
       case TOK_LC:
@@ -7832,17 +7835,18 @@ class AutoClearInDestructuringDecl
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandling,
                                  TripledotHandling tripledotHandling,
                                  PossibleError* possibleError /* = nullptr */,
                                  InvokedPrediction invoked /* = PredictUninvoked */)
 {
-    JS_CHECK_RECURSION(context, return null());
+    if (!CheckRecursionLimit(context))
+        return null();
 
     // It's very common at this point to have a "detectably simple" expression,
     // i.e. a name/number/string token followed by one of the following tokens
     // that obviously isn't part of an expression: , ; : ) ] }
     //
     // (In Parsemark this happens 81.4% of the time;  in code with large
     // numeric arrays, such as some Kraken benchmarks, it happens more often.)
     //
@@ -8162,17 +8166,18 @@ Parser<ParseHandler>::unaryOpExpr(YieldH
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
                                 PossibleError* possibleError /* = nullptr */,
                                 InvokedPrediction invoked /* = PredictUninvoked */)
 {
-    JS_CHECK_RECURSION(context, return null());
+    if (!CheckRecursionLimit(context))
+        return null();
 
     TokenKind tt;
     if (!tokenStream.getToken(&tt, TokenStream::Operand))
         return null();
     uint32_t begin = pos().begin;
     switch (tt) {
       case TOK_VOID:
         return unaryOpExpr(yieldHandling, PNK_VOID, JSOP_VOID, begin);
@@ -8475,17 +8480,18 @@ Parser<ParseHandler>::comprehensionIf(Ge
 
     return handler.newIfStatement(begin, cond, then, null());
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::comprehensionTail(GeneratorKind comprehensionKind)
 {
-    JS_CHECK_RECURSION(context, return null());
+    if (!CheckRecursionLimit(context))
+        return null();
 
     bool matched;
     if (!tokenStream.matchToken(&matched, TOK_FOR, TokenStream::Operand))
         return null();
     if (matched)
         return comprehensionFor(comprehensionKind);
 
     if (!tokenStream.matchToken(&matched, TOK_IF, TokenStream::Operand))
@@ -8671,17 +8677,18 @@ Parser<ParseHandler>::memberExpr(YieldHa
                                  TokenKind tt, bool allowCallSyntax /* = true */,
                                  PossibleError* possibleError /* = nullptr */,
                                  InvokedPrediction invoked /* = PredictUninvoked */)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(tt));
 
     Node lhs;
 
-    JS_CHECK_RECURSION(context, return null());
+    if (!CheckRecursionLimit(context))
+        return null();
 
     /* Check for new expression first. */
     if (tt == TOK_NEW) {
         uint32_t newBegin = pos().begin;
         // Make sure this wasn't a |new.target| in disguise.
         Node newTarget;
         if (!tryNewTarget(newTarget))
             return null();
@@ -9670,17 +9677,18 @@ Parser<ParseHandler>::tryNewTarget(Node 
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
                                   TokenKind tt, PossibleError* possibleError,
                                   InvokedPrediction invoked /* = PredictUninvoked */)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(tt));
-    JS_CHECK_RECURSION(context, return null());
+    if (!CheckRecursionLimit(context))
+        return null();
 
     switch (tt) {
       case TOK_FUNCTION:
         return functionExpr(invoked);
 
       case TOK_CLASS:
         return classDefinition(yieldHandling, ClassExpression, NameRequired);
 
--- a/js/src/irregexp/RegExpEngine.cpp
+++ b/js/src/irregexp/RegExpEngine.cpp
@@ -1215,17 +1215,20 @@ LoopChoiceNode::FilterASCII(int depth, b
 }
 
 // -------------------------------------------------------------------
 // Analysis
 
 void
 Analysis::EnsureAnalyzed(RegExpNode* that)
 {
-    JS_CHECK_RECURSION(cx, failASCII("Stack overflow"); return);
+    if (!CheckRecursionLimit(cx)) {
+        failASCII("Stack overflow");
+        return;
+    }
 
     if (that->info()->been_analyzed || that->info()->being_analyzed)
         return;
     that->info()->being_analyzed = true;
     that->Accept(this);
     that->info()->being_analyzed = false;
     that->info()->been_analyzed = true;
 }
@@ -2491,17 +2494,21 @@ BoyerMooreLookahead::EmitSkipInstruction
     masm->Bind(&cont);
 
     return true;
 }
 
 bool
 BoyerMooreLookahead::CheckOverRecursed()
 {
-    JS_CHECK_RECURSION(compiler()->cx(), compiler()->SetRegExpTooBig(); return false);
+    if (!CheckRecursionLimit(compiler()->cx())) {
+        compiler()->SetRegExpTooBig();
+        return false;
+    }
+
     return true;
 }
 
 // -------------------------------------------------------------------
 // Trace
 
 bool Trace::DeferredAction::Mentions(int that)
 {
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -512,23 +512,16 @@ HasLiveStackValueAtDepth(JSScript* scrip
 
           case JSTRY_FOR_OF:
             // For-of loops have both the iterator and the result object on
             // stack. The iterator is below the result object.
             if (stackDepth == tn->stackDepth - 1)
                 return true;
             break;
 
-          case JSTRY_ITERCLOSE:
-            // Code that need to call IteratorClose have the iterator on the
-            // stack.
-            if (stackDepth == tn->stackDepth)
-                return true;
-            break;
-
           case JSTRY_DESTRUCTURING_ITERCLOSE:
             // Destructuring code that need to call IteratorClose have both
             // the iterator and the "done" value on the stack.
             if (stackDepth == tn->stackDepth || stackDepth == tn->stackDepth - 1)
                 return true;
             break;
 
           default:
@@ -1662,17 +1655,18 @@ jit::BailoutIonToBaseline(JSContext* cx,
     // Do stack check.
     bool overRecursed = false;
     BaselineBailoutInfo *info = builder.info();
     uint8_t* newsp = info->incomingStack - (info->copyStackTop - info->copyStackBottom);
 #ifdef JS_SIMULATOR
     if (Simulator::Current()->overRecursed(uintptr_t(newsp)))
         overRecursed = true;
 #else
-    JS_CHECK_RECURSION_WITH_SP_DONT_REPORT(cx, newsp, overRecursed = true);
+    if (!CheckRecursionLimitWithStackPointerDontReport(cx, newsp))
+        overRecursed = true;
 #endif
     if (overRecursed) {
         JitSpew(JitSpew_BaselineBailouts, "  Overrecursion check failed!");
         return BAILOUT_RETURN_OVERRECURSED;
     }
 
     // Take the reconstructed baseline stack so it doesn't get freed when builder destructs.
     info = builder.takeBuffer();
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1383,16 +1383,36 @@ BaselineCompiler::emit_JSOP_CHECKISOBJ()
     pushArg(Imm32(GET_UINT8(pc)));
     if (!callVM(ThrowCheckIsObjectInfo))
         return false;
 
     masm.bind(&ok);
     return true;
 }
 
+typedef bool (*CheckIsCallableFn)(JSContext*, HandleValue, CheckIsCallableKind);
+static const VMFunction CheckIsCallableInfo =
+    FunctionInfo<CheckIsCallableFn>(CheckIsCallable, "CheckIsCallable");
+
+bool
+BaselineCompiler::emit_JSOP_CHECKISCALLABLE()
+{
+    frame.syncStack(0);
+    masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0);
+
+    prepareVMCall();
+
+    pushArg(Imm32(GET_UINT8(pc)));
+    pushArg(R0);
+    if (!callVM(CheckIsCallableInfo))
+        return false;
+
+    return true;
+}
+
 typedef bool (*ThrowUninitializedThisFn)(JSContext*, BaselineFrame* frame);
 static const VMFunction ThrowUninitializedThisInfo =
     FunctionInfo<ThrowUninitializedThisFn>(BaselineThrowUninitializedThis,
                                            "BaselineThrowUninitializedThis");
 
 bool
 BaselineCompiler::emit_JSOP_CHECKTHIS()
 {
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -213,16 +213,17 @@ namespace jit {
     _(JSOP_CALLEE)             \
     _(JSOP_GETRVAL)            \
     _(JSOP_SETRVAL)            \
     _(JSOP_RETRVAL)            \
     _(JSOP_RETURN)             \
     _(JSOP_FUNCTIONTHIS)       \
     _(JSOP_GLOBALTHIS)         \
     _(JSOP_CHECKISOBJ)         \
+    _(JSOP_CHECKISCALLABLE)    \
     _(JSOP_CHECKTHIS)          \
     _(JSOP_CHECKRETURN)        \
     _(JSOP_NEWTARGET)          \
     _(JSOP_SUPERCALL)          \
     _(JSOP_SPREADSUPERCALL)    \
     _(JSOP_THROWSETCONST)      \
     _(JSOP_THROWSETALIASEDCONST) \
     _(JSOP_THROWSETCALLEE) \
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -108,19 +108,21 @@ CheckFrame(InterpreterFrame* fp)
 static JitExecStatus
 EnterBaseline(JSContext* cx, EnterJitData& data)
 {
     if (data.osrFrame) {
         // Check for potential stack overflow before OSR-ing.
         uint8_t spDummy;
         uint32_t extra = BaselineFrame::Size() + (data.osrNumStackValues * sizeof(Value));
         uint8_t* checkSp = (&spDummy) - extra;
-        JS_CHECK_RECURSION_WITH_SP(cx, checkSp, return JitExec_Aborted);
+        if (!CheckRecursionLimitWithStackPointer(cx, checkSp))
+            return JitExec_Aborted;
     } else {
-        JS_CHECK_RECURSION(cx, return JitExec_Aborted);
+        if (!CheckRecursionLimit(cx))
+            return JitExec_Aborted;
     }
 
 #ifdef DEBUG
     // Assert we don't GC before entering JIT code. A GC could discard JIT code
     // or move the function stored in the CalleeToken (it won't be traced at
     // this point). We use Maybe<> here so we can call reset() to call the
     // AutoAssertNoGC destructor before we enter JIT code.
     mozilla::Maybe<JS::AutoAssertNoGC> nogc;
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -11529,49 +11529,75 @@ class OutOfLineIsCallable : public OutOf
     void accept(CodeGenerator* codegen) {
         codegen->visitOutOfLineIsCallable(this);
     }
     LIsCallable* ins() const {
         return ins_;
     }
 };
 
-void
-CodeGenerator::visitIsCallable(LIsCallable* ins)
-{
-    Register object = ToRegister(ins->object());
-    Register output = ToRegister(ins->output());
-
-    OutOfLineIsCallable* ool = new(alloc()) OutOfLineIsCallable(ins);
-    addOutOfLineCode(ool, ins->mir());
-
+template <CodeGenerator::CallableOrConstructor mode>
+void
+CodeGenerator::emitIsCallableOrConstructor(Register object, Register output, Label* failure)
+{
     Label notFunction, hasCOps, done;
     masm.loadObjClass(object, output);
 
-    // Just skim proxies off. Their notion of isCallable() is more complicated.
-    masm.branchTestClassIsProxy(true, output, ool->entry());
+    // Just skim proxies off. Their notion of isCallable()/isConstructor() is
+    // more complicated.
+    masm.branchTestClassIsProxy(true, output, failure);
 
     // An object is callable iff:
     //   is<JSFunction>() || (getClass()->cOps && getClass()->cOps->call).
+    // An object is constructor iff:
+    //  ((is<JSFunction>() && as<JSFunction>().isConstructor) ||
+    //   (getClass()->cOps && getClass()->cOps->construct)).
     masm.branchPtr(Assembler::NotEqual, output, ImmPtr(&JSFunction::class_), &notFunction);
-    masm.move32(Imm32(1), output);
+    if (mode == Callable) {
+        masm.move32(Imm32(1), output);
+    } else {
+        Label notConstructor;
+        masm.load16ZeroExtend(Address(object, JSFunction::offsetOfFlags()), output);
+        masm.and32(Imm32(JSFunction::CONSTRUCTOR), output);
+        masm.branchTest32(Assembler::Zero, output, output, &notConstructor);
+        masm.move32(Imm32(1), output);
+        masm.jump(&done);
+        masm.bind(&notConstructor);
+        masm.move32(Imm32(0), output);
+    }
     masm.jump(&done);
 
     masm.bind(&notFunction);
     masm.branchPtr(Assembler::NonZero, Address(output, offsetof(js::Class, cOps)),
                    ImmPtr(nullptr), &hasCOps);
     masm.move32(Imm32(0), output);
     masm.jump(&done);
 
     masm.bind(&hasCOps);
     masm.loadPtr(Address(output, offsetof(js::Class, cOps)), output);
-    masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::ClassOps, call)),
+    size_t opsOffset = mode == Callable
+                       ? offsetof(js::ClassOps, call)
+                       : offsetof(js::ClassOps, construct);
+    masm.cmpPtrSet(Assembler::NonZero, Address(output, opsOffset),
                    ImmPtr(nullptr), output);
 
     masm.bind(&done);
+}
+
+void
+CodeGenerator::visitIsCallable(LIsCallable* ins)
+{
+    Register object = ToRegister(ins->object());
+    Register output = ToRegister(ins->output());
+
+    OutOfLineIsCallable* ool = new(alloc()) OutOfLineIsCallable(ins);
+    addOutOfLineCode(ool, ins->mir());
+
+    emitIsCallableOrConstructor<Callable>(object, output, ool->entry());
+
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitOutOfLineIsCallable(OutOfLineIsCallable* ool)
 {
     LIsCallable* ins = ool->ins();
     Register object = ToRegister(ins->object());
@@ -11581,16 +11607,46 @@ CodeGenerator::visitOutOfLineIsCallable(
     masm.setupUnalignedABICall(output);
     masm.passABIArg(object);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ObjectIsCallable));
     masm.storeCallBoolResult(output);
     restoreVolatile(output);
     masm.jump(ool->rejoin());
 }
 
+typedef bool (*CheckIsCallableFn)(JSContext*, HandleValue, CheckIsCallableKind);
+static const VMFunction CheckIsCallableInfo =
+    FunctionInfo<CheckIsCallableFn>(CheckIsCallable, "CheckIsCallable");
+
+void
+CodeGenerator::visitCheckIsCallable(LCheckIsCallable* ins)
+{
+    ValueOperand checkValue = ToValue(ins, LCheckIsCallable::CheckValue);
+    Register temp = ToRegister(ins->temp());
+
+    // OOL code is used in the following 2 cases:
+    //   * checkValue is not callable
+    //   * checkValue is proxy and it's unknown whether it's callable or not
+    // CheckIsCallable checks if passed value is callable, regardless of the
+    // cases above.  IsCallable operation is not observable and checking it
+    // again doesn't matter.
+    OutOfLineCode* ool = oolCallVM(CheckIsCallableInfo, ins,
+                                   ArgList(checkValue, Imm32(ins->mir()->checkKind())),
+                                   StoreNothing());
+
+    masm.branchTestObject(Assembler::NotEqual, checkValue, ool->entry());
+
+    Register object = masm.extractObject(checkValue, temp);
+    emitIsCallableOrConstructor<Callable>(object, temp, ool->entry());
+
+    masm.branchTest32(Assembler::Zero, temp, temp, ool->entry());
+
+    masm.bind(ool->rejoin());
+}
+
 class OutOfLineIsConstructor : public OutOfLineCodeBase<CodeGenerator>
 {
     LIsConstructor* ins_;
 
   public:
     explicit OutOfLineIsConstructor(LIsConstructor* ins)
       : ins_(ins)
     { }
@@ -11607,47 +11663,18 @@ void
 CodeGenerator::visitIsConstructor(LIsConstructor* ins)
 {
     Register object = ToRegister(ins->object());
     Register output = ToRegister(ins->output());
 
     OutOfLineIsConstructor* ool = new(alloc()) OutOfLineIsConstructor(ins);
     addOutOfLineCode(ool, ins->mir());
 
-    Label notFunction, notConstructor, hasCOps, done;
-    masm.loadObjClass(object, output);
-
-    // Just skim proxies off. Their notion of isConstructor() is more complicated.
-    masm.branchTestClassIsProxy(true, output, ool->entry());
-
-    // An object is constructor iff
-    //  ((is<JSFunction>() && as<JSFunction>().isConstructor) ||
-    //   (getClass()->cOps && getClass()->cOps->construct)).
-    masm.branchPtr(Assembler::NotEqual, output, ImmPtr(&JSFunction::class_), &notFunction);
-    masm.load16ZeroExtend(Address(object, JSFunction::offsetOfFlags()), output);
-    masm.and32(Imm32(JSFunction::CONSTRUCTOR), output);
-    masm.branchTest32(Assembler::Zero, output, output, &notConstructor);
-    masm.move32(Imm32(1), output);
-    masm.jump(&done);
-    masm.bind(&notConstructor);
-    masm.move32(Imm32(0), output);
-    masm.jump(&done);
-
-    masm.bind(&notFunction);
-    masm.branchPtr(Assembler::NonZero, Address(output, offsetof(js::Class, cOps)),
-                   ImmPtr(nullptr), &hasCOps);
-    masm.move32(Imm32(0), output);
-    masm.jump(&done);
-
-    masm.bind(&hasCOps);
-    masm.loadPtr(Address(output, offsetof(js::Class, cOps)), output);
-    masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::ClassOps, construct)),
-                   ImmPtr(nullptr), output);
-
-    masm.bind(&done);
+    emitIsCallableOrConstructor<Constructor>(object, output, ool->entry());
+
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitOutOfLineIsConstructor(OutOfLineIsConstructor* ool)
 {
     LIsConstructor* ins = ool->ins();
     Register object = ToRegister(ins->object());
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -363,16 +363,22 @@ class CodeGenerator final : public CodeG
     void visitCallInstanceOf(LCallInstanceOf* ins);
     void visitGetDOMProperty(LGetDOMProperty* lir);
     void visitGetDOMMemberV(LGetDOMMemberV* lir);
     void visitGetDOMMemberT(LGetDOMMemberT* lir);
     void visitSetDOMProperty(LSetDOMProperty* lir);
     void visitCallDOMNative(LCallDOMNative* lir);
     void visitCallGetIntrinsicValue(LCallGetIntrinsicValue* lir);
     void visitCallBindVar(LCallBindVar* lir);
+    enum CallableOrConstructor {
+        Callable,
+        Constructor
+    };
+    template <CallableOrConstructor mode>
+    void emitIsCallableOrConstructor(Register object, Register output, Label* failure);
     void visitIsCallable(LIsCallable* lir);
     void visitOutOfLineIsCallable(OutOfLineIsCallable* ool);
     void visitIsConstructor(LIsConstructor* lir);
     void visitOutOfLineIsConstructor(OutOfLineIsConstructor* ool);
     void visitIsObject(LIsObject* lir);
     void visitIsObjectAndBranch(LIsObjectAndBranch* lir);
     void visitHasClass(LHasClass* lir);
     void visitWasmParameter(LWasmParameter* lir);
@@ -383,16 +389,17 @@ class CodeGenerator final : public CodeG
     void visitLexicalCheck(LLexicalCheck* ins);
     void visitThrowRuntimeLexicalError(LThrowRuntimeLexicalError* ins);
     void visitGlobalNameConflictsCheck(LGlobalNameConflictsCheck* ins);
     void visitDebugger(LDebugger* ins);
     void visitNewTarget(LNewTarget* ins);
     void visitArrowNewTarget(LArrowNewTarget* ins);
     void visitCheckReturn(LCheckReturn* ins);
     void visitCheckIsObj(LCheckIsObj* ins);
+    void visitCheckIsCallable(LCheckIsCallable* ins);
     void visitCheckObjCoercible(LCheckObjCoercible* ins);
     void visitDebugCheckSelfHosted(LDebugCheckSelfHosted* ins);
     void visitNaNToZero(LNaNToZero* ins);
     void visitOutOfLineNaNToZero(OutOfLineNaNToZero* ool);
 
     void visitCheckOverRecursed(LCheckOverRecursed* lir);
     void visitCheckOverRecursedFailure(CheckOverRecursedFailure* ool);
 
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2854,17 +2854,19 @@ jit::CanEnterUsingFastInvoke(JSContext* 
         return Method_Skipped;
 
     return Method_Compiled;
 }
 
 static JitExecStatus
 EnterIon(JSContext* cx, EnterJitData& data)
 {
-    JS_CHECK_RECURSION(cx, return JitExec_Aborted);
+    if (!CheckRecursionLimit(cx))
+        return JitExec_Aborted;
+
     MOZ_ASSERT(jit::IsIonEnabled(cx));
     MOZ_ASSERT(!data.osrFrame);
 
 #ifdef DEBUG
     // See comment in EnterBaseline.
     mozilla::Maybe<JS::AutoAssertNoGC> nogc;
     nogc.emplace(cx);
 #endif
@@ -2988,17 +2990,18 @@ jit::IonCannon(JSContext* cx, RunState& 
         state.setReturnValue(data.result);
 
     return status;
 }
 
 JitExecStatus
 jit::FastInvoke(JSContext* cx, HandleFunction fun, CallArgs& args)
 {
-    JS_CHECK_RECURSION(cx, return JitExec_Error);
+    if (!CheckRecursionLimit(cx))
+        return JitExec_Error;
 
     RootedScript script(cx, fun->nonLazyScript());
 
     if (!Debugger::checkNoExecute(cx, script))
         return JitExec_Error;
 
 #ifdef DEBUG
     // See comment in EnterBaseline.
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -2262,16 +2262,19 @@ IonBuilder::inspectOpcode(JSOp op)
         break;
 
       case JSOP_NEWTARGET:
         return jsop_newtarget();
 
       case JSOP_CHECKISOBJ:
         return jsop_checkisobj(GET_UINT8(pc));
 
+      case JSOP_CHECKISCALLABLE:
+        return jsop_checkiscallable(GET_UINT8(pc));
+
       case JSOP_CHECKOBJCOERCIBLE:
         return jsop_checkobjcoercible();
 
       case JSOP_DEBUGCHECKSELFHOSTED:
       {
 #ifdef DEBUG
         MDebugCheckSelfHosted* check = MDebugCheckSelfHosted::New(alloc(), current->pop());
         current->add(check);
@@ -9415,16 +9418,25 @@ IonBuilder::jsop_checkisobj(uint8_t kind
 
     MCheckIsObj* check = MCheckIsObj::New(alloc(), current->pop(), kind);
     current->add(check);
     current->push(check);
     return Ok();
 }
 
 AbortReasonOr<Ok>
+IonBuilder::jsop_checkiscallable(uint8_t kind)
+{
+    MCheckIsCallable* check = MCheckIsCallable::New(alloc(), current->pop(), kind);
+    current->add(check);
+    current->push(check);
+    return Ok();
+}
+
+AbortReasonOr<Ok>
 IonBuilder::jsop_checkobjcoercible()
 {
     MDefinition* toCheck = current->peek(-1);
 
     if (!toCheck->mightBeType(MIRType::Undefined) &&
         !toCheck->mightBeType(MIRType::Null))
     {
         toCheck->setImplicitlyUsedUnchecked();
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -574,16 +574,17 @@ class IonBuilder
     AbortReasonOr<Ok> jsop_iterend();
     AbortReasonOr<Ok> jsop_in();
     AbortReasonOr<Ok> jsop_instanceof();
     AbortReasonOr<Ok> jsop_getaliasedvar(EnvironmentCoordinate ec);
     AbortReasonOr<Ok> jsop_setaliasedvar(EnvironmentCoordinate ec);
     AbortReasonOr<Ok> jsop_debugger();
     AbortReasonOr<Ok> jsop_newtarget();
     AbortReasonOr<Ok> jsop_checkisobj(uint8_t kind);
+    AbortReasonOr<Ok> jsop_checkiscallable(uint8_t kind);
     AbortReasonOr<Ok> jsop_checkobjcoercible();
     AbortReasonOr<Ok> jsop_pushcallobj();
 
     /* Inlining. */
 
     enum InliningStatus
     {
         InliningStatus_NotInlined,
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -326,17 +326,16 @@ NumArgAndLocalSlots(const InlineFrameIte
     JSScript* script = frame.script();
     return CountArgSlots(script, frame.maybeCalleeTemplate()) + script->nfixed();
 }
 
 static void
 CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, JSTryNote* tn)
 {
     MOZ_ASSERT(tn->kind == JSTRY_FOR_IN ||
-               tn->kind == JSTRY_ITERCLOSE ||
                tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE);
 
     bool isDestructuring = tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE;
     MOZ_ASSERT_IF(!isDestructuring, tn->stackDepth > 0);
     MOZ_ASSERT_IF(isDestructuring, tn->stackDepth > 1);
 
     SnapshotIterator si = frame.snapshotIterator();
 
@@ -437,17 +436,16 @@ HandleExceptionIon(JSContext* cx, const 
     if (!script->hasTrynotes())
         return;
 
     for (TryNoteIterIon tni(cx, frame); !tni.done(); ++tni) {
         JSTryNote* tn = *tni;
 
         switch (tn->kind) {
           case JSTRY_FOR_IN:
-          case JSTRY_ITERCLOSE:
           case JSTRY_DESTRUCTURING_ITERCLOSE:
             MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN,
                           JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER);
             CloseLiveIteratorIon(cx, frame, tn);
             break;
 
           case JSTRY_FOR_OF:
           case JSTRY_LOOP:
@@ -638,29 +636,16 @@ ProcessTryNotesBaseline(JSContext* cx, c
                 // ProcessTryNotes.
                 SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
                 MOZ_ASSERT(**pc == JSOP_ENDITER);
                 return false;
             }
             break;
           }
 
-          case JSTRY_ITERCLOSE: {
-            uint8_t* framePointer;
-            uint8_t* stackPointer;
-            BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer);
-            Value iterValue(*reinterpret_cast<Value*>(stackPointer));
-            RootedObject iterObject(cx, &iterValue.toObject());
-            if (!IteratorCloseForException(cx, iterObject)) {
-                SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
-                return false;
-            }
-            break;
-          }
-
           case JSTRY_DESTRUCTURING_ITERCLOSE: {
             uint8_t* framePointer;
             uint8_t* stackPointer;
             BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer);
             RootedValue doneValue(cx, *(reinterpret_cast<Value*>(stackPointer)));
             bool done = ToBoolean(doneValue);
             if (!done) {
                 Value iterValue(*(reinterpret_cast<Value*>(stackPointer) + 1));
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -4782,16 +4782,29 @@ LIRGenerator::visitCheckIsObj(MCheckIsOb
 
     LCheckIsObj* lir = new(alloc()) LCheckIsObj(useBoxAtStart(checkVal));
     redefine(ins, checkVal);
     add(lir, ins);
     assignSafepoint(lir, ins);
 }
 
 void
+LIRGenerator::visitCheckIsCallable(MCheckIsCallable* ins)
+{
+    MDefinition* checkVal = ins->checkValue();
+    MOZ_ASSERT(checkVal->type() == MIRType::Value);
+
+    LCheckIsCallable* lir = new(alloc()) LCheckIsCallable(useBox(checkVal),
+                                                          temp());
+    redefine(ins, checkVal);
+    add(lir, ins);
+    assignSafepoint(lir, ins);
+}
+
+void
 LIRGenerator::visitCheckObjCoercible(MCheckObjCoercible* ins)
 {
     MDefinition* checkVal = ins->checkValue();
     MOZ_ASSERT(checkVal->type() == MIRType::Value);
 
     LCheckObjCoercible* lir = new(alloc()) LCheckObjCoercible(useBoxAtStart(checkVal));
     redefine(ins, checkVal);
     add(lir, ins);
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -326,16 +326,17 @@ class LIRGenerator : public LIRGenerator
     void visitDebugger(MDebugger* ins);
     void visitNewTarget(MNewTarget* ins);
     void visitArrowNewTarget(MArrowNewTarget* ins);
     void visitNaNToZero(MNaNToZero *ins);
     void visitAtomicIsLockFree(MAtomicIsLockFree* ins);
     void visitGuardSharedTypedArray(MGuardSharedTypedArray* ins);
     void visitCheckReturn(MCheckReturn* ins);
     void visitCheckIsObj(MCheckIsObj* ins);
+    void visitCheckIsCallable(MCheckIsCallable* ins);
     void visitCheckObjCoercible(MCheckObjCoercible* ins);
     void visitDebugCheckSelfHosted(MDebugCheckSelfHosted* ins);
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_Lowering_h */
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -13511,18 +13511,19 @@ class MDebugger : public MNullaryInstruc
 };
 
 class MCheckIsObj
   : public MUnaryInstruction,
     public BoxInputsPolicy::Data
 {
     uint8_t checkKind_;
 
-    explicit MCheckIsObj(MDefinition* toCheck, uint8_t checkKind)
-      : MUnaryInstruction(toCheck), checkKind_(checkKind)
+    MCheckIsObj(MDefinition* toCheck, uint8_t checkKind)
+      : MUnaryInstruction(toCheck),
+        checkKind_(checkKind)
     {
         setResultType(MIRType::Value);
         setResultTypeSet(toCheck->resultTypeSet());
         setGuard();
     }
 
   public:
     INSTRUCTION_HEADER(CheckIsObj)
@@ -13531,16 +13532,43 @@ class MCheckIsObj
 
     uint8_t checkKind() const { return checkKind_; }
 
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 };
 
+class MCheckIsCallable
+  : public MUnaryInstruction,
+    public BoxInputsPolicy::Data
+{
+    uint8_t checkKind_;
+
+    MCheckIsCallable(MDefinition* toCheck, uint8_t checkKind)
+      : MUnaryInstruction(toCheck),
+        checkKind_(checkKind)
+    {
+        setResultType(MIRType::Value);
+        setResultTypeSet(toCheck->resultTypeSet());
+        setGuard();
+    }
+
+  public:
+    INSTRUCTION_HEADER(CheckIsCallable)
+    TRIVIAL_NEW_WRAPPERS
+    NAMED_OPERANDS((0, checkValue))
+
+    uint8_t checkKind() const { return checkKind_; }
+
+    AliasSet getAliasSet() const override {
+        return AliasSet::None();
+    }
+};
+
 class MCheckObjCoercible
   : public MUnaryInstruction,
     public BoxInputsPolicy::Data
 {
     explicit MCheckObjCoercible(MDefinition* toCheck)
       : MUnaryInstruction(toCheck)
     {
         setGuard();
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -282,16 +282,17 @@ namespace jit {
     _(LexicalCheck)                                                         \
     _(ThrowRuntimeLexicalError)                                             \
     _(GlobalNameConflictsCheck)                                             \
     _(Debugger)                                                             \
     _(NewTarget)                                                            \
     _(ArrowNewTarget)                                                       \
     _(CheckReturn)                                                          \
     _(CheckIsObj)                                                           \
+    _(CheckIsCallable)                                                      \
     _(CheckObjCoercible)                                                    \
     _(DebugCheckSelfHosted)                                                 \
     _(AsmJSNeg)                                                             \
     _(AsmJSLoadHeap)                                                        \
     _(AsmJSStoreHeap)                                                       \
     _(AsmJSCompareExchangeHeap)                                             \
     _(AsmJSAtomicExchangeHeap)                                              \
     _(AsmJSAtomicBinopHeap)                                                 \
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -118,27 +118,41 @@ bool
 InvokeFunctionShuffleNewTarget(JSContext* cx, HandleObject obj, uint32_t numActualArgs,
                                uint32_t numFormalArgs, Value* argv, MutableHandleValue rval)
 {
     MOZ_ASSERT(numFormalArgs > numActualArgs);
     argv[1 + numActualArgs] = argv[1 + numFormalArgs];
     return InvokeFunction(cx, obj, true, numActualArgs, argv, rval);
 }
 
+#ifdef JS_SIMULATOR
+static bool
+CheckSimulatorRecursionLimitWithExtra(JSContext* cx, uint32_t extra)
+{
+    if (cx->simulator()->overRecursedWithExtra(extra)) {
+        ReportOverRecursed(cx);
+        return false;
+    }
+    return true;
+}
+#endif
+
 bool
 CheckOverRecursed(JSContext* cx)
 {
     // We just failed the jitStackLimit check. There are two possible reasons:
     //  - jitStackLimit was the real stack limit and we're over-recursed
     //  - jitStackLimit was set to UINTPTR_MAX by JSRuntime::requestInterrupt
     //    and we need to call JSRuntime::handleInterrupt.
 #ifdef JS_SIMULATOR
-    JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, 0, return false);
+    if (!CheckSimulatorRecursionLimitWithExtra(cx, 0))
+        return false;
 #else
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 #endif
     gc::MaybeVerifyBarriers(cx);
     return cx->handleInterrupt();
 }
 
 // This function can get called in two contexts.  In the usual context, it's
 // called with earlyCheck=false, after the env chain has been initialized on
 // a baseline frame.  In this case, it's ok to throw an exception, so a failed
@@ -157,32 +171,36 @@ CheckOverRecursedWithExtra(JSContext* cx
     // See |CheckOverRecursed| above.  This is a variant of that function which
     // accepts an argument holding the extra stack space needed for the Baseline
     // frame that's about to be pushed.
     uint8_t spDummy;
     uint8_t* checkSp = (&spDummy) - extra;
     if (earlyCheck) {
 #ifdef JS_SIMULATOR
         (void)checkSp;
-        JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, frame->setOverRecursed());
+        if (!CheckSimulatorRecursionLimitWithExtra(cx, extra))
+            frame->setOverRecursed();
 #else
-        JS_CHECK_RECURSION_WITH_SP(cx, checkSp, frame->setOverRecursed());
+        if (!CheckRecursionLimitWithStackPointer(cx, checkSp))
+            frame->setOverRecursed();
 #endif
         return true;
     }
 
     // The OVERRECURSED flag may have already been set on the frame by an
     // early over-recursed check.  If so, throw immediately.
     if (frame->overRecursed())
         return false;
 
 #ifdef JS_SIMULATOR
-    JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, return false);
+    if (!CheckSimulatorRecursionLimitWithExtra(cx, extra))
+        return false;
 #else
-    JS_CHECK_RECURSION_WITH_SP(cx, checkSp, return false);
+    if (!CheckRecursionLimitWithStackPointer(cx, checkSp))
+        return false;
 #endif
 
     gc::MaybeVerifyBarriers(cx);
     return cx->handleInterrupt();
 }
 
 JSObject*
 BindVar(JSContext* cx, HandleObject envChain)
@@ -1478,10 +1496,19 @@ EqualStringsHelper(JSString* str1, JSStr
 
     JSLinearString* str2Linear = str2->ensureLinear(nullptr);
     if (!str2Linear)
         return false;
 
     return EqualChars(&str1->asLinear(), str2Linear);
 }
 
+bool
+CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind)
+{
+    if (!IsCallable(v))
+        return ThrowCheckIsCallable(cx, kind);
+
+    return true;
+}
+
 } // namespace jit
 } // namespace js
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -8,16 +8,17 @@
 #define jit_VMFunctions_h
 
 #include "mozilla/Attributes.h"
 
 #include "jspubtd.h"
 
 #include "jit/CompileInfo.h"
 #include "jit/JitFrames.h"
+#include "vm/Interpreter.h"
 
 namespace js {
 
 class NamedLambdaObject;
 class WithScope;
 class InlineTypedObject;
 class GeneratorObject;
 class TypedArrayObject;
@@ -833,12 +834,15 @@ CallNativeGetter(JSContext* cx, HandleFu
 
 MOZ_MUST_USE bool
 CallNativeSetter(JSContext* cx, HandleFunction callee, HandleObject obj,
                  HandleValue rhs);
 
 MOZ_MUST_USE bool
 EqualStringsHelper(JSString* str1, JSString* str2);
 
+MOZ_MUST_USE bool
+CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind);
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_VMFunctions_h */
--- a/js/src/jit/arm/Simulator-arm.h
+++ b/js/src/jit/arm/Simulator-arm.h
@@ -526,22 +526,14 @@ class SimulatorProcess
     }
 
     static void setRedirection(js::jit::Redirection* redirection) {
         MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
         singleton_->redirection_ = redirection;
     }
 };
 
-#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror)     \
-    JS_BEGIN_MACRO                                                              \
-        if (cx->simulator()->overRecursedWithExtra(extra)) {                    \
-            js::ReportOverRecursed(cx);                                         \
-            onerror;                                                            \
-        }                                                                       \
-    JS_END_MACRO
-
 } // namespace jit
 } // namespace js
 
 #endif /* JS_SIMULATOR_ARM */
 
 #endif /* jit_arm_Simulator_arm_h */
--- a/js/src/jit/arm64/vixl/Simulator-vixl.h
+++ b/js/src/jit/arm64/vixl/Simulator-vixl.h
@@ -41,24 +41,16 @@
 #include "jit/arm64/vixl/Instructions-vixl.h"
 #include "jit/arm64/vixl/Instrument-vixl.h"
 #include "jit/arm64/vixl/Simulator-Constants-vixl.h"
 #include "jit/arm64/vixl/Utils-vixl.h"
 #include "jit/IonTypes.h"
 #include "vm/MutexIDs.h"
 #include "vm/PosixNSPR.h"
 
-#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror)             \
-    JS_BEGIN_MACRO                                                              \
-        if (cx->simulator()->overRecursedWithExtra(extra)) {                    \
-            js::ReportOverRecursed(cx);                                         \
-            onerror;                                                            \
-        }                                                                       \
-    JS_END_MACRO
-
 namespace vixl {
 
 // Assemble the specified IEEE-754 components into the target type and apply
 // appropriate rounding.
 //  sign:     0 = positive, 1 = negative
 //  exponent: Unbiased IEEE-754 exponent.
 //  mantissa: The mantissa of the input. The top bit (which is not encoded for
 //            normal IEEE-754 values) must not be omitted. This bit has the
--- a/js/src/jit/mips32/Simulator-mips32.h
+++ b/js/src/jit/mips32/Simulator-mips32.h
@@ -433,22 +433,14 @@ class SimulatorProcess
     }
 
     static void setRedirection(js::jit::Redirection* redirection) {
         MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
         singleton_->redirection_ = redirection;
     }
 };
 
-#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror)             \
-    JS_BEGIN_MACRO                                                              \
-        if (cx->simulator()->overRecursedWithExtra(extra)) {                    \
-            js::ReportOverRecursed(cx);                                         \
-            onerror;                                                            \
-        }                                                                       \
-    JS_END_MACRO
-
 } // namespace jit
 } // namespace js
 
 #endif /* JS_SIMULATOR_MIPS32 */
 
 #endif /* jit_mips32_Simulator_mips32_h */
--- a/js/src/jit/mips64/Simulator-mips64.h
+++ b/js/src/jit/mips64/Simulator-mips64.h
@@ -447,22 +447,14 @@ class SimulatorProcess
     }
 
     static void setRedirection(js::jit::Redirection* redirection) {
         MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
         singleton_->redirection_ = redirection;
     }
 };
 
-#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror)     \
-    JS_BEGIN_MACRO                                                              \
-        if (cx->simulator()->overRecursedWithExtra(extra)) {                    \
-            js::ReportOverRecursed(cx);                                         \
-            onerror;                                                            \
-        }                                                                       \
-    JS_END_MACRO
-
 } // namespace jit
 } // namespace js
 
 #endif /* JS_SIMULATOR_MIPS64 */
 
 #endif /* jit_mips64_Simulator_mips64_h */
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -8987,16 +8987,37 @@ class LCheckIsObj : public LInstructionH
         setBoxOperand(CheckValue, value);
     }
 
     MCheckIsObj* mir() const {
         return mir_->toCheckIsObj();
     }
 };
 
+class LCheckIsCallable : public LInstructionHelper<BOX_PIECES, BOX_PIECES, 1>
+{
+  public:
+    LIR_HEADER(CheckIsCallable)
+
+    static const size_t CheckValue = 0;
+
+    LCheckIsCallable(const LBoxAllocation& value, const LDefinition& temp) {
+        setBoxOperand(CheckValue, value);
+        setTemp(0, temp);
+    }
+
+    const LDefinition* temp() {
+        return getTemp(0);
+    }
+
+    MCheckIsCallable* mir() const {
+        return mir_->toCheckIsCallable();
+    }
+};
+
 class LCheckObjCoercible : public LCallInstructionHelper<BOX_PIECES, BOX_PIECES, 0>
 {
   public:
     LIR_HEADER(CheckObjCoercible)
 
     static const size_t CheckValue = 0;
 
     explicit LCheckObjCoercible(const LBoxAllocation& value) {
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -400,16 +400,17 @@
     _(LexicalCheck)                 \
     _(ThrowRuntimeLexicalError)     \
     _(GlobalNameConflictsCheck)     \
     _(Debugger)                     \
     _(NewTarget)                    \
     _(ArrowNewTarget)               \
     _(CheckReturn)                  \
     _(CheckIsObj)                   \
+    _(CheckIsCallable)              \
     _(CheckObjCoercible)            \
     _(DebugCheckSelfHosted)         \
     _(AsmJSLoadHeap)                \
     _(AsmJSStoreHeap)               \
     _(AsmJSCompareExchangeHeap)     \
     _(AsmJSAtomicExchangeHeap)      \
     _(AsmJSAtomicBinopHeap)         \
     _(AsmJSAtomicBinopHeapForEffect)\
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -6824,10 +6824,19 @@ SetStopwatchCommitCallback(JSContext*, S
 
 typedef bool
 (*GetGroupsCallback)(JSContext*, PerformanceGroupVector&, void*);
 extern JS_PUBLIC_API(bool)
 SetGetPerformanceGroupsCallback(JSContext*, GetGroupsCallback, void*);
 
 } /* namespace js */
 
+namespace js {
+
+enum class CompletionKind {
+    Normal,
+    Return,
+    Throw
+};
+
+} /* namespace js */
 
 #endif /* jsapi_h */
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -943,17 +943,19 @@ ArraySpeciesCreate(JSContext* cx, Handle
     return true;
 }
 
 #if JS_HAS_TOSOURCE
 
 static bool
 array_toSource(JSContext* cx, unsigned argc, Value* vp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
+
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!args.thisv().isObject()) {
         ReportIncompatible(cx, args);
         return false;
     }
 
     Rooted<JSObject*> obj(cx, &args.thisv().toObject());
@@ -1156,17 +1158,18 @@ ArrayJoinKernel(JSContext* cx, Separator
     return true;
 }
 
 // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce
 // 22.1.3.13 Array.prototype.join ( separator )
 bool
 js::array_join(JSContext* cx, unsigned argc, Value* vp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     AutoGeckoProfilerEntry pseudoFrame(cx->runtime(), "Array.prototype.join");
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
@@ -1259,17 +1262,18 @@ js::array_join(JSContext* cx, unsigned a
 
 // ES2017 draft rev f8a9be8ea4bd97237d176907a1e3080dce20c68f
 // 22.1.3.27 Array.prototype.toLocaleString ([ reserved1 [ , reserved2 ] ])
 // ES2017 Intl draft rev 78bbe7d1095f5ff3760ac4017ed366026e4cb276
 // 13.4.1 Array.prototype.toLocaleString ([ locales [ , options ]])
 static bool
 array_toLocaleString(JSContext* cx, unsigned argc, Value* vp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -267,17 +267,18 @@ assertSameCompartment(JSContext* cx,
 }
 
 #undef START_ASSERT_SAME_COMPARTMENT
 
 STATIC_PRECONDITION_ASSUME(ubound(args.argv_) >= argc)
 MOZ_ALWAYS_INLINE bool
 CallJSNative(JSContext* cx, Native native, const CallArgs& args)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
 #ifdef DEBUG
     bool alreadyThrowing = cx->isExceptionPending();
 #endif
     assertSameCompartment(cx, args);
     bool ok = native(cx, args.length(), args.base());
     if (ok) {
         assertSameCompartment(cx, args.rval());
@@ -341,50 +342,54 @@ CallJSNativeConstructor(JSContext* cx, N
 
     return true;
 }
 
 MOZ_ALWAYS_INLINE bool
 CallJSGetterOp(JSContext* cx, GetterOp op, HandleObject obj, HandleId id,
                MutableHandleValue vp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     assertSameCompartment(cx, obj, id, vp);
     bool ok = op(cx, obj, id, vp);
     if (ok)
         assertSameCompartment(cx, vp);
     return ok;
 }
 
 MOZ_ALWAYS_INLINE bool
 CallJSSetterOp(JSContext* cx, SetterOp op, HandleObject obj, HandleId id, MutableHandleValue vp,
                ObjectOpResult& result)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     assertSameCompartment(cx, obj, id, vp);
     return op(cx, obj, id, vp, result);
 }
 
 inline bool
 CallJSAddPropertyOp(JSContext* cx, JSAddPropertyOp op, HandleObject obj, HandleId id,
                     HandleValue v)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     assertSameCompartment(cx, obj, id, v);
     return op(cx, obj, id, v);
 }
 
 inline bool
 CallJSDeletePropertyOp(JSContext* cx, JSDeletePropertyOp op, HandleObject receiver, HandleId id,
                        ObjectOpResult& result)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     assertSameCompartment(cx, receiver, id);
     if (op)
         return op(cx, receiver, id, result);
     return result.succeed();
 }
 
 MOZ_ALWAYS_INLINE bool
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -396,17 +396,18 @@ JSCompartment::getNonWrapperObjectForCur
 
     // Invoke the prewrap callback. The prewrap callback is responsible for
     // doing similar reification as above, but can account for any additional
     // embedder requirements.
     //
     // We're a bit worried about infinite recursion here, so we do a check -
     // see bug 809295.
     auto preWrap = cx->runtime()->wrapObjectCallbacks->preWrap;
-    JS_CHECK_SYSTEM_RECURSION(cx, return false);
+    if (!CheckSystemRecursionLimit(cx))
+        return false;
     if (preWrap) {
         preWrap(cx, cx->global(), obj, objectPassedToWrap, obj);
         if (!obj)
             return false;
     }
     MOZ_ASSERT(!IsWindow(obj));
 
     return true;
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -489,17 +489,18 @@ Error(JSContext* cx, unsigned argc, Valu
 
 #if JS_HAS_TOSOURCE
 /*
  * Return a string that may eval to something similar to the original object.
  */
 static bool
 exn_toSource(JSContext* cx, unsigned argc, Value* vp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     RootedValue nameVal(cx);
     RootedString name(cx);
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -959,95 +959,117 @@ IsObjectInContextCompartment(JSObject* o
 #define JSITER_OWNONLY    0x8   /* iterate over obj's own properties only */
 #define JSITER_HIDDEN     0x10  /* also enumerate non-enumerable properties */
 #define JSITER_SYMBOLS    0x20  /* also include symbol property keys */
 #define JSITER_SYMBOLSONLY 0x40 /* exclude string property keys */
 
 JS_FRIEND_API(bool)
 RunningWithTrustedPrincipals(JSContext* cx);
 
-inline uintptr_t
+MOZ_ALWAYS_INLINE uintptr_t
 GetNativeStackLimit(JSContext* cx, JS::StackKind kind, int extraAllowance = 0)
 {
     uintptr_t limit = JS::RootingContext::get(cx)->nativeStackLimit[kind];
 #if JS_STACK_GROWTH_DIRECTION > 0
     limit += extraAllowance;
 #else
     limit -= extraAllowance;
 #endif
     return limit;
 }
 
-inline uintptr_t
+MOZ_ALWAYS_INLINE uintptr_t
 GetNativeStackLimit(JSContext* cx, int extraAllowance = 0)
 {
     JS::StackKind kind = RunningWithTrustedPrincipals(cx) ? JS::StackForTrustedScript
                                                           : JS::StackForUntrustedScript;
     return GetNativeStackLimit(cx, kind, extraAllowance);
 }
 
 /*
- * These macros report a stack overflow and run |onerror| if we are close to
- * using up the C stack. The JS_CHECK_CHROME_RECURSION variant gives us a
- * little extra space so that we can ensure that crucial code is able to run.
- * JS_CHECK_RECURSION_CONSERVATIVE allows less space than any other check,
- * including a safety buffer (as in, it uses the untrusted limit and subtracts
- * a little more from it).
+ * These functions return |false| if we are close to using up the C++ stack.
+ * They also report an overrecursion error, except for the DontReport variants.
+ * The CheckSystemRecursionLimit variant gives us a little extra space so we
+ * can ensure that crucial code is able to run. CheckRecursionLimitConservative
+ * allows less space than any other check, including a safety buffer (as in, it
+ * uses the untrusted limit and subtracts a little more from it).
  */
 
-#define JS_CHECK_RECURSION_LIMIT(cx, limit, onerror)                            \
-    JS_BEGIN_MACRO                                                              \
-        int stackDummy_;                                                        \
-        if (!JS_CHECK_STACK_SIZE(limit, &stackDummy_)) {                        \
-            js::ReportOverRecursed(cx);                                         \
-            onerror;                                                            \
-        }                                                                       \
-    JS_END_MACRO
-
-#define JS_CHECK_RECURSION(cx, onerror)                                         \
-    JS_CHECK_RECURSION_LIMIT(cx, js::GetNativeStackLimit(cx), onerror)
-
-#define JS_CHECK_RECURSION_LIMIT_DONT_REPORT(cx, limit, onerror)                \
-    JS_BEGIN_MACRO                                                              \
-        int stackDummy_;                                                        \
-        if (!JS_CHECK_STACK_SIZE(limit, &stackDummy_)) {                        \
-            onerror;                                                            \
-        }                                                                       \
-    JS_END_MACRO
-
-#define JS_CHECK_RECURSION_DONT_REPORT(cx, onerror)                             \
-    JS_CHECK_RECURSION_LIMIT_DONT_REPORT(cx, js::GetNativeStackLimit(cx), onerror)
-
-#define JS_CHECK_RECURSION_WITH_SP_DONT_REPORT(cx, sp, onerror)                 \
-    JS_BEGIN_MACRO                                                              \
-        if (!JS_CHECK_STACK_SIZE(js::GetNativeStackLimit(cx), sp)) {            \
-            onerror;                                                            \
-        }                                                                       \
-    JS_END_MACRO
-
-#define JS_CHECK_RECURSION_WITH_SP(cx, sp, onerror)                             \
-    JS_BEGIN_MACRO                                                              \
-        if (!JS_CHECK_STACK_SIZE(js::GetNativeStackLimit(cx), sp)) {            \
-            js::ReportOverRecursed(cx);                                         \
-            onerror;                                                            \
-        }                                                                       \
-    JS_END_MACRO
-
-#define JS_CHECK_SYSTEM_RECURSION(cx, onerror)                                  \
-    JS_CHECK_RECURSION_LIMIT(cx, js::GetNativeStackLimit(cx, JS::StackForSystemCode), onerror)
-
-#define JS_CHECK_RECURSION_CONSERVATIVE(cx, onerror)                            \
-    JS_CHECK_RECURSION_LIMIT(cx,                                                \
-                             js::GetNativeStackLimit(cx, JS::StackForUntrustedScript, -1024 * int(sizeof(size_t))), \
-                             onerror)
-
-#define JS_CHECK_RECURSION_CONSERVATIVE_DONT_REPORT(cx, onerror)                \
-    JS_CHECK_RECURSION_LIMIT_DONT_REPORT(cx,                                    \
-                                         js::GetNativeStackLimit(cx, JS::StackForUntrustedScript, -1024 * int(sizeof(size_t))), \
-                                         onerror)
+MOZ_ALWAYS_INLINE bool
+CheckRecursionLimit(JSContext* cx, uintptr_t limit)
+{
+    int stackDummy;
+    if (!JS_CHECK_STACK_SIZE(limit, &stackDummy)) {
+        ReportOverRecursed(cx);
+        return false;
+    }
+    return true;
+}
+
+MOZ_ALWAYS_INLINE bool
+CheckRecursionLimitDontReport(JSContext* cx, uintptr_t limit)
+{
+    int stackDummy;
+    return JS_CHECK_STACK_SIZE(limit, &stackDummy);
+}
+
+MOZ_ALWAYS_INLINE bool
+CheckRecursionLimit(JSContext* cx)
+{
+    // GetNativeStackLimit(cx) is pretty slow because it has to do an uninlined
+    // call to RunningWithTrustedPrincipals to determine which stack limit to
+    // use. To work around this, check the untrusted limit first to avoid the
+    // overhead in most cases.
+    uintptr_t untrustedLimit = GetNativeStackLimit(cx, JS::StackForUntrustedScript);
+    if (MOZ_LIKELY(CheckRecursionLimitDontReport(cx, untrustedLimit)))
+        return true;
+    return CheckRecursionLimit(cx, GetNativeStackLimit(cx));
+}
+
+MOZ_ALWAYS_INLINE bool
+CheckRecursionLimitDontReport(JSContext* cx)
+{
+    return CheckRecursionLimitDontReport(cx, GetNativeStackLimit(cx));
+}
+
+MOZ_ALWAYS_INLINE bool
+CheckRecursionLimitWithStackPointerDontReport(JSContext* cx, void* sp)
+{
+    return JS_CHECK_STACK_SIZE(GetNativeStackLimit(cx), sp);
+}
+
+MOZ_ALWAYS_INLINE bool
+CheckRecursionLimitWithStackPointer(JSContext* cx, void* sp)
+{
+    if (!JS_CHECK_STACK_SIZE(GetNativeStackLimit(cx), sp)) {
+        ReportOverRecursed(cx);
+        return false;
+    }
+    return true;
+}
+
+MOZ_ALWAYS_INLINE bool
+CheckSystemRecursionLimit(JSContext* cx)
+{
+    return CheckRecursionLimit(cx, GetNativeStackLimit(cx, JS::StackForSystemCode));
+}
+
+MOZ_ALWAYS_INLINE bool
+CheckRecursionLimitConservative(JSContext* cx)
+{
+    return CheckRecursionLimit(cx, GetNativeStackLimit(cx, JS::StackForUntrustedScript,
+                                                       -1024 * int(sizeof(size_t))));
+}
+
+MOZ_ALWAYS_INLINE bool
+CheckRecursionLimitConservativeDontReport(JSContext* cx)
+{
+    return CheckRecursionLimitDontReport(cx, GetNativeStackLimit(cx, JS::StackForUntrustedScript,
+                                                                 -1024 * int(sizeof(size_t))));
+}
 
 JS_FRIEND_API(void)
 StartPCCountProfiling(JSContext* cx);
 
 JS_FRIEND_API(void)
 StopPCCountProfiling(JSContext* cx);
 
 JS_FRIEND_API(void)
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -467,17 +467,18 @@ js::GetPropertyKeys(JSContext* cx, Handl
                     props);
 }
 
 size_t sCustomIteratorCount = 0;
 
 static inline bool
 GetCustomIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandleObject objp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     RootedValue rval(cx);
     /* Check whether we have a valid __iterator__ method. */
     HandlePropertyName name = cx->names().iteratorIntrinsic;
     if (!GetProperty(cx, obj, obj, name, &rval))
         return false;
 
     /* If there is no custom __iterator__ method, we are done here. */
@@ -1437,17 +1438,18 @@ js::IteratorMore(JSContext* cx, HandleOb
             return false;
 
         if (done)
             rval.setMagic(JS_NO_ITER_VALUE);
         return true;
     }
 
     // We're reentering below and can call anything.
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     // Call the iterator object's .next method.
     if (!GetProperty(cx, iterobj, iterobj, cx->names().next, rval))
         return false;
 
     // Call the .next method.  Fall through to the error-handling cases in the
     // unlikely event that either one of the fallible operations performed
     // during the call process fails.
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -526,17 +526,18 @@ JA(JSContext* cx, HandleObject obj, Stri
 }
 
 static bool
 Str(JSContext* cx, const Value& v, StringifyContext* scx)
 {
     /* Step 11 must be handled by the caller. */
     MOZ_ASSERT(!IsFilteredValue(v));
 
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     /*
      * This method implements the Str algorithm in ES5 15.12.3, but:
      *
      *   * We move property retrieval (step 1) into callers to stream the
      *     stringification process and avoid constantly copying strings.
      *   * We move the preprocessing in steps 2-4 into a helper function to
      *     allow both JO and JA to use this method.  While JA could use it
@@ -754,17 +755,18 @@ js::Stringify(JSContext* cx, MutableHand
 
     return Str(cx, vp, &scx);
 }
 
 /* ES5 15.12.2 Walk. */
 static bool
 Walk(JSContext* cx, HandleObject holder, HandleId name, HandleValue reviver, MutableHandleValue vp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     /* Step 1. */
     RootedValue val(cx);
     if (!GetProperty(cx, holder, holder, name, &val))
         return false;
 
     /* Step 2. */
     if (val.isObject()) {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -80,17 +80,16 @@ CopyScript(JSContext* cx, HandleScript s
  * heuristics that need to know whether a given op is inside a loop.
  */
 enum JSTryNoteKind {
     JSTRY_CATCH,
     JSTRY_FINALLY,
     JSTRY_FOR_IN,
     JSTRY_FOR_OF,
     JSTRY_LOOP,
-    JSTRY_ITERCLOSE,
     JSTRY_DESTRUCTURING_ITERCLOSE
 };
 
 /*
  * Exception handling record.
  */
 struct JSTryNote {
     uint8_t         kind;       /* one of JSTryNoteKind */
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -449,17 +449,18 @@ const Class StringObject::class_ = {
 
 /*
  * Perform the initial |RequireObjectCoercible(thisv)| and |ToString(thisv)|
  * from nearly all String.prototype.* functions.
  */
 static MOZ_ALWAYS_INLINE JSString*
 ToStringForStringFunction(JSContext* cx, HandleValue thisv)
 {
-    JS_CHECK_RECURSION(cx, return nullptr);
+    if (!CheckRecursionLimit(cx))
+        return nullptr;
 
     if (thisv.isString())
         return thisv.toString();
 
     if (thisv.isObject()) {
         RootedObject obj(cx, &thisv.toObject());
         if (obj->is<StringObject>()) {
             StringObject* nobj = &obj->as<StringObject>();
@@ -3140,17 +3141,18 @@ SymbolToSource(JSContext* cx, Symbol* sy
     if (!buf.append(')'))
         return nullptr;
     return buf.finishString();
 }
 
 JSString*
 js::ValueToSource(JSContext* cx, HandleValue v)
 {
-    JS_CHECK_RECURSION(cx, return nullptr);
+    if (!CheckRecursionLimit(cx))
+        return nullptr;
     assertSameCompartment(cx, v);
 
     if (v.isUndefined())
         return cx->names().void0;
     if (v.isString())
         return StringToSource(cx, v.toString());
     if (v.isSymbol())
         return SymbolToSource(cx, v.toSymbol());
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -87,17 +87,19 @@ js::assertEnteredPolicy(JSContext* cx, J
     MOZ_ASSERT(cx->enteredPolicy->enteredAction & act);
 }
 #endif
 
 bool
 Proxy::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
                              MutableHandle<PropertyDescriptor> desc)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
+
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     desc.object().set(nullptr); // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET_PROPERTY_DESCRIPTOR, true);
     if (!policy.allowed())
         return policy.returnValue();
 
     // Special case. See the comment on BaseProxyHandler::mHasPrototype.
     if (handler->hasPrototype())
@@ -105,56 +107,59 @@ Proxy::getPropertyDescriptor(JSContext* 
 
     return handler->getPropertyDescriptor(cx, proxy, id, desc);
 }
 
 bool
 Proxy::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
                                 MutableHandle<PropertyDescriptor> desc)
 {
-    JS_CHECK_RECURSION(cx, return false);
-
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     desc.object().set(nullptr); // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET_PROPERTY_DESCRIPTOR, true);
     if (!policy.allowed())
         return policy.returnValue();
     return handler->getOwnPropertyDescriptor(cx, proxy, id, desc);
 }
 
 bool
 Proxy::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
                       Handle<PropertyDescriptor> desc, ObjectOpResult& result)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
     if (!policy.allowed()) {
         if (!policy.returnValue())
             return false;
         return result.succeed();
     }
     return proxy->as<ProxyObject>().handler()->defineProperty(cx, proxy, id, desc, result);
 }
 
 bool
 Proxy::ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true);
     if (!policy.allowed())
         return policy.returnValue();
     return proxy->as<ProxyObject>().handler()->ownPropertyKeys(cx, proxy, props);
 }
 
 bool
 Proxy::delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
     if (!policy.allowed()) {
         bool ok = policy.returnValue();
         if (ok)
             result.succeed();
         return ok;
     }
@@ -182,64 +187,71 @@ js::AppendUnique(JSContext* cx, AutoIdVe
     }
     return base.appendAll(uniqueOthers);
 }
 
 /* static */ bool
 Proxy::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject proto)
 {
     MOZ_ASSERT(proxy->hasDynamicPrototype());
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     return proxy->as<ProxyObject>().handler()->getPrototype(cx, proxy, proto);
 }
 
 /* static */ bool
 Proxy::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, ObjectOpResult& result)
 {
     MOZ_ASSERT(proxy->hasDynamicPrototype());
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     return proxy->as<ProxyObject>().handler()->setPrototype(cx, proxy, proto, result);
 }
 
 /* static */ bool
 Proxy::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
                               MutableHandleObject proto)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     return proxy->as<ProxyObject>().handler()->getPrototypeIfOrdinary(cx, proxy, isOrdinary,
                                                                       proto);
 }
 
 /* static */ bool
 Proxy::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     return handler->setImmutablePrototype(cx, proxy, succeeded);
 }
 
 /* static */ bool
 Proxy::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     return handler->preventExtensions(cx, proxy, result);
 }
 
 /* static */ bool
 Proxy::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     return proxy->as<ProxyObject>().handler()->isExtensible(cx, proxy, extensible);
 }
 
 bool
 Proxy::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     *bp = false; // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
     if (!policy.allowed())
         return policy.returnValue();
 
     if (handler->hasPrototype()) {
         if (!handler->hasOwn(cx, proxy, id, bp))
@@ -257,17 +269,18 @@ Proxy::has(JSContext* cx, HandleObject p
     }
 
     return handler->has(cx, proxy, id, bp);
 }
 
 bool
 Proxy::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     *bp = false; // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
     if (!policy.allowed())
         return policy.returnValue();
     return handler->hasOwn(cx, proxy, id, bp);
 }
 
@@ -278,17 +291,18 @@ ValueToWindowProxyIfWindow(const Value& 
         return ObjectValue(*ToWindowProxyIfWindow(&v.toObject()));
     return v;
 }
 
 MOZ_ALWAYS_INLINE bool
 Proxy::get(JSContext* cx, HandleObject proxy, HandleValue receiver_, HandleId id,
            MutableHandleValue vp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     vp.setUndefined(); // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
     if (!policy.allowed())
         return policy.returnValue();
 
     // Use the WindowProxy as receiver if receiver_ is a Window. Proxy handlers
     // shouldn't have to know about the Window/WindowProxy distinction.
@@ -329,17 +343,18 @@ js::ProxyGetPropertyByValue(JSContext* c
     RootedValue receiver(cx, ObjectValue(*proxy));
     return Proxy::get(cx, proxy, receiver, id, vp);
 }
 
 bool
 Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver_,
            ObjectOpResult& result)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
     if (!policy.allowed()) {
         if (!policy.returnValue())
             return false;
         return result.succeed();
     }
 
@@ -377,28 +392,30 @@ js::ProxySetPropertyByValue(JSContext* c
     if (!Proxy::set(cx, proxy, id, val, receiver, result))
         return false;
     return result.checkStrictErrorOrWarning(cx, proxy, id, strict);
 }
 
 bool
 Proxy::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true);
     if (!policy.allowed())
         return policy.returnValue();
     return handler->getOwnEnumerablePropertyKeys(cx, proxy, props);
 }
 
 bool
 Proxy::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     objp.set(nullptr); // default result if we refuse to perform this action
 
     if (handler->hasPrototype()) {
         AutoIdVector props(cx);
         if (!Proxy::getOwnEnumerablePropertyKeys(cx, proxy, props))
             return false;
 
@@ -425,17 +442,18 @@ Proxy::enumerate(JSContext* cx, HandleOb
             NewEmptyPropertyIterator(cx, 0, objp);
     }
     return handler->enumerate(cx, proxy, objp);
 }
 
 bool
 Proxy::call(JSContext* cx, HandleObject proxy, const CallArgs& args)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
 
     // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we
     // can only set our default value once we're sure that we're not calling the
     // trap.
     AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
                            BaseProxyHandler::CALL, true);
     if (!policy.allowed()) {
@@ -444,17 +462,18 @@ Proxy::call(JSContext* cx, HandleObject 
     }
 
     return handler->call(cx, proxy, args);
 }
 
 bool
 Proxy::construct(JSContext* cx, HandleObject proxy, const CallArgs& args)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
 
     // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we
     // can only set our default value once we're sure that we're not calling the
     // trap.
     AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
                            BaseProxyHandler::CALL, true);
     if (!policy.allowed()) {
@@ -463,47 +482,51 @@ Proxy::construct(JSContext* cx, HandleOb
     }
 
     return handler->construct(cx, proxy, args);
 }
 
 bool
 Proxy::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, const CallArgs& args)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     RootedObject proxy(cx, &args.thisv().toObject());
     // Note - we don't enter a policy here because our security architecture
     // guards against nativeCall by overriding the trap itself in the right
     // circumstances.
     return proxy->as<ProxyObject>().handler()->nativeCall(cx, test, impl, args);
 }
 
 bool
 Proxy::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     *bp = false; // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, true);
     if (!policy.allowed())
         return policy.returnValue();
     return proxy->as<ProxyObject>().handler()->hasInstance(cx, proxy, v, bp);
 }
 
 bool
 Proxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     return proxy->as<ProxyObject>().handler()->getBuiltinClass(cx, proxy, cls);
 }
 
 bool
 Proxy::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     return proxy->as<ProxyObject>().handler()->isArray(cx, proxy, answer);
 }
 
 const char*
 Proxy::className(JSContext* cx, HandleObject proxy)
 {
     // Check for unbounded recursion, but don't signal an error; className
     // needs to be infallible.
@@ -519,61 +542,67 @@ Proxy::className(JSContext* cx, HandleOb
         return handler->BaseProxyHandler::className(cx, proxy);
     }
     return handler->className(cx, proxy);
 }
 
 JSString*
 Proxy::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent)
 {
-    JS_CHECK_RECURSION(cx, return nullptr);
+    if (!CheckRecursionLimit(cx))
+        return nullptr;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE,
                            BaseProxyHandler::GET, /* mayThrow = */ false);
     // Do the safe thing if the policy rejects.
     if (!policy.allowed())
         return handler->BaseProxyHandler::fun_toString(cx, proxy, indent);
     return handler->fun_toString(cx, proxy, indent);
 }
 
 bool
 Proxy::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     return proxy->as<ProxyObject>().handler()->regexp_toShared(cx, proxy, g);
 }
 
 bool
 Proxy::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     return proxy->as<ProxyObject>().handler()->boxedValue_unbox(cx, proxy, vp);
 }
 
 JSObject * const TaggedProto::LazyProto = reinterpret_cast<JSObject*>(0x1);
 
 /* static */ bool
 Proxy::watch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleObject callable)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     return proxy->as<ProxyObject>().handler()->watch(cx, proxy, id, callable);
 }
 
 /* static */ bool
 Proxy::unwatch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     return proxy->as<ProxyObject>().handler()->unwatch(cx, proxy, id);
 }
 
 /* static */ bool
 Proxy::getElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end,
                    ElementAdder* adder)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET,
                            /* mayThrow = */ true);
     if (!policy.allowed()) {
         if (policy.returnValue()) {
             MOZ_ASSERT(!cx->isExceptionPending());
             return js::GetElementsWithAdder(cx, proxy, proxy, begin, end, adder);
         }
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2680,18 +2680,16 @@ TryNoteName(JSTryNoteKind kind)
       case JSTRY_FINALLY:
         return "finally";
       case JSTRY_FOR_IN:
         return "for-in";
       case JSTRY_FOR_OF:
         return "for-of";
       case JSTRY_LOOP:
         return "loop";
-      case JSTRY_ITERCLOSE:
-        return "iterclose";
       case JSTRY_DESTRUCTURING_ITERCLOSE:
         return "dstr-iterclose";
     }
 
     MOZ_CRASH("Bad JSTryNoteKind");
 }
 
 static MOZ_MUST_USE bool
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -2747,17 +2747,18 @@ DebugEnvironments::onCompartmentUnsetIsD
         envs->missingEnvs.clear();
         envs->liveEnvs.clear();
     }
 }
 
 bool
 DebugEnvironments::updateLiveEnvironments(JSContext* cx)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     /*
      * Note that we must always update the top frame's environment objects'
      * entries in liveEnvs because we can't be sure code hasn't run in that
      * frame to change the environment chain since we were last called. The
      * fp->prevUpToDate() flag indicates whether the environments of frames
      * older than fp are already included in liveEnvs. It might seem simpler
      * to have fp instead carry a flag indicating whether fp itself is
@@ -2988,17 +2989,18 @@ GetDebugEnvironmentForNonEnvironmentObje
         MOZ_ASSERT(!o->is<EnvironmentObject>());
 #endif
     return &enclosing;
 }
 
 static JSObject*
 GetDebugEnvironment(JSContext* cx, const EnvironmentIter& ei)
 {
-    JS_CHECK_RECURSION(cx, return nullptr);
+    if (!CheckRecursionLimit(cx))
+        return nullptr;
 
     if (ei.done())
         return GetDebugEnvironmentForNonEnvironmentObject(ei);
 
     if (ei.hasAnyEnvironmentObject())
         return GetDebugEnvironmentForEnvironmentObject(cx, ei);
 
     if (ei.scope().is<FunctionScope>() ||
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -338,17 +338,18 @@ ExecuteState::pushInterpreterFrame(JSCon
 // stack frames and stack overflow issues, see bug 1167883. Turn off PGO to
 // avoid this.
 #ifdef _MSC_VER
 # pragma optimize("g", off)
 #endif
 bool
 js::RunScript(JSContext* cx, RunState& state)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     // Since any script can conceivably GC, make sure it's safe to do so.
     cx->verifyIsSafeToGC();
 
     MOZ_DIAGNOSTIC_ASSERT(cx->compartment()->isSystem() ||
                           cx->runtime()->allowContentJS());
 
     MOZ_ASSERT(!cx->enableAccessValidation ||
@@ -613,27 +614,29 @@ js::InternalConstructWithProvidedThis(JS
     return true;
 }
 
 bool
 js::CallGetter(JSContext* cx, HandleValue thisv, HandleValue getter, MutableHandleValue rval)
 {
     // Invoke could result in another try to get or set the same id again, see
     // bug 355497.
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     FixedInvokeArgs<0> args(cx);
 
     return Call(cx, getter, thisv, args, rval);
 }
 
 bool
 js::CallSetter(JSContext* cx, HandleValue thisv, HandleValue setter, HandleValue v)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
 
     FixedInvokeArgs<1> args(cx);
 
     args[0].set(v);
 
     RootedValue ignored(cx);
     return Call(cx, setter, thisv, args, &ignored);
 }
@@ -1177,27 +1180,16 @@ ProcessTryNotes(JSContext* cx, Environme
                 // location of the throw (the iterator). Indeed, we must
                 // settle to avoid infinitely handling the same exception.
                 SettleOnTryNote(cx, tn, ei, regs);
                 return ErrorReturnContinuation;
             }
             break;
           }
 
-          case JSTRY_ITERCLOSE: {
-            // The iterator object is at the top of the stack.
-            Value* sp = regs.spForStackDepth(tn->stackDepth);
-            RootedObject iterObject(cx, &sp[-1].toObject());
-            if (!IteratorCloseForException(cx, iterObject)) {
-                SettleOnTryNote(cx, tn, ei, regs);
-                return ErrorReturnContinuation;
-            }
-            break;
-          }
-
           case JSTRY_DESTRUCTURING_ITERCLOSE: {
             // Whether the destructuring iterator is done is at the top of the
             // stack. The iterator object is second from the top.
             MOZ_ASSERT(tn->stackDepth > 1);
             Value* sp = regs.spForStackDepth(tn->stackDepth);
             RootedValue doneValue(cx, sp[-1]);
             bool done = ToBoolean(doneValue);
             if (!done) {
@@ -1876,17 +1868,16 @@ CASE(EnableInterruptsPseudoOpcode)
 
 /* Various 1-byte no-ops. */
 CASE(JSOP_NOP)
 CASE(JSOP_NOP_DESTRUCTURING)
 CASE(JSOP_UNUSED192)
 CASE(JSOP_UNUSED209)
 CASE(JSOP_UNUSED210)
 CASE(JSOP_UNUSED211)
-CASE(JSOP_UNUSED219)
 CASE(JSOP_UNUSED220)
 CASE(JSOP_UNUSED221)
 CASE(JSOP_UNUSED222)
 CASE(JSOP_UNUSED223)
 CASE(JSOP_CONDSWITCH)
 {
     MOZ_ASSERT(CodeSpec[*REGS.pc].length == 1);
     ADVANCE_AND_DISPATCH(1);
@@ -2622,16 +2613,25 @@ CASE(JSOP_CHECKISOBJ)
 {
     if (!REGS.sp[-1].isObject()) {
         MOZ_ALWAYS_FALSE(ThrowCheckIsObject(cx, CheckIsObjectKind(GET_UINT8(REGS.pc))));
         goto error;
     }
 }
 END_CASE(JSOP_CHECKISOBJ)
 
+CASE(JSOP_CHECKISCALLABLE)
+{
+    if (!IsCallable(REGS.sp[-1])) {
+        MOZ_ALWAYS_FALSE(ThrowCheckIsCallable(cx, CheckIsCallableKind(GET_UINT8(REGS.pc))));
+        goto error;
+    }
+}
+END_CASE(JSOP_CHECKISCALLABLE)
+
 CASE(JSOP_CHECKTHIS)
 {
     if (REGS.sp[-1].isMagic(JS_UNINITIALIZED_LEXICAL)) {
         MOZ_ALWAYS_FALSE(ThrowUninitializedThis(cx, REGS.fp()));
         goto error;
     }
 }
 END_CASE(JSOP_CHECKTHIS)
@@ -5036,16 +5036,29 @@ js::ThrowCheckIsObject(JSContext* cx, Ch
         break;
       default:
         MOZ_CRASH("Unknown kind");
     }
     return false;
 }
 
 bool
+js::ThrowCheckIsCallable(JSContext* cx, CheckIsCallableKind kind)
+{
+    switch (kind) {
+      case CheckIsCallableKind::IteratorReturn:
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_RETURN_NOT_CALLABLE);
+        break;
+      default:
+        MOZ_CRASH("Unknown kind");
+    }
+    return false;
+}
+
+bool
 js::ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame)
 {
     RootedFunction fun(cx);
     if (frame.isFunctionFrame()) {
         fun = frame.callee();
     } else {
         Scope* startingScope;
         if (frame.isDebuggerEvalFrame()) {
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -557,16 +557,23 @@ enum class CheckIsObjectKind : uint8_t {
     IteratorReturn,
     IteratorThrow,
     GetIterator
 };
 
 bool
 ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind);
 
+enum class CheckIsCallableKind : uint8_t {
+    IteratorReturn
+};
+
+bool
+ThrowCheckIsCallable(JSContext* cx, CheckIsCallableKind kind);
+
 bool
 ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame);
 
 bool
 DefaultClassConstructor(JSContext* cx, unsigned argc, Value* vp);
 
 bool
 Debug_CheckSelfHosted(JSContext* cx, HandleValue v);
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -2015,17 +2015,18 @@ GetNonexistentProperty(JSContext* cx, Na
 {
     return false;
 }
 
 static inline bool
 GeneralizedGetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue receiver,
                        IsNameLookup nameLookup, MutableHandleValue vp)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!CheckRecursionLimit(cx))
+        return false;
     if (nameLookup) {
         // When nameLookup is true, GetProperty implements ES6 rev 34 (2015 Feb
         // 20) 8.1.1.2.6 GetBindingValue, with step 3 (the call to HasProperty)
         // and step 6 (the call to Get) fused so that only a single lookup is
         // needed.
         //
         // If we get here, we've reached a non-native object. Fall back on the
         // algorithm as specified, with two separate lookups. (Note that we
@@ -2040,17 +2041,18 @@ GeneralizedGetProperty(JSContext* cx, Ha
 
     return GetProperty(cx, obj, receiver, id, vp);
 }
 
 static inline bool
 GeneralizedGetProperty(JSContext* cx, JSObject* obj, jsid id, const Value& receiver,
                        IsNameLookup nameLookup, FakeMutableHandle<Value> vp)
 {
-    JS_CHECK_RECURSION_DONT_REPORT(cx, return false);
+    if (!CheckRecursionLimitDontReport(cx))
+        return false;
     if (nameLookup)
         return false;
     return GetPropertyNoGC(cx, obj, receiver, id, vp.address());
 }
 
 template <AllowGC allowGC>
 static MOZ_ALWAYS_INLINE bool
 NativeGetPropertyInline(JSContext* cx,
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -2202,17 +2202,26 @@ 1234567890123456789012345678901234567890
      * This opcode is used with the JSOP_NEWARRAY opcode.
      *   Category: Literals
      *   Type: Array
      *   Operands:
      *   Stack: => hole
      */ \
     macro(JSOP_HOLE,          218, "hole",         NULL,  1,  0,  1,  JOF_BYTE) \
     \
-    macro(JSOP_UNUSED219,     219,"unused219",     NULL,  1,  0,  0,  JOF_BYTE) \
+    /*
+     * Checks that the top value on the stack is callable, and throws a
+     * TypeError if not. The operand 'kind' is used only to generate an
+     * appropriate error message.
+     *   Category: Statements
+     *   Type: Function
+     *   Operands: uint8_t kind
+     *   Stack: result => result, callable
+     */ \
+    macro(JSOP_CHECKISCALLABLE, 219, "checkiscallable", NULL, 2, 1, 1, JOF_UINT8) \
     macro(JSOP_UNUSED220,     220,"unused220",     NULL,  1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED221,     221,"unused221",     NULL,  1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED222,     222,"unused222",     NULL,  1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED223,     223,"unused223",     NULL,  1,  0,  0,  JOF_BYTE) \
     \
     /*
      * Creates rest parameter array for current function call, and pushes it
      * onto the stack.
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -5798,17 +5798,18 @@ CheckCoercedAtomicsBuiltinCall(FunctionV
     return CoerceResult(f, callNode, ret, actual, type);
 }
 
 static bool
 CheckCoercedCall(FunctionValidator& f, ParseNode* call, Type ret, Type* type)
 {
     MOZ_ASSERT(ret.isCanonical());
 
-    JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
+    if (!CheckRecursionLimitDontReport(f.cx()))
+        return f.m().failOverRecursed();
 
     bool isSimd = false;
     if (IsNumericLiteral(f.m(), call, &isSimd)) {
         if (isSimd)
             f.setUsesSimd();
         NumLit lit = ExtractNumericLiteral(f.m(), call);
         if (!f.writeConstExpr(lit))
             return false;
@@ -6107,17 +6108,18 @@ CheckMultiply(FunctionValidator& f, Pars
     }
 
     return f.fail(star, "multiply operands must be both int, both double? or both float?");
 }
 
 static bool
 CheckAddOrSub(FunctionValidator& f, ParseNode* expr, Type* type, unsigned* numAddOrSubOut = nullptr)
 {
-    JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
+    if (!CheckRecursionLimitDontReport(f.cx()))
+        return f.m().failOverRecursed();
 
     MOZ_ASSERT(expr->isKind(PNK_ADD) || expr->isKind(PNK_SUB));
     ParseNode* lhs = AddSubLeft(expr);
     ParseNode* rhs = AddSubRight(expr);
 
     Type lhsType, rhsType;
     unsigned lhsNumAddOrSub, rhsNumAddOrSub;
 
@@ -6347,17 +6349,18 @@ CheckBitwise(FunctionValidator& f, Parse
     }
 
     return true;
 }
 
 static bool
 CheckExpr(FunctionValidator& f, ParseNode* expr, Type* type)
 {
-    JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
+    if (!CheckRecursionLimitDontReport(f.cx()))
+        return f.m().failOverRecursed();
 
     bool isSimd = false;
     if (IsNumericLiteral(f.m(), expr, &isSimd)) {
         if (isSimd)
             f.setUsesSimd();
         return CheckNumericLiteral(f, expr, type);
     }
 
@@ -7006,17 +7009,18 @@ CheckBreakOrContinue(FunctionValidator& 
     if (PropertyName* maybeLabel = LoopControlMaybeLabel(stmt))
         return f.writeLabeledBreakOrContinue(maybeLabel, isBreak);
     return f.writeUnlabeledBreakOrContinue(isBreak);
 }
 
 static bool
 CheckStatement(FunctionValidator& f, ParseNode* stmt)
 {
-    JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
+    if (!CheckRecursionLimitDontReport(f.cx()))
+        return f.m().failOverRecursed();
 
     switch (stmt->getKind()) {
       case PNK_SEMI:          return CheckExprStatement(f, stmt);
       case PNK_WHILE:         return CheckWhile(f, stmt);
       case PNK_FOR:           return CheckFor(f, stmt);
       case PNK_DOWHILE:       return CheckDoWhile(f, stmt);
       case PNK_LABEL:         return CheckLabel(f, stmt);
       case PNK_IF:            return CheckIf(f, stmt);
--- a/js/xpconnect/src/XPCVariant.cpp
+++ b/js/xpconnect/src/XPCVariant.cpp
@@ -253,17 +253,18 @@ XPCArrayHomogenizer::GetTypeForArray(JSC
             NS_ERROR("bad state");
             return false;
     }
     return true;
 }
 
 bool XPCVariant::InitializeData(JSContext* cx)
 {
-    JS_CHECK_RECURSION(cx, return false);
+    if (!js::CheckRecursionLimit(cx))
+        return false;
 
     RootedValue val(cx, GetJSVal());
 
     if (val.isInt32()) {
         mData.SetFromInt32(val.toInt32());
         return true;
     }
     if (val.isDouble()) {
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -2908,21 +2908,21 @@ PresShell::RecreateFramesFor(nsIContent*
   nsAutoScriptBlocker scriptBlocker;
 
   nsStyleChangeList changeList;
   changeList.AppendChange(nullptr, aContent, nsChangeHint_ReconstructFrame);
 
   // Mark ourselves as not safe to flush while we're doing frame construction.
   ++mChangeNestCount;
   RestyleManager* restyleManager = mPresContext->RestyleManager();
-  nsresult rv = restyleManager->ProcessRestyledFrames(changeList);
+  restyleManager->ProcessRestyledFrames(changeList);
   restyleManager->FlushOverflowChangedTracker();
   --mChangeNestCount;
 
-  return rv;
+  return NS_OK;
 }
 
 void
 nsIPresShell::PostRecreateFramesFor(Element* aElement)
 {
   mPresContext->RestyleManager()->PostRestyleEvent(aElement, nsRestyleHint(0),
                                                    nsChangeHint_ReconstructFrame);
 }
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -1355,23 +1355,23 @@ RestyleManager::GetNextContinuationWithS
     nextContinuation = nullptr;
     if (aHaveMoreContinuations) {
       *aHaveMoreContinuations = true;
     }
   }
   return nextContinuation;
 }
 
-nsresult
+void
 RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList)
 {
   NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
                "Someone forgot a script blocker");
   if (aChangeList.IsEmpty())
-    return NS_OK;
+    return;
 
   PROFILER_LABEL("RestyleManager", "ProcessRestyledFrames",
                  js::ProfileEntry::Category::CSS);
 
   nsPresContext* presContext = PresContext();
   FramePropertyTable* propTable = presContext->PropertyTable();
   nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor();
 
@@ -1672,17 +1672,16 @@ RestyleManager::ProcessRestyledFrames(ns
                data.mFrame->GetType() != nsGkAtoms::viewportFrame) {
       NS_WARNING("Unable to test style tree integrity -- no content node "
                  "(and not a viewport frame)");
     }
 #endif
   }
 
   aChangeList.Clear();
-  return NS_OK;
 }
 
 RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame(
                                                 RestyleManager* aRestyleManager)
   : mRestyleManager(aRestyleManager)
   , mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame)
 {
   MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame,
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -73,17 +73,17 @@ public:
     mOverflowChangedTracker.RemoveFrame(aFrame);
   }
 
   // Note: It's the caller's responsibility to make sure to wrap a
   // ProcessRestyledFrames call in a view update batch and a script blocker.
   // This function does not call ProcessAttachedQueue() on the binding manager.
   // If the caller wants that to happen synchronously, it needs to handle that
   // itself.
-  nsresult ProcessRestyledFrames(nsStyleChangeList& aChangeList);
+  void ProcessRestyledFrames(nsStyleChangeList& aChangeList);
 
   bool IsInStyleRefresh() const { return mInStyleRefresh; }
 
   // AnimationsWithDestroyedFrame is used to stop animations and transitions
   // on elements that have no frame at the end of the restyling process.
   // It only lives during the restyling process.
   class MOZ_STACK_CLASS AnimationsWithDestroyedFrame final {
   public:
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/ServoRestyleManager.h"
 
 #include "mozilla/DocumentStyleRootIterator.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/dom/ChildIterator.h"
 #include "nsContentUtils.h"
+#include "nsCSSFrameConstructor.h"
 #include "nsPrintfCString.h"
 #include "nsRefreshDriver.h"
 #include "nsStyleChangeList.h"
 
 using namespace mozilla::dom;
 
 namespace mozilla {
 
@@ -191,34 +192,47 @@ ServoRestyleManager::RecreateStyleContex
   //
   // Hold the old style context alive, because it could become a dangling
   // pointer during the replacement. In practice it's not a huge deal (on
   // GetNextContinuationWithSameStyle the pointer is not dereferenced, only
   // compared), but better not playing with dangling pointers if not needed.
   RefPtr<nsStyleContext> oldStyleContext =
     styleFrame ? styleFrame->StyleContext() : nullptr;
 
+  UndisplayedNode* displayContentsNode = nullptr;
+  // FIXME(emilio, bug 1303605): This can be simpler for Servo.
+  // Note that we intentionally don't check for display: none content.
+  if (!oldStyleContext) {
+    displayContentsNode =
+      PresContext()->FrameConstructor()->GetDisplayContentsNodeFor(aElement);
+    if (displayContentsNode) {
+      oldStyleContext = displayContentsNode->mStyle;
+    }
+  }
+
   RefPtr<ServoComputedValues> computedValues =
     aStyleSet->ResolveServoStyle(aElement);
 
   // Note that we rely in the fact that we don't cascade pseudo-element styles
   // separately right now (that is, if a pseudo style changes, the normal style
   // changes too).
   //
   // Otherwise we should probably encode that information somehow to avoid
   // expensive checks in the common case.
   //
   // Also, we're going to need to check for pseudos of display: contents
   // elements, though that is buggy right now even in non-stylo mode, see
   // bug 1251799.
   const bool recreateContext = oldStyleContext &&
     oldStyleContext->StyleSource().AsServoComputedValues() != computedValues;
 
+  RefPtr<nsStyleContext> newContext = nullptr;
   if (recreateContext) {
-    RefPtr<nsStyleContext> newContext =
+    MOZ_ASSERT(styleFrame || displayContentsNode);
+    newContext =
       aStyleSet->GetContext(computedValues.forget(), aParentContext, nullptr,
                             CSSPseudoElementType::NotPseudo, aElement);
 
     newContext->EnsureStructsForServo(oldStyleContext);
 
     // XXX This could not always work as expected: there are kinds of content
     // with the first split and the last sharing style, but others not. We
     // should handle those properly.
@@ -230,16 +244,21 @@ ServoRestyleManager::RecreateStyleContex
     if (isTable) {
       nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
       MOZ_ASSERT(primaryFrame->StyleContext()->GetPseudo() ==
                    nsCSSAnonBoxes::tableWrapper,
                  "What sort of frame is this?");
       UpdateStyleContextForTableWrapper(primaryFrame, newContext, aStyleSet);
     }
 
+    if (MOZ_UNLIKELY(displayContentsNode)) {
+      MOZ_ASSERT(!styleFrame);
+      displayContentsNode->mStyle = newContext;
+    }
+
     // Update pseudo-elements state if appropriate.
     const static CSSPseudoElementType pseudosToRestyle[] = {
       CSSPseudoElementType::before,
       CSSPseudoElementType::after,
     };
 
     for (CSSPseudoElementType pseudoType : pseudosToRestyle) {
       nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(pseudoType);
@@ -268,34 +287,26 @@ ServoRestyleManager::RecreateStyleContex
         }
       }
     }
   }
 
   bool traverseElementChildren = aElement->HasDirtyDescendantsForServo();
   bool traverseTextChildren = recreateContext;
   if (traverseElementChildren || traverseTextChildren) {
+    nsStyleContext* upToDateContext =
+      recreateContext ? newContext : oldStyleContext;
+
     StyleChildrenIterator it(aElement);
     for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
       if (traverseElementChildren && n->IsElement()) {
-        if (!styleFrame) {
-          // The frame constructor presumably decided to suppress frame
-          // construction on this subtree. Just clear the dirty descendants
-          // bit from the subtree, since there's no point in harvesting the
-          // change hints.
-          MOZ_ASSERT(!n->AsElement()->GetPrimaryFrame(),
-                     "Only display:contents should do this, and we don't handle that yet");
-          ClearDirtyDescendantsFromSubtree(n->AsElement());
-        } else {
-          RecreateStyleContexts(n->AsElement(), styleFrame->StyleContext(),
-                                aStyleSet, aChangeListToProcess);
-        }
+        RecreateStyleContexts(n->AsElement(), upToDateContext,
+                              aStyleSet, aChangeListToProcess);
       } else if (traverseTextChildren && n->IsNodeOfType(nsINode::eTEXT)) {
-        RecreateStyleContextsForText(n, styleFrame->StyleContext(),
-                                     aStyleSet);
+        RecreateStyleContextsForText(n, upToDateContext, aStyleSet);
       }
     }
   }
 
   aElement->UnsetHasDirtyDescendantsForServo();
 }
 
 void
@@ -322,16 +333,17 @@ ServoRestyleManager::FrameForPseudoEleme
 {
   MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement());
   nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
 
   if (!aPseudoTagOrNull) {
     return primaryFrame;
   }
 
+  // FIXME(emilio): Need to take into account display: contents pseudos!
   if (!primaryFrame) {
     return nullptr;
   }
 
   // NOTE: we probably need to special-case display: contents here. Gecko's
   // RestyleManager passes the primary frame of the parent instead.
   if (aPseudoTagOrNull == nsCSSPseudoElements::before) {
     return nsLayoutUtils::GetBeforeFrameForContent(primaryFrame, aContent);
--- a/layout/base/nsFrameManager.cpp
+++ b/layout/base/nsFrameManager.cpp
@@ -201,33 +201,53 @@ nsFrameManager::ClearPlaceholderFrameMap
     auto entry = static_cast<PlaceholderMapEntry*>(iter.Get());
     entry->placeholderFrame->SetOutOfFlowFrame(nullptr);
   }
   mPlaceholderMap.Clear();
 }
 
 //----------------------------------------------------------------------
 
+static nsIContent*
+ParentForUndisplayedMap(const nsIContent* aContent)
+{
+  MOZ_ASSERT(aContent);
+
+  nsIContent* parent = aContent->GetParentElementCrossingShadowRoot();
+  MOZ_ASSERT(parent || !aContent->GetParent(), "no non-elements");
+
+  return parent;
+}
+
 /* static */ nsStyleContext*
-nsFrameManager::GetStyleContextInMap(UndisplayedMap* aMap, nsIContent* aContent)
+nsFrameManager::GetStyleContextInMap(UndisplayedMap* aMap,
+                                     const nsIContent* aContent)
+{
+  UndisplayedNode* node = GetUndisplayedNodeInMapFor(aMap, aContent);
+  return node ? node->mStyle.get() : nullptr;
+}
+
+/* static */ UndisplayedNode*
+nsFrameManager::GetUndisplayedNodeInMapFor(UndisplayedMap* aMap,
+                                           const nsIContent* aContent)
 {
   if (!aContent) {
     return nullptr;
   }
-  nsIContent* parent = aContent->GetParentElementCrossingShadowRoot();
-  MOZ_ASSERT(parent || !aContent->GetParent(), "no non-elements");
+  nsIContent* parent = ParentForUndisplayedMap(aContent);
   for (UndisplayedNode* node = aMap->GetFirstNode(parent);
          node; node = node->mNext) {
     if (node->mContent == aContent)
-      return node->mStyle;
+      return node;
   }
 
   return nullptr;
 }
 
+
 /* static */ UndisplayedNode*
 nsFrameManager::GetAllUndisplayedNodesInMapFor(UndisplayedMap* aMap,
                                                nsIContent* aParentContent)
 {
   return aMap ? aMap->GetFirstNode(aParentContent) : nullptr;
 }
 
 UndisplayedNode*
@@ -247,18 +267,17 @@ nsFrameManager::SetStyleContextInMap(Und
 #if defined(DEBUG_UNDISPLAYED_MAP) || defined(DEBUG_DISPLAY_BOX_CONTENTS_MAP)
   static int i = 0;
   printf("SetStyleContextInMap(%d): p=%p \n", i++, (void *)aContent);
 #endif
 
   NS_ASSERTION(!GetStyleContextInMap(aMap, aContent),
                "Already have an entry for aContent");
 
-  nsIContent* parent = aContent->GetParentElementCrossingShadowRoot();
-  MOZ_ASSERT(parent || !aContent->GetParent(), "no non-elements");
+  nsIContent* parent = ParentForUndisplayedMap(aContent);
 #ifdef DEBUG
   nsIPresShell* shell = aStyleContext->PresContext()->PresShell();
   NS_ASSERTION(parent || (shell && shell->GetDocument() &&
                           shell->GetDocument()->GetRootElement() == aContent),
                "undisplayed content must have a parent, unless it's the root "
                "element");
 #endif
   aMap->AddNodeFor(parent, aContent, aStyleContext);
--- a/layout/base/nsFrameManager.h
+++ b/layout/base/nsFrameManager.h
@@ -97,17 +97,17 @@ public:
   // Placeholder frame functions
   nsPlaceholderFrame* GetPlaceholderFrameFor(const nsIFrame* aFrame);
   void RegisterPlaceholderFrame(nsPlaceholderFrame* aPlaceholderFrame);
   void UnregisterPlaceholderFrame(nsPlaceholderFrame* aPlaceholderFrame);
 
   void      ClearPlaceholderFrameMap();
 
   // Mapping undisplayed content
-  nsStyleContext* GetUndisplayedContent(nsIContent* aContent)
+  nsStyleContext* GetUndisplayedContent(const nsIContent* aContent)
   {
     if (!mUndisplayedMap) {
       return nullptr;
     }
     return GetStyleContextInMap(mUndisplayedMap, aContent);
   }
   mozilla::UndisplayedNode*
     GetAllUndisplayedContentIn(nsIContent* aParentContent);
@@ -122,29 +122,42 @@ public:
   void ClearUndisplayedContentIn(nsIContent* aContent,
                                  nsIContent* aParentContent);
   void ClearAllUndisplayedContentIn(nsIContent* aParentContent);
 
   // display:contents related methods:
   /**
    * Return the registered display:contents style context for aContent, if any.
    */
-  nsStyleContext* GetDisplayContentsStyleFor(nsIContent* aContent)
+  nsStyleContext* GetDisplayContentsStyleFor(const nsIContent* aContent)
   {
     if (!mDisplayContentsMap) {
       return nullptr;
     }
     return GetStyleContextInMap(mDisplayContentsMap, aContent);
   }
 
   /**
    * Return the linked list of UndisplayedNodes containing the registered
    * display:contents children of aParentContent, if any.
    */
   mozilla::UndisplayedNode* GetAllDisplayContentsIn(nsIContent* aParentContent);
+
+  /**
+   * Return the relevant undisplayed node for a given content with display:
+   * contents style.
+   */
+  mozilla::UndisplayedNode* GetDisplayContentsNodeFor(
+      const nsIContent* aContent) {
+    if (!mDisplayContentsMap) {
+      return nullptr;
+    }
+    return GetUndisplayedNodeInMapFor(mDisplayContentsMap, aContent);
+  }
+
   /**
    * Register aContent having a display:contents style context.
    */
   void SetDisplayContents(nsIContent* aContent,
                           nsStyleContext* aStyleContext);
   /**
    * Change the registered style context for aContent to aStyleContext.
    */
@@ -202,17 +215,20 @@ public:
    */
   void CaptureFrameStateFor(nsIFrame*              aFrame,
                                         nsILayoutHistoryState* aState);
 
   void RestoreFrameStateFor(nsIFrame*              aFrame,
                                         nsILayoutHistoryState* aState);
 protected:
   static nsStyleContext* GetStyleContextInMap(UndisplayedMap* aMap,
-                                              nsIContent* aContent);
+                                              const nsIContent* aContent);
+  static mozilla::UndisplayedNode*
+    GetUndisplayedNodeInMapFor(UndisplayedMap* aMap,
+                               const nsIContent* aContent);
   static mozilla::UndisplayedNode*
     GetAllUndisplayedNodesInMapFor(UndisplayedMap* aMap,
                                    nsIContent* aParentContent);
   static void SetStyleContextInMap(UndisplayedMap* aMap,
                                    nsIContent* aContent,
                                    nsStyleContext* aStyleContext);
   static void ChangeStyleContextInMap(UndisplayedMap* aMap,
                                       nsIContent* aContent,
--- a/layout/reftests/css-grid/reftest-stylo.list
+++ b/layout/reftests/css-grid/reftest-stylo.list
@@ -169,18 +169,18 @@ fails == grid-repeat-auto-fill-fit-002.h
 fails == grid-repeat-auto-fill-fit-003.html grid-repeat-auto-fill-fit-003.html
 fails == grid-repeat-auto-fill-fit-004.html grid-repeat-auto-fill-fit-004.html
 fails == grid-repeat-auto-fill-fit-005.html grid-repeat-auto-fill-fit-005.html
 fails == grid-repeat-auto-fill-fit-006.html grid-repeat-auto-fill-fit-006.html
 fails == grid-repeat-auto-fill-fit-007.html grid-repeat-auto-fill-fit-007.html
 fails == grid-repeat-auto-fill-fit-008.html grid-repeat-auto-fill-fit-008.html
 fails == grid-repeat-auto-fill-fit-009.html grid-repeat-auto-fill-fit-009.html
 fails == grid-repeat-auto-fill-fit-010.html grid-repeat-auto-fill-fit-010.html
-# == grid-repeat-auto-fill-fit-011.html grid-repeat-auto-fill-fit-011.html # bug 1342710
-fails == grid-item-blockifying-001.html grid-item-blockifying-001.html # bug 1335339
+fails == grid-repeat-auto-fill-fit-011.html grid-repeat-auto-fill-fit-011.html
+fails == grid-item-blockifying-001.html grid-item-blockifying-001.html
 fails == grid-fragmentation-001.html grid-fragmentation-001.html
 fails == grid-fragmentation-002.html grid-fragmentation-002.html
 fails == grid-fragmentation-003.html grid-fragmentation-003.html
 fails == grid-fragmentation-004.html grid-fragmentation-004.html
 fails == grid-fragmentation-005.html grid-fragmentation-005.html
 fails == grid-fragmentation-006.html grid-fragmentation-006.html
 fails == grid-fragmentation-007.html grid-fragmentation-007.html
 fails == grid-fragmentation-008.html grid-fragmentation-008.html
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.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 "mozilla/ServoBindings.h"
 
 #include "ChildIterator.h"
 #include "gfxFontFamilyList.h"
 #include "nsAttrValueInlines.h"
+#include "nsCSSFrameConstructor.h"
 #include "nsCSSProps.h"
 #include "nsCSSParser.h"
 #include "nsCSSPseudoElements.h"
 #include "nsCSSRuleProcessor.h"
 #include "nsContentUtils.h"
 #include "nsDOMTokenList.h"
 #include "nsIContentInlines.h"
 #include "nsIDOMNode.h"
@@ -274,21 +275,31 @@ Gecko_SetOwnerDocumentNeedsStyleFlush(Ra
 
 nsStyleContext*
 Gecko_GetStyleContext(RawGeckoNodeBorrowed aNode, nsIAtom* aPseudoTagOrNull)
 {
   MOZ_ASSERT(aNode->IsContent());
   nsIFrame* relevantFrame =
     ServoRestyleManager::FrameForPseudoElement(aNode->AsContent(),
                                                aPseudoTagOrNull);
-  if (!relevantFrame) {
+  if (relevantFrame) {
+    return relevantFrame->StyleContext();
+  }
+
+  if (aPseudoTagOrNull) {
     return nullptr;
   }
 
-  return relevantFrame->StyleContext();
+  // FIXME(emilio): Is there a shorter path?
+  nsCSSFrameConstructor* fc =
+    aNode->OwnerDoc()->GetShell()->GetPresContext()->FrameConstructor();
+
+  // NB: This is only called for CalcStyleDifference, and we handle correctly
+  // the display: none case since Servo still has the older style.
+  return fc->GetDisplayContentsStyleFor(aNode->AsContent());
 }
 
 nsChangeHint
 Gecko_CalcStyleDifference(nsStyleContext* aOldStyleContext,
                           ServoComputedValuesBorrowed aComputedValues)
 {
   MOZ_ASSERT(aOldStyleContext);
   MOZ_ASSERT(aComputedValues);