Merge m-i to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Fri, 25 Nov 2016 21:26:21 -0800
changeset 324331 f8f4eaac1701107f794b48891bcca2c95d39d503
parent 324310 180c7af5922ee89f848c25c3847bd1fb22a71e37 (current diff)
parent 324330 5b45f7938182ea8bf814ab0bb6373bcf803755b5 (diff)
child 324332 a8f717430769a9e2b79f244569b0a4613a924f7f
child 324335 f485eaccbb98034a1111eaf4f4871de1868bcb70
child 324346 95a822efc877b26b46b348aa334fd482377267ab
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersmerge
milestone53.0a1
Merge m-i to m-c, a=merge
--- a/browser/base/content/test/general/browser_remoteTroubleshoot.js
+++ b/browser/base/content/test/general/browser_remoteTroubleshoot.js
@@ -32,18 +32,18 @@ function promiseNewChannelResponse(uri) 
     gBrowser.removeTab(tab);
     return data;
   });
 }
 
 add_task(function*() {
   // We haven't set a permission yet - so even the "good" URI should fail.
   let got = yield promiseNewChannelResponse(TEST_URI_GOOD);
-  // Should have no data.
-  Assert.ok(got.message === undefined, "should have failed to get any data");
+  // Should return an error.
+  Assert.ok(got.message.errno === 2, "should have failed with errno 2, no such channel");
 
   // Add a permission manager entry for our URI.
   Services.perms.add(TEST_URI_GOOD,
                      "remote-troubleshooting",
                      Services.perms.ALLOW_ACTION);
   registerCleanupFunction(() => {
     Services.perms.remove(TEST_URI_GOOD, "remote-troubleshooting");
   });
@@ -71,19 +71,19 @@ add_task(function*() {
                  "should have correct update channel.");
   }
 
 
   // And check some keys we know we decline to return.
   Assert.ok(!got.message.modifiedPreferences, "should not have a modifiedPreferences key");
   Assert.ok(!got.message.crashes, "should not have crash info");
 
-  // Now a http:// URI - should get nothing even with the permission setup.
+  // Now a http:// URI - should receive an error
   got = yield promiseNewChannelResponse(TEST_URI_BAD);
-  Assert.ok(got.message === undefined, "should have failed to get any data");
+  Assert.ok(got.message.errno === 2, "should have failed with errno 2, no such channel");
 
   // Check that the page can send an object as well if it's in the whitelist
   let webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
   let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
   let newWhitelist = origWhitelist + " https://example.com";
   Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
   registerCleanupFunction(() => {
     Services.prefs.clearUserPref(webchannelWhitelistPref);
--- a/browser/base/content/test/general/browser_web_channel.html
+++ b/browser/base/content/test/general/browser_web_channel.html
@@ -31,16 +31,22 @@
         test_unsolicited();
         break;
       case "bubbles":
         test_bubbles();
         break;
       case "object":
         test_object();
         break;
+      case "error_thrown":
+        test_error_thrown();
+        break;
+      case "error_invalid_channel":
+        test_error_invalid_channel();
+        break;
       default:
         throw new Error(`INVALID TEST NAME ${testName}`);
     }
   };
 
   function test_generic() {
     var event = new window.CustomEvent("WebChannelMessageToChrome", {
       detail: JSON.stringify({
@@ -167,16 +173,54 @@
       })
     });
     // Test fails if objectMessage is received, we send stringMessage to know
     // when we should stop listening for objectMessage
     window.dispatchEvent(objectMessage);
     window.dispatchEvent(stringMessage);
   }
 
+  function test_error_thrown() {
+   var event = new window.CustomEvent("WebChannelMessageToChrome", {
+     detail: JSON.stringify({
+       id: "error",
+       message: {
+         command: "oops"
+       }
+     })
+   });
+
+   // echo the response back to chrome - chrome will check it is the
+   // expected error.
+   window.addEventListener("WebChannelMessageToContent", function(e) {
+     echoEventToChannel(e, "echo");
+   }, true);
+
+   window.dispatchEvent(event);
+  }
+
+  function test_error_invalid_channel() {
+   var event = new window.CustomEvent("WebChannelMessageToChrome", {
+     detail: JSON.stringify({
+       id: "invalid-channel",
+       message: {
+         command: "oops"
+       }
+     })
+   });
+
+   // echo the response back to chrome - chrome will check it is the
+   // expected error.
+   window.addEventListener("WebChannelMessageToContent", function(e) {
+     echoEventToChannel(e, "echo");
+   }, true);
+
+   window.dispatchEvent(event);
+  }
+
   function echoEventToChannel(e, channelId) {
     var echoedEvent = new window.CustomEvent("WebChannelMessageToChrome", {
       detail: JSON.stringify({
         id: channelId,
         message: e.detail.message,
       })
     });
 
--- a/browser/base/content/test/general/browser_web_channel.js
+++ b/browser/base/content/test/general/browser_web_channel.js
@@ -391,17 +391,78 @@ var gTests = [
         gBrowser,
         url: HTTP_PATH + HTTP_ENDPOINT + "?object"
       }, function* () {
         yield testDonePromise;
         Services.prefs.setCharPref(webchannelWhitelistPref, origWhitelist);
         channel.stopListening();
       });
     }
-  }
+  },
+  {
+    desc: "WebChannel errors handling the message are delivered back to content",
+    run: function* () {
+      const ERRNO_UNKNOWN_ERROR              = 999; // WebChannel.jsm doesn't export this.
+
+      // The channel where we purposely fail responding to a command.
+      let channel = new WebChannel("error", Services.io.newURI(HTTP_PATH, null, null));
+      // The channel where we see the response when the content sees the error
+      let echoChannel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null));
+
+      let testDonePromise = new Promise((resolve, reject) => {
+        // listen for the confirmation that content saw the error.
+        echoChannel.listen((id, message, sender) => {
+          is(id, "echo");
+          is(message.error, "oh no");
+          is(message.errno, ERRNO_UNKNOWN_ERROR);
+          resolve();
+        });
+
+        // listen for a message telling us to simulate an error.
+        channel.listen((id, message, sender) => {
+          is(id, "error");
+          is(message.command, "oops");
+          throw new Error("oh no");
+        });
+      });
+      yield BrowserTestUtils.withNewTab({
+        gBrowser,
+        url: HTTP_PATH + HTTP_ENDPOINT + "?error_thrown"
+      }, function* () {
+        yield testDonePromise;
+        channel.stopListening();
+        echoChannel.stopListening();
+      });
+    }
+  },
+  {
+    desc: "WebChannel errors due to an invalid channel are delivered back to content",
+    run: function* () {
+      const ERRNO_NO_SUCH_CHANNEL            = 2; // WebChannel.jsm doesn't export this.
+      // The channel where we see the response when the content sees the error
+      let echoChannel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null));
+
+      let testDonePromise = new Promise((resolve, reject) => {
+        // listen for the confirmation that content saw the error.
+        echoChannel.listen((id, message, sender) => {
+          is(id, "echo");
+          is(message.error, "No Such Channel");
+          is(message.errno, ERRNO_NO_SUCH_CHANNEL);
+          resolve();
+        });
+      });
+      yield BrowserTestUtils.withNewTab({
+        gBrowser,
+        url: HTTP_PATH + HTTP_ENDPOINT + "?error_invalid_channel"
+      }, function* () {
+        yield testDonePromise;
+        echoChannel.stopListening();
+      });
+    }
+  },
 ]; // gTests
 
 function test() {
   waitForExplicitFinish();
 
   Task.spawn(function* () {
     for (let testCase of gTests) {
       info("Running: " + testCase.desc);
--- a/browser/themes/shared/aboutNetError.css
+++ b/browser/themes/shared/aboutNetError.css
@@ -105,20 +105,16 @@ body.certerror #advancedButton {
 }
 
 span#hostname {
   font-weight: bold;
 }
 
 #automaticallyReportInFuture {
   cursor: pointer;
-  display: inline-block;
-  padding-inline-start: 2.3em;
-  text-indent: -2.3em;
-  line-height: 16px
 }
 
 #errorCode:not([href]) {
   color: var(--in-content-page-color);
   cursor: text;
   text-decoration: none;
 }
 
--- a/browser/themes/shared/downloads/allDownloadsViewOverlay.inc.css
+++ b/browser/themes/shared/downloads/allDownloadsViewOverlay.inc.css
@@ -91,17 +91,16 @@
   padding: 5px;
   color: inherit;
 }
 
 .downloadButton > .button-box {
   -moz-appearance: none;
   padding: 2px !important;
   border-radius: 50%;
-  color: graytext;
 }
 
 .downloadButton > .button-box > .button-icon {
   width: 16px;
   height: 16px;
   margin: 0;
   filter: url("chrome://global/skin/filters.svg#fill");
   fill: currentColor;
@@ -115,20 +114,16 @@
   background-color: graytext;
   color: -moz-field;
 }
 
 .downloadButton:hover:active > .button-box {
   background-color: -moz-fieldtext;
 }
 
-@itemFocused@ > .downloadButtonArea > .downloadButton > .button-box {
-  color: inherit;
-}
-
 @itemFocused@ > .downloadButtonArea > .downloadButton:hover > .button-box {
   background-color: HighlightText;
   color: Highlight;
 }
 
 @itemFocused@ > .downloadButtonArea > .downloadButton:hover:active > .button-box {
   background-color: -moz-field;
   color: -moz-fieldtext;
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -255,17 +255,17 @@ richlistitem[type="download"] > .downloa
 
 .downloadButton {
   -moz-appearance: none;
   min-width: 58px;
   margin: 0;
   border: none;
   background: transparent;
   padding: 8px;
-  color: graytext;
+  color: inherit;
 }
 
 .downloadButton > .button-box > .button-icon {
   width: 16px;
   height: 16px;
   margin: 1px;
   filter: url("chrome://global/skin/filters.svg#fill");
   fill: currentColor;
@@ -302,20 +302,16 @@ richlistitem[type="download"] > .downloa
 
 @item@[verdict="Malware"]:hover,
 @item@[verdict="Malware"]:hover:active,
 @item@[verdict="Malware"][showingsubview] {
   background-color: #aa1b08;
   color: white;
 }
 
-@item@[verdict="Malware"]:hover > .downloadButtonArea > .downloadButton {
-  color: inherit;
-}
-
 /*** Button icons ***/
 
 .downloadIconCancel > .button-box > .button-icon {
   list-style-image: url("chrome://browser/skin/panel-icons.svg#cancel");
 }
 
 .downloadIconShow > .button-box > .button-icon {
 %ifdef XP_MACOSX
--- a/devtools/client/inspector/markup/test/browser_markup_html_edit_01.js
+++ b/devtools/client/inspector/markup/test/browser_markup_html_edit_01.js
@@ -1,16 +1,18 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 /* import-globals-from helper_outerhtml_test_runner.js */
 "use strict";
 
 // Test outerHTML edition via the markup-view
 
+requestLongerTimeout(2);
+
 loadHelperScript("helper_outerhtml_test_runner.js");
 
 const TEST_DATA = [{
   selector: "#one",
   oldHTML: '<div id="one">First <em>Div</em></div>',
   newHTML: '<div id="one">First Div</div>',
   validate: function* ({pageNodeFront, selectedNodeFront, testActor}) {
     let text = yield testActor.getProperty("#one", "textContent");
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -65,16 +65,19 @@ pref("devtools.inspector.showAllAnonymou
 pref("devtools.inspector.mdnDocsTooltip.enabled", true);
 
 // Enable the Font Inspector
 pref("devtools.fontinspector.enabled", true);
 
 // Enable the Layout View
 pref("devtools.layoutview.enabled", false);
 
+// Grid highlighter preferences
+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);
 
 // Length to collapse attributes
 pref("devtools.markup.collapseAttributeLength", 120);
--- a/devtools/client/responsive.html/browser/tunnel.js
+++ b/devtools/client/responsive.html/browser/tunnel.js
@@ -309,17 +309,18 @@ function copyPermanentKey(outer, inner) 
   // outer browser (that we're hiding the inner browser within) as part of its history.
   // We want SessionStore's view of the history for our tab to only have the page content
   // of the inner browser, so we want to hide this message from SessionStore, but we have
   // no direct mechanism to do so.  As a workaround, we wait until the one errant message
   // has gone by, and then we copy the permanentKey after that, since the permanentKey is
   // what SessionStore uses to identify each browser.
   let outerMM = outer[FRAME_LOADER].messageManager;
   let onHistoryEntry = message => {
-    let history = message.data.data.history;
+    let data = message.data.data;
+    let history = data.history || data.historychange;
     if (!history || !history.entries) {
       // Wait for a message that contains history data
       return;
     }
     outerMM.removeMessageListener("SessionStore:update", onHistoryEntry);
     debug("Got session update for outer browser");
     DevToolsUtils.executeSoon(() => {
       debug("Copy inner permanentKey to outer browser");
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1731,28 +1731,16 @@ Element::BindToTree(nsIDocument* aDocume
          child = child->GetNextSibling()) {
       rv = child->BindToTree(nullptr, shadowRoot,
                              shadowRoot->GetBindingParent(),
                              aCompileEventHandlers);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
-  // It would be cleanest to mark nodes as dirty when (a) they're created and
-  // (b) they're unbound from a tree. However, we can't easily do (a) right now,
-  // because IsStyledByServo() is not always easy to check at node creation time,
-  // and the bits have different meaning in the non-IsStyledByServo case.
-  //
-  // So for now, we just mark nodes as dirty when they're inserted into a
-  // document or shadow tree.
-  if (IsStyledByServo() && IsInComposedDoc()) {
-    MOZ_ASSERT(!HasServoData());
-    SetIsDirtyForServo();
-  }
-
   // XXXbz script execution during binding can trigger some of these
   // postcondition asserts....  But we do want that, since things will
   // generally be quite broken when that happens.
   NS_POSTCONDITION(aDocument == GetUncomposedDoc(), "Bound to wrong document");
   NS_POSTCONDITION(aParent == GetParent(), "Bound to wrong parent");
   NS_POSTCONDITION(aBindingParent == GetBindingParent(),
                    "Bound to wrong binding parent");
 
@@ -3917,8 +3905,17 @@ Element::UpdateIntersectionObservation(D
   for (auto& reg : *observers) {
     if (reg.observer == aObserver && reg.previousThreshold != aThreshold) {
       reg.previousThreshold = aThreshold;
       return true;
     }
   }
   return false;
 }
+
+void
+Element::ClearServoData() {
+#ifdef MOZ_STYLO
+  Servo_Element_ClearData(this);
+#else
+  MOZ_CRASH("Accessing servo node data in non-stylo build");
+#endif
+}
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -11,16 +11,17 @@
  */
 
 #ifndef mozilla_dom_Element_h__
 #define mozilla_dom_Element_h__
 
 #include "mozilla/dom/FragmentOrElement.h" // for base class
 #include "nsChangeHint.h"                  // for enum
 #include "mozilla/EventStates.h"           // for member
+#include "mozilla/ServoTypes.h"
 #include "mozilla/dom/DirectionalityUtils.h"
 #include "nsIDOMElement.h"
 #include "nsILinkHandler.h"
 #include "nsINodeList.h"
 #include "nsNodeUtils.h"
 #include "nsAttrAndChildArray.h"
 #include "mozFlushType.h"
 #include "nsDOMAttributeMap.h"
@@ -159,16 +160,24 @@ public:
   explicit Element(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo) :
     FragmentOrElement(aNodeInfo),
     mState(NS_EVENT_STATE_MOZ_READONLY)
   {
     MOZ_ASSERT(mNodeInfo->NodeType() == nsIDOMNode::ELEMENT_NODE,
                "Bad NodeType in aNodeInfo");
     SetIsElement();
   }
+
+  ~Element()
+  {
+#ifdef MOZ_STYLO
+    NS_ASSERTION(!HasServoData(), "expected ServoData to be cleared earlier");
+#endif
+  }
+
 #endif // MOZILLA_INTERNAL_API
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ELEMENT_IID)
 
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
 
   /**
    * Method to get the full state of this element.  See mozilla/EventStates.h
@@ -386,16 +395,50 @@ public:
   // false
   inline bool HasDirAuto() const {
     return (!HasFixedDir() &&
             (HasValidDir() || IsHTMLElement(nsGkAtoms::bdi)));
   }
 
   Directionality GetComputedDirectionality() const;
 
+  inline Element* GetFlattenedTreeParentElement() const;
+
+  bool HasDirtyDescendantsForServo() const
+  {
+    MOZ_ASSERT(IsStyledByServo());
+    return HasFlag(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+  }
+
+  void SetHasDirtyDescendantsForServo() {
+    MOZ_ASSERT(IsStyledByServo());
+    SetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+  }
+
+  void UnsetHasDirtyDescendantsForServo() {
+    MOZ_ASSERT(IsStyledByServo());
+    UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+  }
+
+  inline void NoteDirtyDescendantsForServo();
+
+#ifdef DEBUG
+  inline bool DirtyDescendantsBitIsPropagatedForServo();
+#endif
+
+  bool HasServoData() {
+#ifdef MOZ_STYLO
+    return !!mServoData.Get();
+#else
+    MOZ_CRASH("Accessing servo node data in non-stylo build");
+#endif
+  }
+
+  void ClearServoData();
+
 protected:
   /**
    * Method to get the _intrinsic_ content state of this element.  This is the
    * state that is independent of the element's presentation.  To get the full
    * content state, use State().  See mozilla/EventStates.h for
    * the possible bits that could be set here.
    */
   virtual EventStates IntrinsicState() const;
@@ -1386,16 +1429,20 @@ private:
    */
   nsRect GetClientAreaRect();
 
   nsIScrollableFrame* GetScrollFrame(nsIFrame **aStyledFrame = nullptr,
                                      bool aFlushLayout = true);
 
   // Data members
   EventStates mState;
+#ifdef MOZ_STYLO
+  // Per-node data managed by Servo.
+  mozilla::ServoCell<ServoNodeData*> mServoData;
+#endif
 };
 
 class RemoveFromBindingManagerRunnable : public mozilla::Runnable
 {
 public:
   RemoveFromBindingManagerRunnable(nsBindingManager* aManager,
                                    nsIContent* aContent,
                                    nsIDocument* aDoc);
@@ -1483,17 +1530,17 @@ inline mozilla::dom::Element* nsINode::A
 inline const mozilla::dom::Element* nsINode::AsElement() const
 {
   MOZ_ASSERT(IsElement());
   return static_cast<const mozilla::dom::Element*>(this);
 }
 
 inline void nsINode::UnsetRestyleFlagsIfGecko()
 {
-  if (IsElement() && !IsStyledByServo()) {
+  if (IsElement() && !AsElement()->IsStyledByServo()) {
     UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS);
   }
 }
 
 /**
  * Macros to implement Clone(). _elementName is the class for which to implement
  * Clone.
  */
--- a/dom/base/ElementInlines.h
+++ b/dom/base/ElementInlines.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_ElementInlines_h
 #define mozilla_dom_ElementInlines_h
 
 #include "mozilla/dom/Element.h"
+#include "nsIContentInlines.h"
 #include "nsIDocument.h"
 
 namespace mozilla {
 namespace dom {
 
 inline void
 Element::RegisterActivityObserver()
 {
@@ -20,12 +21,52 @@ Element::RegisterActivityObserver()
 }
 
 inline void
 Element::UnregisterActivityObserver()
 {
   OwnerDoc()->UnregisterActivityObserver(this);
 }
 
+inline Element*
+Element::GetFlattenedTreeParentElement() const
+{
+  nsINode* parentNode = GetFlattenedTreeParentNode();
+  if MOZ_LIKELY(parentNode && parentNode->IsElement()) {
+    return parentNode->AsElement();
+  }
+
+  return nullptr;
+}
+
+inline void
+Element::NoteDirtyDescendantsForServo()
+{
+  Element* curr = this;
+  while (curr && !curr->HasDirtyDescendantsForServo()) {
+    curr->SetHasDirtyDescendantsForServo();
+    curr = curr->GetFlattenedTreeParentElement();
+  }
+
+  MOZ_ASSERT(DirtyDescendantsBitIsPropagatedForServo());
+}
+
+#ifdef DEBUG
+inline bool
+Element::DirtyDescendantsBitIsPropagatedForServo()
+{
+  Element* curr = this;
+  while (curr) {
+    if (!curr->HasDirtyDescendantsForServo()) {
+      return false;
+    }
+    nsINode* parentNode = curr->GetParentNode();
+    curr = curr->GetFlattenedTreeParentElement();
+    MOZ_ASSERT_IF(!curr, parentNode == OwnerDoc());
+  }
+  return true;
+}
+#endif
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ElementInlines_h
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -2339,56 +2339,8 @@ FragmentOrElement::SetIsElementInStyleSc
   NS_ASSERTION(IsElement(), "calling SetIsElementInStyleScopeFlagOnShadowTree "
                             "on a non-Element is useless");
   ShadowRoot* shadowRoot = GetShadowRoot();
   while (shadowRoot) {
     shadowRoot->SetIsElementInStyleScopeFlagOnSubtree(aInStyleScope);
     shadowRoot = shadowRoot->GetOlderShadowRoot();
   }
 }
-
-#ifdef DEBUG
-static void
-AssertDirtyDescendantsBitPropagated(nsINode* aNode)
-{
-  MOZ_ASSERT(aNode->HasDirtyDescendantsForServo());
-  nsINode* parent = aNode->GetFlattenedTreeParentNode();
-  if (!parent->IsContent()) {
-    MOZ_ASSERT(parent == aNode->OwnerDoc());
-    MOZ_ASSERT(parent->HasDirtyDescendantsForServo());
-  } else {
-    AssertDirtyDescendantsBitPropagated(parent);
-  }
-}
-#else
-static void AssertDirtyDescendantsBitPropagated(nsINode* aNode) {}
-#endif
-
-void
-nsIContent::MarkAncestorsAsHavingDirtyDescendantsForServo()
-{
-  MOZ_ASSERT(IsInComposedDoc());
-
-  // Get the parent in the flattened tree.
-  nsINode* parent = GetFlattenedTreeParentNode();
-
-  // Loop until we hit a base case.
-  while (true) {
-
-    // Base case: the document.
-    if (!parent->IsContent()) {
-      MOZ_ASSERT(parent == OwnerDoc());
-      parent->SetHasDirtyDescendantsForServo();
-      return;
-    }
-
-    // Base case: the parent is already marked, and therefore
-    // so are all its ancestors.
-    if (parent->HasDirtyDescendantsForServo()) {
-      AssertDirtyDescendantsBitPropagated(parent);
-      return;
-    }
-
-    // Mark the parent and iterate.
-    parent->SetHasDirtyDescendantsForServo();
-    parent = parent->GetFlattenedTreeParentNode();
-  }
-}
--- a/dom/base/nsGenericDOMDataNode.cpp
+++ b/dom/base/nsGenericDOMDataNode.cpp
@@ -547,28 +547,16 @@ nsGenericDOMDataNode::BindToTree(nsIDocu
 
   nsNodeUtils::ParentChainChanged(this);
   if (!hadParent && IsRootOfNativeAnonymousSubtree()) {
     nsNodeUtils::NativeAnonymousChildListChange(this, false);
   }
 
   UpdateEditableState(false);
 
-  // It would be cleanest to mark nodes as dirty when (a) they're created and
-  // (b) they're unbound from a tree. However, we can't easily do (a) right now,
-  // because IsStyledByServo() is not always easy to check at node creation time,
-  // and the bits have different meaning in the non-IsStyledByServo case.
-  //
-  // So for now, we just mark nodes as dirty when they're inserted into a
-  // document or shadow tree.
-  if (IsStyledByServo() && IsInComposedDoc()) {
-    MOZ_ASSERT(!HasServoData());
-    SetIsDirtyForServo();
-  }
-
   NS_POSTCONDITION(aDocument == GetUncomposedDoc(), "Bound to wrong document");
   NS_POSTCONDITION(aParent == GetParent(), "Bound to wrong parent");
   NS_POSTCONDITION(aBindingParent == GetBindingParent(),
                    "Bound to wrong binding parent");
 
   return NS_OK;
 }
 
@@ -590,26 +578,16 @@ nsGenericDOMDataNode::UnbindFromTree(boo
       NS_RELEASE(mParent);
     } else {
       mParent = nullptr;
     }
     SetParentIsContent(false);
   }
   ClearInDocument();
 
-  // Computed styled data isn't useful for detached nodes, and we'll need to
-  // recomputed it anyway if we ever insert the nodes back into a document.
-  if (IsStyledByServo()) {
-    ClearServoData();
-  } else {
-#ifdef MOZ_STYLO
-    MOZ_ASSERT(!HasServoData());
-#endif
-  }
-
   if (aNullParent || !mParent->IsInShadowTree()) {
     UnsetFlags(NODE_IS_IN_SHADOW_TREE);
 
     // Begin keeping track of our subtree root.
     SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
   }
 
   if (document && !GetContainingShadow()) {
--- a/dom/base/nsIContent.h
+++ b/dom/base/nsIContent.h
@@ -933,24 +933,16 @@ public:
 
   /**
    * If the content is a part of HTML editor, this returns editing
    * host content.  When the content is in designMode, this returns its body
    * element.  Also, when the content isn't editable, this returns null.
    */
   mozilla::dom::Element* GetEditingHost();
 
-
-  /**
-   * Set NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO all the way up the flattened
-   * parent chain to the document. If an ancestor is found with the bit already
-   * set, this method asserts that all of its ancestors also have the bit set.
-   */
-  void MarkAncestorsAsHavingDirtyDescendantsForServo();
-
   /**
    * Determining language. Look at the nearest ancestor element that has a lang
    * attribute in the XML namespace or is an HTML/SVG element and has a lang in
    * no namespace attribute.  Returns false if no language was specified.
    */
   bool GetLang(nsAString& aResult) const {
     for (const nsIContent* content = this; content; content = content->GetParent()) {
       if (content->GetAttrCount() > 0) {
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -147,20 +147,16 @@ nsINode::nsSlots::Unlink()
 }
 
 //----------------------------------------------------------------------
 
 nsINode::~nsINode()
 {
   MOZ_ASSERT(!HasSlots(), "nsNodeUtils::LastRelease was not called?");
   MOZ_ASSERT(mSubtreeRoot == this, "Didn't restore state properly?");
-#ifdef MOZ_STYLO
-  NS_ASSERTION(!HasServoData(), "expected ServoNodeData to be cleared earlier");
-  ClearServoData();
-#endif
 }
 
 void*
 nsINode::GetProperty(uint16_t aCategory, nsIAtom *aPropertyName,
                      nsresult *aStatus) const
 {
   return OwnerDoc()->PropertyTable(aCategory)->GetProperty(this, aPropertyName,
                                                            aStatus);
@@ -1395,25 +1391,16 @@ nsINode::UnoptimizableCCNode() const
                                       NODE_IS_IN_SHADOW_TREE);
   return HasFlag(problematicFlags) ||
          NodeType() == nsIDOMNode::ATTRIBUTE_NODE ||
          // For strange cases like xbl:content/xbl:children
          (IsElement() &&
           AsElement()->IsInNamespace(kNameSpaceID_XBL));
 }
 
-void
-nsINode::ClearServoData() {
-#ifdef MOZ_STYLO
-  Servo_Node_ClearNodeData(this);
-#else
-  MOZ_CRASH("Accessing servo node data in non-stylo build");
-#endif
-}
-
 /* static */
 bool
 nsINode::Traverse(nsINode *tmp, nsCycleCollectionTraversalCallback &cb)
 {
   if (MOZ_LIKELY(!cb.WantAllTraces())) {
     nsIDocument *currentDoc = tmp->GetUncomposedDoc();
     if (currentDoc &&
         nsCCUncollectableMarker::InGeneration(currentDoc->GetMarkedCCGeneration())) {
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -3,17 +3,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsINode_h___
 #define nsINode_h___
 
 #include "mozilla/Likely.h"
-#include "mozilla/ServoTypes.h"
 #include "mozilla/UniquePtr.h"
 #include "nsCOMPtr.h"               // for member, local
 #include "nsGkAtoms.h"              // for nsGkAtoms::baseURIProperty
 #include "nsIDOMNode.h"
 #include "mozilla/dom/NodeInfo.h"            // member (in nsCOMPtr)
 #include "nsIVariant.h"             // for use in GetUserData()
 #include "nsNodeInfoManager.h"      // for use in NodePrincipal()
 #include "nsPropertyTable.h"        // for typedefs
@@ -181,24 +180,23 @@ enum {
 
   NODE_CHROME_ONLY_ACCESS =               NODE_FLAG_BIT(19),
 
   NODE_IS_ROOT_OF_CHROME_ONLY_ACCESS =    NODE_FLAG_BIT(20),
 
   // These two bits are shared by Gecko's and Servo's restyle systems for
   // different purposes. They should not be accessed directly, and access to
   // them should be properly guarded by asserts.
+  //
+  // FIXME(bholley): These should move to Element, and we only need one now.
   NODE_SHARED_RESTYLE_BIT_1 =             NODE_FLAG_BIT(21),
   NODE_SHARED_RESTYLE_BIT_2 =             NODE_FLAG_BIT(22),
 
-  // Whether this node is dirty for Servo's style system.
-  NODE_IS_DIRTY_FOR_SERVO =               NODE_SHARED_RESTYLE_BIT_1,
-
   // Whether this node has dirty descendants for Servo's style system.
-  NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO =  NODE_SHARED_RESTYLE_BIT_2,
+  NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO =  NODE_SHARED_RESTYLE_BIT_1,
 
   // Remaining bits are node type specific.
   NODE_TYPE_SPECIFIC_BITS_OFFSET =        23
 };
 
 // Make sure we have space for our bits
 #define ASSERT_NODE_FLAGS_SPACE(n) \
   static_assert(WRAPPER_CACHE_FLAGS_BITS_USED + (n) <=                          \
@@ -976,58 +974,16 @@ public:
    * style system.
    */
 #ifdef MOZ_STYLO
   bool IsStyledByServo() const;
 #else
   bool IsStyledByServo() const { return false; }
 #endif
 
-  bool IsDirtyForServo() const
-  {
-    MOZ_ASSERT(IsStyledByServo());
-    return HasFlag(NODE_IS_DIRTY_FOR_SERVO);
-  }
-
-  bool HasDirtyDescendantsForServo() const
-  {
-    MOZ_ASSERT(IsStyledByServo());
-    return HasFlag(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
-  }
-
-  void SetIsDirtyForServo() {
-    MOZ_ASSERT(IsStyledByServo());
-    SetFlags(NODE_IS_DIRTY_FOR_SERVO);
-  }
-
-  void SetHasDirtyDescendantsForServo() {
-    MOZ_ASSERT(IsStyledByServo());
-    SetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
-  }
-
-  void SetIsDirtyAndHasDirtyDescendantsForServo() {
-    MOZ_ASSERT(IsStyledByServo());
-    SetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO | NODE_IS_DIRTY_FOR_SERVO);
-  }
-
-  void UnsetIsDirtyForServo() {
-    MOZ_ASSERT(IsStyledByServo());
-    UnsetFlags(NODE_IS_DIRTY_FOR_SERVO);
-  }
-
-  void UnsetHasDirtyDescendantsForServo() {
-    MOZ_ASSERT(IsStyledByServo());
-    UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
-  }
-
-  void UnsetIsDirtyAndHasDirtyDescendantsForServo() {
-    MOZ_ASSERT(IsStyledByServo());
-    UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO | NODE_IS_DIRTY_FOR_SERVO);
-  }
-
   inline void UnsetRestyleFlagsIfGecko();
 
   /**
    * Adds a mutation observer to be notified when this node, or any of its
    * descendants, are modified. The node will hold a weak reference to the
    * observer, which means that it is the responsibility of the observer to
    * remove itself in case it dies before the node.  If an observer is added
    * while observers are being notified, it may also be notified.  In general,
@@ -2056,26 +2012,16 @@ public:
   void SetOn##name_(mozilla::dom::EventHandlerNonNull* listener);
 #define TOUCH_EVENT EVENT
 #define DOCUMENT_ONLY_EVENT EVENT
 #include "mozilla/EventNameList.h"
 #undef DOCUMENT_ONLY_EVENT
 #undef TOUCH_EVENT
 #undef EVENT
 
-  bool HasServoData() {
-#ifdef MOZ_STYLO
-    return !!mServoData.Get();
-#else
-    MOZ_CRASH("Accessing servo node data in non-stylo build");
-#endif
-  }
-
-  void ClearServoData();
-
 protected:
   static bool Traverse(nsINode *tmp, nsCycleCollectionTraversalCallback &cb);
   static void Unlink(nsINode *tmp);
 
   RefPtr<mozilla::dom::NodeInfo> mNodeInfo;
 
   // mParent is an owning ref most of the time, except for the case of document
   // nodes, so it cannot be represented by nsCOMPtr, so mark is as
@@ -2103,21 +2049,16 @@ protected:
     // Pointer to the root of our subtree.  Might be null.
     // This reference is non-owning and safe, since it either points to the
     // object itself, or is reset by ClearSubtreeRootPointer.
     nsINode* MOZ_NON_OWNING_REF mSubtreeRoot;
   };
 
   // Storage for more members that are usually not needed; allocated lazily.
   nsSlots* mSlots;
-
-#ifdef MOZ_STYLO
-  // Per-node data managed by Servo.
-  mozilla::ServoCell<ServoNodeData*> mServoData;
-#endif
 };
 
 inline nsIDOMNode* GetAsDOMNode(nsINode* aNode)
 {
   return aNode ? aNode->AsDOMNode() : nullptr;
 }
 
 // Useful inline function for getting a node given an nsIContent and an
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -2937,21 +2937,24 @@ IsOrHasAncestorWithDisplayNone(Element* 
     }
     elementsToCheck.AppendElement(e);
   }
 
   if (elementsToCheck.IsEmpty()) {
     return false;
   }
 
+  // XXXbholley: This could be done more directly with Servo's style system.
   StyleSetHandle styleSet = aPresShell->StyleSet();
   RefPtr<nsStyleContext> sc;
   for (int32_t i = elementsToCheck.Length() - 1; i >= 0; --i) {
     if (sc) {
-      sc = styleSet->ResolveStyleFor(elementsToCheck[i], sc);
+      sc = styleSet->ResolveStyleFor(elementsToCheck[i], sc,
+                                     ConsumeStyleBehavior::DontConsume,
+                                     LazyComputeBehavior::Assert);
     } else {
       sc = nsComputedDOMStyle::GetStyleContextForElementNoFlush(elementsToCheck[i],
                                                                 nullptr, aPresShell);
     }
     if (sc->StyleDisplay()->mDisplay == StyleDisplay::None) {
       return true;
     }
   }
--- a/dom/workers/test/sharedWorker_sharedWorker.js
+++ b/dom/workers/test/sharedWorker_sharedWorker.js
@@ -67,20 +67,19 @@ onconnect = function(event) {
   }
   if (!(event.ports[0] == event.source)) {
     throw new Error("'connect' event source property is incorrect!");
   }
   if (event.data) {
     throw new Error("'connect' event has data: " + event.data);
   }
 
-  // The expression closures should trigger a warning in debug builds, but NOT
-  // fire error events at us. If we ever actually remove expression closures
-  // (in bug 1083458), we'll need something else to test this case.
-  (function() "Expected console warning: expression closures are deprecated");
+  // Statement after return should trigger a warning, but NOT fire error events
+  // at us.
+  (function() { return; 1; });
 
   event.ports[0].onmessage = function(event) {
     if (!(event instanceof MessageEvent)) {
       throw new Error("'message' event is not a MessageEvent!");
     }
     if (!("ports" in event)) {
       throw new Error("'message' event doesn't have a 'ports' property!");
     }
--- a/dom/workers/test/test_sharedWorker.html
+++ b/dom/workers/test/test_sharedWorker.html
@@ -17,17 +17,17 @@
       <script class="testbody">
         "use strict";
 
         const href = window.location.href;
         const filename = "sharedWorker_sharedWorker.js";
         const sentMessage = "ping";
         const errorFilename = href.substring(0, href.lastIndexOf("/") + 1) +
                               filename;
-        const errorLine = 91;
+        const errorLine = 90;
         const errorColumn = 0;
 
         var worker = new SharedWorker(filename);
 
         ok(worker instanceof SharedWorker, "Got SharedWorker instance");
         ok(!("postMessage" in worker), "SharedWorker has no 'postMessage'");
         ok(worker.port instanceof MessagePort,
           "Shared worker has MessagePort");
--- a/dom/xbl/nsXBLBinding.cpp
+++ b/dom/xbl/nsXBLBinding.cpp
@@ -421,17 +421,16 @@ nsXBLBinding::GenerateAnonymousContent()
       mContent->UnsetAttr(namespaceID, name, false);
   }
 
   // Now that we've finished shuffling the tree around, go ahead and restyle it
   // since frame construction is about to happen.
   nsIPresShell* presShell = mBoundElement->OwnerDoc()->GetShell();
   ServoStyleSet* servoSet = presShell->StyleSet()->GetAsServo();
   if (servoSet) {
-    mBoundElement->SetHasDirtyDescendantsForServo();
     servoSet->StyleNewChildren(mBoundElement);
   }
 }
 
 nsIURI*
 nsXBLBinding::GetSourceDocURI()
 {
   nsIContent* targetContent =
--- a/image/ImageCacheKey.cpp
+++ b/image/ImageCacheKey.cpp
@@ -136,31 +136,18 @@ ImageCacheKey::ComputeHash(ImageURL* aUR
 {
   // Since we frequently call Hash() several times in a row on the same
   // ImageCacheKey, as an optimization we compute our hash once and store it.
 
   nsPrintfCString ptr("%p", aControlledDocument);
   nsAutoCString suffix;
   aAttrs.CreateSuffix(suffix);
 
-  if (aBlobSerial) {
-    // For blob URIs, we hash the serial number of the underlying blob, so that
-    // different blob URIs which point to the same blob share a cache entry. We
-    // also include the ref portion of the URI to support media fragments which
-    // requires us to create different Image objects even if the source data is
-    // the same.
-    nsAutoCString ref;
-    aURI->GetRef(ref);
-    return HashGeneric(*aBlobSerial, HashString(ref + suffix + ptr));
-  }
-
-  // For non-blob URIs, we hash the URI spec.
-  nsAutoCString spec;
-  aURI->GetSpec(spec);
-  return HashString(spec + suffix + ptr);
+  return AddToHash(0, aURI->ComputeHash(aBlobSerial),
+                   HashString(suffix), HashString(ptr));
 }
 
 /* static */ void*
 ImageCacheKey::GetControlledDocumentToken(nsIDocument* aDocument)
 {
   // For non-controlled documents, we just return null.  For controlled
   // documents, we cast the pointer into a void* to avoid dereferencing
   // it (since we only use it for comparisons), and return it.
--- a/image/ImageURL.h
+++ b/image/ImageURL.h
@@ -4,20 +4,24 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_ImageURL_h
 #define mozilla_image_ImageURL_h
 
 #include "nsIURI.h"
 #include "MainThreadUtils.h"
 #include "nsNetUtil.h"
+#include "mozilla/HashFunctions.h"
+#include "nsHashKeys.h"
 
 namespace mozilla {
 namespace image {
 
+class ImageCacheKey;
+
 /** ImageURL
  *
  * nsStandardURL is not threadsafe, so this class is created to hold only the
  * necessary URL data required for image loading and decoding.
  *
  * Note: Although several APIs have the same or similar prototypes as those
  * found in nsIURI/nsStandardURL, the class does not implement nsIURI. This is
  * intentional; functionality is limited, and is only useful for imagelib code.
@@ -108,16 +112,32 @@ public:
   }
 
   bool HasSameRef(const ImageURL& aOther) const
   {
     return mRef == aOther.mRef;
   }
 
 private:
+  friend class ImageCacheKey;
+
+  uint32_t ComputeHash(const Maybe<uint64_t>& aBlobSerial) const
+  {
+    if (aBlobSerial) {
+      // For blob URIs, we hash the serial number of the underlying blob, so that
+      // different blob URIs which point to the same blob share a cache entry. We
+      // also include the ref portion of the URI to support media fragments which
+      // requires us to create different Image objects even if the source data is
+      // the same.
+      return HashGeneric(*aBlobSerial, HashString(mRef));
+    }
+    // For non-blob URIs, we hash the URI spec.
+    return HashString(mSpec);
+  }
+
   // Since this is a basic storage class, no duplication of spec parsing is
   // included in the functionality. Instead, the class depends upon the
   // parsing implementation in the nsIURI class used in object construction.
   // This means each field is stored separately, but since only a few are
   // required, this small memory tradeoff for threadsafe usage should be ok.
   nsAutoCString mSpec;
   nsAutoCString mScheme;
   nsAutoCString mRef;
--- a/js/src/jit-test/tests/SIMD/bug1273483.js
+++ b/js/src/jit-test/tests/SIMD/bug1273483.js
@@ -1,9 +1,9 @@
 if (typeof SIMD === 'undefined')
     quit();
 
 Int8x16 = SIMD.Int8x16;
 var Int32x4 = SIMD.Int32x4;
-function testSwizzleForType(type) type();
+function testSwizzleForType(type) { return type(); }
 testSwizzleForType(Int8x16);
-function testSwizzleInt32x4() testSwizzleForType(Int32x4);
+function testSwizzleInt32x4() { return testSwizzleForType(Int32x4); }
 testSwizzleInt32x4();
--- a/js/src/jit-test/tests/TypedObject/jit-write-references-2.js
+++ b/js/src/jit-test/tests/TypedObject/jit-write-references-2.js
@@ -5,12 +5,13 @@ try {
     gczeal(4)
 } catch (exc) {}
 var T = TypedObject;
 var ValueStruct = new T.StructType({
     f: T.Any
 })
 var v = new ValueStruct;
 new class get extends Number {};
-function writeValue(o, v)
-  o.f = v
+function writeValue(o, v) {
+  return o.f = v;
+}
 for (var i = 0; i < 5; i++)
   writeValue(v, {}, "helo")
--- a/js/src/jit-test/tests/arguments/defaults-destructuring-expression-closure.js
+++ b/js/src/jit-test/tests/arguments/defaults-destructuring-expression-closure.js
@@ -1,14 +1,14 @@
-function f1(a, bIs, cIs, dIs, {b}={b: 3}, c=4, [d]=[5]) (
+function f1(a, bIs, cIs, dIs, {b}={b: 3}, c=4, [d]=[5]) {
   assertEq(a, 1),
   assertEq(b, bIs),
   assertEq(c, cIs),
   assertEq(d, dIs)
-);
+}
 assertEq(f1.length, 4);
 f1(1, 3, 4, 5);
 f1(1, 42, 43, 44, {b: 42}, 43, [44]);
 
 let f2 = (a, bIs, cIs, dIs, {b}={b: 3}, c=4, [d]=[5]) => (
   assertEq(a, 1),
   assertEq(b, bIs),
   assertEq(c, cIs),
--- a/js/src/jit-test/tests/arguments/destructuring-exprbody.js
+++ b/js/src/jit-test/tests/arguments/destructuring-exprbody.js
@@ -1,8 +1,8 @@
 // See bug 763313
 load(libdir + "iteration.js");
-function f([a]) a
+function f([a]) { return a; }
 var i = 0;
 var o = {[Symbol.iterator]: function () { i++; return {
   next: function () { i++; return {value: 42, done: false}; }}}};
 assertEq(f(o), 42);
 assertEq(i, 2);
--- a/js/src/jit-test/tests/asm.js/testCall.js
+++ b/js/src/jit-test/tests/asm.js/testCall.js
@@ -53,17 +53,17 @@ assertEq(asmLink(asmCompile(USE_ASM+"fun
 assertEq(asmLink(asmCompile(USE_ASM+"function e() { return 42 } function f(i) { i=i|0; switch(i|0) { case 0: return e()|0; default: return 13 } return 0 } function g() { return f(0)|0 } return g"))(), 42);
 
 var rec = asmLink(asmCompile(USE_ASM+"function rec() { rec() } return rec"));
 assertThrowsInstanceOf(rec, InternalError);
 
 var rec = asmLink(asmCompile(USE_ASM+"function rec(i) { i=i|0; if (!i) return 0; return ((rec((i-1)|0)|0)+1)|0 } return rec"));
 assertEq(rec(100), 100);
 assertEq(rec(1000), 1000);
-assertThrowsInstanceOf(function() rec(100000000000), InternalError);
+assertThrowsInstanceOf(() => rec(100000000000), InternalError);
 assertEq(rec(2000), 2000);
 
 assertAsmTypeFail(USE_ASM+"function f(){return 0} function g() { var i=0; i=f() } return g");
 assertAsmTypeFail(USE_ASM+"function f(){return 0.0} function g() { var i=0.0; i=f() } return g");
 assertAsmTypeFail(USE_ASM+"function f(){return 0} function g() { return (f()+1)|0 } return g");
 assertAsmTypeFail(USE_ASM+"function f(){return 0.0} function g() { return +(f()+1.0) } return g");
 
 assertEq(asmLink(asmCompile(USE_ASM + "const M = 42; function f() { return M } function g() { return f()|0 } return f"))(), 42);
--- a/js/src/jit-test/tests/auto-regress/bug1264823.js
+++ b/js/src/jit-test/tests/auto-regress/bug1264823.js
@@ -1,11 +1,13 @@
 if (!('oomTest' in this))
     quit();
 
 loadFile("");
 loadFile("");
 loadFile(` function lalala() {}
     new Map([[1, 2]]).forEach(lalala)
     `);
-function loadFile(lfVarx) oomTest(function() {
-    eval(lfVarx)
-})
+function loadFile(lfVarx) {
+    return oomTest(function() {
+        eval(lfVarx)
+    });
+}
--- a/js/src/jit-test/tests/auto-regress/bug1317460.js
+++ b/js/src/jit-test/tests/auto-regress/bug1317460.js
@@ -1,11 +1,11 @@
 // |jit-test| error:TypeError
 
 g = newGlobal();
 g.parent = this;
 g.eval("(" + function() {
-    Debugger(parent).onExceptionUnwind = function() 0;
+    Debugger(parent).onExceptionUnwind = function() { return 0; };
 } + ")()");
 async function f() {
     t;
 }
 f();
--- a/js/src/jit-test/tests/auto-regress/bug488034.js
+++ b/js/src/jit-test/tests/auto-regress/bug488034.js
@@ -1,7 +1,7 @@
 // Binary: cache/js-dbg-64-e1257570fef8-linux
 // Flags:
 //
 (function(){
   var x;
-  eval("var x; ((function ()x)())");
+  eval("var x; ((function () { return x; })())");
 })()
--- a/js/src/jit-test/tests/auto-regress/bug488421.js
+++ b/js/src/jit-test/tests/auto-regress/bug488421.js
@@ -1,12 +1,12 @@
 // Binary: cache/js-dbg-64-862693caa320-linux
 // Flags:
 //
 function f(foo)
 {
   var x;
-  eval("this.__defineGetter__(\"y\", function ()x)");
+  eval("this.__defineGetter__(\"y\", function () { return x; })");
 }
 f("");
 try {
 ((function(){ throw y })())
 } catch(exc1) {}
--- a/js/src/jit-test/tests/auto-regress/bug490191.js
+++ b/js/src/jit-test/tests/auto-regress/bug490191.js
@@ -1,15 +1,15 @@
 // Binary: cache/js-dbg-64-daefd30072a6-linux
 // Flags:
 //
 function f(param) {
   var w;
   return eval("\
     (function(){\
-      this.__defineGetter__(\"y\", function()({\
+      this.__defineGetter__(\"y\", function() { return ({\
         x: function(){ return w }()\
-      }))\
+      }); })\
     });\
   ");
 }
 (f())();
 (new Function("eval(\"y\")"))();
--- a/js/src/jit-test/tests/auto-regress/bug563034.js
+++ b/js/src/jit-test/tests/auto-regress/bug563034.js
@@ -1,11 +1,11 @@
 // Binary: cache/js-dbg-64-c9212eb6175b-linux
 // Flags:
 //
 function f(a) {
    function g() {
-       yield function () a;
+       yield function () { return a; };
    }
    return g();
 }
 var x;
 f(7).next()();
--- a/js/src/jit-test/tests/auto-regress/bug630770.js
+++ b/js/src/jit-test/tests/auto-regress/bug630770.js
@@ -1,5 +1,5 @@
 // Binary: cache/js-dbg-64-3b3710520c0e-linux
 // Flags:
 //
 options('strict')
-Function("function y(x,x)d")
+Function("function y(x,x) { return d; }")
--- a/js/src/jit-test/tests/auto-regress/bug650658.js
+++ b/js/src/jit-test/tests/auto-regress/bug650658.js
@@ -1,9 +1,10 @@
 // |jit-test| error:TypeError
 
 // Binary: cache/js-dbg-64-ac0989a03bf1-linux
 // Flags: -m -n -a
 //
 AddRegExpCases(/a*b/, "xxx", 0, null );
 AddRegExpCases(/x\d\dy/, "abcx45ysss235", 3,[] );
-function AddRegExpCases(regexp, pattern, index, matches_array )
-        (matches_array.length, regexp)
+function AddRegExpCases(regexp, pattern, index, matches_array ) {
+    return (matches_array.length, regexp);
+}
--- a/js/src/jit-test/tests/auto-regress/bug659779.js
+++ b/js/src/jit-test/tests/auto-regress/bug659779.js
@@ -2,18 +2,18 @@
 // Flags: -m -n -a
 //
 
 var gTestcases = new Array;
 function TestCase(n, d, e, a) {
   this.description=d
   gTestcases[gTc++]=this
 }
-TestCase.prototype.dump=function () + toPrinted(this.description)
-function toPrinted(value) value=value;
+TestCase.prototype.dump=function () { return + toPrinted(this.description); };
+function toPrinted(value) { return value=value; }
 function reportCompare (expected, actual, description) {
   new TestCase("unknown-test-name", description, expected, actual)
 }
 function enterFunc (funcName) {
   try {
     expectCompile = 'No Error'
     var actualCompile;
     reportCompare(expectCompile, actualCompile, ': compile actual')
--- a/js/src/jit-test/tests/auto-regress/bug730806.js
+++ b/js/src/jit-test/tests/auto-regress/bug730806.js
@@ -1,15 +1,16 @@
 // Binary: cache/js-opt-32-2dc40eb83023-linux
 // Flags: -m -n -a
 //
-function toPrinted(value)
-  value = value.replace(/\\n/g, 'NL')
-               .replace(/\\r/g, 'CR')
-               .replace(/[^\x20-\x7E]+/g, escapeString);
+function toPrinted(value) {
+  return value = value.replace(/\\n/g, 'NL')
+                      .replace(/\\r/g, 'CR')
+                      .replace(/[^\x20-\x7E]+/g, escapeString);
+}
 function escapeString (str)
 {
   var a, b, c, d;
   var len = str.length;
   var result = "";
   var digits = ["0", "1", "2", "3", "4", "5", "6", "7",
                 "8", "9", "A", "B", "C", "D", "E", "F"];
   for (var i=0; i<len; i++)
--- a/js/src/jit-test/tests/auto-regress/bug756236.js
+++ b/js/src/jit-test/tests/auto-regress/bug756236.js
@@ -1,14 +1,15 @@
 // Binary: cache/js-dbg-64-14735b4dbccc-linux
 // Flags: --ion-eager
 //
 
 gczeal(4);
 function startTest() {}
-function TestCase(n, d, e, a)
-    dump = (function () {});
+function TestCase(n, d, e, a) {
+    return dump = (function () {});
+}
   if (typeof document != "object" || !document.location.href.match(/jsreftest.html/)) {}
 function writeHeaderToLog( string ) {}
 var SECTION = "11.4.5";
 new TestCase( SECTION,  "var MYVAR= void 0; --MYVAR", NaN, eval("var MYVAR=void 0; --MYVAR") );
 new TestCase( SECTION, "var MYVAR=0;--MYVAR;MYVAR", -1, eval("var MYVAR=0;--MYVAR;MYVAR") );
 new TestCase( SECTION, "var MYVAR=0;--MYVAR;MYVAR", -1, eval("var MYVAR=0;--MYVAR;MYVAR") );
--- a/js/src/jit-test/tests/auto-regress/bug778557.js
+++ b/js/src/jit-test/tests/auto-regress/bug778557.js
@@ -1,7 +1,7 @@
 // Binary: cache/js-dbg-64-90828ac18dcf-linux
 // Flags:
 //
 x = Set;
-eval("function y()(Iterator)", this);
+eval("function y() { return Iterator; }", this);
 x.__iterator__ = y;
 new Iterator(x)
--- a/js/src/jit-test/tests/baseline/bug1216140.js
+++ b/js/src/jit-test/tests/baseline/bug1216140.js
@@ -1,6 +1,6 @@
-function newFunc(x) Function(x)()
+function newFunc(x) { return Function(x)(); }
 newFunc(` 
   var BUGNUMBER = 8[ anonymous = true ]--;
   () => BUGNUMBER;
 `);
 
--- a/js/src/jit-test/tests/baseline/bug1238815.js
+++ b/js/src/jit-test/tests/baseline/bug1238815.js
@@ -1,13 +1,15 @@
 // This program crashes the ARM code generator because the machine code is
 // longer than the 32MB range of ARM branch instructions.
 //
 // Baseline should not attempt to compile the script.
 
 i = 1;
-function test(s) eval("line0 = Error.lineNumber\ndebugger\n" + s);
+function test(s) {
+    return eval("line0 = Error.lineNumber\ndebugger\n" + s);
+}
 function repeat(s) {
         return Array(65 << 13).join(s)
 }
 long_expr = repeat(" + i")
 long_throw_stmt = long_expr;
 test(long_throw_stmt);
--- a/js/src/jit-test/tests/baseline/bug852801.js
+++ b/js/src/jit-test/tests/baseline/bug852801.js
@@ -9,65 +9,68 @@ TestCase.prototype.dump = function () {}
 TestCase.prototype.testPassed = (function TestCase_testPassed() { return this.passed; });
 TestCase.prototype.testFailed = (function TestCase_testFailed() { return !this.passed; });
 function printStatus (msg) {
     var lines = msg.split ("\n");
     for (var i=0; i<lines.length; i++)
 	print (STATUS + lines[i]);
 }
 function printBugNumber (num) {}
-function toPrinted(value)
-function escapeString (str) {}
+function toPrinted(value) {
+    return function escapeString (str) {};
+}
 function reportCompare (expected, actual, description) {
     var actual_t = typeof actual;
     var output = "";
     printStatus (
 	"Expected value '"
 	    + toPrinted(expected)
 	    + toPrinted(actual)
     );
     var testcase = new TestCase("unknown-test-name", description, expected, actual);
     testcase.reason = output;
     if (typeof document != "object" ||      !document.location.href.match(/jsreftest.html/)) {
 	if (testcase.passed)    {    }
     }
     return testcase.passed;
 }
 function reportMatch (expectedRegExp, actual, description) {}
-function enterFunc (funcName)
-function BigO(data) {
-    function LinearRegression(data)   {  }
+function enterFunc (funcName) {
+    return function BigO(data) {
+        function LinearRegression(data)   {  }
+    };
 }
 function compareSource(expect, actual, summary) {}
 function optionsInit() {
     var optionNames = options().split(',');
 }
 function optionsClear() {}
 function optionsPush() {}
 optionsInit();
 optionsClear();
-function getTestCaseResult(expected, actual)
-function test() {
-    for ( gTc=0; gTc < gTestcases.length; gTc++ ) {}
+function getTestCaseResult(expected, actual) {
+    return function test() {
+        for ( gTc=0; gTc < gTestcases.length; gTc++ ) {}
+    };
 }
 var lfcode = new Array();
 lfcode.push("4");
 lfcode.push("gcparam(\"maxBytes\", gcparam(\"gcBytes\") + 1024);");
 lfcode.push("");
 lfcode.push("\
 var UBound = 0;\n\
 var BUGNUMBER = 74474;\n\
 var actual = '';\n\
 var actualvalues = [ ];\n\
 var expectedvalues = [ ];\n\
 addThis();\n\
 addThis();\n\
 tryThis(1);\n\
 function tryThis(x)\n\
-addThis();\n\
+{ return addThis(); }\n\
 test();\n\
 function addThis() {\n\
 actualvalues[UBound] = actual;\n\
 UBound++;\n\
 }\n\
 function test() {\n\
 enterFunc ('test');\n\
 printBugNumber(BUGNUMBER);\n\
--- a/js/src/jit-test/tests/basic/bug1135718.js
+++ b/js/src/jit-test/tests/basic/bug1135718.js
@@ -1,12 +1,13 @@
 
 setJitCompilerOption("ion.warmup.trigger", 30);
-function ArrayCallback(state)
-  this.state = state;
+function ArrayCallback(state) {
+  return this.state = state;
+}
 ArrayCallback.prototype.isUpperCase = function(v, index, array) {
     return this.state ? true : (v == v.toUpperCase());
 };
 strings = ['hello', 'Array', 'WORLD'];
 obj = new ArrayCallback(false);
 strings.filter(obj.isUpperCase, obj)
 obj = new ArrayCallback(true);
 strings.filter(obj.isUpperCase, obj)
--- a/js/src/jit-test/tests/basic/bug560234b.js
+++ b/js/src/jit-test/tests/basic/bug560234b.js
@@ -1,11 +1,11 @@
 function f(a) {
    function g() {
-       yield function () a;
+       yield function () { return a; };
    }
    if (a == 8)
        return g();
    a = 3;
 }
 var x;
 for (var i = 0; i < 9; i++)
    x = f(i);
--- a/js/src/jit-test/tests/basic/bug650148.js
+++ b/js/src/jit-test/tests/basic/bug650148.js
@@ -3,11 +3,12 @@
 summary=/(?!AB+D)AB/.exec("AB") + '';
 try {
   var s = "throw 42";
 } catch (e) {}
 test();
 function test() {
   [ {0xBe: /l/|| 'Error' ? s++ : summary } ]
 }
-function foo(code)
-        Function(code)();
+function foo(code) {
+    return Function(code)();
+}
 foo("for each (y in this);");
--- a/js/src/jit-test/tests/basic/bug651966.js
+++ b/js/src/jit-test/tests/basic/bug651966.js
@@ -21,17 +21,17 @@ f();
 f();
 try { f("function x(){}(x())"); } catch (e) {}
 
 function f2() {
     a = {
         x
     } = x, (x._)
     function
-    x()({})
+    x(){ return ({}); }
 }
 try { f2(); } catch (e) {}
 
 function f3() {
   var x = 0;
   with ({}) { x = 'three'; }
   return x;
 }
--- a/js/src/jit-test/tests/basic/bug657225.js
+++ b/js/src/jit-test/tests/basic/bug657225.js
@@ -1,9 +1,9 @@
 
-function reportCompare(expected, actual, description) + ++actual + "'";
+function reportCompare(expected, actual, description) { return  + ++actual + "'"; }
 var summary = 'Object.prototype.toLocaleString() should track Object.prototype.toString() ';
 var o = {
     toString: function () {}
 };
 expect = o;
 actual = o.toLocaleString();
 reportCompare(expect, actual, summary);
--- a/js/src/jit-test/tests/basic/bug673766.js
+++ b/js/src/jit-test/tests/basic/bug673766.js
@@ -8,13 +8,13 @@ function reportCompare(expected, actual,
 var actual = '';
 var expect = '';
 for (var i = 0; i < 2; ++i) reportCompare(expect, actual, ': 2');
 try {
     ({
         valueOf: gc
     } - [])
 } catch (prop) {}
-function addThis() reportCompare(expect, actual, 'ok');
+function addThis() { return reportCompare(expect, actual, 'ok'); }
 Object.defineProperty(Object.prototype, "name", {
     set: function (newValue) {}
 });
 addThis()
--- a/js/src/jit-test/tests/basic/bug709634.js
+++ b/js/src/jit-test/tests/basic/bug709634.js
@@ -1,6 +1,6 @@
 
-Function.prototype.toString = function () f(this, true);
+Function.prototype.toString = function () { return f(this, true); };
 function f(obj) {
   f.caller.p
 }
 decodeURI + 3;
--- a/js/src/jit-test/tests/basic/bug728086.js
+++ b/js/src/jit-test/tests/basic/bug728086.js
@@ -8,18 +8,19 @@ function toPrinted(value) {
 function escapeString (str)   {}
 function reportCompare (expected, actual, description) {
   var expected_t = typeof expected;
   var actual_t = typeof actual;
   var output = "";
   var testcase = new TestCase("unknown-test-name", description, expected, actual);
       reportFailure (description + " : " + output);
 }
-function enterFunc (funcName)
-  callStack.push(funcName);
+function enterFunc (funcName) {
+  return callStack.push(funcName);
+}
   try  {
     reportCompare(expectCompile, actualCompile,
                   summary + ': compile actual');
   }  catch(ex)  {  }
 var lfcode = new Array();
 lfcode.push("gczeal(2);\
 enterFunc ('test');\
 ");
--- a/js/src/jit-test/tests/basic/bug738841.js
+++ b/js/src/jit-test/tests/basic/bug738841.js
@@ -35,15 +35,15 @@ try {
     }())
 } catch (e) {}
 try {
     m
 } catch (e) {}
 try {
     var f = function() {
         {
-            print(new function(q)("", s))
+            print(new function(q) { return ("", s); })
             let u
         }
     };
     dis(f);
     f();
 } catch (e) {}
--- a/js/src/jit-test/tests/basic/bug744285.js
+++ b/js/src/jit-test/tests/basic/bug744285.js
@@ -42,17 +42,17 @@ function check(b, desc) {
     }
     function isCloneable(pair) {    }
     function assertIsCloneOf(a, b, path) {
         ca = classOf(a)
         assertEq(ca, classOf(b), path)
         assertEq(Object.getPrototypeOf(a), ca == "[object Object]" ? Object.prototype : Array.prototype, path)
         pb = ownProperties(b).filter(isCloneable)
         pa = ownProperties(a)
-        function byName(a, b) 0
+        function byName(a, b) { return 0; }
         byName
         (pa.length, pb.length, "should see the same number of properties " + path)
         for (var i = 0; i < pa.length; i++) {
                 gczeal(4)
         }
     }
     banner = desc || uneval()
     a = deserialize(serialize(b))
--- a/js/src/jit-test/tests/basic/bug808067.js
+++ b/js/src/jit-test/tests/basic/bug808067.js
@@ -1,10 +1,11 @@
-function TestCase(n, d, e, a)
-  this.reason = '';
+function TestCase(n, d, e, a) {
+  return this.reason = '';
+}
 function reportCompare (expected, actual, description) {
   var output = "";
   var testcase = new TestCase("unknown-test-name", description, expected, actual);
   testcase.reason = output;
 }
 gcPreserveCode();
 var summary = 'return with argument and lazy generator detection';
 expect = "generator function foo returns a value";
--- a/js/src/jit-test/tests/basic/bug832203.js
+++ b/js/src/jit-test/tests/basic/bug832203.js
@@ -1,11 +1,11 @@
 // Don't assert.
 gczeal(2,1);
 eval("(function() { " + "\
 var g1 = newGlobal('same-compartment');\
 function test(str, f) {\
     var x = f(eval(str));\
     assertEq(x, f(g1.eval(str)));\
 }\
-test('new RegExp(\"1\")', function(r) assertEq('a1'.search(r), 1));\
+test('new RegExp(\"1\")', function(r) { return assertEq('a1'.search(r), 1); });\
 " + " })();");
 eval("(function() { " + "" + " })();");
--- a/js/src/jit-test/tests/basic/bug951213.js
+++ b/js/src/jit-test/tests/basic/bug951213.js
@@ -1,8 +1,8 @@
 
 enableShellAllocationMetadataBuilder();
 function foo(x, y) {
   this.g = x + y;
 }
 var a = 0;
-var b = { valueOf: function() Object.defineProperty(Object.prototype, 'g', {}) };
+var b = { valueOf: function() { return Object.defineProperty(Object.prototype, 'g', {}); } };
 var c = new foo(a, b);
--- a/js/src/jit-test/tests/basic/decompile-script.js
+++ b/js/src/jit-test/tests/basic/decompile-script.js
@@ -1,6 +1,6 @@
 function example(a, b, c) {
     var complicated = 3;
     perform_some_operations();
 }
-var myfun = function (a, b) a + b;
+var myfun = function (a, b) { return a + b; }
 assertEq(decompileThis(), snarf(thisFilename()));
--- a/js/src/jit-test/tests/basic/function-tosource-getset.js
+++ b/js/src/jit-test/tests/basic/function-tosource-getset.js
@@ -1,7 +1,7 @@
-var o = {get prop() a + b, set prop(x) a + b};
+var o = {get prop() { a + b; }, set prop(x) { a + b; }};
 var prop = Object.getOwnPropertyDescriptor(o, "prop");
-assertEq(prop.get.toString(), "function get prop() a + b");
-assertEq(prop.get.toSource(), "(function get prop() a + b)");
-assertEq(prop.set.toString(), "function set prop(x) a + b");
-assertEq(prop.set.toSource(), "(function set prop(x) a + b)");
-assertEq(o.toSource(), "({get prop () a + b, set prop (x) a + b})");
+assertEq(prop.get.toString(), "function get prop() { a + b; }");
+assertEq(prop.get.toSource(), "(function get prop() { a + b; })");
+assertEq(prop.set.toString(), "function set prop(x) { a + b; }");
+assertEq(prop.set.toSource(), "(function set prop(x) { a + b; })");
+assertEq(o.toSource(), "({get prop () { a + b; }, set prop (x) { a + b; }})");
--- a/js/src/jit-test/tests/basic/shapelessCalleeTest.js
+++ b/js/src/jit-test/tests/basic/shapelessCalleeTest.js
@@ -38,27 +38,27 @@ function shapelessUnknownCalleeLoop(n, f
     g = h;
   }
 }
 
 function shapelessCalleeTest()
 {
   var a = [];
 
-  var helper = function (i, a) a[i] = i;
-  shapelessArgCalleeLoop(helper, helper, function (i, a) a[i] = -i, a);
+  var helper = function (i, a) { a[i] = i; };
+  shapelessArgCalleeLoop(helper, helper, function (i, a) { a[i] = -i; }, a);
 
-  helper = function (i, a) a[10 + i] = i;
-  shapelessVarCalleeLoop(helper, helper, function (i, a) a[10 + i] = -i, a);
+  helper = function (i, a) { a[10 + i] = i; };
+  shapelessVarCalleeLoop(helper, helper, function (i, a) { a[10 + i] = -i; }, a);
 
-  helper = function (i, a) a[20 + i] = i;
-  shapelessLetCalleeLoop(helper, helper, function (i, a) a[20 + i] = -i, a);
+  helper = function (i, a) { a[20 + i] = i; };
+  shapelessLetCalleeLoop(helper, helper, function (i, a) { a[20 + i] = -i; }, a);
 
-  helper = function (i, a) a[30 + i] = i;
-  shapelessUnknownCalleeLoop(null, helper, helper, function (i, a) a[30 + i] = -i, a);
+  helper = function (i, a) { a[30 + i] = i; };
+  shapelessUnknownCalleeLoop(null, helper, helper, function (i, a) { a[30 + i] = -i; }, a);
 
   try {
     helper = {hack: 42};
     shapelessUnknownCalleeLoop(null, helper, helper, helper, a);
   } catch (e) {
     if (e + "" != "TypeError: f is not a function")
       print("shapelessUnknownCalleeLoop: unexpected exception " + e);
   }
--- a/js/src/jit-test/tests/basic/spread-call-funapply.js
+++ b/js/src/jit-test/tests/basic/spread-call-funapply.js
@@ -48,41 +48,41 @@ function checkNormal(f) {
   assertEqArray(f.apply(null, ...[[]]), [undefined, undefined, undefined]);
   assertEqArray(f.apply(null, ...[[1]]), [1, undefined, undefined]);
   assertEqArray(f.apply(null, ...[[1, 2]]), [1, 2, undefined]);
   assertEqArray(f.apply(null, ...[[1, 2, 3, 4]]), [1, 2, 3]);
 
   assertEqArray(f.apply(null, ...[[undefined]]), [undefined, undefined, undefined]);
 }
 
-checkNormal(function(a, b, c) [a, b, c]);
+checkNormal(function(a, b, c) { return [a, b, c]; });
 checkNormal((a, b, c) => [a, b, c]);
 
 function checkDefault(f) {
   checkCommon(f);
 
   assertEqArray(f.apply(null, ...[[]]), [-1, -2, -3]);
   assertEqArray(f.apply(null, ...[[1]]), [1, -2, -3]);
   assertEqArray(f.apply(null, ...[[1, 2]]), [1, 2, -3]);
   assertEqArray(f.apply(null, ...[[1, 2, 3, 4]]), [1, 2, 3]);
 
   assertEqArray(f.apply(null, ...[[undefined]]), [-1, -2, -3]);
 }
 
-checkDefault(function(a = -1, b = -2, c = -3) [a, b, c]);
+checkDefault(function(a = -1, b = -2, c = -3) { return [a, b, c]; });
 checkDefault((a = -1, b = -2, c = -3) => [a, b, c]);
 
 function checkRest(f) {
   checkCommon(f);
 
   assertEqArray(f.apply(null, ...[[]]), []);
   assertEqArray(f.apply(null, ...[[1]]), [1]);
   assertEqArray(f.apply(null, ...[[1, 2]]), [1, 2]);
   assertEqArray(f.apply(null, ...[[1, 2, 3, 4]]), [1, 2, 3, 4]);
 
   assertEqArray(f.apply(null, ...[[undefined]]), [undefined]);
 
   // other iterable objects
   assertEqArray(f.apply(null, ...new Map([[["a", "A"], ["b", "B"]]])).map(([k, v]) => k + v), ["aA", "bB"]);
 }
 
-checkRest(function(...x) x);
+checkRest(function(...x) { return x; });
 checkRest((...x) => x);
--- a/js/src/jit-test/tests/basic/spread-call-funcall.js
+++ b/js/src/jit-test/tests/basic/spread-call-funcall.js
@@ -2,10 +2,10 @@ load(libdir + "eqArrayHelper.js");
 
 function check(f) {
   assertEqArray(f.call(...[null], 1, 2, 3), [1, 2, 3]);
   assertEqArray(f.call(...[null], 1, ...[2, 3], 4, ...[5, 6]), [1, 2, 3, 4, 5, 6]);
   assertEqArray(f.call(...[null, 1], ...[2, 3], 4, ...[5, 6]), [1, 2, 3, 4, 5, 6]);
   assertEqArray(f.call(...[null, 1, ...[2, 3], 4, ...[5, 6]]), [1, 2, 3, 4, 5, 6]);
 }
 
-check(function(...x) x);
+check(function(...x) { return x; });
 check((...x) => x);
--- a/js/src/jit-test/tests/basic/spread-call-length.js
+++ b/js/src/jit-test/tests/basic/spread-call-length.js
@@ -39,14 +39,14 @@ function checkLength(f, makeFn) {
   assertEq(makeFn("...arg")(f, itr), 3);
   function* gen() {
       for (let i = 1; i < 4; i ++)
           yield i;
   }
   assertEq(makeFn("...arg")(f, gen()), 3);
 }
 
-checkLength(function(x) arguments.length, makeCall);
-checkLength(function(x) arguments.length, makeFunCall);
+checkLength(function(x) { return arguments.length; }, makeCall);
+checkLength(function(x) { return arguments.length; }, makeFunCall);
 function lengthClass(x) {
   this.length = arguments.length;
 }
 checkLength(lengthClass, makeNew);
--- a/js/src/jit-test/tests/basic/spread-call-not-iterable.js
+++ b/js/src/jit-test/tests/basic/spread-call-not-iterable.js
@@ -9,20 +9,20 @@ assertThrowsInstanceOf(() => Math.sin(..
 assertThrowsInstanceOf(() => Math.sin(...(x => x)), TypeError);
 assertThrowsInstanceOf(() => Math.sin(...1), TypeError);
 assertThrowsInstanceOf(() => Math.sin(...{}), TypeError);
 var foo = {}
 
 foo[Symbol.iterator] = 10;
 assertThrowsInstanceOf(() => Math.sin(...foo), TypeError);
 
-foo[Symbol.iterator] = function() undefined;
+foo[Symbol.iterator] = function() { return undefined; };
 assertThrowsInstanceOf(() => Math.sin(...foo), TypeError);
 
-foo[Symbol.iterator] = function() this;
+foo[Symbol.iterator] = function() { return this; };
 assertThrowsInstanceOf(() => Math.sin(...foo), TypeError);
 
-foo[Symbol.iterator] = function() this;
+foo[Symbol.iterator] = function() { return this; };
 foo.next = function() { throw 10; };
 assertThrowsValue(() => Math.sin(...foo), 10);
 
 assertThrowsInstanceOf(() => Math.sin(.../a/), TypeError);
 assertThrowsInstanceOf(() => Math.sin(...new Error()), TypeError);
--- a/js/src/jit-test/tests/basic/spread-call-recursion.js
+++ b/js/src/jit-test/tests/basic/spread-call-recursion.js
@@ -4,15 +4,15 @@ a.length = 30;
 function check(f) {
   try {
     f();
   } catch (e) {
     assertEq(e.message, "too much recursion");
   }
 }
 
-let f = function() f(...a) + 1;
+let f = function() { return f(...a) + 1; };
 let g = () => g(...a) + 1;
-let h = function() new h(...a) + 1;
+let h = function() { return new h(...a) + 1; };
 
 check(f);
 check(g);
 check(h);
--- a/js/src/jit-test/tests/basic/spread-call.js
+++ b/js/src/jit-test/tests/basic/spread-call.js
@@ -52,18 +52,18 @@ function checkNormal(f, makeFn) {
   assertEqArray(makeFn("...[]")(f), [undefined, undefined, undefined]);
   assertEqArray(makeFn("...[1]")(f), [1, undefined, undefined]);
   assertEqArray(makeFn("...[1, 2]")(f), [1, 2, undefined]);
   assertEqArray(makeFn("...[1, 2, 3, 4]")(f), [1, 2, 3]);
 
   assertEqArray(makeFn("...[undefined]")(f), [undefined, undefined, undefined]);
 }
 
-checkNormal(function(a, b, c) [a, b, c], makeCall);
-checkNormal(function(a, b, c) [a, b, c], makeFunCall);
+checkNormal(function(a, b, c) { return [a, b, c]; }, makeCall);
+checkNormal(function(a, b, c) { return [a, b, c]; }, makeFunCall);
 checkNormal((a, b, c) => [a, b, c], makeCall);
 checkNormal((a, b, c) => [a, b, c], makeFunCall);
 function normalClass(a, b, c) {
   this.value = [a, b, c];
   assertEq(Object.getPrototypeOf(this), normalClass.prototype);
 }
 checkNormal(normalClass, makeNew);
 
@@ -73,18 +73,18 @@ function checkDefault(f, makeFn) {
   assertEqArray(makeFn("...[]")(f), [-1, -2, -3]);
   assertEqArray(makeFn("...[1]")(f), [1, -2, -3]);
   assertEqArray(makeFn("...[1, 2]")(f), [1, 2, -3]);
   assertEqArray(makeFn("...[1, 2, 3, 4]")(f), [1, 2, 3]);
 
   assertEqArray(makeFn("...[undefined]")(f), [-1, -2, -3]);
 }
 
-checkDefault(function(a = -1, b = -2, c = -3) [a, b, c], makeCall);
-checkDefault(function(a = -1, b = -2, c = -3) [a, b, c], makeFunCall);
+checkDefault(function(a = -1, b = -2, c = -3) { return [a, b, c]; }, makeCall);
+checkDefault(function(a = -1, b = -2, c = -3) { return [a, b, c]; }, makeFunCall);
 checkDefault((a = -1, b = -2, c = -3) => [a, b, c], makeCall);
 checkDefault((a = -1, b = -2, c = -3) => [a, b, c], makeFunCall);
 function defaultClass(a = -1, b = -2, c = -3) {
   this.value = [a, b, c];
   assertEq(Object.getPrototypeOf(this), defaultClass.prototype);
 }
 checkDefault(defaultClass, makeNew);
 
@@ -94,17 +94,17 @@ function checkRest(f, makeFn) {
   assertEqArray(makeFn("...[]")(f), []);
   assertEqArray(makeFn("1, ...[2, 3, 4], 5")(f), [1, 2, 3, 4, 5]);
   assertEqArray(makeFn("1, ...[], 2")(f), [1, 2]);
   assertEqArray(makeFn("1, ...[2, 3], 4, ...[5, 6]")(f), [1, 2, 3, 4, 5, 6]);
 
   assertEqArray(makeFn("...[undefined]")(f), [undefined]);
 }
 
-checkRest(function(...x) x, makeCall);
-checkRest(function(...x) x, makeFunCall);
+checkRest(function(...x) { return x; }, makeCall);
+checkRest(function(...x) { return x; }, makeFunCall);
 checkRest((...x) => x, makeCall);
 checkRest((...x) => x, makeFunCall);
 function restClass(...x) {
   this.value = x;
   assertEq(Object.getPrototypeOf(this), restClass.prototype);
 }
 checkRest(restClass, makeNew);
--- a/js/src/jit-test/tests/basic/testBrandedVsGeneric.js
+++ b/js/src/jit-test/tests/basic/testBrandedVsGeneric.js
@@ -1,17 +1,17 @@
 const C = function (a, b, c) {
     return function C() {
-        this.m1 = function () a;
-        this.m2 = function () b;
-        this.m3 = function () c;
+        this.m1 = function () { return a; };
+        this.m2 = function () { return b; };
+        this.m3 = function () { return c; };
     }
 }(2,3,4);
 var c = new C();
-var d = function (e) {return {m0: function () e}}(5);
+var d = function (e) {return {m0: function () { return e; }}}(5);
 for (var i = 0; i < 5; i++)
     d.m0();
 C.call(d);
 d.__iterator__ = function() {yield 55};
 for (i = 0; i < 5; i++) {
     for (j in d)
         print(j);
 }
--- a/js/src/jit-test/tests/basic/testBug740442.js
+++ b/js/src/jit-test/tests/basic/testBug740442.js
@@ -1,10 +1,11 @@
 function g1() {}
-function g2() 
-function Int8Array () {}   
+function g2() {
+    return function Int8Array () {};
+}
 function f1(other) {
     eval("gc(); h = g1");
     for(var i=0; i<20; i++) {
         i = i.name;
     }
 }
 f1(g2);
--- a/js/src/jit-test/tests/basic/testBug783441.js
+++ b/js/src/jit-test/tests/basic/testBug783441.js
@@ -1,1 +1,1 @@
-assertEq((function(x, y, x) { return (function() x+y)(); })(1,2,5), 7);
+assertEq((function(x, y, x) { return (function() { return x+y; })(); })(1,2,5), 7);
--- a/js/src/jit-test/tests/basic/testCallProtoMethod.js
+++ b/js/src/jit-test/tests/basic/testCallProtoMethod.js
@@ -1,14 +1,14 @@
 function testCallProtoMethod() {
     function X() { this.x = 1; }
     X.prototype.getName = function () { return "X"; }
 
     function Y() { this.x = 2; }
-    Y.prototype.getName = function() "Y";
+    Y.prototype.getName = function() { return "Y"; };
 
     var a = [new X, new X, new X, new X, new Y];
     var s = '';
     for (var i = 0; i < a.length; i++)
         s += a[i].getName();
     return s;
 }
 assertEq(testCallProtoMethod(), 'XXXXY');
--- a/js/src/jit-test/tests/basic/testDynamicUsage.js
+++ b/js/src/jit-test/tests/basic/testDynamicUsage.js
@@ -1,17 +1,17 @@
 assertEq((function() { var x = 3; return (function() { return x } )() })(), 3);
 assertEq((function() { var g = function() { return x }; var x = 3; return g()})(), 3);
 assertEq((function() { var x; x = 3; return (function() { return x } )() })(), 3);
 assertEq((function() { x = 3; var x; return (function() { return x } )() })(), 3);
 assertEq((function() { var x; var g = function() { return x }; x = 3; return g() })(), 3);
 
-assertEq((function() { function f() { return 3 }; assertEq(f(), 3); return (function() f())() })(), 3);
+assertEq((function() { function f() { return 3 }; assertEq(f(), 3); return (function() { return f(); })(); })(), 3);
 assertEq((function() { function f() { return 3 }; assertEq(f(), 3); return eval('f()') })(), 3);
-assertEq((function() { function f() { return 3 }; (function() f())(); return f() })(), 3);
+assertEq((function() { function f() { return 3 }; (function() { return f(); })(); return f() })(), 3);
 
 assertEq((function() { var x = 3; return eval("x") })(), 3);
 assertEq((function() { var x; x = 3; return eval("x") })(), 3);
 assertEq((function() { x = 3; var x; return eval("x") })(), 3);
 
 assertEq((function() { var x; (function() {x = 2})(); return ++x })(), 3);
 assertEq((function() { var x; (function() {x = 2})(); x++; return x })(), 3);
 assertEq((function() { var x; (function() {x = 4})(); return --x })(), 3);
@@ -21,60 +21,60 @@ assertEq((function(x) { return (function
 assertEq((function(x) { var x = 3; return (function() { return x } )() })(4), 3);
 assertEq((function(x) { x = 3; return (function() { return x } )() })(4), 3);
 assertEq((function(x) { var g = function() { return x }; x = 3; return g()})(3), 3);
 
 assertEq((function(x) { return eval("x") })(3), 3);
 assertEq((function(x) { x = 3; return eval("x") })(4), 3);
 
 assertEq((function(a) { var [x,y] = a; (function() { x += y })(); return x })([1,2]), 3);
-assertEq((function(a) { var [x,y] = a; x += y; return (function() x)() })([1,2]), 3);
-assertEq((function(a) { var [[l, x],[m, y]] = a; x += y; return (function() x)() })([[0,1],[0,2]]), 3);
+assertEq((function(a) { var [x,y] = a; x += y; return (function() { return x; })() })([1,2]), 3);
+assertEq((function(a) { var [[l, x],[m, y]] = a; x += y; return (function() { return x; })() })([[0,1],[0,2]]), 3);
 assertEq((function(a) { var [x,y] = a; eval('x += y'); return x })([1,2]), 3);
 assertEq((function(a) { var [x,y] = a; x += y; return eval('x') })([1,2]), 3);
 assertEq((function(a) { var [x,y] = a; (function() { x += y })(); return x })([1,2]), 3);
-assertEq((function(a) { var [x,y] = a; x += y; return (function() x)() })([1,2]), 3);
+assertEq((function(a) { var [x,y] = a; x += y; return (function() { return x; })() })([1,2]), 3);
 assertEq((function(a,x,y) { [x,y] = a; (function() { eval('x += y') })(); return x })([1,2]), 3);
-assertEq((function(a,x,y) { [x,y] = a; x += y; return (function() eval('x'))() })([1,2]), 3);
+assertEq((function(a,x,y) { [x,y] = a; x += y; return (function() { return eval('x'); })() })([1,2]), 3);
 
-assertEq((function() { var [x,y] = [1,2]; x += y; return (function() x)() })(), 3);
-assertEq((function() { var [x,y] = [1,2]; (function() x += y)(); return x })(), 3);
-assertEq((function() { { let [x,y] = [1,2]; x += y; return (function() x)() } })(), 3);
-assertEq((function() { { let [x,y] = [1,2]; (function() x += y)(); return x } })(), 3);
+assertEq((function() { var [x,y] = [1,2]; x += y; return (function() { return x; })() })(), 3);
+assertEq((function() { var [x,y] = [1,2]; (function() { return x += y; })(); return x })(), 3);
+assertEq((function() { { let [x,y] = [1,2]; x += y; return (function() { return x; })() } })(), 3);
+assertEq((function() { { let [x,y] = [1,2]; (function() { return x += y; })(); return x } })(), 3);
 
 assertEq((function([x,y]) { (function() { x += y })(); return x })([1,2]), 3);
-assertEq((function([x,y]) { x += y; return (function() x)() })([1,2]), 3);
+assertEq((function([x,y]) { x += y; return (function() { return x; })() })([1,2]), 3);
 assertEq((function([[l,x],[m,y]]) { (function() { x += y })(); return x })([[0,1],[0,2]]), 3);
-assertEq((function([[l,x],[m,y]]) { x += y; return (function() x)() })([[0,1],[0,2]]), 3);
+assertEq((function([[l,x],[m,y]]) { x += y; return (function() { return x; })() })([[0,1],[0,2]]), 3);
 assertEq((function([x,y]) { (function() { eval('x += y') })(); return x })([1,2]), 3);
-assertEq((function([x,y]) { x += y; return (function() eval('x'))() })([1,2]), 3);
+assertEq((function([x,y]) { x += y; return (function() { return eval('x'); })() })([1,2]), 3);
 assertEq((function() { try { throw [1,2] } catch([x,y]) { eval('x += y'); return x }})(), 3);
 assertEq((function() { try { throw [1,2] } catch([x,y]) { x += y; return eval('x') }})(), 3);
 assertEq((function() { try { throw [1,2] } catch([x,y]) { (function() { x += y })(); return x }})(), 3);
-assertEq((function() { try { throw [1,2] } catch([x,y]) { x += y; return (function() x)() }})(), 3);
+assertEq((function() { try { throw [1,2] } catch([x,y]) { x += y; return (function() { return x; })() }})(), 3);
 assertEq((function() { try { throw [1,2] } catch([x,y]) { (function() { eval('x += y') })(); return x }})(), 3);
-assertEq((function() { try { throw [1,2] } catch([x,y]) { x += y; return (function() eval('x'))() }})(), 3);
+assertEq((function() { try { throw [1,2] } catch([x,y]) { x += y; return (function() { return eval('x'); })() }})(), 3);
 
 assertEq((function(a) { let [x,y] = a; (function() { x += y })(); return x })([1,2]), 3);
-assertEq((function(a) { let [x,y] = a; x += y; return (function() x)() })([1,2]), 3);
+assertEq((function(a) { let [x,y] = a; x += y; return (function() { return x; })() })([1,2]), 3);
 assertEq((function(a) { { let [x,y] = a; (function() { x += y })(); return x } })([1,2]), 3);
-assertEq((function(a) { { let [x,y] = a; x += y; return (function() x)() } })([1,2]), 3);
+assertEq((function(a) { { let [x,y] = a; x += y; return (function() { return x; })() } })([1,2]), 3);
 assertEq((function(a) { { let [[l, x],[m, y]] = a; (function() { x += y })(); return x } })([[0,1],[0,2]]), 3);
-assertEq((function(a) { { let [[l, x],[m, y]] = a; x += y; return (function() x)() } })([[0,1],[0,2]]), 3);
+assertEq((function(a) { { let [[l, x],[m, y]] = a; x += y; return (function() { return x; })() } })([[0,1],[0,2]]), 3);
 
 assertEq((function() { let x = 3; return (function() { return x })() })(), 3);
 assertEq((function() { let g = function() { return x }; let x = 3; return g() })(), 3);
 
 assertEq((function() { { let x = 3; return (function() { return x })() } })(), 3);
 assertEq((function() { { let x = 3; (function() { assertEq(x, 3) })(); return x } })(), 3);
 assertEq((function() { { let x = 2; x = 3; return (function() { return x })() } })(), 3);
 assertEq((function() { { let x = 1; { let x = 3; (function() { assertEq(x,3) })() } return x } })(), 1);
 
 assertEq((function() { try { throw 3 } catch (e) { (function(){assertEq(e,3)})(); return e } })(), 3);
-assertEq((function() { try { throw 3 } catch (e) { assertEq(e, 3); return (function() e)() } })(), 3);
+assertEq((function() { try { throw 3 } catch (e) { assertEq(e, 3); return (function() { return e; })() } })(), 3);
 assertEq((function() { try { throw 3 } catch (e) { (function(){eval('assertEq(e,3)')})(); return e } })(), 3);
 
 assertEq((function() { var x; function f() { return x } function f() { return 3 }; return f() })(), 3);
 assertEq((function() { var x = 3; function f() { return 3 } function f() { return x }; return f() })(), 3);
 
 assertEq((function() { function f(x,y) { (function() { assertEq(f.length, 2) })(); }; return f.length })(), 2);
 assertEq((function f(x,y) { (function() { assertEq(f.length, 2) })(); return f.length})(), 2);
 function f1(x,y) { (function() { assertEq(f1.length, 2) })(); assertEq(f1.length, 2) }; f1();
--- a/js/src/jit-test/tests/debug/Debugger-findScripts-12.js
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-12.js
@@ -15,18 +15,19 @@ g2.load(url1);
 g2.load(url2);
 var g3 = newGlobal();
 
 var dbg = new Debugger(g1, g2, g3);
 
 function script(func) {
     var gw = dbg.addDebuggee(func.global);
     var script = gw.makeDebuggeeValue(func).script;
-    script.toString = function ()
-        "[Debugger.Script for " + func.name + " in " + uneval(func.global) + "]";
+    script.toString = function () {
+        return "[Debugger.Script for " + func.name + " in " + uneval(func.global) + "]";
+    };
     return script;
 }
 
 // The function scripts we know of. There may be random eval scripts involved, but
 // we don't care about those.
 var allScripts = ([g1.f, g1.f(), g1.g, g1.h, g1.h(), g1.i,
                    g2.f, g2.f(), g2.g, g2.h, g2.h(), g2.i].map(script));
 
--- a/js/src/jit-test/tests/debug/bug-1240090.js
+++ b/js/src/jit-test/tests/debug/bug-1240090.js
@@ -1,12 +1,12 @@
 gczeal(2);
 g = newGlobal();
 dbg = Debugger(g);
-dbg.onNewScript = function() function() this;
+dbg.onNewScript = function() { return function() { return this; } };
 schedulegc(10);
 g.eval("setLazyParsingDisabled(true)");
 g.evaluate("function one() {}");
 g.evaluate(`
            function target () {}
            function two2() {}
            `, {});
 g.evaluate(`
--- a/js/src/jit-test/tests/debug/bug1240803.js
+++ b/js/src/jit-test/tests/debug/bug1240803.js
@@ -10,15 +10,15 @@ if (!('oomAfterAllocations' in this))
     g.toggle = function(d) {
         if (d) {
             dbg.addDebuggee(g);
             dbg.getNewestFrame();
             oomAfterAllocations(2);
             setBreakpoint;
         }
     }
-    g.eval("" + function f(d) toggle(d))
+    g.eval("" + function f(d) { return toggle(d); })
     g.eval("(" + function() {
         f(false);
         f(true);
     } + ")()")
 })();
 
--- a/js/src/jit-test/tests/debug/bug1252453.js
+++ b/js/src/jit-test/tests/debug/bug1252453.js
@@ -8,14 +8,14 @@ const dbg = new Debugger;
 const wrappedRoot = dbg.addDebuggee(root);
 dbg.memory.trackingAllocationSites = 1;
 root.eval("(" + function immediate() { '_'  << foo } + "())");
 `);
 file = lfcode.shift();
 loadFile(file);
 function loadFile(lfVarx) {
     try {
-        function newFunc(x) Function(x)();
+        function newFunc(x) { return Function(x)(); }
         newFunc(lfVarx)();
     } catch (lfVare) {
         print(lfVare)
     }
 }
--- a/js/src/jit-test/tests/debug/bug1252464.js
+++ b/js/src/jit-test/tests/debug/bug1252464.js
@@ -1,14 +1,14 @@
 // |jit-test| error: ReferenceError
 
 g = newGlobal();
 dbg = Debugger(g);
 hits = 0;
-dbg.onNewScript = function () hits++;
+dbg.onNewScript = function () { return hits++; };
 assertEq(g.eval("eval('2 + 3')"), 5);
 this.gczeal(hits,1);
 dbg = Debugger(g);
 g.h = function () {
   var env = dbg.getNewestFrame().environment;
   dbg =  0;
   assertThrowsInstanceOf;
 }
--- a/js/src/jit-test/tests/debug/bug1275001.js
+++ b/js/src/jit-test/tests/debug/bug1275001.js
@@ -15,16 +15,16 @@ function check_one(expected, f, err) {
     }
 }
 ieval = eval
 function check(expr, expected = expr) {
     var end, err
     for ([end, err] of[[".random_prop", " is undefined" ]]) 
          statement = "o = {};" + expr + end;
          cases = [
-            function() ieval("var undef;" + statement),
+            function() { return ieval("var undef;" + statement); },
             Function(statement)
         ]
         for (f of cases) 
             check_one(expected, f, err)
 }
 check("undef");
 check("o.b");
--- a/js/src/jit-test/tests/gc/bug-1016016.js
+++ b/js/src/jit-test/tests/gc/bug-1016016.js
@@ -1,12 +1,13 @@
 // |jit-test| error:ReferenceError
 toPrinted(this.reason);
-function toPrinted(value)
-  value = String(value);
+function toPrinted(value) {
+  return value = String(value);
+}
 var lfcode = new Array();
 lfcode.push = loadFile;
 lfcode.push("enableTrackAllocations();");
 lfcode.push("\
 gczeal(9, 2);\
 newGlobal();\
 ''.addDebuggee(g1);\
 ");
--- a/js/src/jit-test/tests/gc/bug-1136597.js
+++ b/js/src/jit-test/tests/gc/bug-1136597.js
@@ -3,18 +3,19 @@ var evalInFrame = (function (global) {
   var dbgGlobal = newGlobal();
   var dbg = new dbgGlobal.Debugger();
   return function evalInFrame(upCount, code) {
     dbg.addDebuggee(global);
   };
 })(this);
 var gTestcases = new Array();
 var gTc = gTestcases.length;
-function TestCase()
-  gTestcases[gTc++] = this;
+function TestCase() {
+  return gTestcases[gTc++] = this;
+}
 function checkCollation(extensionCoValue, usageValue) {
     var collator = new Intl.Collator(["de-DE"]);
     collator.resolvedOptions().collation;
 }
 checkCollation(undefined, "sort");
 checkCollation();
 for ( addpow = 0; addpow < 33; addpow++ ) {
     new TestCase();
--- a/js/src/jit-test/tests/gc/bug-1155455.js
+++ b/js/src/jit-test/tests/gc/bug-1155455.js
@@ -1,17 +1,18 @@
 // |jit-test| error: TypeError
 if (!("gczeal" in this))
     quit();
 var g = newGlobal();
 gczeal(10, 2)
 var dbg = Debugger(g);
 dbg.onDebuggerStatement = function (frame1) {
-    function hit(frame2)
-      hit[0] = "mutated";
+    function hit(frame2) {
+      return hit[0] = "mutated";
+    }
     var s = frame1.script;
     var offs = s.getLineOffsets(g.line0 + 2);
     for (var i = 0; i < offs.length; i++)
       s.setBreakpoint(offs[i], {hit: hit});
     return;
 };
 var lfGlobal = newGlobal();
 g.eval("var line0 = Error().lineNumber;\n debugger;\nx = 1;\n");
--- a/js/src/jit-test/tests/gc/bug-1252103.js
+++ b/js/src/jit-test/tests/gc/bug-1252103.js
@@ -10,13 +10,13 @@ function foo() {
     TO = TypedObject;
     PointType = new TO.StructType({
         y: TO.float64,
         name: TO.string
     })
     LineType = new TO.StructType({
         PointType
     })
-    function testBasic() new LineType;
+    function testBasic() { return new LineType; }
     testBasic();
 }
 evaluate("foo()");
 
--- a/js/src/jit-test/tests/gc/bug-1261329.js
+++ b/js/src/jit-test/tests/gc/bug-1261329.js
@@ -1,10 +1,10 @@
 if (!('oomTest' in this))
     quit();
 
 print = function() {}
-function k() dissrc(print);
-function j() k();
-function h() j();
-function f() h();
+function k() { return dissrc(print); }
+function j() { return k(); }
+function h() { return j(); }
+function f() { return h(); }
 f();
 oomTest(() => f())
--- a/js/src/jit-test/tests/gc/bug-1263871.js
+++ b/js/src/jit-test/tests/gc/bug-1263871.js
@@ -1,8 +1,9 @@
 if (!('oomTest' in this))
     quit();
 
 lfLogBuffer = `this[''] = function() {}`;
 loadFile(lfLogBuffer);
 loadFile(lfLogBuffer);
-function loadFile(lfVarx)
-    oomTest(function() parseModule(lfVarx))
+function loadFile(lfVarx) {
+    return oomTest(function() { return parseModule(lfVarx); });
+}
--- a/js/src/jit-test/tests/gc/bug-1305220.js
+++ b/js/src/jit-test/tests/gc/bug-1305220.js
@@ -10,14 +10,14 @@ evalcx("\
 ", s);
 gczeal(0);
 evalcx("\
     var g = newGlobal();\
     b = new Debugger;\
     g.h = function() {\
         g.oomAfterAllocations(1);\
     };\
-    g.eval(\"\" + function f() g());\
-    g.eval(\"\" + function g() h());\
+    g.eval(\"\" + function f() { return g(); });\
+    g.eval(\"\" + function g() { return h(); });\
     g.eval(\"(\" + function() {\
         f();\
     } + \")()\");\
 ", s);
--- a/js/src/jit-test/tests/gc/bug-1310589.js
+++ b/js/src/jit-test/tests/gc/bug-1310589.js
@@ -7,17 +7,17 @@ e2 = Set
 v2 = b2 = new ArrayBuffer
 t2 = new Uint8ClampedArray
 minorgc()
 x = /x/
 for (var i = 0; i < 4; ++i) {
     function f1() {}
 }
 Object.defineProperty(a, 12, {}).push(1);
-toString = (function() a.reverse())
+toString = (function() { return a.reverse(); })
 oomTest(Date.prototype.toJSON)
 function f1000(){}
 function f1001(){}
 function f1002(){}
 function f1003(){}
 function f1004(){}
 function f1005(){}
 function f1006(){}
--- a/js/src/jit-test/tests/gc/bug-906243.js
+++ b/js/src/jit-test/tests/gc/bug-906243.js
@@ -1,12 +1,12 @@
 // |jit-test| need-for-each
 
 a2 = []
-g = function() r
+g = function() { return r; };
 Object.defineProperty(a2, 0, {
     set: function() {}
 })
 for (var x = 0; x < 70; ++x) {
     Array.prototype.unshift.call(a2, g)
 }
 a2.length = 8
 for each(e in [0, 0]) {
--- a/js/src/jit-test/tests/gc/bug-945275.js
+++ b/js/src/jit-test/tests/gc/bug-945275.js
@@ -1,11 +1,11 @@
 function TestCase(n) {
   this.name = undefined;
   this.description = undefined;
 }
 gczeal(7,1);
 eval("\
-function reportCompare() new TestCase;\
+function reportCompare() { return new TestCase; };\
 reportCompare();\
 Object.defineProperty(Object.prototype, 'name', {});\
 reportCompare();\
 ");
--- a/js/src/jit-test/tests/gc/bug-957114.js
+++ b/js/src/jit-test/tests/gc/bug-957114.js
@@ -2,12 +2,12 @@ gczeal(7,1);
 function TestCase(n) {
   this.name = '';
   this.description = '';
   this.expect = '';
   this.actual = '';
   this.reason = '';
   this.passed = '';
 }
-function test() new TestCase;
+function test() { return new TestCase; }
 test();
 Object.defineProperty(Object.prototype, "name", {});
 test();
--- a/js/src/jit-test/tests/gc/bug-961877.js
+++ b/js/src/jit-test/tests/gc/bug-961877.js
@@ -1,14 +1,14 @@
 g = Function("", "for (var i = 0; i < 0; ++i) { eval('this.arg'+0 +'=arg'+0); }");
 Math.abs(undefined);
 gczeal(2,300);
 evaluate("\
 var toFloat32 = (function() {\
     var f32 = new Float32Array(1);\
-    function f(x) f32[0] = x;\
+    function f(x) { return f32[0] = x; }\
     return f;\
 })();\
 for (var i = 0; i < 64; ++i) {\
     var p = Math.pow(2, i) + 1;\
     g(toFloat32(p));\
     toFloat32(-p);\
 }");
--- a/js/src/jit-test/tests/gc/bug-981295.js
+++ b/js/src/jit-test/tests/gc/bug-981295.js
@@ -1,9 +1,9 @@
 var NotEarlyErrorString = "NotEarlyError";
 var NotEarlyError = new Error(NotEarlyErrorString);
 var juneDate = new Date(2000, 5, 20, 0, 0, 0, 0);
-for (var i = 0; i < function(x) myObj(Date.prototype.toString.apply(x)); void i) {
+for (var i = 0; i < function(x) { return myObj(Date.prototype.toString.apply(x)); }; void i) {
     eval(a.text.replace(/@/g, ""))
 }
 gcslice(2601);
 function testcase() {}
 new Uint16Array(testcase);
--- a/js/src/jit-test/tests/gc/oomInDtoa.js
+++ b/js/src/jit-test/tests/gc/oomInDtoa.js
@@ -1,4 +1,4 @@
 if (!('oomTest' in this))
     quit();
 
-oomTest(function() 1e300)
+oomTest(function() { return 1e300; })
--- a/js/src/jit-test/tests/ion/bug1215600.js
+++ b/js/src/jit-test/tests/ion/bug1215600.js
@@ -7,17 +7,17 @@ for (let i = 0; i < 10; i++) {
     file = lfcode.shift()
     loadFile(file)
 }
 function loadFile(lfVarx) {
     try {
         if (lfVarx.length != 1)
             switch (lfRunTypeId) {
                 case 3:
-                    function newFunc(x) Function(x)()
+                    function newFunc(x) { return Function(x)(); }
                     newFunc(lfVarx)
                 case 5:
                     for (lfLocal in this);
             }
         isNaN();
         lfRunTypeId = parseInt(lfVarx);
     } catch (lfVare) {}
 }
--- a/js/src/jit-test/tests/ion/bug1264948.js
+++ b/js/src/jit-test/tests/ion/bug1264948.js
@@ -5,17 +5,18 @@ loadFile(`
   T = TypedObject
   ObjectStruct = new T.StructType({f: T.Object})
   var o = new ObjectStruct
   function testGC(p) {
     for (; i < 5; i++)
         whatever.push;
   }
   testGC(o)
-  function writeObject()
-    o.f = v
+  function writeObject() {
+    return o.f = v;
+  }
     writeObject({function() { } })
   for (var i ; i < 5 ; ++i)
     try {} catch (StringStruct) {}
 `);
 function loadFile(lfVarx) {
   oomTest(Function(lfVarx));
 }
--- a/js/src/jit-test/tests/ion/bug1284491.js
+++ b/js/src/jit-test/tests/ion/bug1284491.js
@@ -6,10 +6,10 @@ loadFile(`
     switch(value) {
       case 0:break
       case isNaN: break
     }
   }
   SwitchTest();
 `)
 function loadFile(lfVarx) {
-  oomTest(function() eval(lfVarx))
+  oomTest(function() { return eval(lfVarx); })
 }
--- a/js/src/jit-test/tests/ion/bug1299007.js
+++ b/js/src/jit-test/tests/ion/bug1299007.js
@@ -11,18 +11,18 @@ evalInFrame = function(global) {
    }
 }(this);
 function h() {
     evalInFrame(0, "")
     evalInFrame(0, "i")
     evalInFrame(0, "a.push")
     evalInFrame(1, "a.pushy")
 }
-function g() h()
-function f() g()
+function g() { return h(); }
+function f() { return g(); }
 f()
 evaluate(`
 g()
 g()
 g()
 g()
 g()
 g()
--- a/js/src/jit-test/tests/ion/bug691747.js
+++ b/js/src/jit-test/tests/ion/bug691747.js
@@ -1,9 +1,9 @@
-function reportCompare(actual) - ++actual + "'";
+function reportCompare(actual) { return - ++actual + "'"; }
 var UBound = 0;
 var actualvalues = [];
 for (var li = 0; li < 6; ++li) addThis();
 function addThis() {
   UBound++;
   for (var i=0; i<UBound; i++)  {
     reportCompare(actualvalues[i]);
   }
--- a/js/src/jit-test/tests/ion/bug724944.js
+++ b/js/src/jit-test/tests/ion/bug724944.js
@@ -1,10 +1,11 @@
-function TestCase(n, d, e, a)
-function writeHeaderToLog( string ) {}
+function TestCase(n, d, e, a) {
+  return function writeHeaderToLog( string ) {};
+}
 var SECTION = "15.1.2.5-2";
 for ( var CHARCODE = 0; CHARCODE < 256; CHARCODE += 16 ) {
   new TestCase( SECTION, unescape( "%" + (ToHexString(CHARCODE)).substring(0,1) )  );
 }
 function ToHexString( n ) {
   var hex = new Array();
   for ( var mag = 1; Math.pow(16,mag) <= n ; mag++ ) {  }
   for ( index = 0, mag -= 1; mag > 0; index++, mag-- ) {  }
--- a/js/src/jit-test/tests/ion/bug729573.js
+++ b/js/src/jit-test/tests/ion/bug729573.js
@@ -1,10 +1,11 @@
-function TestCase(n, d, e, a)
-function writeHeaderToLog( string ) {}
+function TestCase(n, d, e, a) {
+  return function writeHeaderToLog( string ) {};
+}
 var SECTION = "11.7.2";
 for ( power = 0; power <= 32; power++ ) {
   shiftexp = Math.pow( 2, power );
   for ( addexp = 0; addexp <= 32; addexp++ ) {
     new TestCase( SECTION, SignedRightShift( shiftexp, addexp ), shiftexp >> addexp );
   }
 }
 function ToInt32BitString( n ) {
--- a/js/src/jit-test/tests/ion/bug729788.js
+++ b/js/src/jit-test/tests/ion/bug729788.js
@@ -3,17 +3,17 @@ function Day(t) {
     return Math.floor(t / msPerDay);
 }
 function YearFromTime(t) {
     sign = 1
     year = sign < 0
 }
 function MonthFromTime(t) {
     DayWithinYear(t)
-    function DayWithinYear(t) Day(t) - YearFromTime()
+    function DayWithinYear(t) { return Day(t) - YearFromTime(); }
     function WeekDay(t) {
         weekday = Day(t) + 4
         return (weekday < 0 ? weekday : weekday);
     }
     time = year
     for (var last_sunday = time; WeekDay(last_sunday) == 0;) {}
 }
 addTestCase(0, 946684800000);
--- a/js/src/jit-test/tests/ion/bug732851.js
+++ b/js/src/jit-test/tests/ion/bug732851.js
@@ -1,10 +1,11 @@
 var OMIT = {};
 var WRITABLES = [true, false, OMIT];
 {
   var desc = {};
-  function put(field, value)
-    desc[field] = value;
+  function put(field, value) {
+    return desc[field] = value;
+  }
   WRITABLES.forEach(function(writable) {
     put("writable", writable)
   });
 };
--- a/js/src/jit-test/tests/ion/bug732859.js
+++ b/js/src/jit-test/tests/ion/bug732859.js
@@ -1,10 +1,11 @@
-function TestCase(n, d, e, a)
-function writeHeaderToLog( string ) {}
+function TestCase(n, d, e, a) {
+  return function writeHeaderToLog( string ) {};
+}
 var SECTION = "15.1.2.4";
 for ( var CHARCODE = 128; CHARCODE < 256; CHARCODE++ ) {
   new TestCase( SECTION, "%"+ToHexString(CHARCODE), escape(String.fromCharCode(CHARCODE)));
 }
 function ToHexString( n ) {
   var hex = new Array();
   hex[hex.length] = n % 16;
   var string ="";
--- a/js/src/jit-test/tests/ion/bug756780.js
+++ b/js/src/jit-test/tests/ion/bug756780.js
@@ -1,8 +1,9 @@
 gczeal(4);
 var i = (29);
 var status = '';
 var statusmessages = new Array();
 addThis();
 addThis();
-function addThis()
-  statusmessages[i] = status;
+function addThis() {
+  return statusmessages[i] = status;
+}
--- a/js/src/jit-test/tests/ion/bug819865.js
+++ b/js/src/jit-test/tests/ion/bug819865.js
@@ -1,2 +1,2 @@
-(function x() (x == x))();
+(function x() { return (x == x); })();
 
--- a/js/src/jit-test/tests/ion/bug852140.js
+++ b/js/src/jit-test/tests/ion/bug852140.js
@@ -1,7 +1,8 @@
 function reportCompare (expected, actual) {
   if (expected != actual) {}
 }
-function exitFunc (funcName)
-  reportCompare(undefined, '');
+function exitFunc (funcName) {
+  return reportCompare(undefined, '');
+}
 reportCompare('', '');
 exitFunc();
--- a/js/src/jit-test/tests/ion/bug852174.js
+++ b/js/src/jit-test/tests/ion/bug852174.js
@@ -1,9 +1,12 @@
 
-function eval()
-  isPrototypeOf[Iterator.length] 
-function DoWhile_3()
-  eval();
+function eval() {
+  return isPrototypeOf[Iterator.length];
+}
+function DoWhile_3() {
+  return eval();
+}
 DoWhile_3();
-function f()
-  DoWhile_3(f - 0);
+function f() {
+  return DoWhile_3(f - 0);
+}
 for (var i in f());
--- a/js/src/jit-test/tests/ion/bug862100.js
+++ b/js/src/jit-test/tests/ion/bug862100.js
@@ -4,11 +4,11 @@ function reportCompare (expected, actual
   new TestCase("", description, expected, actual);
 }
 new TestCase( "", "", 0, Number(new Number()) );
 reportCompare(true, true);
 evaluate("\
 function TestCase(n, d, e, a) {}\
 test_negation(-2147483648, 2147483648);\
 test_negation(2147483647, -2147483647);\
-function test_negation(value, expected)\
+function test_negation(value, expected) {\
     reportCompare(expected, '', '-(' + value + ') == ' + expected);\
-");
+}");
--- a/js/src/jit-test/tests/ion/bug862357.js
+++ b/js/src/jit-test/tests/ion/bug862357.js
@@ -1,11 +1,12 @@
 // |jit-test| error: ReferenceError
-function TestCase(e, a)
-  this.passed = (e == a);
+function TestCase(e, a) {
+  return this.passed = (e == a);
+}
 function reportCompare (expected, actual) {
   var expected_t = typeof expected;
   var actual_t = typeof actual;
   if (expected_t != actual_t)
     printStatus();
   new TestCase(expected, actual);
 }
 var expect = '';
--- a/js/src/jit-test/tests/ion/recover-lambdas-bug1118911.js
+++ b/js/src/jit-test/tests/ion/recover-lambdas-bug1118911.js
@@ -1,9 +1,10 @@
 
 function test() {
-  function f()
-    k.apply(this, arguments);
+  function f() {
+    return k.apply(this, arguments);
+  }
   if (undefined >> undefined !== 0) {}
   for (var [ v , c ] in this.tracemonkey) {  }
 }
 try { test(); } catch(exc1) {}
 try { test(); } catch(exc1) {}
--- a/js/src/jit-test/tests/jaeger/bug549396.js
+++ b/js/src/jit-test/tests/jaeger/bug549396.js
@@ -1,1 +1,1 @@
-x = this.__defineSetter__("x", function(z) function() { z })
+x = this.__defineSetter__("x", function(z) { return function() { z }; })
--- a/js/src/jit-test/tests/jaeger/bug819035.js
+++ b/js/src/jit-test/tests/jaeger/bug819035.js
@@ -3,17 +3,17 @@
 (function (a, u) {
   var sum = function (array, callback) {
     for (var i = 0; i < array.length; i++)
       callback(array[i]);
   };
   (function () {
     (function r(t) {
        t !== u,
-         sum(t, function (v)  r(v) );
+         sum(t, function (v) { return r(v); } );
     })(arguments);
   })(a);
 }) (
     [
         [
             [1], [1], [1], [1], [1], [1], [1], [1], [1], [1],
             [1], [1], [1], [1], [1], [1], [1], [1], [1], [1],
             [1], [1], [1], [1], [1], [1], [1], [1], [1], [1],
--- a/js/src/jit-test/tests/jaeger/loops/bug659452.js
+++ b/js/src/jit-test/tests/jaeger/loops/bug659452.js
@@ -1,5 +1,5 @@
 test();
 function test() {
-    var t = function () function printStatus() {};
+    var t = function () { return function printStatus() {}; };
     for (var j = 0; j < 10; j++) t["-1"]
 }
--- a/js/src/jit-test/tests/jaeger/recompile/bug647199.js
+++ b/js/src/jit-test/tests/jaeger/recompile/bug647199.js
@@ -1,11 +1,11 @@
 TryInWhile( new TryObject( "hello", ThrowException, true ) );
 function TryObject( value, throwFunction, result ) {
   this.thrower=throwFunction
 }
-function ThrowException() TryInWhile(1);
+function ThrowException() { return TryInWhile(1); }
 function TryInWhile( object ) {
     try {
       object.thrower()
     } catch ( e ) {
     }  
 }
--- a/js/src/jit-test/tests/jaeger/recompile/bug658212.js
+++ b/js/src/jit-test/tests/jaeger/recompile/bug658212.js
@@ -1,17 +1,20 @@
 var gTestcases = Array;
 function TestCase(n, d, e, a) {
     this.description = d
     gTestcases[gTc] = this
 }
-TestCase.prototype.dump=function ()  +  +  +
+TestCase.prototype.dump=function () { return +  +  +
           + this.description +  +
-               +  + '\n';function printStatus (msg)
-function toPrinted(value) {
+               +  + '\n';
+};
+function printStatus (msg) {
+  return function toPrinted(value) {
+  };
 }
 function reportCompare(expected, actual, description) {
     new TestCase("unknown-test-name", description, expected, actual)
 }
 gTc = 0;;
 function jsTestDriverEnd() {
     for (var i = 0; i < gTestcases.length; i++)
     gTestcases[i].dump()
--- a/js/src/jit-test/tests/jaeger/recompile/bug658561.js
+++ b/js/src/jit-test/tests/jaeger/recompile/bug658561.js
@@ -1,5 +1,5 @@
 var s1 = 'xx';
 for (var x = 0; x < 10 ; ++x ) { 
-  new function() s1++;
+  new function() { return s1++; };
   gc();
 }
--- a/js/src/jit-test/tests/jaeger/recompile/bug658777.js
+++ b/js/src/jit-test/tests/jaeger/recompile/bug658777.js
@@ -1,9 +1,9 @@
-function Employee(name, dept) this.name = name || "";
+function Employee(name, dept) { return this.name = name || ""; }
 function WorkerBee(name, dept, projs) {
     this.base = Employee
     this.base(name, dept)
 }
 function Engineer(name, projs, machine) {
     this.base = WorkerBee
     this.base(name, "engineering", projs)
     __proto__["a" + constructor] = 1
--- a/js/src/jit-test/tests/jaeger/recompile/bug659766.js
+++ b/js/src/jit-test/tests/jaeger/recompile/bug659766.js
@@ -1,29 +1,29 @@
 var gTestcases = new Array;
 var gTc = gTestcases;
 function TestCase(n, d, e, a) {
   this.description=d
   this.reason=''
   gTestcases[gTc++]=this
 }
-TestCase.prototype.dump=function () + toPrinted(this.description) + toPrinted(this.reason) + '\n';
-function toPrinted(value) value=value.replace(/\\n/g, 'NL').replace(/[^\x20-\x7E]+/g, escapeString);
+TestCase.prototype.dump=function () { return + toPrinted(this.description) + toPrinted(this.reason) + '\n'; };
+function toPrinted(value) { return value=value.replace(/\\n/g, 'NL').replace(/[^\x20-\x7E]+/g, escapeString); }
 function escapeString (str) {
   try {
      err
   } catch(ex) { }
 }
 function jsTestDriverEnd() {
   for (var i = 0; i < gTestcases.length; i++)
   gTestcases[i].dump()
 }
 var SECTION = "dowhile-007";
 DoWhile();
-function DoWhile( object ) result1=false;
+function DoWhile( object ) { return result1=false; }
 new TestCase(
     SECTION,
     "break one: ",
     result1 
 );
 jsTestDriverEnd();
 new TestCase( SECTION, "'�O� �:i��'.match(new RegExp('.+'))", [], '�O� �:i��');
 jsTestDriverEnd();
--- a/js/src/jit-test/tests/jaeger/recompile/bug661859.js
+++ b/js/src/jit-test/tests/jaeger/recompile/bug661859.js
@@ -1,24 +1,24 @@
-function TestCase(n, d, e, a) this.expect = e;
+function TestCase(n, d, e, a) { return this.expect = e; }
 function reportCompare(expected, actual, description) {
     typeof actual
 }
 expect = 1;
 var summary = 'Do not assert: top < ss->printer->script->depth';
 var actual = 'No Crash';
 var expect = 'No Crash';
 test();
 function notInlined(f) {
     // prevent inlining this function, as a consequence, it prevent inlining
     // Array.prototype.some (Bug 1087468)
     with ({}) {}
     return f;
 }
 function test(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z) {
     try {
-        p = [1].some(notInlined(function (y) test())) ? 4 : 0x0041;
+        p = [1].some(notInlined(function (y) { return test(); })) ? 4 : 0x0041;
     } catch (ex) {}
     reportCompare(expect, actual, summary)
 }
 test();
 TestCase();
 test()
--- a/js/src/jit-test/tests/modules/bug-1233915.js
+++ b/js/src/jit-test/tests/modules/bug-1233915.js
@@ -1,10 +1,11 @@
 g = newGlobal();
 g.parent = this;
 g.eval("(" + function() {
     Debugger(parent)
-        .onExceptionUnwind = function(frame)
-    frame.eval("")
+        .onExceptionUnwind = function(frame) {
+        return frame.eval("");
+    };
 } + ")()");
 m = parseModule(` s1 `);
 m.declarationInstantiation();
 m.evaluation();
--- a/js/src/jit-test/tests/modules/bug-1245518.js
+++ b/js/src/jit-test/tests/modules/bug-1245518.js
@@ -3,13 +3,13 @@ evalInFrame = function(global) {
   dbg = new dbgGlobal.Debugger();
   return function(upCount, code) {
     dbg.addDebuggee(global);
     frame = dbg.getNewestFrame().older;
     frame.eval(code);
   }
 }(this);
 m = parseModule(`
-  function g() this.hours = 0;
+  function g() { return this.hours = 0; }
   evalInFrame.call(0, 0, "g()")
 `);
 m.declarationInstantiation();
 m.evaluation();
--- a/js/src/jit-test/tests/parser/bug-1263355-16.js
+++ b/js/src/jit-test/tests/parser/bug-1263355-16.js
@@ -1,10 +1,11 @@
 // |jit-test| error: ReferenceError
 
 let m = parseModule(`
 var i = 0;
 addThis();
-function addThis()
-  statusmessages[i] = Number;
+function addThis() {
+  return statusmessages[i] = Number;
+}
 `);
 m.declarationInstantiation();
 m.evaluation();
--- a/js/src/jit-test/tests/pic/arguments.js
+++ b/js/src/jit-test/tests/pic/arguments.js
@@ -13,11 +13,11 @@ assertEq(f.apply(null, [1, 2, 3, 4]), 4)
 
 function g(arg) {
     var r;
     for (var i = 0; i < arg.length; i++)
         r = arg[i];
     return r;
 }
 
-assertEq(g((function () arguments).call(null, 1, 2, 3)), 3);
+assertEq(g((function () { return arguments; }).call(null, 1, 2, 3)), 3);
 assertEq(g(new Float32Array(3)), 0.0);
 assertEq(g([1, 2, 3, 4]), 4);
--- a/js/src/jit-test/tests/pic/bug558099.js
+++ b/js/src/jit-test/tests/pic/bug558099.js
@@ -1,11 +1,11 @@
 // |jit-test| need-for-each
 
-(function()[function() function() function() function() function() function() {}]);
+(function() { return [function() { return function() { return function() { return function() { return function() { return function() {}; }; }; }; }; }]; });
 foo = [{
   text: "(function(){if(d){(1)}})",
   s: function() {},
   test: function() {
     try {
       f
     } catch(e) {}
   }
@@ -42,19 +42,19 @@ foo = [{
 }]; (function() {
   for (i = 0; i < foo.length; ++i) {
     a = foo[i]
     text = a.text
     eval(text.replace(/@/, ""));
     if (a.test()) {}
   }
 } ());
-s = [function() function() function() function() function() function() {}]
-[function() function() function() function() {}];
-(function() { [function() function() {}] });
+s = [function() { return function() { return function() { return function() { return function() { return function() {}; }; }; }; }; }]
+[function() { return function() { return function() { return function() {}; }; }; }];
+(function() { [function() { return function() {}; }] });
 (function() {});
 (eval("\
   (function(){\
     for each(d in[\
       0,0,0,0,0,0,0,0,0,0,0,0,0,null,NaN,1,Boolean(false),Boolean(false)\
     ]){\
       [].filter(new Function,gczeal(2))\
     }\
--- a/js/src/jit-test/tests/profiler/bug1164448.js
+++ b/js/src/jit-test/tests/profiler/bug1164448.js
@@ -1,14 +1,15 @@
 // |jit-test| error: TypeError
 
 print = function(s) { return s.toString(); }
 var gTestcases = new Array();
-function TestCase(n, d, e, a)
-  gTestcases[gTc++] = this;
+function TestCase(n, d, e, a) {
+  return gTestcases[gTc++] = this;
+}
     dump = print;
   for ( gTc=0; gTc < gTestcases.length; gTc++ ) {}
 function jsTestDriverEnd() {
   for (var i = 0; i < gTestcases.length; i++)
     gTestcases[i].dump();
 }
 TestCase();
 var g = newGlobal();
--- a/js/src/jit-test/tests/profiler/bug1242840.js
+++ b/js/src/jit-test/tests/profiler/bug1242840.js
@@ -5,12 +5,12 @@ enableSPSProfiling();
 oomTest(() => {
     try {
         for (quit of ArrayBuffer);
     } catch (e) {
         switch (1) {
             case 0:
                 let x
             case 1:
-                (function() x)()
+                (function() { return x; })()
         }
     }
 })
--- a/js/src/jit-test/tests/profiler/bug1261324.js
+++ b/js/src/jit-test/tests/profiler/bug1261324.js
@@ -13,12 +13,12 @@ try {
 function assertThrowsInstanceOf(f) {
     try {
         f()
     } catch (exc) {}
 }
 function testThrow(thunk) {
     for (i = 0; i < 20; i++) {
         iter = thunk()
-        assertThrowsInstanceOf(function() iter.throw())
+        assertThrowsInstanceOf(function() { return iter.throw(); })
     }
 }
 testThrow(function*() {})
--- a/js/src/jit-test/tests/profiler/test-bug1026485.js
+++ b/js/src/jit-test/tests/profiler/test-bug1026485.js
@@ -1,11 +1,12 @@
 
-function TestCase(n, d, e, a)
-  TestCase.prototype.dump = function () {}
+function TestCase(n, d, e, a) {
+  return TestCase.prototype.dump = function () {};
+}
 enableSPSProfiling();
 new TestCase(typeof Number(new Number()));
 new TestCase(typeof Number(new Number(Number.NaN)));
 test();
 function test() {
     try {
         test();
     } catch (e) {
--- a/js/src/jit/BaselineCacheIR.cpp
+++ b/js/src/jit/BaselineCacheIR.cpp
@@ -507,24 +507,16 @@ class MOZ_RAII BaselineCacheIRCompiler :
         }
 
         if (!failurePaths.append(Move(newFailure)))
             return false;
 
         *failure = &failurePaths.back();
         return true;
     }
-    void emitEnterTypeMonitorIC() {
-        allocator.discardStack(masm);
-        EmitEnterTypeMonitorIC(masm);
-    }
-    void emitReturnFromIC() {
-        allocator.discardStack(masm);
-        EmitReturnFromIC(masm);
-    }
 };
 
 void
 BaselineCacheIRCompiler::enterStubFrame(MacroAssembler& masm, Register scratch)
 {
     if (engine_ == ICStubEngine::Baseline) {
         EmitBaselineEnterStubFrame(masm, scratch);
 #ifdef DEBUG
@@ -599,16 +591,18 @@ BaselineCacheIRCompiler::compile()
 
           default:
             MOZ_CRASH("Invalid op");
         }
 
         allocator.nextOp();
     } while (reader.more());
 
+    masm.assumeUnreachable("Should have returned from IC");
+
     // Done emitting the main IC code. Now emit the failure paths.
     for (size_t i = 0; i < failurePaths.length(); i++) {
         emitFailurePath(i);
         EmitStubGuardFailure(masm);
     }
 
     Linker linker(masm);
     AutoFlushICache afc("getStubCode");
@@ -1052,31 +1046,29 @@ BaselineCacheIRCompiler::emitGuardAndLoa
 bool
 BaselineCacheIRCompiler::emitLoadFixedSlotResult()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
 
     masm.load32(stubAddress(reader.stubOffset()), scratch);
     masm.loadValue(BaseIndex(obj, scratch, TimesOne), R0);
-    emitEnterTypeMonitorIC();
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitLoadDynamicSlotResult()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
 
     // We're about to return, so it's safe to clobber obj now.
     masm.load32(stubAddress(reader.stubOffset()), scratch);
     masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), obj);
     masm.loadValue(BaseIndex(obj, scratch, TimesOne), R0);
-    emitEnterTypeMonitorIC();
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitCallScriptedGetterResult()
 {
     MOZ_ASSERT(engine_ == ICStubEngine::Baseline);
 
@@ -1137,18 +1129,16 @@ BaselineCacheIRCompiler::emitCallScripte
         masm.loadPtr(Address(code, JitCode::offsetOfCode()), code);
         masm.movePtr(ImmWord(0), ArgumentsRectifierReg);
     }
 
     masm.bind(&noUnderflow);
     masm.callJit(code);
 
     leaveStubFrame(masm, true);
-
-    emitEnterTypeMonitorIC();
     return true;
 }
 
 typedef bool (*DoCallNativeGetterFn)(JSContext*, HandleFunction, HandleObject, MutableHandleValue);
 static const VMFunction DoCallNativeGetterInfo =
     FunctionInfo<DoCallNativeGetterFn>(DoCallNativeGetter, "DoCallNativeGetter");
 
 bool
@@ -1175,38 +1165,29 @@ BaselineCacheIRCompiler::emitCallNativeG
 
     masm.Push(obj);
     masm.Push(scratch);
 
     if (!callVM(masm, DoCallNativeGetterInfo))
         return false;
 
     leaveStubFrame(masm);
-
-    emitEnterTypeMonitorIC();
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitLoadUnboxedPropertyResult()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
 
     JSValueType fieldType = reader.valueType();
-
     Address fieldOffset(stubAddress(reader.stubOffset()));
     masm.load32(fieldOffset, scratch);
     masm.loadUnboxedProperty(BaseIndex(obj, scratch, TimesOne), fieldType, R0);
-
-    if (fieldType == JSVAL_TYPE_OBJECT)
-        emitEnterTypeMonitorIC();
-    else
-        emitReturnFromIC();
-
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitGuardNoDetachedTypedObjects()
 {
     FailurePath* failure;
     if (!addFailurePath(&failure))
@@ -1229,28 +1210,22 @@ BaselineCacheIRCompiler::emitLoadTypedOb
 
     // Get the object's data pointer.
     LoadTypedThingData(masm, layout, obj, scratch1);
 
     // Get the address being written to.
     masm.load32(fieldOffset, scratch2);
     masm.addPtr(scratch2, scratch1);
 
-    // Only monitor the result if the type produced by this stub might vary.
-    bool monitorLoad;
     if (SimpleTypeDescrKeyIsScalar(typeDescr)) {
         Scalar::Type type = ScalarTypeFromSimpleTypeDescrKey(typeDescr);
-        monitorLoad = type == Scalar::Uint32;
-
         masm.loadFromTypedArray(type, Address(scratch1, 0), R0, /* allowDouble = */ true,
                                 scratch2, nullptr);
     } else {
         ReferenceTypeDescr::Type type = ReferenceTypeFromSimpleTypeDescrKey(typeDescr);
-        monitorLoad = type != ReferenceTypeDescr::TYPE_STRING;
-
         switch (type) {
           case ReferenceTypeDescr::TYPE_ANY:
             masm.loadValue(Address(scratch1, 0), R0);
             break;
 
           case ReferenceTypeDescr::TYPE_OBJECT: {
             Label notNull, done;
             masm.loadPtr(Address(scratch1, 0), scratch1);
@@ -1268,32 +1243,23 @@ BaselineCacheIRCompiler::emitLoadTypedOb
             masm.tagValue(JSVAL_TYPE_STRING, scratch1, R0);
             break;
 
           default:
             MOZ_CRASH("Invalid ReferenceTypeDescr");
         }
     }
 
-    if (monitorLoad)
-        emitEnterTypeMonitorIC();
-    else
-        emitReturnFromIC();
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitLoadUndefinedResult()
 {
     masm.moveValue(UndefinedValue(), R0);
-
-    // Normally for this op, the result would have to be monitored by TI.
-    // However, since this stub ALWAYS returns UndefinedValue(), and we can be sure
-    // that undefined is already registered with the type-set, this can be avoided.
-    emitReturnFromIC();
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitLoadInt32ArrayLengthResult()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
@@ -1303,33 +1269,25 @@ BaselineCacheIRCompiler::emitLoadInt32Ar
         return false;
 
     masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
     masm.load32(Address(scratch, ObjectElements::offsetOfLength()), scratch);
 
     // Guard length fits in an int32.
     masm.branchTest32(Assembler::Signed, scratch, scratch, failure->label());
     masm.tagValue(JSVAL_TYPE_INT32, scratch, R0);
-
-    // The int32 type was monitored when attaching the stub, so we can
-    // just return.
-    emitReturnFromIC();
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitLoadUnboxedArrayLengthResult()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     masm.load32(Address(obj, UnboxedArrayObject::offsetOfLength()), R0.scratchReg());
     masm.tagValue(JSVAL_TYPE_INT32, R0.scratchReg(), R0);
-
-    // The int32 type was monitored when attaching the stub, so we can
-    // just return.
-    emitReturnFromIC();
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitLoadArgumentsObjectLengthResult()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
@@ -1346,17 +1304,32 @@ BaselineCacheIRCompiler::emitLoadArgumen
                       scratch,
                       Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
                       failure->label());
 
     // Shift out arguments length and return it. No need to type monitor
     // because this stub always returns int32.
     masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), scratch);
     masm.tagValue(JSVAL_TYPE_INT32, scratch, R0);
-    emitReturnFromIC();
+    return true;
+}
+
+bool
+BaselineCacheIRCompiler::emitTypeMonitorResult()
+{
+    allocator.discardStack(masm);
+    EmitEnterTypeMonitorIC(masm);
+    return true;
+}
+
+bool
+BaselineCacheIRCompiler::emitReturnFromIC()
+{
+    allocator.discardStack(masm);
+    EmitReturnFromIC(masm);
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitLoadObject()
 {
     Register reg = allocator.defineRegister(masm, reader.objOperandId());
     masm.loadPtr(stubAddress(reader.stubOffset()), reg);
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -241,16 +241,31 @@ EmitReadSlotResult(CacheIRWriter& writer
         EmitLoadSlotResult(writer, holderId, &holder->as<NativeObject>(), shape);
     } else {
         MOZ_ASSERT(!holderId.valid());
         writer.loadUndefinedResult();
     }
 }
 
 static void
+EmitReadSlotReturn(CacheIRWriter& writer, JSObject*, JSObject* holder, Shape* shape)
+{
+    // Slot access.
+    if (holder) {
+        MOZ_ASSERT(shape);
+        writer.typeMonitorResult();
+    } else {
+        // Normally for this op, the result would have to be monitored by TI.
+        // However, since this stub ALWAYS returns UndefinedValue(), and we can be sure
+        // that undefined is already registered with the type-set, this can be avoided.
+        writer.returnFromIC();
+    }
+}
+
+static void
 EmitCallGetterResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
                      Shape* shape, ObjOperandId objId)
 {
     Maybe<ObjOperandId> expandoId;
     TestMatchingReceiver(writer, obj, shape, objId, &expandoId);
 
     if (obj != holder) {
         GeneratePrototypeGuards(writer, obj, holder, objId);
@@ -259,24 +274,26 @@ EmitCallGetterResult(CacheIRWriter& writ
         ObjOperandId holderId = writer.loadObject(holder);
         writer.guardShape(holderId, holder->as<NativeObject>().lastProperty());
     }
 
     if (IsCacheableGetPropCallNative(obj, holder, shape)) {
         JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
         MOZ_ASSERT(target->isNative());
         writer.callNativeGetterResult(objId, target);
+        writer.typeMonitorResult();
         return;
     }
 
     MOZ_ASSERT(IsCacheableGetPropCallScripted(obj, holder, shape));
 
     JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
     MOZ_ASSERT(target->hasJITCode());
     writer.callScriptedGetterResult(objId, target);
+    writer.typeMonitorResult();
 }
 
 bool
 GetPropIRGenerator::tryAttachNative(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId)
 {
     MOZ_ASSERT(!emitted_);
 
     RootedShape shape(cx_);
@@ -298,16 +315,17 @@ GetPropIRGenerator::tryAttachNative(Cach
                 // See the comment in StripPreliminaryObjectStubs.
                 if (IsPreliminaryObject(obj))
                     preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
                 else
                     preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
             }
         }
         EmitReadSlotResult(writer, obj, holder, shape, objId);
+        EmitReadSlotReturn(writer, obj, holder, shape);
         break;
       case CanAttachCallGetter:
         EmitCallGetterResult(writer, obj, holder, shape, objId);
         break;
       default:
         MOZ_CRASH("Bad NativeGetPropCacheability");
     }
 
@@ -376,16 +394,21 @@ GetPropIRGenerator::tryAttachUnboxed(Cac
         return true;
 
     if (!cx_->runtime()->jitSupportsFloatingPoint)
         return true;
 
     writer.guardGroup(objId, obj->group());
     writer.loadUnboxedPropertyResult(objId, property->type,
                                      UnboxedPlainObject::offsetOfData() + property->offset);
+    if (property->type == JSVAL_TYPE_OBJECT)
+        writer.typeMonitorResult();
+    else
+        writer.returnFromIC();
+
     emitted_ = true;
     preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachUnboxedExpando(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId)
 {
@@ -400,16 +423,17 @@ GetPropIRGenerator::tryAttachUnboxedExpa
 
     Shape* shape = expando->lookup(cx_, NameToId(name_));
     if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot())
         return true;
 
     emitted_ = true;
 
     EmitReadSlotResult(writer, obj, obj, shape, objId);
+    EmitReadSlotReturn(writer, obj, obj, shape);
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachTypedObject(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId)
 {
     MOZ_ASSERT(!emitted_);
 
@@ -437,16 +461,32 @@ GetPropIRGenerator::tryAttachTypedObject
     TypedThingLayout layout = GetTypedThingLayout(shape->getObjectClass());
 
     uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex);
     uint32_t typeDescr = SimpleTypeDescrKey(&fieldDescr->as<SimpleTypeDescr>());
 
     writer.guardNoDetachedTypedObjects();
     writer.guardShape(objId, shape);
     writer.loadTypedObjectResult(objId, fieldOffset, layout, typeDescr);
+
+    // Only monitor the result if the type produced by this stub might vary.
+    bool monitorLoad = false;
+    if (SimpleTypeDescrKeyIsScalar(typeDescr)) {
+        Scalar::Type type = ScalarTypeFromSimpleTypeDescrKey(typeDescr);
+        monitorLoad = type == Scalar::Uint32;
+    } else {
+        ReferenceTypeDescr::Type type = ReferenceTypeFromSimpleTypeDescrKey(typeDescr);
+        monitorLoad = type != ReferenceTypeDescr::TYPE_STRING;
+    }
+
+    if (monitorLoad)
+        writer.typeMonitorResult();
+    else
+        writer.returnFromIC();
+
     emitted_ = true;
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachObjectLength(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId)
 {
     MOZ_ASSERT(!emitted_);
@@ -457,35 +497,38 @@ GetPropIRGenerator::tryAttachObjectLengt
     if (obj->is<ArrayObject>()) {
         // Make sure int32 is added to the TypeSet before we attach a stub, so
         // the stub can return int32 values without monitoring the result.
         if (obj->as<ArrayObject>().length() > INT32_MAX)
             return true;
 
         writer.guardClass(objId, GuardClassKind::Array);
         writer.loadInt32ArrayLengthResult(objId);
+        writer.returnFromIC();
         emitted_ = true;
         return true;
     }
 
     if (obj->is<UnboxedArrayObject>()) {
         writer.guardClass(objId, GuardClassKind::UnboxedArray);
         writer.loadUnboxedArrayLengthResult(objId);
+        writer.returnFromIC();
         emitted_ = true;
         return true;
     }
 
     if (obj->is<ArgumentsObject>() && !obj->as<ArgumentsObject>().hasOverriddenLength()) {
         if (obj->is<MappedArgumentsObject>()) {
             writer.guardClass(objId, GuardClassKind::MappedArguments);
         } else {
             MOZ_ASSERT(obj->is<UnmappedArgumentsObject>());
             writer.guardClass(objId, GuardClassKind::UnmappedArguments);
         }
         writer.loadArgumentsObjectLengthResult(objId);
+        writer.returnFromIC();
         emitted_ = true;
         return true;
     }
 
     return true;
 }
 
 bool
@@ -512,16 +555,17 @@ GetPropIRGenerator::tryAttachModuleNames
 
     emitted_ = true;
 
     // Check for the specific namespace object.
     writer.guardSpecificObject(objId, ns);
 
     ObjOperandId envId = writer.loadObject(env);
     EmitLoadSlotResult(writer, envId, env, shape);
+    writer.typeMonitorResult();
     return true;
 }
 
 bool
 GetPropIRGenerator::tryAttachPrimitive(CacheIRWriter& writer, ValOperandId valId)
 {
     MOZ_ASSERT(!emitted_);
 
@@ -560,12 +604,13 @@ GetPropIRGenerator::tryAttachPrimitive(C
     if (!shape || !shape->hasSlot() || !shape->hasDefaultGetter())
         return true;
 
     writer.guardType(valId, primitiveType);
 
     ObjOperandId protoId = writer.loadObject(proto);
     writer.guardShape(protoId, proto->lastProperty());
     EmitLoadSlotResult(writer, protoId, proto, shape);
+    writer.typeMonitorResult();
 
     emitted_ = true;
     return true;
 }
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -97,17 +97,20 @@ class ObjOperandId : public OperandId
     _(LoadDynamicSlotResult)              \
     _(LoadUnboxedPropertyResult)          \
     _(LoadTypedObjectResult)              \
     _(LoadInt32ArrayLengthResult)         \
     _(LoadUnboxedArrayLengthResult)       \
     _(LoadArgumentsObjectLengthResult)    \
     _(CallScriptedGetterResult)           \
     _(CallNativeGetterResult)             \
-    _(LoadUndefinedResult)
+    _(LoadUndefinedResult)                \
+                                          \
+    _(TypeMonitorResult)                  \
+    _(ReturnFromIC)
 
 enum class CacheOp {
 #define DEFINE_OP(op) op,
     CACHE_IR_OPS(DEFINE_OP)
 #undef DEFINE_OP
 };
 
 struct StubField {
@@ -342,16 +345,23 @@ class MOZ_RAII CacheIRWriter
     void callScriptedGetterResult(ObjOperandId obj, JSFunction* getter) {
         writeOpWithOperandId(CacheOp::CallScriptedGetterResult, obj);
         addStubWord(uintptr_t(getter), StubField::GCType::JSObject);
     }
     void callNativeGetterResult(ObjOperandId obj, JSFunction* getter) {
         writeOpWithOperandId(CacheOp::CallNativeGetterResult, obj);
         addStubWord(uintptr_t(getter), StubField::GCType::JSObject);
     }
+
+    void typeMonitorResult() {
+        writeOp(CacheOp::TypeMonitorResult);
+    }
+    void returnFromIC() {
+        writeOp(CacheOp::ReturnFromIC);
+    }
 };
 
 class CacheIRStubInfo;
 
 // Helper class for reading CacheIR bytecode.
 class MOZ_RAII CacheIRReader
 {
     CompactBufferReader buffer_;
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -25,16 +25,17 @@ enum MethodStatus
     Method_Error,
     Method_CantCompile,
     Method_Skipped,
     Method_Compiled
 };
 
 enum AbortReason {
     AbortReason_Alloc,
+    AbortReason_Inlining,
     AbortReason_PreliminaryObjects,
     AbortReason_Disable,
     AbortReason_Error,
     AbortReason_NoAbort
 };
 
 // A JIT context is needed to enter into either an JIT method or an instance
 // of a JIT compiler. It points to a temporary allocator and the active
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -5251,18 +5251,23 @@ IonBuilder::inlineScriptedCall(CallInfo&
             abortReason_ = AbortReason_Error;
             return InliningStatus_Error;
         }
 
         // Inlining the callee failed. Mark the callee as uninlineable only if
         // the inlining was aborted for a non-exception reason.
         if (inlineBuilder.abortReason_ == AbortReason_Disable) {
             calleeScript->setUninlineable();
-            current = backup.restore();
-            return InliningStatus_NotInlined;
+            if (!JitOptions.disableInlineBacktracking) {
+                current = backup.restore();
+                return InliningStatus_NotInlined;
+            }
+            abortReason_ = AbortReason_Inlining;
+        } else if (inlineBuilder.abortReason_ == AbortReason_Inlining) {
+            abortReason_ = AbortReason_Inlining;
         } else if (inlineBuilder.abortReason_ == AbortReason_Alloc) {
             abortReason_ = AbortReason_Alloc;
         } else if (inlineBuilder.abortReason_ == AbortReason_PreliminaryObjects) {
             const ObjectGroupVector& groups = inlineBuilder.abortedPreliminaryGroups();
             MOZ_ASSERT(!groups.empty());
             for (size_t i = 0; i < groups.length(); i++)
                 addAbortedPreliminaryGroup(groups[i]);
             abortReason_ = AbortReason_PreliminaryObjects;
@@ -5281,18 +5286,22 @@ IonBuilder::inlineScriptedCall(CallInfo&
     // Inherit the slots from current and pop |fun|.
     returnBlock->inheritSlots(current);
     returnBlock->pop();
 
     // Accumulate return values.
     if (returns.empty()) {
         // Inlining of functions that have no exit is not supported.
         calleeScript->setUninlineable();
-        current = backup.restore();
-        return InliningStatus_NotInlined;
+        if (!JitOptions.disableInlineBacktracking) {
+            current = backup.restore();
+            return InliningStatus_NotInlined;
+        }
+        abortReason_ = AbortReason_Inlining;
+        return InliningStatus_Error;
     }
     MDefinition* retvalDefn = patchInlinedReturns(callInfo, returns, returnBlock);
     if (!retvalDefn)
         return InliningStatus_Error;
     returnBlock->push(retvalDefn);
 
     // Initialize entry slots now that the stack has been fixed up.
     if (!returnBlock->initEntrySlots(alloc()))
--- a/js/src/jit/JitOptions.cpp
+++ b/js/src/jit/JitOptions.cpp
@@ -70,16 +70,19 @@ DefaultJitOptions::DefaultJitOptions()
     // are not modified before its OsiPoint.
     SET_DEFAULT(checkOsiPointRegisters, false);
 #endif
 
     // Whether to enable extra code to perform dynamic validation of
     // RangeAnalysis results.
     SET_DEFAULT(checkRangeAnalysis, false);
 
+    // Toggles whether IonBuilder fallbacks to a call if we fail to inline.
+    SET_DEFAULT(disableInlineBacktracking, true);
+
     // Toggles whether Alignment Mask Analysis is globally disabled.
     SET_DEFAULT(disableAma, false);
 
     // Toggles whether Effective Address Analysis is globally disabled.
     SET_DEFAULT(disableEaa, false);
 
     // Toggle whether eager simd unboxing is globally disabled.
     SET_DEFAULT(disableEagerSimdUnbox, false);
--- a/js/src/jit/JitOptions.h
+++ b/js/src/jit/JitOptions.h
@@ -42,16 +42,17 @@ LookupRegisterAllocator(const char* name
 struct DefaultJitOptions
 {
     bool checkGraphConsistency;
 #ifdef CHECK_OSIPOINT_REGISTERS
     bool checkOsiPointRegisters;
 #endif
     bool checkRangeAnalysis;
     bool runExtraChecks;
+    bool disableInlineBacktracking;
     bool disableAma;
     bool disableEaa;
     bool disableEagerSimdUnbox;
     bool disableEdgeCaseAnalysis;
     bool disableFlowAA;
     bool disableGvn;
     bool disableInlining;
     bool disableLicm;
--- a/js/src/tests/ecma_3/FunExpr/regress-545980.js
+++ b/js/src/tests/ecma_3/FunExpr/regress-545980.js
@@ -22,17 +22,17 @@ function run_test() {
 
   (function doSearch(query) {
     ac.startSearch(query, "", null, {
       onSearchResult: function() {
         var num = tagIds.length;
 
         var timer = new Timer;
         var next = query.slice(1);
-        timer.initWithCallback({ notify: function() doSearch(next) });
+        timer.initWithCallback({ notify: function() { return doSearch(next); } });
       }
     });
   })("title");
 }
 
 run_test();
 later.onSearchResult();
 for (var i in Timer.q)
--- a/js/src/tests/ecma_5/strict/13.1.js
+++ b/js/src/tests/ecma_5/strict/13.1.js
@@ -81,21 +81,21 @@ assertEq(testLenientAndStrict('Function(
                               completesNormally),
          true);
 
 
 /*
  * The parameter lists of function expressions should not contain
  * duplicate identifiers.
  */
-assertEq(testLenientAndStrict('(function (x,x) 2)',
+assertEq(testLenientAndStrict('(function (x,x) {})',
                                parsesSuccessfully,
                                parseRaisesException(SyntaxError)),
          true);
-assertEq(testLenientAndStrict('(function (x,y) 2)',
+assertEq(testLenientAndStrict('(function (x,y) {})',
                               parsesSuccessfully,
                               parsesSuccessfully),
          true);
 
 /*
  * All permutations of:
  * - For the two magic identifiers 'arguments' or 'eval'
  *   - For function definitions, function expressions, expression closures,
@@ -168,29 +168,29 @@ assertEq(testLenientAndStrict('(function
 assertEq(testLenientAndStrict('(function f({x:eval}){"use strict";})',
                               parseRaisesException(SyntaxError),
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict('(function eval(){"use strict";})',
                               parseRaisesException(SyntaxError),
                               parseRaisesException(SyntaxError)),
          true);
-assertEq(testLenientAndStrict('(function f(eval) 2)',
+assertEq(testLenientAndStrict('(function f(eval) {})',
                               parsesSuccessfully,
                               parseRaisesException(SyntaxError)),
          true);
-assertEq(testLenientAndStrict('(function f([eval]) 2)',
+assertEq(testLenientAndStrict('(function f([eval]) {})',
                               parsesSuccessfully,
                               parseRaisesException(SyntaxError)),
          true);
-assertEq(testLenientAndStrict('(function f({x:eval}) 2)',
+assertEq(testLenientAndStrict('(function f({x:eval}) {})',
                               parsesSuccessfully,
                               parseRaisesException(SyntaxError)),
          true);
-assertEq(testLenientAndStrict('(function eval() 2)',
+assertEq(testLenientAndStrict('(function eval() {})',
                               parsesSuccessfully,
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict('({set x(eval){}})',
                               parsesSuccessfully,
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict('({set x([eval]){}})',
@@ -272,29 +272,29 @@ assertEq(testLenientAndStrict('(function
 assertEq(testLenientAndStrict('(function f({x:arguments}){"use strict";})',
                               parseRaisesException(SyntaxError),
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict('(function arguments(){"use strict";})',
                               parseRaisesException(SyntaxError),
                               parseRaisesException(SyntaxError)),
          true);
-assertEq(testLenientAndStrict('(function f(arguments) 2)',
+assertEq(testLenientAndStrict('(function f(arguments) {})',
                               parsesSuccessfully,
                               parseRaisesException(SyntaxError)),
          true);
-assertEq(testLenientAndStrict('(function f([arguments]) 2)',
+assertEq(testLenientAndStrict('(function f([arguments]) {})',
                               parsesSuccessfully,
                               parseRaisesException(SyntaxError)),
          true);
-assertEq(testLenientAndStrict('(function f({x:arguments}) 2)',
+assertEq(testLenientAndStrict('(function f({x:arguments}) {})',
                               parsesSuccessfully,
                               parseRaisesException(SyntaxError)),
          true);
-assertEq(testLenientAndStrict('(function arguments() 2)',
+assertEq(testLenientAndStrict('(function arguments() {})',
                               parsesSuccessfully,
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict('({set x(arguments){}})',
                               parsesSuccessfully,
                               parseRaisesException(SyntaxError)),
          true);
 assertEq(testLenientAndStrict('({set x([arguments]){}})',
--- a/js/src/tests/ecma_6/Class/newTargetEval.js
+++ b/js/src/tests/ecma_6/Class/newTargetEval.js
@@ -5,17 +5,17 @@ try {
 } catch (e if e instanceof SyntaxError) { }
 
 // new.target is invalid inside eval inside top-level arrow functions
 assertThrowsInstanceOf(() => eval('new.target'), SyntaxError);
 
 // new.target is invalid inside indirect eval.
 let ieval = eval;
 try {
-    (function () ieval('new.target'))();
+    (function () { return ieval('new.target'); })();
     assertEq(false, true);
 } catch (e if e instanceof SyntaxError) { }
 
 function assertNewTarget(expected) {
     assertEq(eval('new.target'), expected);
     assertEq((()=>eval('new.target'))(), expected);
 
     // Also test nestings "by induction"
--- a/js/src/tests/js1_8/extensions/regress-452913.js
+++ b/js/src/tests/js1_8/extensions/regress-452913.js
@@ -7,12 +7,12 @@
 var BUGNUMBER = 452913;
 var summary = 'Do not crash with defined getter and for (let)';
 var actual = '';
 var expect = '';
 
 printBugNumber(BUGNUMBER);
 printStatus (summary);
  
-(this.__defineGetter__("x", function (x)'foo'.replace(/o/g, [1].push)));
+(this.__defineGetter__("x", function (x) { return 'foo'.replace(/o/g, [1].push); }));
 for(let y in [,,,]) for(let y in [,,,]) x = x;
 
 reportCompare(expect, actual, summary);
--- a/js/src/tests/js1_8/extensions/regress-454744.js
+++ b/js/src/tests/js1_8/extensions/regress-454744.js
@@ -17,17 +17,17 @@ function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
 
   try
   {
-    this.__defineGetter__('x', function() 2); for (var j=0;j<4;++j) { x=1; }
+    this.__defineGetter__('x', function() { return 2; }); for (var j=0;j<4;++j) { x=1; }
   }
   catch(ex)
   {
   }
 
 
   reportCompare(expect, actual, summary);
 
--- a/js/src/tests/js1_8/extensions/regress-469625.js
+++ b/js/src/tests/js1_8/extensions/regress-469625.js
@@ -20,17 +20,17 @@ function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
  
   expect = 'TypeError: [].__proto__ is not a function';
 
 
-  Array.prototype.__proto__ = function () 3; 
+  Array.prototype.__proto__ = function () { return 3; };
 
   try
   {
     [].__proto__();
   }
   catch(ex)
   {
     print(actual = ex + '');
--- a/js/src/tests/js1_8/regress/regress-452491.js
+++ b/js/src/tests/js1_8/regress/regress-452491.js
@@ -15,15 +15,15 @@ test();
 
 function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
 
-  for (var j=0;j<5;++j) (new (function(q) q)).a;
+  for (var j=0;j<5;++j) (new (function(q) { return q; })).a;
 
 
   reportCompare(expect, actual, summary);
 
   exitFunc ('test');
 }
--- a/js/src/tests/js1_8/regress/regress-459389.js
+++ b/js/src/tests/js1_8/regress/regress-459389.js
@@ -48,18 +48,19 @@ var p=new SNI.MetaData.Parameter();
 this.addParameter=p.addParameter;
 this.getParameter=p.getParameter;
 };
 function Ad() {
 var url=new SNI.Ads.Url();
 this.addParameter=url.addParameter;
 this.getParameter=url.getParameter;
 }
-function DartAd()
-AdUrl.prototype=new Ad();
+function DartAd() {
+  return AdUrl.prototype=new Ad();
+}
 function AdUrl() { }
 function AdRestriction() {
 var p=new SNI.MetaData.Parameter();
 this.addParameter=p.addParameter;
 this.getParameter=p.getParameter;
 this.getKeys=p.getKeys;
 }
 function AdRestrictionManager(){
--- a/js/src/tests/js1_8/regress/regress-464096.js
+++ b/js/src/tests/js1_8/regress/regress-464096.js
@@ -17,16 +17,16 @@ test();
 function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
 
   for (let f in [1,1]);
-  Object.prototype.__defineGetter__('x', function() gc());
+  Object.prototype.__defineGetter__('x', function() { return gc(); });
   (function() { for each (let j in [1,1,1,1,1]) { var y = .2; } })();
 
 
   reportCompare(expect, actual, summary);
 
   exitFunc ('test');
 }
--- a/js/src/tests/js1_8/regress/regress-465220.js
+++ b/js/src/tests/js1_8/regress/regress-465220.js
@@ -20,17 +20,17 @@ function test()
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
   expect = 'TypeError: can\'t convert o to primitive type';
 
  
   try
   {
-    var o = {toString: function()(i > 2) ? this : "foo"};
+    var o = {toString: function() { return (i > 2) ? this : "foo"; }};
     var s = "";
     for (var i = 0; i < 5; i++)
       s += o + o;
     print(s);
     actual = 'No Exception';
   }
   catch(ex)
   {
--- a/js/src/tests/js1_8/regress/regress-465460-01.js
+++ b/js/src/tests/js1_8/regress/regress-465460-01.js
@@ -18,15 +18,15 @@ function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
  
   expect = '11111';
 
 
-  (function(d) { for (let j = 0; j < 5; ++j) { actual += ('' + d); } })({valueOf: function()1});
+  (function(d) { for (let j = 0; j < 5; ++j) { actual += ('' + d); } })({valueOf: function() { return 1; }});
 
 
   reportCompare(expect, actual, summary);
 
   exitFunc ('test');
 }
--- a/js/src/tests/js1_8/regress/regress-467495-01.js
+++ b/js/src/tests/js1_8/regress/regress-467495-01.js
@@ -15,14 +15,14 @@ test();
 //-----------------------------------------------------------------------------
 
 function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
-  (function() { x = 0; function x() 4; var x; const y = 1; })();
+  (function() { x = 0; function x() { return 4; }; var x; const y = 1; })();
 
   reportCompare(expect, actual, summary);
 
   exitFunc ('test');
 }
--- a/js/src/tests/js1_8_1/extensions/regress-452498-196.js
+++ b/js/src/tests/js1_8_1/extensions/regress-452498-196.js
@@ -29,16 +29,16 @@ function test()
   x+=NaN;
 
   reportCompare(expect, actual, summary + ': 1');
 
 // Assertion failure: lexdep->isLet(), at ../jsparse.cpp:1900
 
   (function (){
     var x;
-    eval("var x; (function ()x)");
+    eval("var x; (function () { return x; })");
   }
     )();
 
   reportCompare(expect, actual, summary + ': 2');
 
   exitFunc ('test');
 }
--- a/js/src/tests/js1_8_1/regress/regress-452498-027.js
+++ b/js/src/tests/js1_8_1/regress/regress-452498-027.js
@@ -19,17 +19,17 @@ function test()
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
   expect = '5';
 
 // ------- Comment #27 From Brendan Eich
 
-  function f(x){function g(y)x+y;return g}
+  function f(x){function g(y) { return x+y; } return g}
   g = f(2);
 
   actual = String(g(3));
 
   reportCompare(expect, actual, summary);
 
   exitFunc ('test');
 }
--- a/js/src/tests/js1_8_1/regress/regress-452498-079.js
+++ b/js/src/tests/js1_8_1/regress/regress-452498-079.js
@@ -19,16 +19,16 @@ test();
 function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
 // ------- Comment #79 From Jason Orendorff
 
-  x; var x; function x() 0
+  x; var x; function x() { return 0; }
 
 // Assertion failure: !(pn->pn_dflags & flag), at ../jsparse.h:635
 
   reportCompare(expect, actual, summary);
 
   exitFunc ('test');
 }
--- a/js/src/tests/js1_8_1/regress/regress-452498-102.js
+++ b/js/src/tests/js1_8_1/regress/regress-452498-102.js
@@ -19,17 +19,17 @@ function test()
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
  
 // ------- Comment #102 From Gary Kwong [:nth10sd]
 
 // =====
 
-  (function(){function x(){} function x()y})();
+  (function(){function x(){} function x() { return y; }})();
 
 // Assertion failure: JOF_OPTYPE(op) == JOF_ATOM, at ../jsemit.cpp:1710
 
 // =====
   function f() {
     "" + (function(){
         for( ; [function(){}] ; x = 0)
           with({x: ""}) {
--- a/js/src/tests/js1_8_1/regress/regress-452498-121.js
+++ b/js/src/tests/js1_8_1/regress/regress-452498-121.js
@@ -18,16 +18,16 @@ function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
 // ------- Comment #121 From Gary Kwong [:nth10sd]
 
 // without -j
-  x = function()x;
+  x = function() { return x; };
 
 // Assertion failure: !(pn->pn_dflags & flag), at ../jsparse.h:651
 
   reportCompare(expect, actual, summary);
 
   exitFunc ('test');
 }
--- a/js/src/tests/js1_8_1/regress/regress-452498-160.js
+++ b/js/src/tests/js1_8_1/regress/regress-452498-160.js
@@ -17,18 +17,18 @@ test();
 
 function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
 // crash [@ js_Interpret]
-  (eval("(function(){ this.watch(\"x\", function () { new function ()y } ); const y = undefined });"))();
+  (eval("(function(){ this.watch(\"x\", function () { new function () { return y; } } ); const y = undefined });"))();
   x = NaN;
   reportCompare(expect, actual, summary + ': 2');
 
 // Assertion failure: JOF_OPTYPE(op) == JOF_ATOM, at ../jsemit.cpp:5916
-  ({ set z(v){},  set y(v)--x, set w(v)--w });
+  ({ set z(v){},  set y(v) { return --x; }, set w(v) { return --w; } });
   reportCompare(expect, actual, summary + ': 3');
 
   exitFunc ('test');
 }
--- a/js/src/tests/js1_8_5/regress/regress-607863.js
+++ b/js/src/tests/js1_8_5/regress/regress-607863.js
@@ -1,14 +1,14 @@
 // |reftest| fails-if(!xulRuntime.shell)
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/
  */
 
 var sandbox = evalcx('');
-var foreign = evalcx('({ get f() this, set x(v) { result = this } })', sandbox);
+var foreign = evalcx('({ get f() { return this; }, set x(v) { result = this } })', sandbox);
 var local = Object.create(foreign);
 
 reportCompare(local, local.f, "this should be set correctly in getters");
 local.x = 42;
 reportCompare(local, sandbox.result, "this should be set correctly in setters");
--- a/js/src/tests/js1_8_5/regress/regress-646820-1.js
+++ b/js/src/tests/js1_8_5/regress/regress-646820-1.js
@@ -1,9 +1,9 @@
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/licenses/publicdomain/
 
 (function () {
-    var [x, y] = [1, function () x];
+    var [x, y] = [1, function () { return x; }];
     assertEq(y(), 1);
 })();
 
 reportCompare(0, 0, 'ok');
--- a/js/src/tests/js1_8_5/regress/regress-646820-2.js
+++ b/js/src/tests/js1_8_5/regress/regress-646820-2.js
@@ -1,11 +1,11 @@
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/licenses/publicdomain/
 
 (function () {
     var obj = {prop: 1};
-    var [x, {prop: y}] = [function () y, obj];
+    var [x, {prop: y}] = [function () { return y; }, obj];
     assertEq(y, 1);
     assertEq(x(), 1);
 })();
 
 reportCompare(0, 0, 'ok');
--- a/js/src/tests/js1_8_5/regress/regress-646820-3.js
+++ b/js/src/tests/js1_8_5/regress/regress-646820-3.js
@@ -1,9 +1,9 @@
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/licenses/publicdomain/
 
 (function () {
-    var [x, y] = [function () y, 13];
+    var [x, y] = [function () { return y; }, 13];
     assertEq(x(), 13);
 })();
 
 reportCompare(0, 0, 'ok');
--- a/layout/base/RestyleManagerBase.h
+++ b/layout/base/RestyleManagerBase.h
@@ -76,16 +76,18 @@ public:
 
   // 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);
 
+  bool IsInStyleRefresh() const { return mInStyleRefresh; }
+
 protected:
   void ContentStateChangedInternal(Element* aElement,
                                    EventStates aStateMask,
                                    nsChangeHint* aOutChangeHint,
                                    nsRestyleHint* aOutRestyleHint);
 
   bool IsDisconnected() { return mPresContext == nullptr; }
 
--- a/layout/base/RestyleManagerHandle.h
+++ b/layout/base/RestyleManagerHandle.h
@@ -12,16 +12,17 @@
 #include "mozilla/HandleRefPtr.h"
 #include "mozilla/RefCountType.h"
 #include "mozilla/StyleBackendType.h"
 #include "nsChangeHint.h"
 
 namespace mozilla {
 class RestyleManager;
 class ServoRestyleManager;
+class RestyleManagerBase;
 namespace dom {
 class Element;
 } // namespace dom
 } // namespace mozilla
 class nsAttrValue;
 class nsIAtom;
 class nsIContent;
 class nsIFrame;
@@ -90,16 +91,24 @@ public:
     {
       MOZ_ASSERT(IsServo());
       return const_cast<Ptr*>(this)->AsServo();
     }
 
     const RestyleManager* GetAsGecko() const { return IsGecko() ? AsGecko() : nullptr; }
     const ServoRestyleManager* GetAsServo() const { return IsServo() ? AsServo() : nullptr; }
 
+    const mozilla::RestyleManagerBase* AsBase() const {
+      return reinterpret_cast<const RestyleManagerBase*>(mValue & ~SERVO_BIT);
+    }
+
+    mozilla::RestyleManagerBase* AsBase() {
+      return reinterpret_cast<RestyleManagerBase*>(mValue & ~SERVO_BIT);
+    }
+
     // These inline methods are defined in RestyleManagerHandleInlines.h.
     inline MozExternalRefCountType AddRef();
     inline MozExternalRefCountType Release();
 
     // Restyle manager interface.  These inline methods are defined in
     // RestyleManagerHandleInlines.h and just forward to the underlying
     // RestyleManager or ServoRestyleManager.  See corresponding comments in
     // RestyleManager.h for descriptions of these methods.
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -13,16 +13,17 @@
 #include "nsStyleChangeList.h"
 
 using namespace mozilla::dom;
 
 namespace mozilla {
 
 ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext)
   : RestyleManagerBase(aPresContext)
+  , mReentrantChanges(nullptr)
 {
 }
 
 void
 ServoRestyleManager::PostRestyleEvent(Element* aElement,
                                       nsRestyleHint aRestyleHint,
                                       nsChangeHint aMinChangeHint)
 {
@@ -30,29 +31,38 @@ ServoRestyleManager::PostRestyleEvent(El
       MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
     return;
   }
 
   if (aRestyleHint == 0 && !aMinChangeHint && !HasPendingRestyles()) {
     return; // Nothing to do.
   }
 
+  // We allow posting change hints during restyling, but not restyle hints
+  // themselves, since those would require us to re-traverse the tree.
+  MOZ_ASSERT_IF(mInStyleRefresh, aRestyleHint == 0);
+
+  // Processing change hints sometimes causes new change hints to be generated.
+  // Doing this after the gecko post-traversal is problematic, so instead we just
+  // queue them up for special handling.
+  if (mReentrantChanges) {
+    MOZ_ASSERT(aRestyleHint == 0);
+    mReentrantChanges->AppendElement(ReentrantChange { aElement, aMinChangeHint });
+    return;
+  }
+
   // XXX This is a temporary hack to make style attribute change works.
   //     In the future, we should be able to use this hint directly.
   if (aRestyleHint & eRestyle_StyleAttribute) {
     aRestyleHint &= ~eRestyle_StyleAttribute;
     aRestyleHint |= eRestyle_Self | eRestyle_Subtree;
   }
 
-  // Note that unlike in Servo, we don't mark elements as dirty until we process
-  // the restyle hints in ProcessPendingRestyles.
   if (aRestyleHint || aMinChangeHint) {
-    ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
-    snapshot->AddExplicitRestyleHint(aRestyleHint);
-    snapshot->AddExplicitChangeHint(aMinChangeHint);
+    Servo_NoteExplicitHints(aElement, aRestyleHint, aMinChangeHint);
   }
 
   PostRestyleEventInternal(false);
 }
 
 void
 ServoRestyleManager::PostRestyleEventForLazyConstruction()
 {
@@ -68,133 +78,77 @@ ServoRestyleManager::RebuildAllStyleData
 
 void
 ServoRestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
                                                   nsRestyleHint aRestyleHint)
 {
   NS_WARNING("stylo: ServoRestyleManager::PostRebuildAllStyleDataEvent not implemented");
 }
 
-static void
-MarkSelfAndDescendantsAsNotDirtyForServo(nsIContent* aContent)
+/* static */ void
+ServoRestyleManager::ClearServoDataFromSubtree(Element* aElement)
 {
-  aContent->UnsetIsDirtyForServo();
+  aElement->ClearServoData();
 
-  if (aContent->HasDirtyDescendantsForServo()) {
-    aContent->UnsetHasDirtyDescendantsForServo();
-
-    StyleChildrenIterator it(aContent);
-    for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
-      MarkSelfAndDescendantsAsNotDirtyForServo(n);
+  StyleChildrenIterator it(aElement);
+  for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+    if (n->IsElement()) {
+      ClearServoDataFromSubtree(n->AsElement());
     }
   }
+
+  aElement->UnsetHasDirtyDescendantsForServo();
 }
 
 /* static */ void
-ServoRestyleManager::ClearServoDataFromSubtree(nsIContent* aContent)
+ServoRestyleManager::ClearDirtyDescendantsFromSubtree(Element* aElement)
 {
-  aContent->ClearServoData();
-  aContent->SetIsDirtyForServo();
-  aContent->UnsetHasDirtyDescendantsForServo();
+  StyleChildrenIterator it(aElement);
+  for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+    if (n->IsElement()) {
+      ClearDirtyDescendantsFromSubtree(n->AsElement());
+    }
+  }
 
-  AllChildrenIterator it(aContent, nsIContent::eAllChildren);
-  for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
-    ClearServoDataFromSubtree(n);
-  }
+  aElement->UnsetHasDirtyDescendantsForServo();
 }
 
 void
-ServoRestyleManager::RecreateStyleContexts(nsIContent* aContent,
+ServoRestyleManager::RecreateStyleContexts(Element* aElement,
                                            nsStyleContext* aParentContext,
                                            ServoStyleSet* aStyleSet,
                                            nsStyleChangeList& aChangeListToProcess)
 {
-  MOZ_ASSERT(aContent->IsElement() || aContent->IsNodeOfType(nsINode::eTEXT));
+  nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
 
-  nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
-  if (!primaryFrame && !aContent->IsDirtyForServo()) {
-    // This happens when, for example, a display: none child of a
-    // HAS_DIRTY_DESCENDANTS content is reached as part of the traversal.
-    MarkSelfAndDescendantsAsNotDirtyForServo(aContent);
-    return;
+  // FIXME(bholley): Once we transfer ownership of the styles to the frame, we
+  // can fast-reject without the FFI call by checking mServoData for null.
+  nsChangeHint changeHint = Servo_CheckChangeHint(aElement);
+  if (changeHint) {
+      aChangeListToProcess.AppendChange(primaryFrame, aElement, changeHint);
   }
 
-  // Work on text before.
-  if (!aContent->IsElement()) {
-    if (primaryFrame) {
-      RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext();
-      RefPtr<nsStyleContext> newContext =
-        aStyleSet->ResolveStyleForText(aContent, aParentContext);
-
-      for (nsIFrame* f = primaryFrame; f;
-           f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
-        f->SetStyleContext(newContext);
-      }
-    }
-
-    aContent->UnsetIsDirtyForServo();
+  // If our change hint is reconstruct, we delegate to the frame constructor,
+  // which consumes the new style and expects the old style to be on the frame.
+  //
+  // XXXbholley: We should teach the frame constructor how to clear the dirty
+  // descendants bit to avoid the traversal here.
+  if (changeHint & nsChangeHint_ReconstructFrame) {
+    ClearDirtyDescendantsFromSubtree(aElement);
     return;
   }
 
-  Element* element = aContent->AsElement();
-  if (element->IsDirtyForServo()) {
-    RefPtr<ServoComputedValues> computedValues =
-      Servo_ComputedValues_Get(aContent).Consume();
-    MOZ_ASSERT(computedValues);
-
-    nsChangeHint changeHint = nsChangeHint(0);
-
-    // Add an explicit change hint if appropriate.
-    ServoElementSnapshot* snapshot;
-    if (mModifiedElements.Get(element, &snapshot)) {
-      changeHint |= snapshot->ExplicitChangeHint();
-    }
-
-    // Add the stored change hint if there's a frame. If there isn't a frame,
-    // generate a ReconstructFrame change hint if the new display value
-    // (which we can get from the ComputedValues stored on the node) is not
-    // none.
-    if (primaryFrame) {
-      changeHint |= primaryFrame->StyleContext()->ConsumeStoredChangeHint();
-    } else {
-      const nsStyleDisplay* currentDisplay =
-        Servo_GetStyleDisplay(computedValues);
-      if (currentDisplay->mDisplay != StyleDisplay::None) {
-        changeHint |= nsChangeHint_ReconstructFrame;
-      }
-    }
-
-    // Add the new change hint to the list of elements to process if
-    // we need to do any work.
-    if (changeHint) {
-      aChangeListToProcess.AppendChange(primaryFrame, element, changeHint);
-    }
-
-    // The frame reconstruction step (if needed) will ask for the descendants'
-    // style correctly. If not needed, we're done too.
-    //
-    // Note that we must leave the old style on an existing frame that is
-    // about to be reframed, since some frame constructor code wants to
-    // inspect the old style to work out what to do.
-    if (changeHint & nsChangeHint_ReconstructFrame) {
-      // Since we might still have some dirty bits set on descendants,
-      // inconsistent with the clearing of HasDirtyDescendants we will do as
-      // we return from these recursive RecreateStyleContexts calls, we
-      // explicitly clear them here.  Otherwise we will trigger assertions
-      // when we soon process the frame reconstruction.
-      MarkSelfAndDescendantsAsNotDirtyForServo(element);
-      return;
-    }
-
-    // If there is no frame, and we didn't generate a ReconstructFrame change
-    // hint, then we don't need to do any more work.
-    if (!primaryFrame) {
-      aContent->UnsetIsDirtyForServo();
-      return;
-    }
+  // If we have a frame and a non-zero + non-reconstruct change hint, we need to
+  // attach a new style context.
+  bool recreateContext = primaryFrame && changeHint;
+  if (recreateContext) {
+    RefPtr<ServoComputedValues> computedValues
+      = Servo_ResolveStyle(aElement, aStyleSet->mRawSet.get(),
+                           ConsumeStyleBehavior::Consume,
+                           LazyComputeBehavior::Assert).Consume();
 
     // 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 = primaryFrame->StyleContext();
     MOZ_ASSERT(oldStyleContext);
 
@@ -214,79 +168,78 @@ ServoRestyleManager::RecreateStyleContex
     const static CSSPseudoElementType pseudosToRestyle[] = {
       CSSPseudoElementType::before,
       CSSPseudoElementType::after,
     };
 
     for (CSSPseudoElementType pseudoType : pseudosToRestyle) {
       nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(pseudoType);
 
-      if (nsIFrame* pseudoFrame = FrameForPseudoElement(element, pseudoTag)) {
+      if (nsIFrame* pseudoFrame = FrameForPseudoElement(aElement, pseudoTag)) {
         // TODO: we could maybe make this more performant via calling into
         // Servo just once to know which pseudo-elements we've got to restyle?
         RefPtr<nsStyleContext> pseudoContext =
-          aStyleSet->ProbePseudoElementStyle(element, pseudoType, newContext);
-
-        // If pseudoContext is null here, it means the frame is going away, so
-        // our change hint computation should have already indicated we need
-        // to reframe.
-        MOZ_ASSERT_IF(!pseudoContext,
-                      changeHint & nsChangeHint_ReconstructFrame);
-        if (pseudoContext) {
-          pseudoFrame->SetStyleContext(pseudoContext);
+          aStyleSet->ProbePseudoElementStyle(aElement, pseudoType, newContext);
+        MOZ_ASSERT(pseudoContext, "should have taken the ReconstructFrame path above");
+        pseudoFrame->SetStyleContext(pseudoContext);
 
-          // We only care restyling text nodes, since other type of nodes
-          // (images), are still not supported. If that eventually changes, we
-          // may have to write more code here... Or not, I don't think too
-          // many inherited properties can affect those other frames.
-          StyleChildrenIterator it(pseudoFrame->GetContent());
-          for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
-            if (n->IsNodeOfType(nsINode::eTEXT)) {
-              RefPtr<nsStyleContext> childContext =
-                aStyleSet->ResolveStyleForText(n, pseudoContext);
-              MOZ_ASSERT(n->GetPrimaryFrame(),
-                         "How? This node is created at FC time!");
-              n->GetPrimaryFrame()->SetStyleContext(childContext);
-            }
+        // We only care restyling text nodes, since other type of nodes
+        // (images), are still not supported. If that eventually changes, we
+        // may have to write more code here... Or not, I don't think too
+        // many inherited properties can affect those other frames.
+        StyleChildrenIterator it(pseudoFrame->GetContent());
+        for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+          if (n->IsNodeOfType(nsINode::eTEXT)) {
+            RefPtr<nsStyleContext> childContext =
+              aStyleSet->ResolveStyleForText(n, pseudoContext);
+            MOZ_ASSERT(n->GetPrimaryFrame(),
+                       "How? This node is created at FC time!");
+            n->GetPrimaryFrame()->SetStyleContext(childContext);
           }
         }
       }
     }
-
-    aContent->UnsetIsDirtyForServo();
   }
 
-  if (aContent->HasDirtyDescendantsForServo()) {
-    MOZ_ASSERT(primaryFrame,
-               "Frame construction should be scheduled, and it takes the "
-               "correct style for the children, so no need to be here.");
-    StyleChildrenIterator it(aContent);
+  bool traverseElementChildren = aElement->HasDirtyDescendantsForServo();
+  bool traverseTextChildren = recreateContext;
+  if (traverseElementChildren || traverseTextChildren) {
+    StyleChildrenIterator it(aElement);
     for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
-      if (n->IsElement() || n->IsNodeOfType(nsINode::eTEXT)) {
-        RecreateStyleContexts(n, primaryFrame->StyleContext(),
+      if (traverseElementChildren && n->IsElement()) {
+        MOZ_ASSERT(primaryFrame,
+                   "Frame construction should be scheduled, and it takes the "
+                   "correct style for the children, so no need to be here.");
+        RecreateStyleContexts(n->AsElement(), primaryFrame->StyleContext(),
                               aStyleSet, aChangeListToProcess);
+      } else if (traverseTextChildren && n->IsNodeOfType(nsINode::eTEXT)) {
+        RecreateStyleContextsForText(n, primaryFrame->StyleContext(),
+                                     aStyleSet);
       }
     }
-    aContent->UnsetHasDirtyDescendantsForServo();
   }
+
+  aElement->UnsetHasDirtyDescendantsForServo();
 }
 
-static void
-MarkChildrenAsDirtyForServo(nsIContent* aContent)
+void
+ServoRestyleManager::RecreateStyleContextsForText(nsIContent* aTextNode,
+                                                  nsStyleContext* aParentContext,
+                                                  ServoStyleSet* aStyleSet)
 {
-  StyleChildrenIterator it(aContent);
+  nsIFrame* primaryFrame = aTextNode->GetPrimaryFrame();
+  if (primaryFrame) {
+    RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext();
+    RefPtr<nsStyleContext> newContext =
+      aStyleSet->ResolveStyleForText(aTextNode, aParentContext);
 
-  nsIContent* n = it.GetNextChild();
-  bool hadChildren = bool(n);
-  for (; n; n = it.GetNextChild()) {
-    n->SetIsDirtyForServo();
-  }
-
-  if (hadChildren) {
-    aContent->SetHasDirtyDescendantsForServo();
+    for (nsIFrame* f = primaryFrame; f;
+         f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
+      f->SetStyleContext(newContext);
+    }
   }
 }
 
 /* static */ nsIFrame*
 ServoRestyleManager::FrameForPseudoElement(const nsIContent* aContent,
                                            nsIAtom* aPseudoTagOrNull)
 {
   MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement());
@@ -310,53 +263,16 @@ ServoRestyleManager::FrameForPseudoEleme
     return nsLayoutUtils::GetAfterFrameForContent(primaryFrame, aContent);
   }
 
   MOZ_CRASH("Unkown pseudo-element given to "
             "ServoRestyleManager::FrameForPseudoElement");
   return nullptr;
 }
 
-/* static */ void
-ServoRestyleManager::NoteRestyleHint(Element* aElement, nsRestyleHint aHint)
-{
-  const nsRestyleHint HANDLED_RESTYLE_HINTS = eRestyle_Self |
-                                              eRestyle_Subtree |
-                                              eRestyle_LaterSiblings |
-                                              eRestyle_SomeDescendants;
-  // NB: For Servo, at least for now, restyling and running selector-matching
-  // against the subtree is necessary as part of restyling the element, so
-  // processing eRestyle_Self will perform at least as much work as
-  // eRestyle_Subtree.
-  if (aHint & (eRestyle_Self | eRestyle_Subtree)) {
-    aElement->SetIsDirtyForServo();
-    aElement->MarkAncestorsAsHavingDirtyDescendantsForServo();
-  // NB: Servo gives us a eRestyle_SomeDescendants when it expects us to run
-  // selector matching on all the descendants. There's a bug on Servo to align
-  // meanings here (#12710) to avoid this potential source of confusion.
-  } else if (aHint & eRestyle_SomeDescendants) {
-    MarkChildrenAsDirtyForServo(aElement);
-    aElement->MarkAncestorsAsHavingDirtyDescendantsForServo();
-  }
-
-  if (aHint & eRestyle_LaterSiblings) {
-    aElement->MarkAncestorsAsHavingDirtyDescendantsForServo();
-    for (nsIContent* cur = aElement->GetNextSibling(); cur;
-         cur = cur->GetNextSibling()) {
-      cur->SetIsDirtyForServo();
-    }
-  }
-
-  // TODO: Handle all other nsRestyleHint values.
-  if (aHint & ~HANDLED_RESTYLE_HINTS) {
-    NS_WARNING(nsPrintfCString("stylo: Unhandled restyle hint %s",
-                               RestyleManagerBase::RestyleHintToString(aHint).get()).get());
-  }
-}
-
 void
 ServoRestyleManager::ProcessPendingRestyles()
 {
   MOZ_ASSERT(PresContext()->Document(), "No document?  Pshaw!");
   MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!");
 
   if (MOZ_UNLIKELY(!PresContext()->PresShell()->DidInitialize())) {
     // PresShell::FlushPendingNotifications doesn't early-return in the case
@@ -369,77 +285,59 @@ ServoRestyleManager::ProcessPendingResty
 
   if (!HasPendingRestyles()) {
     return;
   }
 
   ServoStyleSet* styleSet = StyleSet();
   nsIDocument* doc = PresContext()->Document();
   Element* root = doc->GetRootElement();
-  if (root) {
-    // ProcessPendingRestyles can generate new restyles (e.g. from the
-    // frame constructor if it decides that a ReconstructFrame change must
-    // apply to the parent of the element that generated that hint).  So
-    // we loop while mModifiedElements still has some restyles in it, clearing
-    // it after each RecreateStyleContexts call below.
-    while (!mModifiedElements.IsEmpty()) {
-      for (auto iter = mModifiedElements.Iter(); !iter.Done(); iter.Next()) {
-        ServoElementSnapshot* snapshot = iter.UserData();
-        Element* element = iter.Key();
+
+  // XXXbholley: Should this be while() per bug 1316247?
+  if (HasPendingRestyles()) {
+    MOZ_ASSERT(root);
+    mInStyleRefresh = true;
+    styleSet->StyleDocument();
 
-        // The element is no longer in the document, so don't bother computing
-        // a final restyle hint for it.
-        //
-        // XXXheycam RestyleTracker checks that the element's GetComposedDoc()
-        // matches the document we're restyling.  Do we need to do that too?
-        if (!element->IsInComposedDoc()) {
-          continue;
-        }
+    // First do any queued-up frame creation. (see bugs 827239 and 997506).
+    //
+    // XXXEmilio I'm calling this to avoid random behavior changes, since we
+    // delay frame construction after styling we should re-check once our
+    // model is more stable whether we can skip this call.
+    //
+    // Note this has to be *after* restyling, because otherwise frame
+    // construction will find unstyled nodes, and that's not funny.
+    PresContext()->FrameConstructor()->CreateNeededFrames();
 
-        // TODO: avoid the ComputeRestyleHint call if we already have the highest
-        // explicit restyle hint?
-        nsRestyleHint hint = styleSet->ComputeRestyleHint(element, snapshot);
-        hint |= snapshot->ExplicitRestyleHint();
-
-        if (hint) {
-          NoteRestyleHint(element, hint);
-        }
-      }
+    // Recreate style contexts and queue up change hints.
+    nsStyleChangeList currentChanges;
+    RecreateStyleContexts(root, nullptr, styleSet, currentChanges);
 
-      if (!root->IsDirtyForServo() && !root->HasDirtyDescendantsForServo()) {
-        mModifiedElements.Clear();
-        break;
+    // Process the change hints.
+    //
+    // Unfortunately, the frame constructor can generate new change hints while
+    // processing existing ones. We redirect those into a secondary queue and
+    // iterate until there's nothing left.
+    ReentrantChangeList newChanges;
+    mReentrantChanges = &newChanges;
+    while (!currentChanges.IsEmpty()) {
+      ProcessRestyledFrames(currentChanges);
+      MOZ_ASSERT(currentChanges.IsEmpty());
+      for (ReentrantChange& change: newChanges)  {
+        currentChanges.AppendChange(change.mContent->GetPrimaryFrame(),
+                                    change.mContent, change.mHint);
       }
-
-      mInStyleRefresh = true;
-      styleSet->StyleDocument(/* aLeaveDirtyBits = */ true);
+      newChanges.Clear();
+    }
+    mReentrantChanges = nullptr;
 
-      // First do any queued-up frame creation. (see bugs 827239 and 997506).
-      //
-      // XXXEmilio I'm calling this to avoid random behavior changes, since we
-      // delay frame construction after styling we should re-check once our
-      // model is more stable whether we can skip this call.
-      //
-      // Note this has to be *after* restyling, because otherwise frame
-      // construction will find unstyled nodes, and that's not funny.
-      PresContext()->FrameConstructor()->CreateNeededFrames();
-
-      nsStyleChangeList changeList;
-      RecreateStyleContexts(root, nullptr, styleSet, changeList);
-
-      mModifiedElements.Clear();
-      ProcessRestyledFrames(changeList);
-
-      mInStyleRefresh = false;
-    }
+    styleSet->AssertTreeIsClean();
+    mInStyleRefresh = false;
   }
 
-  MOZ_ASSERT(!doc->IsDirtyForServo());
-  doc->UnsetHasDirtyDescendantsForServo();
-
   IncrementRestyleGeneration();
 }
 
 void
 ServoRestyleManager::RestyleForInsertOrChange(nsINode* aContainer,
                                               nsIContent* aChild)
 {
   //
@@ -449,41 +347,16 @@ ServoRestyleManager::RestyleForInsertOrC
   //
   // Bug 1297899 tracks this work.
   //
 }
 
 void
 ServoRestyleManager::ContentInserted(nsINode* aContainer, nsIContent* aChild)
 {
-  if (aContainer == aContainer->OwnerDoc()) {
-    // If we're getting this notification for the insertion of a root element,
-    // that means either:
-    //   (a) We initialized the PresShell before the root element existed, or
-    //   (b) The root element was removed and it or another root is being
-    //       inserted.
-    //
-    // Either way the whole tree is dirty, so we should style the document.
-    MOZ_ASSERT(aChild == aChild->OwnerDoc()->GetRootElement());
-    MOZ_ASSERT(aChild->IsDirtyForServo());
-    StyleSet()->StyleDocument(/* aLeaveDirtyBits = */ false);
-    return;
-  }
-
-  if (!aContainer->HasServoData()) {
-    // This can happen with display:none. Bug 1297249 tracks more investigation
-    // and assertions here.
-    return;
-  }
-
-  // Style the new subtree because we will most likely need it during subsequent
-  // frame construction. Bug 1298281 tracks deferring this work in the lazy
-  // frame construction case.
-  StyleSet()->StyleNewSubtree(aChild);
-
   RestyleForInsertOrChange(aContainer, aChild);
 }
 
 void
 ServoRestyleManager::RestyleForAppend(nsIContent* aContainer,
                                       nsIContent* aFirstNewContent)
 {
   //
@@ -494,32 +367,16 @@ ServoRestyleManager::RestyleForAppend(ns
   // Bug 1297899 tracks this work.
   //
 }
 
 void
 ServoRestyleManager::ContentAppended(nsIContent* aContainer,
                                      nsIContent* aFirstNewContent)
 {
-  if (!aContainer->HasServoData()) {
-    // This can happen with display:none. Bug 1297249 tracks more investigation
-    // and assertions here.
-    return;
-  }
-
-  // Style the new subtree because we will most likely need it during subsequent
-  // frame construction. Bug 1298281 tracks deferring this work in the lazy
-  // frame construction case.
-  if (aFirstNewContent->GetNextSibling()) {
-    aContainer->SetHasDirtyDescendantsForServo();
-    StyleSet()->StyleNewChildren(aContainer);
-  } else {
-    StyleSet()->StyleNewSubtree(aFirstNewContent);
-  }
-
   RestyleForAppend(aContainer, aFirstNewContent);
 }
 
 void
 ServoRestyleManager::ContentRemoved(nsINode* aContainer,
                                     nsIContent* aOldChild,
                                     nsIContent* aFollowingSibling)
 {
@@ -556,52 +413,51 @@ ServoRestyleManager::ContentStateChanged
   // PostRestyleEvent.
   //
   // If we definitely take the snapshot approach, we should take rid of
   // HasStateDependentStyle, etc (though right now they're no-ops).
   ContentStateChangedInternal(aElement, aChangedBits, &changeHint,
                               &restyleHint);
 
   EventStates previousState = aElement->StyleState() ^ aChangedBits;
-  ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
-  snapshot->AddState(previousState);
+  ServoElementSnapshot* snapshot = Servo_Element_GetSnapshot(aElement);
+  if (snapshot) {
+    snapshot->AddState(previousState);
+    PostRestyleEvent(aElement, restyleHint, changeHint);
+  }
 
-  PostRestyleEvent(aElement, restyleHint, changeHint);
   return NS_OK;
 }
 
 void
 ServoRestyleManager::AttributeWillChange(Element* aElement,
                                          int32_t aNameSpaceID,
                                          nsIAtom* aAttribute, int32_t aModType,
                                          const nsAttrValue* aNewValue)
 {
-  ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
-  snapshot->AddAttrs(aElement);
+  ServoElementSnapshot* snapshot = Servo_Element_GetSnapshot(aElement);
+  if (snapshot) {
+    snapshot->AddAttrs(aElement);
+  }
 }
 
 void
 ServoRestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
                                       nsIAtom* aAttribute, int32_t aModType,
                                       const nsAttrValue* aOldValue)
 {
-  MOZ_ASSERT(SnapshotForElement(aElement)->HasAttrs());
+#ifdef DEBUG
+  ServoElementSnapshot* snapshot = Servo_Element_GetSnapshot(aElement);
+  MOZ_ASSERT_IF(snapshot, snapshot->HasAttrs());
+#endif
   if (aAttribute == nsGkAtoms::style) {
     PostRestyleEvent(aElement, eRestyle_StyleAttribute, nsChangeHint(0));
   }
 }
 
 nsresult
 ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
 {
   NS_WARNING("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
   return NS_OK;
 }
 
-ServoElementSnapshot*
-ServoRestyleManager::SnapshotForElement(Element* aElement)
-{
-  // NB: aElement is the argument for the construction of the snapshot in the
-  // not found case.
-  return mModifiedElements.LookupOrAdd(aElement, aElement);
-}
-
 } // namespace mozilla
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ServoRestyleManager_h
 #define mozilla_ServoRestyleManager_h
 
 #include "mozilla/EventStates.h"
 #include "mozilla/RestyleManagerBase.h"
+#include "mozilla/ServoBindings.h"
 #include "mozilla/ServoElementSnapshot.h"
 #include "nsChangeHint.h"
 #include "nsHashKeys.h"
 #include "nsINode.h"
 #include "nsISupportsImpl.h"
 #include "nsPresContext.h"
 
 namespace mozilla {
@@ -74,67 +75,76 @@ public:
   void AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
                         nsIAtom* aAttribute, int32_t aModType,
                         const nsAttrValue* aOldValue);
 
   nsresult ReparentStyleContext(nsIFrame* aFrame);
 
   bool HasPendingRestyles()
   {
-    return !mModifiedElements.IsEmpty() ||
-           PresContext()->Document()->HasDirtyDescendantsForServo();
+    Element* root = PresContext()->Document()->GetRootElement();
+    return root && root->HasDirtyDescendantsForServo();
   }
 
 
   /**
    * Gets the appropriate frame given a content and a pseudo-element tag.
    *
    * Right now only supports a null tag, before or after. If the pseudo-element
    * is not null, the content needs to be an element.
    */
   static nsIFrame* FrameForPseudoElement(const nsIContent* aContent,
                                          nsIAtom* aPseudoTagOrNull);
 
   /**
-   * Clears the ServoNodeData from all content nodes in the subtree rooted
-   * at aContent, and sets the IsDirtyForServo bit and clears the
-   * HasDirtyDescendantsForServo bit on them too.
+   * Clears the ServoElementData and HasDirtyDescendants from all elements
+   * in the subtree rooted at aElement.
    */
-  static void ClearServoDataFromSubtree(nsIContent* aContent);
+  static void ClearServoDataFromSubtree(Element* aElement);
+
+  /**
+   * Clears HasDirtyDescendants from all elements in the subtree rooted at
+   * aElement.
+   */
+  static void ClearDirtyDescendantsFromSubtree(Element* aElement);
 
 protected:
-  ~ServoRestyleManager() {}
+  ~ServoRestyleManager() { MOZ_ASSERT(!mReentrantChanges); }
 
 private:
-  ServoElementSnapshot* SnapshotForElement(Element* aElement);
-
-  /**
-   * The element-to-element snapshot table to compute restyle hints.
-   */
-  nsClassHashtable<nsRefPtrHashKey<Element>, ServoElementSnapshot>
-    mModifiedElements;
-
   /**
    * Traverses a tree of content that Servo has just restyled, recreating style
    * contexts for their frames with the new style data.
    */
-  void RecreateStyleContexts(nsIContent* aContent,
+  void RecreateStyleContexts(Element* aElement,
                              nsStyleContext* aParentContext,
                              ServoStyleSet* aStyleSet,
                              nsStyleChangeList& aChangeList);
 
-  /**
-   * Marks the tree with the appropriate dirty flags for the given restyle hint.
-   */
-  static void NoteRestyleHint(Element* aElement, nsRestyleHint aRestyleHint);
+  void RecreateStyleContextsForText(nsIContent* aTextNode,
+                                    nsStyleContext* aParentContext,
+                                    ServoStyleSet* aStyleSet);
 
   inline ServoStyleSet* StyleSet() const
   {
     MOZ_ASSERT(PresContext()->StyleSet()->IsServo(),
                "ServoRestyleManager should only be used with a Servo-flavored "
                "style backend");
     return PresContext()->StyleSet()->AsServo();
   }
+
+  // We use a separate data structure from nsStyleChangeList because we need a
+  // frame to create nsStyleChangeList entries, and the primary frame may not be
+  // attached yet.
+  struct ReentrantChange {
+    nsCOMPtr<nsIContent> mContent;
+    nsChangeHint mHint;
+  };
+  typedef AutoTArray<ReentrantChange, 10> ReentrantChangeList;
+
+  // Only non-null while processing change hints. See the comment in
+  // ProcessPendingRestyles.
+  ReentrantChangeList* mReentrantChanges;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ServoRestyleManager_h
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -2424,17 +2424,19 @@ nsCSSFrameConstructor::ConstructDocEleme
   aDocElement->UnsetRestyleFlagsIfGecko();
 
   // --------- CREATE AREA OR BOX FRAME -------
   // FIXME: Should this use ResolveStyleContext?  (The calls in this
   // function are the only case in nsCSSFrameConstructor where we don't
   // do so for the construction of a style context for an element.)
   RefPtr<nsStyleContext> styleContext;
   styleContext = mPresShell->StyleSet()->ResolveStyleFor(aDocElement,
-                                                         nullptr);
+                                                         nullptr,
+                                                         ConsumeStyleBehavior::Consume,
+                                                         LazyComputeBehavior::Allow);
 
   const nsStyleDisplay* display = styleContext->StyleDisplay();
 
   // Ensure that our XBL bindings are installed.
   if (display->mBinding) {
     // Get the XBL loader.
     nsresult rv;
     bool resolveStyle;
@@ -2459,21 +2461,29 @@ nsCSSFrameConstructor::ConstructDocEleme
     }
 
     if (resolveStyle) {
       // FIXME: Should this use ResolveStyleContext?  (The calls in this
       // function are the only case in nsCSSFrameConstructor where we
       // don't do so for the construction of a style context for an
       // element.)
       styleContext = mPresShell->StyleSet()->ResolveStyleFor(aDocElement,
-                                                             nullptr);
+                                                             nullptr,
+                                                             ConsumeStyleBehavior::Consume,
+                                                             LazyComputeBehavior::Allow);
       display = styleContext->StyleDisplay();
     }
   }
 
+  // We delay traversing the entire document until here, since we per above we
+  // may invalidate the root style when we load doc stylesheets.
+  if (ServoStyleSet* set = mPresShell->StyleSet()->GetAsServo()) {
+    set->StyleDocument();
+  }
+
   // --------- IF SCROLLABLE WRAP IN SCROLLFRAME --------
 
   NS_ASSERTION(!display->IsScrollableOverflow() ||
                state.mPresContext->IsPaginated() ||
                propagatedScrollFrom == aDocElement,
                "Scrollbars should have been propagated to the viewport");
 
   if (MOZ_UNLIKELY(display->mDisplay == StyleDisplay::None)) {
@@ -5017,20 +5027,24 @@ nsCSSFrameConstructor::ResolveStyleConte
   StyleSetHandle styleSet = mPresShell->StyleSet();
   aContent->OwnerDoc()->FlushPendingLinkUpdates();
 
   RefPtr<nsStyleContext> result;
   if (aContent->IsElement()) {
     if (aState) {
       result = styleSet->ResolveStyleFor(aContent->AsElement(),
                                          aParentStyleContext,
+                                         ConsumeStyleBehavior::Consume,
+                                         LazyComputeBehavior::Assert,
                                          aState->mTreeMatchContext);
     } else {
       result = styleSet->ResolveStyleFor(aContent->AsElement(),
-                                         aParentStyleContext);
+                                         aParentStyleContext,
+                                         ConsumeStyleBehavior::Consume,
+                                         LazyComputeBehavior::Assert);
     }
   } else {
     NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
                  "shouldn't waste time creating style contexts for "
                  "comments and processing instructions");
     result = styleSet->ResolveStyleForText(aContent, aParentStyleContext);
   }
 
@@ -5708,16 +5722,21 @@ nsCSSFrameConstructor::AddFrameConstruct
       return;
 
     if (newPendingBinding->mBinding) {
       pendingBinding = newPendingBinding;
       // aState takes over owning newPendingBinding
       aState.AddPendingBinding(newPendingBinding.forget());
     }
 
+    if (aContent->IsStyledByServo()) {
+      NS_WARNING("stylo: Skipping Unsupported binding re-resolve. This needs fixing.");
+      resolveStyle = false;
+    }
+
     if (resolveStyle) {
       styleContext =
         ResolveStyleContext(styleContext->GetParent(), aContent, &aState);
       display = styleContext->StyleDisplay();
       aStyleContext = styleContext;
     }
 
     aTag = mDocument->BindingManager()->ResolveTag(aContent, &aNameSpaceID);
@@ -7337,19 +7356,38 @@ nsCSSFrameConstructor::ContentAppended(n
   // See comment in ContentRangeInserted for why this is necessary.
   if (!GetContentInsertionFrameFor(aContainer) &&
       !aContainer->IsActiveChildrenElement()) {
     return NS_OK;
   }
 
   if (aAllowLazyConstruction &&
       MaybeConstructLazily(CONTENTAPPEND, aContainer, aFirstNewContent)) {
+    if (aContainer->IsStyledByServo()) {
+      aContainer->AsElement()->NoteDirtyDescendantsForServo();
+    }
     return NS_OK;
   }
 
+  // We couldn't construct lazily. Make Servo eagerly traverse the subtree.
+  if (ServoStyleSet* set = mPresShell->StyleSet()->GetAsServo()) {
+    // We use the same codepaths to handle both of the following cases:
+    //   (a) Newly-appended content for which lazy frame construction is disallowed.
+    //   (b) Lazy frame construction driven by the restyle manager.
+    // We need the styles for (a). In the case of (b), the Servo traversal has
+    // already happened, so we don't need to do it again.
+    if (!RestyleManager()->AsBase()->IsInStyleRefresh()) {
+      if (aFirstNewContent->GetNextSibling()) {
+        set->StyleNewChildren(aContainer);
+      } else {
+        set->StyleNewSubtree(aFirstNewContent);
+      }
+    }
+  }
+
   LAYOUT_PHASE_TEMP_EXIT();
   InsertionPoint insertion =
     GetRangeInsertionPoint(aContainer, aFirstNewContent, nullptr,
                            aAllowLazyConstruction);
   nsContainerFrame*& parentFrame = insertion.mParentFrame;
   LAYOUT_PHASE_TEMP_REENTER();
   if (!parentFrame) {
     return NS_OK;
@@ -7782,20 +7820,35 @@ nsCSSFrameConstructor::ContentRangeInser
     }
 
     // Otherwise, we've got parent content. Find its frame.
     NS_ASSERTION(!parentFrame || parentFrame->GetContent() == aContainer ||
                  GetDisplayContentsStyleFor(aContainer), "New XBL code is possibly wrong!");
 
     if (aAllowLazyConstruction &&
         MaybeConstructLazily(CONTENTINSERT, aContainer, aStartChild)) {
+      if (aContainer->IsStyledByServo()) {
+        aContainer->AsElement()->NoteDirtyDescendantsForServo();
+      }
       return NS_OK;
     }
   }
 
+  // We couldn't construct lazily. Make Servo eagerly traverse the subtree.
+  if (ServoStyleSet* set = mPresShell->StyleSet()->GetAsServo()) {
+    // We use the same codepaths to handle both of the following cases:
+    //   (a) Newly-appended content for which lazy frame construction is disallowed.
+    //   (b) Lazy frame construction driven by the restyle manager.
+    // We need the styles for (a). In the case of (b), the Servo traversal has
+    // already happened, so we don't need to do it again.
+    if (!RestyleManager()->AsBase()->IsInStyleRefresh()) {
+      set->StyleNewSubtree(aStartChild);
+    }
+  }
+
   InsertionPoint insertion;
   if (isSingleInsert) {
     // See if we have an XBL insertion point. If so, then that's our
     // real parent frame; if not, then the frame hasn't been built yet
     // and we just bail.
     insertion = GetInsertionPoint(aContainer, aStartChild);
   } else {
     // Get our insertion point. If we need to issue single ContentInserted's
@@ -9250,17 +9303,19 @@ nsCSSFrameConstructor::MaybeRecreateFram
     if (!oldContext) {
       return nullptr;
     }
     oldDisplay = StyleDisplay::Contents;
   }
 
   // The parent has a frame, so try resolving a new context.
   RefPtr<nsStyleContext> newContext = mPresShell->StyleSet()->
-    ResolveStyleFor(aElement, oldContext->GetParent());
+    ResolveStyleFor(aElement, oldContext->GetParent(),
+                    ConsumeStyleBehavior::Consume,
+                    LazyComputeBehavior::Assert);
 
   if (oldDisplay == StyleDisplay::None) {
     ChangeUndisplayedContent(aElement, newContext);
   } else {
     ChangeDisplayContents(aElement, newContext);
   }
 
   const nsStyleDisplay* disp = newContext->StyleDisplay();
@@ -10622,24 +10677,25 @@ nsCSSFrameConstructor::AddFCItemsForAnon
       parentDisplayBasedStyleFixupSkipper(aState.mTreeMatchContext);
     if (aAnonymousItems[i].mStyleContext) {
       // If we have an explicit style context, that means that the anonymous
       // content creator had its own plan for the style, and doesn't need the
       // computed style obtained by cascading this content as a normal node.
       // This happens when a native anonymous node is used to implement a
       // pseudo-element. Allowing Servo to traverse these nodes would be wasted
       // work, so assert that we didn't do that.
-      MOZ_ASSERT_IF(content->IsStyledByServo(), !content->HasServoData());
+      MOZ_ASSERT_IF(content->IsStyledByServo(),
+                    !content->IsElement() || !content->AsElement()->HasServoData());
       styleContext = aAnonymousItems[i].mStyleContext.forget();
     } else {
       // If we don't have an explicit style context, that means we need the
       // ordinary computed values. Make sure we eagerly cascaded them when the
       // anonymous nodes were created.
       MOZ_ASSERT_IF(content->IsStyledByServo() && content->IsElement(),
-                    content->HasServoData());
+                    content->AsElement()->HasServoData());
       styleContext = ResolveStyleContext(aFrame, content, &aState);
     }
 
     nsTArray<nsIAnonymousContentCreator::ContentInfo>* anonChildren = nullptr;
     if (!aAnonymousItems[i].mChildren.IsEmpty()) {
       anonChildren = &aAnonymousItems[i].mChildren;
     }
 
--- a/layout/base/nsChangeHint.h
+++ b/layout/base/nsChangeHint.h
@@ -12,16 +12,18 @@
 #include "nsDebug.h"
 #include "nsTArray.h"
 
 struct nsCSSSelector;
 
 // Defines for various style related constants
 
 enum nsChangeHint {
+  nsChangeHint_Empty = 0,
+
   // change was visual only (e.g., COLOR=)
   // Invalidates all descendant frames (including following
   // placeholders to out-of-flow frames).
   nsChangeHint_RepaintFrame = 1 << 0,
 
   // For reflow, we want flags to give us arbitrary FrameNeedsReflow behavior.
   // just do a FrameNeedsReflow.
   nsChangeHint_NeedReflow = 1 << 1,
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 sw=2 et tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* container for a document and its presentation */
 
+#include "mozilla/ServoRestyleManager.h"
 #include "mozilla/ServoStyleSet.h"
 #include "nsAutoPtr.h"
 #include "nscore.h"
 #include "nsCOMPtr.h"
 #include "nsCRT.h"
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsIContent.h"
@@ -4464,16 +4465,25 @@ NS_IMETHODIMP nsDocumentViewer::SetPageM
 
   if (mPresContext) {
     DestroyPresContext();
   }
 
   mViewManager  = nullptr;
   mWindow       = nullptr;
 
+  // We're creating a new presentation context for an existing document.
+  // Drop any associated Servo data.
+#ifdef MOZ_STYLO
+  Element* root = mDocument->GetRootElement();
+  if (root && root->IsStyledByServo()) {
+    ServoRestyleManager::ClearServoDataFromSubtree(root);
+  }
+#endif
+
   NS_ENSURE_STATE(mDocument);
   if (aPageMode)
   {    
     mPresContext = CreatePresContext(mDocument,
         nsPresContext::eContext_PageLayout, FindContainerView());
     NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY);
     mPresContext->SetPaginatedScrolling(true);
     mPresContext->SetPrintSettings(aPrintSettings);
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -1379,17 +1379,19 @@ GetPropagatedScrollbarStylesForViewport(
   // docElement might be null if we're doing this after removing it.
   if (!docElement) {
     return nullptr;
   }
 
   // Check the style on the document root element
   StyleSetHandle styleSet = aPresContext->StyleSet();
   RefPtr<nsStyleContext> rootStyle;
-  rootStyle = styleSet->ResolveStyleFor(docElement, nullptr);
+  rootStyle = styleSet->ResolveStyleFor(docElement, nullptr,
+                                        ConsumeStyleBehavior::DontConsume,
+                                        LazyComputeBehavior::Allow);
   if (CheckOverflow(rootStyle->StyleDisplay(), aStyles)) {
     // tell caller we stole the overflow style from the root element
     return docElement;
   }
 
   // Don't look in the BODY for non-HTML documents or HTML documents
   // with non-HTML roots
   // XXX this should be earlier; we shouldn't even look at the document root
@@ -1407,17 +1409,19 @@ GetPropagatedScrollbarStylesForViewport(
 
   if (!bodyElement ||
       !bodyElement->NodeInfo()->Equals(nsGkAtoms::body)) {
     // The body is not a <body> tag, it's a <frameset>.
     return nullptr;
   }
 
   RefPtr<nsStyleContext> bodyStyle;
-  bodyStyle = styleSet->ResolveStyleFor(bodyElement->AsElement(), rootStyle);
+  bodyStyle = styleSet->ResolveStyleFor(bodyElement->AsElement(), rootStyle,
+                                        ConsumeStyleBehavior::DontConsume,
+                                        LazyComputeBehavior::Allow);
 
   if (CheckOverflow(bodyStyle->StyleDisplay(), aStyles)) {
     // tell caller we stole the overflow style from the body element
     return bodyElement;
   }
 
   return nullptr;
 }
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -1687,27 +1687,16 @@ PresShell::Initialize(nscoord aWidth, ns
   }
 #endif
 
   // XXX Do a full invalidate at the beginning so that invalidates along
   // the way don't have region accumulation issues?
 
   mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
 
-  if (mStyleSet->IsServo() && mDocument->GetRootElement()) {
-    // If we have the root element already, go ahead style it along with any
-    // descendants.
-    //
-    // Some things, like nsDocumentViewer::GetPageMode, recreate the PresShell
-    // while keeping the content tree alive (see bug 1292280) - so we
-    // unconditionally mark the root as dirty.
-    mDocument->GetRootElement()->SetIsDirtyForServo();
-    mStyleSet->AsServo()->StyleDocument(/* aLeaveDirtyBits = */ false);
-  }
-
   // Get the root frame from the frame manager
   // XXXbz it would be nice to move this somewhere else... like frame manager
   // Init(), say.  But we need to make sure our views are all set up by the
   // time we do this!
   nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
   NS_ASSERTION(!rootFrame, "How did that happen, exactly?");
   if (!rootFrame) {
     nsAutoScriptBlocker scriptBlocker;
--- a/layout/generic/nsFrameSetFrame.cpp
+++ b/layout/generic/nsFrameSetFrame.cpp
@@ -297,17 +297,19 @@ nsHTMLFramesetFrame::Init(nsIContent*   
     // nsCSSFrameConstructor::ContentAppended/Inserted/Removed
     if (!child->IsHTMLElement())
       continue;
 
     if (child->IsAnyOfHTMLElements(nsGkAtoms::frameset, nsGkAtoms::frame)) {
       RefPtr<nsStyleContext> kidSC;
 
       kidSC = shell->StyleSet()->ResolveStyleFor(child->AsElement(),
-                                                 mStyleContext);
+                                                 mStyleContext,
+                                                 ConsumeStyleBehavior::DontConsume,
+                                                 LazyComputeBehavior::Allow);
       if (child->IsHTMLElement(nsGkAtoms::frameset)) {
         frame = NS_NewHTMLFramesetFrame(shell, kidSC);
 
         nsHTMLFramesetFrame* childFrame = (nsHTMLFramesetFrame*)frame;
         childFrame->SetParentFrameborder(frameborder);
         childFrame->SetParentBorderWidth(borderWidth);
         childFrame->SetParentBorderColor(borderColor);
         frame->Init(child, this, nullptr);
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -2053,17 +2053,19 @@ nsImageFrame::GetCursor(const nsPoint& a
     if (area) {
       // Use the cursor from the style of the *area* element.
       // XXX Using the image as the parent style context isn't
       // technically correct, but it's probably the right thing to do
       // here, since it means that areas on which the cursor isn't
       // specified will inherit the style from the image.
       RefPtr<nsStyleContext> areaStyle = 
         PresContext()->PresShell()->StyleSet()->
-          ResolveStyleFor(area->AsElement(), StyleContext());
+          ResolveStyleFor(area->AsElement(), StyleContext(),
+                          ConsumeStyleBehavior::DontConsume,
+                          LazyComputeBehavior::Allow);
       FillCursorInformationFromStyle(areaStyle->StyleUserInterface(),
                                      aCursor);
       if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
         aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
       }
       return NS_OK;
     }
   }
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -13,18 +13,18 @@
  * - 'return_' the return type of the binding function
  * and the parameter list of the function.
  *
  * Users of this list should define a macro
  * SERVO_BINDING_FUNC(name_, return_, ...)
  * before including this file.
  */
 
-// Node data
-SERVO_BINDING_FUNC(Servo_Node_ClearNodeData, void, RawGeckoNodeBorrowed node)
+// Element data
+SERVO_BINDING_FUNC(Servo_Element_ClearData, void, RawGeckoElementBorrowed node)
 
 // Styleset and Stylesheet management
 SERVO_BINDING_FUNC(Servo_StyleSheet_Empty, RawServoStyleSheetStrong,
                    mozilla::css::SheetParsingMode parsing_mode)
 SERVO_BINDING_FUNC(Servo_StyleSheet_FromUTF8Bytes, RawServoStyleSheetStrong,
                    const nsACString* data,
                    mozilla::css::SheetParsingMode parsing_mode,
                    const nsACString* base_url,
@@ -128,18 +128,16 @@ SERVO_BINDING_FUNC(Servo_DeclarationBloc
                    RawServoDeclarationBlockBorrowed declarations,
                    nsIAtom* property, bool is_custom)
 
 // CSS supports()
 SERVO_BINDING_FUNC(Servo_CSSSupports, bool,
                    const nsACString* name, const nsACString* value)
 
 // Computed style data
-SERVO_BINDING_FUNC(Servo_ComputedValues_Get, ServoComputedValuesStrong,
-                   RawGeckoNodeBorrowed node)
 SERVO_BINDING_FUNC(Servo_ComputedValues_GetForAnonymousBox,
                    ServoComputedValuesStrong,
                    ServoComputedValuesBorrowedOrNull parent_style_or_null,
                    nsIAtom* pseudoTag, RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_ComputedValues_GetForPseudoElement,
                    ServoComputedValuesStrong,
                    ServoComputedValuesBorrowed parent_style,
                    RawGeckoElementBorrowed match_element, nsIAtom* pseudo_tag,
@@ -151,24 +149,36 @@ SERVO_BINDING_FUNC(Servo_ComputedValues_
 SERVO_BINDING_FUNC(Servo_ComputedValues_Release, void,
                    ServoComputedValuesBorrowed computed_values)
 
 // Initialize Servo components. Should be called exactly once at startup.
 SERVO_BINDING_FUNC(Servo_Initialize, void)
 // Shut down Servo components. Should be called exactly once at shutdown.
 SERVO_BINDING_FUNC(Servo_Shutdown, void)
 
-// Restyle hints
-SERVO_BINDING_FUNC(Servo_ComputeRestyleHint, nsRestyleHint,
-                   RawGeckoElementBorrowed element, ServoElementSnapshot* snapshot,
-                   RawServoStyleSetBorrowed set)
+// Gets the snapshot for the element. This will return null if the element
+// has never been styled, since snapshotting in that case is wasted work.
+SERVO_BINDING_FUNC(Servo_Element_GetSnapshot, ServoElementSnapshot*,
+                   RawGeckoElementBorrowed element)
+
+// Restyle and change hints.
+SERVO_BINDING_FUNC(Servo_NoteExplicitHints, void, RawGeckoElementBorrowed element,
+                   nsRestyleHint restyle_hint, nsChangeHint change_hint)
+SERVO_BINDING_FUNC(Servo_CheckChangeHint, nsChangeHint, RawGeckoElementBorrowed element)
+SERVO_BINDING_FUNC(Servo_ResolveStyle, ServoComputedValuesStrong,
+                   RawGeckoElementBorrowed element, RawServoStyleSetBorrowed set,
+                   mozilla::ConsumeStyleBehavior consume, mozilla::LazyComputeBehavior compute)
 
 // Restyle the given subtree.
-SERVO_BINDING_FUNC(Servo_RestyleSubtree, void,
-                   RawGeckoNodeBorrowed node, RawServoStyleSetBorrowed set)
+SERVO_BINDING_FUNC(Servo_TraverseSubtree, void,
+                   RawGeckoElementBorrowed root, RawServoStyleSetBorrowed set,
+                   mozilla::SkipRootBehavior skip_root)
+
+// Assert that the tree has no pending or unconsumed restyles.
+SERVO_BINDING_FUNC(Servo_AssertTreeIsClean, void, RawGeckoElementBorrowed root)
 
 // Style-struct management.
 #define STYLE_STRUCT(name, checkdata_cb)                            \
   struct nsStyle##name;                                             \
   SERVO_BINDING_FUNC(Servo_GetStyle##name, const nsStyle##name*,  \
                      ServoComputedValuesBorrowedOrNull computed_values)
 #include "nsStyleStructList.h"
 #undef STYLE_STRUCT
--- a/layout/style/ServoBindingTypes.h
+++ b/layout/style/ServoBindingTypes.h
@@ -13,27 +13,29 @@
 struct ServoComputedValues;
 struct ServoCssRules;
 struct RawServoStyleSheet;
 struct RawServoStyleSet;
 struct RawServoDeclarationBlock;
 struct RawServoStyleRule;
 
 namespace mozilla {
+  class ServoElementSnapshot;
 namespace dom {
 class Element;
 class StyleChildrenIterator;
 } // namespace dom
 } // namespace mozilla
 
 class nsCSSValue;
 class nsIDocument;
 class nsINode;
 
 using mozilla::dom::StyleChildrenIterator;
+using mozilla::ServoElementSnapshot;
 
 typedef nsINode RawGeckoNode;
 typedef mozilla::dom::Element RawGeckoElement;
 typedef nsIDocument RawGeckoDocument;
 
 // We have these helper types so that we can directly generate
 // things like &T or Borrowed<T> on the Rust side in the function, providing
 // additional safety benefits.
@@ -80,29 +82,31 @@ DECL_ARC_REF_TYPE_FOR(RawServoDeclaratio
 DECL_ARC_REF_TYPE_FOR(RawServoStyleRule)
 // This is a reference to a reference of RawServoDeclarationBlock, which
 // corresponds to Option<&Arc<RawServoDeclarationBlock>> in Servo side.
 DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawServoDeclarationBlockStrong)
 
 DECL_OWNED_REF_TYPE_FOR(RawServoStyleSet)
 DECL_NULLABLE_OWNED_REF_TYPE_FOR(StyleChildrenIterator)
 DECL_OWNED_REF_TYPE_FOR(StyleChildrenIterator)
+DECL_OWNED_REF_TYPE_FOR(ServoElementSnapshot)
 
 // We don't use BorrowedMut because the nodes may alias
 // Servo itself doesn't directly read or mutate these;
 // it only asks Gecko to do so. In case we wish to in
 // the future, we should ensure that things being mutated
 // are protected from noalias violations by a cell type
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoNode)
 DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoNode)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoElement)
 DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoElement)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoDocument)
 DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoDocument)
 DECL_BORROWED_MUT_REF_TYPE_FOR(StyleChildrenIterator)
+DECL_BORROWED_MUT_REF_TYPE_FOR(ServoElementSnapshot)
 DECL_BORROWED_REF_TYPE_FOR(nsCSSValue)
 DECL_BORROWED_MUT_REF_TYPE_FOR(nsCSSValue)
 
 #undef DECL_ARC_REF_TYPE_FOR
 #undef DECL_OWNED_REF_TYPE_FOR
 #undef DECL_NULLABLE_OWNED_REF_TYPE_FOR
 #undef DECL_BORROWED_REF_TYPE_FOR
 #undef DECL_NULLABLE_BORROWED_REF_TYPE_FOR
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -92,18 +92,17 @@ RawGeckoNodeBorrowedOrNull
 Gecko_GetNextSibling(RawGeckoNodeBorrowed aNode)
 {
   return aNode->GetNextSibling();
 }
 
 RawGeckoElementBorrowedOrNull
 Gecko_GetParentElement(RawGeckoElementBorrowed aElement)
 {
-  nsINode* parentNode = aElement->GetFlattenedTreeParentNode();
-  return parentNode->IsElement() ? parentNode->AsElement() : nullptr;
+  return aElement->GetFlattenedTreeParentElement();
 }
 
 RawGeckoElementBorrowedOrNull
 Gecko_GetFirstChildElement(RawGeckoElementBorrowed aElement)
 {
   return aElement->GetFirstElementChild();
 }
 
@@ -270,55 +269,28 @@ Gecko_CalcStyleDifference(nsStyleContext
     aOldStyleContext->CalcStyleDifference(aComputedValues,
                                           forDescendants,
                                           &equalStructs,
                                           &samePointerStructs);
 
   return result;
 }
 
-void
-Gecko_StoreStyleDifference(RawGeckoNodeBorrowed aNode, nsChangeHint aChangeHintToStore)
+ServoElementSnapshotOwned
+Gecko_CreateElementSnapshot(RawGeckoElementBorrowed aElement)
 {
-#ifdef MOZ_STYLO
-  MOZ_ASSERT(aNode->IsElement());
-  MOZ_ASSERT(aNode->IsDirtyForServo(),
-             "Change hint stored in a not-dirty node");
+  return new ServoElementSnapshot(aElement);
+}
 
-  const Element* aElement = aNode->AsElement();
-  nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
-  if (!primaryFrame) {
-    // If there's no primary frame, that means that either this content is
-    // undisplayed (so we only need to check at the restyling phase for the
-    // display value on the element), or is a display: contents element.
-    //
-    // In this second case, we should store it in the frame constructor display
-    // contents map. Note that while this operation looks hairy, this would be
-    // thread-safe because the content should be there already (we'd only need
-    // to read the map and modify our entry).
-    //
-    // That being said, we still don't support display: contents anyway, so it's
-    // probably not worth it to do all the roundtrip just yet until we have a
-    // more concrete plan.
-    return;
-  }
-
-  if ((aChangeHintToStore & nsChangeHint_ReconstructFrame) &&
-      aNode->IsInNativeAnonymousSubtree())
-  {
-    NS_WARNING("stylo: Removing forbidden frame reconstruction hint on native "
-               "anonymous content. Fix this in bug 1297857!");
-    aChangeHintToStore &= ~nsChangeHint_ReconstructFrame;
-  }
-
-  primaryFrame->StyleContext()->StoreChangeHint(aChangeHintToStore);
-#else
-  MOZ_CRASH("stylo: Shouldn't call Gecko_StoreStyleDifference in "
-            "non-stylo build");
-#endif
+void
+Gecko_DropElementSnapshot(ServoElementSnapshotOwned aSnapshot)
+{
+  MOZ_ASSERT(NS_IsMainThread(),
+             "ServoAttrSnapshots can only be dropped on the main thread");
+  delete aSnapshot;
 }
 
 RawServoDeclarationBlockStrongBorrowedOrNull
 Gecko_GetServoDeclarationBlock(RawGeckoElementBorrowed aElement)
 {
   const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::style);
   if (!attr || attr->Type() != nsAttrValue::eCSSDeclaration) {
     return nullptr;
@@ -576,17 +548,17 @@ ClassOrClassList(Implementor* aElement, 
   }                                                                            \
   uint32_t prefix_##ClassOrClassList(implementor_ aElement, nsIAtom** aClass,  \
                                      nsIAtom*** aClassList)                    \
   {                                                                            \
     return ClassOrClassList(aElement, aClass, aClassList);                     \
   }
 
 SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_, RawGeckoElementBorrowed)
-SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_Snapshot, ServoElementSnapshot*)
+SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_Snapshot, const ServoElementSnapshot*)
 
 #undef SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS
 
 nsIAtom*
 Gecko_Atomize(const char* aString, uint32_t aLength)
 {
   return NS_Atomize(nsDependentCSubstring(aString, aLength)).take();
 }
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -133,17 +133,17 @@ nsIAtom* Gecko_GetElementId(RawGeckoElem
                               nsIAtom* name, nsIAtom* str);                   \
   bool prefix_##AttrHasSuffix(implementor_ element, nsIAtom* ns,              \
                               nsIAtom* name, nsIAtom* str);                   \
   uint32_t prefix_##ClassOrClassList(implementor_ element, nsIAtom** class_,  \
                                      nsIAtom*** classList);
 
 SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_, RawGeckoElementBorrowed)
 SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_Snapshot,
-                                              ServoElementSnapshot*)
+                                              const ServoElementSnapshot*)
 
 #undef SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS
 
 // Style attributes.
 RawServoDeclarationBlockStrongBorrowedOrNull
 Gecko_GetServoDeclarationBlock(RawGeckoElementBorrowed element);
 
 // Atoms.
@@ -215,27 +215,26 @@ void Gecko_SetMozBinding(nsStyleDisplay*
 void Gecko_CopyMozBindingFrom(nsStyleDisplay* des, const nsStyleDisplay* src);
 
 // Dirtiness tracking.
 uint32_t Gecko_GetNodeFlags(RawGeckoNodeBorrowed node);
 void Gecko_SetNodeFlags(RawGeckoNodeBorrowed node, uint32_t flags);
 void Gecko_UnsetNodeFlags(RawGeckoNodeBorrowed node, uint32_t flags);
 
 // Incremental restyle.
-// TODO: We would avoid a few ffi calls if we decide to make an API like the
-// former CalcAndStoreStyleDifference, but that would effectively mean breaking
-// some safety guarantees in the servo side.
-//
 // Also, we might want a ComputedValues to ComputedValues API for animations?
 // Not if we do them in Gecko...
 nsStyleContext* Gecko_GetStyleContext(RawGeckoNodeBorrowed node,
                                       nsIAtom* aPseudoTagOrNull);
 nsChangeHint Gecko_CalcStyleDifference(nsStyleContext* oldstyle,
                                        ServoComputedValuesBorrowed newstyle);
-void Gecko_StoreStyleDifference(RawGeckoNodeBorrowed node, nsChangeHint change);
+
+// Element snapshot.
+ServoElementSnapshotOwned Gecko_CreateElementSnapshot(RawGeckoElementBorrowed element);
+void Gecko_DropElementSnapshot(ServoElementSnapshotOwned snapshot);
 
 // `array` must be an nsTArray
 // If changing this signature, please update the
 // friend function declaration in nsTArray.h
 void Gecko_EnsureTArrayCapacity(void* array, size_t capacity, size_t elem_size);
 
 // Same here, `array` must be an nsTArray<T>, for some T.
 //
--- a/layout/style/ServoElementSnapshot.cpp
+++ b/layout/style/ServoElementSnapshot.cpp
@@ -6,28 +6,32 @@
 
 #include "mozilla/ServoElementSnapshot.h"
 #include "mozilla/dom/Element.h"
 #include "nsIContentInlines.h"
 #include "nsContentUtils.h"
 
 namespace mozilla {
 
-ServoElementSnapshot::ServoElementSnapshot(Element* aElement)
+ServoElementSnapshot::ServoElementSnapshot(const Element* aElement)
   : mContains(Flags(0))
   , mState(0)
-  , mExplicitRestyleHint(nsRestyleHint(0))
-  , mExplicitChangeHint(nsChangeHint(0))
 {
+  MOZ_COUNT_CTOR(ServoElementSnapshot);
   mIsHTMLElementInHTMLDocument =
     aElement->IsHTMLElement() && aElement->IsInHTMLDocument();
   mIsInChromeDocument =
     nsContentUtils::IsChromeDoc(aElement->OwnerDoc());
 }
 
+ServoElementSnapshot::~ServoElementSnapshot()
+{
+  MOZ_COUNT_DTOR(ServoElementSnapshot);
+}
+
 void
 ServoElementSnapshot::AddAttrs(Element* aElement)
 {
   MOZ_ASSERT(aElement);
 
   if (HasAny(Flags::Attributes)) {
     return;
   }
--- a/layout/style/ServoElementSnapshot.h
+++ b/layout/style/ServoElementSnapshot.h
@@ -62,17 +62,18 @@ class ServoElementSnapshot
 {
   typedef dom::BorrowedAttrInfo BorrowedAttrInfo;
   typedef dom::Element Element;
   typedef EventStates::ServoType ServoStateType;
 
 public:
   typedef ServoElementSnapshotFlags Flags;
 
-  explicit ServoElementSnapshot(Element* aElement);
+  explicit ServoElementSnapshot(const Element* aElement);
+  ~ServoElementSnapshot();
 
   bool HasAttrs() { return HasAny(Flags::Attributes); }
 
   bool HasState() { return HasAny(Flags::State); }
 
   /**
    * Captures the given state (if not previously captured).
    */
@@ -84,30 +85,16 @@ public:
     }
   }
 
   /**
    * Captures the given element attributes (if not previously captured).
    */
   void AddAttrs(Element* aElement);
 
-  void AddExplicitChangeHint(nsChangeHint aMinChangeHint)
-  {
-    mExplicitChangeHint |= aMinChangeHint;
-  }
-
-  void AddExplicitRestyleHint(nsRestyleHint aRestyleHint)
-  {
-    mExplicitRestyleHint |= aRestyleHint;
-  }
-
-  nsRestyleHint ExplicitRestyleHint() { return mExplicitRestyleHint; }
-
-  nsChangeHint ExplicitChangeHint() { return mExplicitChangeHint; }
-
   /**
    * Needed methods for attribute matching.
    */
   BorrowedAttrInfo GetAttrInfoAt(uint32_t aIndex) const
   {
     if (aIndex >= mAttrs.Length()) {
       return BorrowedAttrInfo(nullptr, nullptr);
     }
@@ -153,17 +140,15 @@ public:
 private:
   // TODO: Profile, a 1 or 2 element AutoTArray could be worth it, given we know
   // we're dealing with attribute changes when we take snapshots of attributes,
   // though it can be wasted space if we deal with a lot of state-only
   // snapshots.
   Flags mContains;
   nsTArray<ServoAttrSnapshot> mAttrs;
   ServoStateType mState;
-  nsRestyleHint mExplicitRestyleHint;
-  nsChangeHint mExplicitChangeHint;
   bool mIsHTMLElementInHTMLDocument;
   bool mIsInChromeDocument;
 };
 
 } // namespace mozilla
 
 #endif
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -84,30 +84,36 @@ ServoStyleSet::EndUpdate()
   }
 
   // ... do something ...
   return NS_OK;
 }
 
 already_AddRefed<nsStyleContext>
 ServoStyleSet::ResolveStyleFor(Element* aElement,
-                               nsStyleContext* aParentContext)
+                               nsStyleContext* aParentContext,
+                               ConsumeStyleBehavior aConsume,
+                               LazyComputeBehavior aMayCompute)
 {
   return GetContext(aElement, aParentContext, nullptr,
-                    CSSPseudoElementType::NotPseudo);
+                    CSSPseudoElementType::NotPseudo, aConsume, aMayCompute);
 }
 
 already_AddRefed<nsStyleContext>
 ServoStyleSet::GetContext(nsIContent* aContent,
                           nsStyleContext* aParentContext,
                           nsIAtom* aPseudoTag,
-                          CSSPseudoElementType aPseudoType)
+                          CSSPseudoElementType aPseudoType,
+                          ConsumeStyleBehavior aConsume,
+                          LazyComputeBehavior aMayCompute)
 {
+  MOZ_ASSERT(aContent->IsElement());
+  Element* element = aContent->AsElement();
   RefPtr<ServoComputedValues> computedValues =
-    Servo_ComputedValues_Get(aContent).Consume();
+    Servo_ResolveStyle(element, mRawSet.get(), aConsume, aMayCompute).Consume();
   MOZ_ASSERT(computedValues);
   return GetContext(computedValues.forget(), aParentContext, aPseudoTag, aPseudoType);
 }
 
 already_AddRefed<nsStyleContext>
 ServoStyleSet::GetContext(already_AddRefed<ServoComputedValues> aComputedValues,
                           nsStyleContext* aParentContext,
                           nsIAtom* aPseudoTag,
@@ -121,22 +127,24 @@ ServoStyleSet::GetContext(already_AddRef
 
   return NS_NewStyleContext(aParentContext, mPresContext, aPseudoTag,
                             aPseudoType, Move(aComputedValues), skipFixup);
 }
 
 already_AddRefed<nsStyleContext>
 ServoStyleSet::ResolveStyleFor(Element* aElement,
                                nsStyleContext* aParentContext,
+                               ConsumeStyleBehavior aConsume,
+                               LazyComputeBehavior aMayCompute,
                                TreeMatchContext& aTreeMatchContext)
 {
   // aTreeMatchContext is used to speed up selector matching,
   // but if the element already has a ServoComputedValues computed in
   // advance, then we shouldn't need to use it.
-  return ResolveStyleFor(aElement, aParentContext);
+  return ResolveStyleFor(aElement, aParentContext, aConsume, aMayCompute);
 }
 
 already_AddRefed<nsStyleContext>
 ServoStyleSet::ResolveStyleForText(nsIContent* aTextNode,
                                    nsStyleContext* aParentContext)
 {
   MOZ_ASSERT(aTextNode && aTextNode->IsNodeOfType(nsINode::eTEXT));
   MOZ_ASSERT(aTextNode->GetParent());
@@ -432,63 +440,44 @@ ServoStyleSet::HasStateDependentStyle(do
                                       CSSPseudoElementType aPseudoType,
                                      dom::Element* aPseudoElement,
                                      EventStates aStateMask)
 {
   NS_WARNING("stylo: HasStateDependentStyle always returns zero!");
   return nsRestyleHint(0);
 }
 
-nsRestyleHint
-ServoStyleSet::ComputeRestyleHint(dom::Element* aElement,
-                                  ServoElementSnapshot* aSnapshot)
-{
-  return Servo_ComputeRestyleHint(aElement, aSnapshot, mRawSet.get());
-}
-
-static void
-ClearDirtyBits(nsIContent* aContent)
-{
-  bool traverseDescendants = aContent->HasDirtyDescendantsForServo();
-  aContent->UnsetIsDirtyAndHasDirtyDescendantsForServo();
-  if (!traverseDescendants) {
-    return;
-  }
-
-  StyleChildrenIterator it(aContent);
-  for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
-    ClearDirtyBits(n);
-  }
-}
-
 void
-ServoStyleSet::StyleDocument(bool aLeaveDirtyBits)
+ServoStyleSet::StyleDocument()
 {
   // Grab the root.
   nsIDocument* doc = mPresContext->Document();
-  nsIContent* root = doc->GetRootElement();
+  Element* root = doc->GetRootElement();
   MOZ_ASSERT(root);
 
-  // Restyle the document, clearing the dirty bits if requested.
-  Servo_RestyleSubtree(root, mRawSet.get());
-  if (!aLeaveDirtyBits) {
-    ClearDirtyBits(root);
-    doc->UnsetHasDirtyDescendantsForServo();
-  }
+  // Restyle the document.
+  Servo_TraverseSubtree(root, mRawSet.get(), SkipRootBehavior::DontSkip);
 }
 
 void
 ServoStyleSet::StyleNewSubtree(nsIContent* aContent)
 {
-  MOZ_ASSERT(aContent->IsDirtyForServo());
-  if (aContent->IsElement() || aContent->IsNodeOfType(nsINode::eTEXT)) {
-    Servo_RestyleSubtree(aContent, mRawSet.get());
+  if (aContent->IsElement()) {
+    Servo_TraverseSubtree(aContent->AsElement(), mRawSet.get(), SkipRootBehavior::DontSkip);
   }
-  ClearDirtyBits(aContent);
 }
 
 void
 ServoStyleSet::StyleNewChildren(nsIContent* aParent)
 {
-  MOZ_ASSERT(aParent->HasDirtyDescendantsForServo());
-  Servo_RestyleSubtree(aParent, mRawSet.get());
-  ClearDirtyBits(aParent);
+  MOZ_ASSERT(aParent->IsElement());
+  Servo_TraverseSubtree(aParent->AsElement(), mRawSet.get(), SkipRootBehavior::Skip);
 }
+
+#ifdef DEBUG
+void
+ServoStyleSet::AssertTreeIsClean()
+{
+  if (Element* root = mPresContext->Document()->GetRootElement()) {
+    Servo_AssertTreeIsClean(root);
+  }
+}
+#endif
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -51,21 +51,25 @@ public:
   bool GetAuthorStyleDisabled() const;
   nsresult SetAuthorStyleDisabled(bool aStyleDisabled);
 
   void BeginUpdate();
   nsresult EndUpdate();
 
   already_AddRefed<nsStyleContext>
   ResolveStyleFor(dom::Element* aElement,
-                  nsStyleContext* aParentContext);
+                  nsStyleContext* aParentContext,
+                  ConsumeStyleBehavior aConsume,
+                  LazyComputeBehavior aMayCompute);
 
   already_AddRefed<nsStyleContext>
   ResolveStyleFor(dom::Element* aElement,
                   nsStyleContext* aParentContext,
+                  ConsumeStyleBehavior aConsume,
+                  LazyComputeBehavior aMayCompute,
                   TreeMatchContext& aTreeMatchContext);
 
   already_AddRefed<nsStyleContext>
   ResolveStyleForText(nsIContent* aTextNode,
                       nsStyleContext* aParentContext);
 
   already_AddRefed<nsStyleContext>
   ResolveStyleForOtherNonElement(nsStyleContext* aParentContext);
@@ -113,29 +117,20 @@ public:
   // Test if style is dependent on content state
   nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
                                        EventStates aStateMask);
   nsRestyleHint HasStateDependentStyle(
     dom::Element* aElement, mozilla::CSSPseudoElementType aPseudoType,
     dom::Element* aPseudoElement, EventStates aStateMask);
 
   /**
-   * Computes a restyle hint given a element and a previous element snapshot.
-   */
-  nsRestyleHint ComputeRestyleHint(dom::Element* aElement,
-                                   ServoElementSnapshot* aSnapshot);
-
-  /**
    * Performs a Servo traversal to compute style for all dirty nodes in the
    * document. The root element must be non-null.
-   *
-   * If aLeaveDirtyBits is true, the dirty/dirty-descendant bits are not
-   * cleared.
    */
-  void StyleDocument(bool aLeaveDirtyBits);
+  void StyleDocument();
 
   /**
    * Eagerly styles a subtree of dirty nodes that were just appended to the
    * tree. This is used in situations where we need the style immediately and
    * cannot wait for a future batch restyle.
    *
    * The subtree must have the root dirty bit set, which currently gets
    * propagated to all descendants. The dirty bits are cleared before
@@ -147,26 +142,34 @@ public:
    * Like the above, but does not assume that the root node is dirty. When
    * appending multiple children to a potentially-non-dirty node, it's
    * preferable to call StyleNewChildren on the node rather than making multiple
    * calls to StyleNewSubtree on each child, since it allows for more
    * parallelism.
    */
   void StyleNewChildren(nsIContent* aParent);
 
+#ifdef DEBUG
+  void AssertTreeIsClean();
+#else
+  void AssertTreeIsClean() {}
+#endif
+
 private:
   already_AddRefed<nsStyleContext> GetContext(already_AddRefed<ServoComputedValues>,
                                               nsStyleContext* aParentContext,
                                               nsIAtom* aPseudoTag,
                                               CSSPseudoElementType aPseudoType);
 
   already_AddRefed<nsStyleContext> GetContext(nsIContent* aContent,
                                               nsStyleContext* aParentContext,
                                               nsIAtom* aPseudoTag,
-                                              CSSPseudoElementType aPseudoType);
+                                              CSSPseudoElementType aPseudoType,
+                                              ConsumeStyleBehavior aConsume,
+                                              LazyComputeBehavior aMayCompute);
 
   nsPresContext* mPresContext;
   UniquePtr<RawServoStyleSet> mRawSet;
   EnumeratedArray<SheetType, SheetType::Count,
                   nsTArray<RefPtr<ServoStyleSheet>>> mSheets;
   int32_t mBatching;
 };
 
--- a/layout/style/ServoTypes.h
+++ b/layout/style/ServoTypes.h
@@ -30,11 +30,34 @@ struct ServoUnsafeCell {
 template<typename T>
 struct ServoCell {
   ServoUnsafeCell<T> value;
   T Get() const { return value.value; }
   void Set(T arg) { value.value = arg; }
   ServoCell() : value() {};
 };
 
+// Indicates whether a style resolution should be considered to consume the style
+// for the construction of a layout frame, or whether the caller is just
+// peeking.
+enum class ConsumeStyleBehavior {
+  Consume,
+  DontConsume,
+};
+
+// Indicates whether the Servo style system should expect the style on an element
+// to have already been resolved (i.e. via a parallel traversal), or whether it
+// may be lazily computed.
+enum class LazyComputeBehavior {
+  Allow,
+  Assert,
+};
+
+// Indicates whether the Servo style system should skip processing on the root
+// element and start processing with its children.
+enum class SkipRootBehavior {
+  Skip,
+  DontSkip,
+};
+
 } // namespace mozilla
 
 #endif // mozilla_ServoTypes_h
--- a/layout/style/StyleSetHandle.h
+++ b/layout/style/StyleSetHandle.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_StyleSetHandle_h
 #define mozilla_StyleSetHandle_h
 
 #include "mozilla/EventStates.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/ServoTypes.h"
 #include "mozilla/SheetType.h"
 #include "mozilla/StyleBackendType.h"
 #include "mozilla/StyleSheet.h"
 #include "nsChangeHint.h"
 #include "nsCSSPseudoElements.h"
 #include "nsTArray.h"
 
 namespace mozilla {
@@ -109,20 +110,24 @@ public:
     inline void BeginShutdown();
     inline void Shutdown();
     inline bool GetAuthorStyleDisabled() const;
     inline nsresult SetAuthorStyleDisabled(bool aStyleDisabled);
     inline void BeginUpdate();
     inline nsresult EndUpdate();
     inline already_AddRefed<nsStyleContext>
     ResolveStyleFor(dom::Element* aElement,
-                    nsStyleContext* aParentContext);
+                    nsStyleContext* aParentContext,
+                    ConsumeStyleBehavior aConsume,
+                    LazyComputeBehavior aMayCompute);
     inline already_AddRefed<nsStyleContext>
     ResolveStyleFor(dom::Element* aElement,
                     nsStyleContext* aParentContext,
+                    ConsumeStyleBehavior aConsume,
+                    LazyComputeBehavior aMayCompute,
                     TreeMatchContext& aTreeMatchContext);
     inline already_AddRefed<nsStyleContext>
     ResolveStyleForText(nsIContent* aTextNode,
                         nsStyleContext* aParentContext);
     inline already_AddRefed<nsStyleContext>
     ResolveStyleForOtherNonElement(nsStyleContext* aParentContext);
     inline already_AddRefed<nsStyleContext>
     ResolvePseudoElementStyle(dom::Element* aParentElement,
--- a/layout/style/StyleSetHandleInlines.h
+++ b/layout/style/StyleSetHandleInlines.h
@@ -74,27 +74,31 @@ nsresult
 StyleSetHandle::Ptr::EndUpdate()
 {
   FORWARD(EndUpdate, ());
 }
 
 // resolve a style context
 already_AddRefed<nsStyleContext>
 StyleSetHandle::Ptr::ResolveStyleFor(dom::Element* aElement,
-                                     nsStyleContext* aParentContext)
+                                     nsStyleContext* aParentContext,
+                                     ConsumeStyleBehavior aConsume,
+                                     LazyComputeBehavior aMayCompute)
 {
-  FORWARD(ResolveStyleFor, (aElement, aParentContext));
+  FORWARD(ResolveStyleFor, (aElement, aParentContext, aConsume, aMayCompute));
 }
 
 already_AddRefed<nsStyleContext>
 StyleSetHandle::Ptr::ResolveStyleFor(dom::Element* aElement,
                                      nsStyleContext* aParentContext,
+                                     ConsumeStyleBehavior aConsume,
+                                     LazyComputeBehavior aMayCompute,
                                      TreeMatchContext& aTreeMatchContext)
 {
-  FORWARD(ResolveStyleFor, (aElement, aParentContext, aTreeMatchContext));
+  FORWARD(ResolveStyleFor, (aElement, aParentContext, aConsume, aMayCompute, aTreeMatchContext));
 }
 
 already_AddRefed<nsStyleContext>
 StyleSetHandle::Ptr::ResolveStyleForText(nsIContent* aTextNode,
                                          nsStyleContext* aParentContext)
 {
   FORWARD(ResolveStyleForText, (aTextNode, aParentContext));
 }
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -490,17 +490,18 @@ nsComputedDOMStyle::GetStyleContextForEl
       return nullptr;
     }
     nsIFrame* frame = nsLayoutUtils::GetStyleFrame(aElement);
     Element* pseudoElement =
       frame && inDocWithShell ? frame->GetPseudoElement(type) : nullptr;
     sc = styleSet->ResolvePseudoElementStyle(aElement, type, parentContext,
                                              pseudoElement);
   } else {
-    sc = styleSet->ResolveStyleFor(aElement, parentContext);
+    sc = styleSet->ResolveStyleFor(aElement, parentContext, ConsumeStyleBehavior::DontConsume,
+                                   LazyComputeBehavior::Allow);
   }
 
   if (aStyleType == eDefaultOnly) {
     if (styleSet->IsServo()) {
       NS_ERROR("stylo: ServoStyleSets cannot supply UA-only styles yet");
       return nullptr;
     }
 
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -558,18 +558,18 @@ static nscoord CalcLengthWith(const nsCS
         const nsStyleFont *rootStyleFont = styleFont;
         Element* docElement = aPresContext->Document()->GetRootElement();
 
         if (docElement) {
           nsIFrame* rootFrame = docElement->GetPrimaryFrame();
           if (rootFrame) {
             rootStyle = rootFrame->StyleContext();
           } else {
-            rootStyle = aPresContext->StyleSet()->ResolveStyleFor(docElement,
-                                                                  nullptr);
+            rootStyle = aPresContext->StyleSet()->AsGecko()->ResolveStyleFor(docElement,
+                                                                             nullptr);
           }
           rootStyleFont = rootStyle->StyleFont();
         }
 
         rootFontSize = rootStyleFont->mFont.size;
       }
 
       return ScaleCoordRound(aValue, float(rootFontSize));
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -86,22 +86,16 @@ nsStyleContext::nsStyleContext(nsStyleCo
   , mPseudoTag(aPseudoTag)
   , mSource(Move(aSource))
 #ifdef MOZ_STYLO
   , mPresContext(nullptr)
 #endif
   , mCachedResetData(nullptr)
   , mBits(((uint64_t)aPseudoType) << NS_STYLE_CONTEXT_TYPE_SHIFT)
   , mRefCnt(0)
-#ifdef MOZ_STYLO
-  , mStoredChangeHint(nsChangeHint(0))
-#ifdef DEBUG
-  , mConsumedChangeHint(false)
-#endif
-#endif
 #ifdef DEBUG
   , mFrameRefCnt(0)
   , mComputingStruct(nsStyleStructID_None)
 #endif
 {
   MOZ_COUNT_CTOR(nsStyleContext);
 }
 
@@ -791,17 +785,17 @@ nsStyleContext::ApplyStyleFixups(bool aS
 
   // CSS 2.1 10.1: Propagate the root element's 'direction' to the ICB.
   // (PageContentFrame/CanvasFrame etc will inherit 'direction')
   if (mPseudoTag == nsCSSAnonBoxes::viewport) {
     nsPresContext* presContext = PresContext();
     mozilla::dom::Element* docElement = presContext->Document()->GetRootElement();
     if (docElement) {
       RefPtr<nsStyleContext> rootStyle =
-        presContext->StyleSet()->ResolveStyleFor(docElement, nullptr);
+        presContext->StyleSet()->AsGecko()->ResolveStyleFor(docElement, nullptr);
       auto dir = rootStyle->StyleVisibility()->mDirection;
       if (dir != StyleVisibility()->mDirection) {
         nsStyleVisibility* uniqueVisibility = GET_UNIQUE_STYLE_DATA(Visibility);
         uniqueVisibility->mDirection = dir;
       }
     }
   }
 
--- a/layout/style/nsStyleContext.h
+++ b/layout/style/nsStyleContext.h
@@ -507,55 +507,16 @@ public:
     } else {
       cachedData = mCachedInheritedData.mStyleStructs[aSID];
     }
     return cachedData;
   }
 
   mozilla::NonOwningStyleContextSource StyleSource() const { return mSource.AsRaw(); }
 
-#ifdef MOZ_STYLO
-  // NOTE: It'd be great to assert here that the previous change hint is always
-  // consumed.
-  //
-  // This is not the case right now, since the changes of childs of frames that
-  // go through frame construction are not consumed.
-  void StoreChangeHint(nsChangeHint aHint)
-  {
-    MOZ_ASSERT(!IsShared());
-    mStoredChangeHint = aHint;
-#ifdef DEBUG
-    mConsumedChangeHint = false;
-#endif
-  }
-
-  nsChangeHint ConsumeStoredChangeHint()
-  {
-    MOZ_ASSERT(!mConsumedChangeHint, "Re-consuming the same change hint!");
-    nsChangeHint result = mStoredChangeHint;
-    mStoredChangeHint = nsChangeHint(0);
-#ifdef DEBUG
-    mConsumedChangeHint = true;
-#endif
-    return result;
-  }
-#else
-  void StoreChangeHint(nsChangeHint aHint)
-  {
-    MOZ_CRASH("stylo: Called nsStyleContext::StoreChangeHint in a non MOZ_STYLO "
-              "build.");
-  }
-
-  nsChangeHint ConsumeStoredChangeHint()
-  {
-    MOZ_CRASH("stylo: Called nsStyleContext::ComsumeStoredChangeHint in a non "
-               "MOZ_STYLO build.");
-  }
-#endif
-
 private:
   // Private destructor, to discourage deletion outside of Release():
   ~nsStyleContext();
 
   // Delegated Helper constructor.
   nsStyleContext(nsStyleContext* aParent,
                  mozilla::OwningStyleContextSource&& aSource,
                  nsIAtom* aPseudoTag,
@@ -804,25 +765,16 @@ private:
   //    inherited from the parent context or owned by the rule node (i.e.,
   //    not owned by the style context).
   //  - It also stores the additional bits listed at the top of
   //    nsStyleStruct.h.
   uint64_t                mBits;
 
   uint32_t                mRefCnt;
 
-  // For now we store change hints on the style context during parallel traversal.
-  // We should improve this - see bug 1289861.
-#ifdef MOZ_STYLO
-  nsChangeHint            mStoredChangeHint;
-#ifdef DEBUG
-  bool                    mConsumedChangeHint;
-#endif
-#endif
-
 #ifdef DEBUG
   uint32_t                mFrameRefCnt; // number of frames that use this
                                         // as their style context
 
   nsStyleStructID         mComputingStruct;
 
   static bool DependencyAllowed(nsStyleStructID aOuterSID,
                                 nsStyleStructID aInnerSID)
--- a/layout/style/nsStyleSet.h
+++ b/layout/style/nsStyleSet.h
@@ -12,16 +12,17 @@
 #ifndef nsStyleSet_h_
 #define nsStyleSet_h_
 
 #include "mozilla/Attributes.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/ServoTypes.h"
 #include "mozilla/SheetType.h"
 
 #include "nsIStyleRuleProcessor.h"
 #include "nsBindingManager.h"
 #include "nsRuleNode.h"
 #include "nsTArray.h"
 #include "nsCOMArray.h"
 #include "nsIStyleRule.h"
@@ -110,18 +111,37 @@ class nsStyleSet final
   // get a style context for a non-pseudo frame.
   already_AddRefed<nsStyleContext>
   ResolveStyleFor(mozilla::dom::Element* aElement,
                   nsStyleContext* aParentContext);
 
   already_AddRefed<nsStyleContext>
   ResolveStyleFor(mozilla::dom::Element* aElement,
                   nsStyleContext* aParentContext,
+                  mozilla::ConsumeStyleBehavior,
+                  mozilla::LazyComputeBehavior)
+  {
+    return ResolveStyleFor(aElement, aParentContext);
+  }
+
+  already_AddRefed<nsStyleContext>
+  ResolveStyleFor(mozilla::dom::Element* aElement,
+                  nsStyleContext* aParentContext,
                   TreeMatchContext& aTreeMatchContext);
 
+  already_AddRefed<nsStyleContext>
+  ResolveStyleFor(mozilla::dom::Element* aElement,
+                  nsStyleContext* aParentContext,
+                  mozilla::ConsumeStyleBehavior aConsume,
+                  mozilla::LazyComputeBehavior aMayCompute,
+                  TreeMatchContext& aTreeMatchContext)
+  {
+    return ResolveStyleFor(aElement, aParentContext, aTreeMatchContext);
+  }
+
   // Get a style context (with the given parent) for the
   // sequence of style rules in the |aRules| array.
   already_AddRefed<nsStyleContext>
   ResolveStyleForRules(nsStyleContext* aParentContext,
                        const nsTArray< nsCOMPtr<nsIStyleRule> > &aRules);
 
   // Get a style context that represents aBaseContext, but as though
   // it additionally matched the rules in the aRules array (in that
--- a/layout/xul/nsListBoxBodyFrame.cpp
+++ b/layout/xul/nsListBoxBodyFrame.cpp
@@ -697,17 +697,19 @@ nsListBoxBodyFrame::ComputeIntrinsicISiz
   nsCOMPtr<nsIDOMElement> firstRowEl;
   GetItemAtIndex(index, getter_AddRefs(firstRowEl));
   nsCOMPtr<nsIContent> firstRowContent(do_QueryInterface(firstRowEl));
 
   if (firstRowContent) {
     RefPtr<nsStyleContext> styleContext;
     nsPresContext *presContext = aBoxLayoutState.PresContext();
     styleContext = presContext->StyleSet()->
-      ResolveStyleFor(firstRowContent->AsElement(), nullptr);
+      ResolveStyleFor(firstRowContent->AsElement(), nullptr,
+                      ConsumeStyleBehavior::DontConsume,
+                      LazyComputeBehavior::Allow);
 
     nscoord width = 0;
     nsMargin margin(0,0,0,0);
 
     if (styleContext->StylePadding()->GetPadding(margin))
       width += margin.LeftRight();
     width += styleContext->StyleBorder()->GetComputedBorder().LeftRight();
     if (styleContext->StyleMargin()->GetMargin(margin))
--- a/layout/xul/nsSplitterFrame.cpp
+++ b/layout/xul/nsSplitterFrame.cpp
@@ -281,17 +281,19 @@ nsSplitterFrame::Init(nsIContent*       
   if (aParent && aParent->IsXULBoxFrame()) {
     if (!aParent->IsXULHorizontal()) {
       if (!nsContentUtils::HasNonEmptyAttr(aContent, kNameSpaceID_None,
                                            nsGkAtoms::orient)) {
         aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
                           NS_LITERAL_STRING("vertical"), false);
         nsStyleContext* parentStyleContext = StyleContext()->GetParent();
         RefPtr<nsStyleContext> newContext = PresContext()->StyleSet()->
-          ResolveStyleFor(aContent->AsElement(), parentStyleContext);
+          ResolveStyleFor(aContent->AsElement(), parentStyleContext,
+                          ConsumeStyleBehavior::Consume,
+                          LazyComputeBehavior::Allow);
         SetStyleContextWithoutNotification(newContext);
       }
     }
   }
 
   nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
 
   mInner->mState = nsSplitterFrameInner::Open;
--- a/netwerk/base/PrivateBrowsingChannel.h
+++ b/netwerk/base/PrivateBrowsingChannel.h
@@ -9,16 +9,17 @@
 
 #include "nsIPrivateBrowsingChannel.h"
 #include "nsCOMPtr.h"
 #include "nsILoadGroup.h"
 #include "nsILoadContext.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsNetUtil.h"
+#include "mozilla/Unused.h"
 
 namespace mozilla {
 namespace net {
 
 template <class Channel>
 class PrivateBrowsingChannel : public nsIPrivateBrowsingChannel
 {
 public:
@@ -66,20 +67,30 @@ public:
   // Must be called every time the channel's callbacks or loadGroup is updated
   void UpdatePrivateBrowsing()
   {
       // once marked as private we never go un-private
       if (mPrivateBrowsing) {
           return;
       }
 
+      auto channel = static_cast<Channel*>(this);
+
       nsCOMPtr<nsILoadContext> loadContext;
-      NS_QueryNotificationCallbacks(static_cast<Channel*>(this), loadContext);
+      NS_QueryNotificationCallbacks(channel, loadContext);
       if (loadContext) {
           mPrivateBrowsing = loadContext->UsePrivateBrowsing();
+          return;
+      }
+
+      nsCOMPtr<nsILoadInfo> loadInfo;
+      Unused << channel->GetLoadInfo(getter_AddRefs(loadInfo));
+      if (loadInfo) {
+          NeckoOriginAttributes attrs = loadInfo->GetOriginAttributes();
+          mPrivateBrowsing = attrs.mPrivateBrowsingId > 0;
       }
   }
 
   bool CanSetCallbacks(nsIInterfaceRequestor* aCallbacks) const
   {
       // Make sure that the private bit override flag is not set.
       // This is a fatal error in debug builds, and a runtime error in release
       // builds.
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -154,26 +154,30 @@ void CrashWithReason(const char * reason
 }
 
 const char*
 NeckoParent::GetValidatedOriginAttributes(const SerializedLoadContext& aSerialized,
                                           PContentParent* aContent,
                                           nsIPrincipal* aRequestingPrincipal,
                                           DocShellOriginAttributes& aAttrs)
 {
-  if (!aSerialized.IsNotNull()) {
-    if (UsingNeckoIPCSecurity()) {
-      CrashWithReason("GetValidatedOriginAttributes | SerializedLoadContext from child is null");
-      return "SerializedLoadContext from child is null";
+  if (!UsingNeckoIPCSecurity()) {
+    if (!aSerialized.IsNotNull()) {
+      // If serialized is null, we cannot validate anything. We have to assume
+      // that this requests comes from a SystemPrincipal.
+      aAttrs = DocShellOriginAttributes(NECKO_NO_APP_ID, false);
+    } else {
+      aAttrs = aSerialized.mOriginAttributes;
     }
+    return nullptr;
+  }
 
-    // If serialized is null, we cannot validate anything. We have to assume
-    // that this requests comes from a SystemPrincipal.
-    aAttrs = DocShellOriginAttributes(NECKO_NO_APP_ID, false);
-    return nullptr;
+  if (!aSerialized.IsNotNull()) {
+    CrashWithReason("GetValidatedOriginAttributes | SerializedLoadContext from child is null");
+    return "SerializedLoadContext from child is null";
   }
 
   nsTArray<TabContext> contextArray =
     static_cast<ContentParent*>(aContent)->GetManagedTabContext();
 
   nsAutoCString serializedSuffix;
   aSerialized.mOriginAttributes.CreateAnonymizedSuffix(serializedSuffix);
 
@@ -208,22 +212,16 @@ NeckoParent::GetValidatedOriginAttribute
     if (swm &&
         swm->MayHaveActiveServiceWorkerInstance(static_cast<ContentParent*>(aContent),
                                                 aRequestingPrincipal)) {
       aAttrs = aSerialized.mOriginAttributes;
       return nullptr;
     }
   }
 
-  if (!UsingNeckoIPCSecurity()) {
-    // We are running some tests
-    aAttrs = aSerialized.mOriginAttributes;
-    return nullptr;
-  }
-
   nsAutoCString errorString;
   errorString.Append("GetValidatedOriginAttributes | App does not have permission -");
   errorString.Append(debugString);
 
   // Leak the buffer on the heap to make sure that it lives long enough, as
   // MOZ_CRASH_ANNOTATE expects the pointer passed to it to live to the end of
   // the program.
   char * error = strdup(errorString.BeginReading());
--- a/python/mozboot/mozboot/base.py
+++ b/python/mozboot/mozboot/base.py
@@ -70,18 +70,19 @@ We recommend the following tools for ins
 
     pyenv   -- https://github.com/yyuu/pyenv)
     pythonz -- https://github.com/saghul/pythonz
     official installers -- http://www.python.org/
 '''
 
 RUST_NOT_IN_PATH = '''
 You have some rust files in %(cargo_bin)s, but they're not part of the
-standard PATH.
+standard PATH.'''
 
+RUST_PATH_ADVICE = '''
 To make these available, please add this directory to the PATH variable
 in your shell initialization script, which may be called ~/.bashrc or
 ~/.bash_profile or ~/.profile. Edit this and add the following line:
 
     source %(cargo_home)s/env
 
 Then restart your shell and run the bootstrap script again.
 '''
@@ -509,35 +510,39 @@ class BaseBootstrapper(object):
         cargo = self.which('cargo')
 
         our = self._parse_version(rustc)
         if not our:
             return False, None
 
         return our >= MODERN_RUST_VERSION, our
 
+    def cargo_home(self):
+        cargo_home = os.environ.get('CARGO_HOME',
+                os.path.expanduser(os.path.join('~', '.cargo')))
+        cargo_bin = os.path.join(cargo_home, 'bin')
+        return cargo_home, cargo_bin
+
     def ensure_rust_modern(self):
         modern, version = self.is_rust_modern()
 
         if modern:
             print('Your version of Rust (%s) is new enough.' % version)
             return
 
         if not version:
             # Rust wasn't in PATH. Try the standard path.
-            cargo_home = os.environ.get('CARGO_HOME',
-                    os.path.expanduser(os.path.join('~', '.cargo')))
-            cargo_bin = os.path.join(cargo_home, 'bin')
+            cargo_home, cargo_bin = self.cargo_home()
             try_rustc = os.path.join(cargo_bin, 'rustc' + rust.exe_suffix())
             try_cargo = os.path.join(cargo_bin, 'cargo' + rust.exe_suffix())
             have_rustc = os.path.exists(try_rustc)
             have_cargo = os.path.exists(try_cargo)
             if have_rustc or have_cargo:
-                print(RUST_NOT_IN_PATH % { 'cargo_bin': cargo_bin,
-                                           'cargo_home': cargo_home })
+                print(RUST_NOT_IN_PATH % { 'cargo_bin': cargo_bin })
+                print(RUST_PATH_ADVICE % { 'cargo_home': cargo_home })
                 sys.exit(1)
 
         rustup = self.which('rustup')
         if rustup:
             rustup_version = self._parse_version(rustup)
             if not rustup_version:
                 print(RUSTUP_OLD)
                 sys.exit(1)
@@ -545,27 +550,26 @@ class BaseBootstrapper(object):
                 # We have rustup but no rustc.
                 # Try running rustup; maybe it will fix things.
                 print('Found rustup. Will try to upgrade.')
             else:
                 # We have both rustup and rustc.
                 print('Your version of Rust (%s) is too old. Will try to upgrade.' %
                   version)
             self.upgrade_rust(rustup)
+
+            modern, after = self.is_rust_modern()
+            if not modern:
+                print(RUST_UPGRADE_FAILED % (MODERN_RUST_VERSION, after))
+                sys.exit(1)
         else:
             # No rustc or rustup.
             print('Will try to install Rust.')
             self.install_rust()
 
-        modern, after = self.is_rust_modern()
-
-        if not modern:
-            print(RUST_UPGRADE_FAILED % (MODERN_RUST_VERSION, after))
-            sys.exit(1)
-
     def upgrade_rust(self, rustup):
         """Upgrade Rust.
 
         Invoke rustup from the given path to update the rust install."""
         subprocess.check_call([rustup, 'update'])
 
     def install_rust(self):
         """Download and run the rustup installer."""
@@ -586,17 +590,20 @@ class BaseBootstrapper(object):
             mode = os.stat(rustup_init).st_mode
             os.chmod(rustup_init, mode | stat.S_IRWXU)
             print('Ok')
             print('Running rustup-init...')
             subprocess.check_call([rustup_init, '-y',
                 '--default-toolchain', 'stable',
                 '--default-host', platform,
             ])
+            cargo_home, cargo_bin = self.cargo_home()
             print('Rust installation complete.')
+            print('You should now have rustc and cargo in %s' % cargo_bin)
+            print(RUST_PATH_ADVICE % { 'cargo_home': cargo_home })
         finally:
             try:
                 os.remove(rustup_init)
             except OSError as e:
                 if e.errno != errno.ENOENT:
                     raise
 
     def http_download_and_save(self, url, dest, sha256hexhash):
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -46,16 +46,18 @@ skip-if = os == 'android' # Android does
 [test_ext_background_canvas.html]
 [test_ext_content_security_policy.html]
 [test_ext_contentscript.html]
 [test_ext_contentscript_api_injection.html]
 [test_ext_contentscript_context.html]
 [test_ext_contentscript_create_iframe.html]
 [test_ext_contentscript_devtools_metadata.html]
 [test_ext_contentscript_exporthelpers.html]
+[test_ext_contentscript_incognito.html]
+skip-if = os == 'android' # Android does not multiple windows.
 [test_ext_contentscript_css.html]
 [test_ext_contentscript_about_blank.html]
 [test_ext_contentscript_teardown.html]
 skip-if = (os == 'android') # Android does not support tabs API. Bug 1260250
 [test_ext_exclude_include_globs.html]
 [test_ext_i18n_css.html]
 [test_ext_generate.html]
 [test_ext_notifications.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_incognito.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for content script private browsing ID</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+add_task(function* test_contentscript_incognito() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      content_scripts: [
+        {
+          "matches": ["http://mochi.test/*/file_sample.html"],
+          "js": ["content_script.js"],
+        },
+      ],
+    },
+
+    background() {
+      let windowId;
+
+      browser.test.onMessage.addListener(([msg, url]) => {
+        if (msg === "open-window") {
+          browser.windows.create({url, incognito: true}).then(window => {
+            windowId = window.id;
+          });
+        } else if (msg === "close-window") {
+          browser.windows.remove(windowId).then(() => {
+            browser.test.sendMessage("done");
+          });
+        }
+      });
+    },
+
+    files: {
+      "content_script.js": async () => {
+        const COOKIE = "foo=florgheralzps";
+        document.cookie = COOKIE;
+
+        let url = new URL("return_headers.sjs", location.href);
+
+        let responses = [
+          new Promise(resolve => {
+            let xhr = new XMLHttpRequest();
+            xhr.open("GET", url);
+            xhr.onload = () => resolve(JSON.parse(xhr.responseText));
+            xhr.send();
+          }),
+
+          fetch(url, {credentials: "include"}).then(body => body.json()),
+        ];
+
+        try {
+          for (let response of await Promise.all(responses)) {
+            browser.test.assertEq(COOKIE, response.cookie, "Got expected cookie header");
+          }
+          browser.test.notifyPass("cookies");
+        } catch (e) {
+          browser.test.fail(`Error: ${e}`);
+          browser.test.notifyFail("cookies");
+        }
+      },
+    },
+  });
+
+  yield extension.startup();
+
+  extension.sendMessage(["open-window", SimpleTest.getTestFileURL("file_sample.html")]);
+
+  yield extension.awaitFinish("cookies");
+
+  extension.sendMessage(["close-window"]);
+  yield extension.awaitMessage("done");
+
+  yield extension.unload();
+});
+</script>
+
+</body>
+</html>
+
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_suspend.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_suspend.html
@@ -59,11 +59,81 @@ add_task(function* test_suspend() {
 
   let headers = JSON.parse(yield result.text());
 
   is(headers.foo, "Bar", "Request header was correctly set on suspended request");
 
   yield extension.unload();
 });
 
+
+// Test that requests that were canceled while suspended for a blocking
+// listener are correctly resumed.
+add_task(function* test_error_resume() {
+  let chromeScript = SpecialPowers.loadChromeScript(() => {
+    const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+    Cu.import("resource://gre/modules/Services.jsm");
+
+    let observer = channel => {
+      if (channel instanceof Ci.nsIHttpChannel && channel.URI.spec === "http://example.com/") {
+        Services.obs.removeObserver(observer, "http-on-modify-request");
+
+        // Wait until the next tick to make sure this runs after WebRequest observers.
+        Promise.resolve().then(() => {
+          channel.cancel(Cr.NS_BINDING_ABORTED);
+        });
+      }
+    };
+
+    Services.obs.addObserver(observer, "http-on-modify-request", false);
+  });
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: [
+        "webRequest",
+        "webRequestBlocking",
+      ],
+    },
+
+    background() {
+      browser.webRequest.onBeforeSendHeaders.addListener(
+        details => {
+          browser.test.log(`onBeforeSendHeaders({url: ${details.url}})`);
+
+          if (details.url === "http://example.com/") {
+            browser.test.sendMessage("got-before-send-headers");
+          }
+        },
+        {urls: ["<all_urls>"]},
+        ["blocking"]);
+
+      browser.webRequest.onErrorOccurred.addListener(
+        details => {
+          browser.test.log(`onErrorOccurred({url: ${details.url}})`);
+
+          if (details.url === "http://example.com/") {
+            browser.test.sendMessage("got-error-occurred");
+          }
+        },
+        {urls: ["<all_urls>"]});
+    },
+  });
+
+  yield extension.startup();
+
+  try {
+    yield fetch("http://example.com/");
+    ok(false, "Fetch should have failed.");
+  } catch (e) {
+    ok(true, "Got expected error.");
+  }
+
+  yield extension.awaitMessage("got-before-send-headers");
+  yield extension.awaitMessage("got-error-occurred");
+
+  yield extension.unload();
+  chromeScript.destroy();
+});
+
 </script>
 </body>
 </html>
--- a/toolkit/modules/WebChannel.jsm
+++ b/toolkit/modules/WebChannel.jsm
@@ -4,16 +4,18 @@
 
 /**
  * WebChannel is an abstraction that uses the Message Manager and Custom Events
  * to create a two-way communication channel between chrome and content code.
  */
 
 this.EXPORTED_SYMBOLS = ["WebChannel", "WebChannelBroker"];
 
+const ERRNO_MISSING_PRINCIPAL          = 1;
+const ERRNO_NO_SUCH_CHANNEL            = 2;
 const ERRNO_UNKNOWN_ERROR              = 999;
 const ERROR_UNKNOWN                    = "UNKNOWN_ERROR";
 
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
@@ -78,32 +80,32 @@ var WebChannelBroker = Object.create({
       } catch (e) {
         Cu.reportError("Failed to parse WebChannel data as a JSON object");
         return;
       }
     }
 
     if (data && data.id) {
       if (!event.principal) {
-        this._sendErrorEventToContent(data.id, sendingContext, "Message principal missing");
+        this._sendErrorEventToContent(data.id, sendingContext, ERRNO_MISSING_PRINCIPAL, "Message principal missing");
       } else {
         let validChannelFound = false;
         data.message = data.message || {};
 
         for (var channel of this._channelMap.keys()) {
           if (channel.id === data.id &&
             channel._originCheckCallback(event.principal)) {
             validChannelFound = true;
             channel.deliver(data, sendingContext);
           }
         }
 
         // if no valid origins send an event that there is no such valid channel
         if (!validChannelFound) {
-          this._sendErrorEventToContent(data.id, sendingContext, "No Such Channel");
+          this._sendErrorEventToContent(data.id, sendingContext, ERRNO_NO_SUCH_CHANNEL, "No Such Channel");
         }
       }
     } else {
       Cu.reportError("WebChannel channel id missing");
     }
   },
   /**
    * The global message manager operates on every <browser>
@@ -122,25 +124,28 @@ var WebChannelBroker = Object.create({
    * @param id {String}
    *        The WebChannel id to include in the message
    * @param sendingContext {Object}
    *        Message sending context
    * @param [errorMsg] {String}
    *        Error message
    * @private
    */
-  _sendErrorEventToContent: function(id, sendingContext, errorMsg) {
+  _sendErrorEventToContent: function(id, sendingContext, errorNo, errorMsg) {
     let { browser: targetBrowser, eventTarget, principal: targetPrincipal } = sendingContext;
 
     errorMsg = errorMsg || "Web Channel Broker error";
 
     if (targetBrowser && targetBrowser.messageManager) {
       targetBrowser.messageManager.sendAsyncMessage("WebChannelMessageToContent", {
         id: id,
-        error: errorMsg,
+        message: {
+          errno: errorNo,
+          error: errorMsg,
+        },
       }, { eventTarget: eventTarget }, targetPrincipal);
     } else {
       Cu.reportError("Failed to send a WebChannel error. Target invalid.");
     }
     Cu.reportError(id.toString() + " error message. " + errorMsg);
   },
 });
 
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -775,32 +775,28 @@ HttpObserverManager = {
         if (opts.requestHeaders && result.requestHeaders && requestHeaders) {
           requestHeaders.applyChanges(result.requestHeaders);
         }
 
         if (opts.responseHeaders && result.responseHeaders && responseHeaders) {
           responseHeaders.applyChanges(result.responseHeaders);
         }
       }
+
+      if (kind === "opening") {
+        yield this.runChannelListener(channel, loadContext, "modify");
+      } else if (kind === "modify") {
+        yield this.runChannelListener(channel, loadContext, "afterModify");
+      }
     } catch (e) {
       Cu.reportError(e);
     }
 
-    if (kind === "opening") {
-      return this.runChannelListener(channel, loadContext, "modify");
-    } else if (kind === "modify") {
-      return this.runChannelListener(channel, loadContext, "afterModify");
-    }
-
-    // Only resume the channel if either it was suspended by this call,
-    // and this callback is not part of the opening-modify-afterModify
-    // chain, or if this is an afterModify callback. This allows us to
-    // chain the aforementioned handlers without repeatedly suspending
-    // and resuming the request.
-    if (shouldResume || kind === "afterModify") {
+    // Only resume the channel if either it was suspended by this call.
+    if (shouldResume) {
       this.maybeResume(channel);
     }
   }),
 
   examine(channel, topic, data) {
     let loadContext = this.getLoadContext(channel);
 
     if (this.needTracing) {
--- a/toolkit/themes/linux/global/notification.css
+++ b/toolkit/themes/linux/global/notification.css
@@ -63,12 +63,12 @@ notification[type="critical"] {
 
 .messageCloseButton {
   padding-left: 11px;
   padding-right: 11px;
 }
 
 %include ../../shared/popupnotification.inc.css
 
-.popup-notification-button:focus {
+.popup-notification-button:-moz-focusring {
   outline: 1px -moz-dialogtext dotted;
   outline-offset: -5px;
 }
--- a/toolkit/themes/osx/global/notification.css
+++ b/toolkit/themes/osx/global/notification.css
@@ -100,16 +100,16 @@ notification[type="info"]:not([value="tr
 @media (min-resolution: 2dppx) {
   .messageCloseButton > .toolbarbutton-icon {
     width: 16px;
   }
 }
 
 %include ../../shared/popupnotification.inc.css
 
-.popup-notification-button:focus {
+.popup-notification-button:-moz-focusring {
   outline: 2px -moz-mac-focusring solid;
   outline-offset: -2px;
 }
 
 .popup-notification-warning {
   color: #aa1b08;
 }
--- a/toolkit/themes/windows/global/notification.css
+++ b/toolkit/themes/windows/global/notification.css
@@ -63,17 +63,17 @@ notification[type="critical"] {
 }
 
 .messageCloseButton > .toolbarbutton-icon {
   margin-inline-end: 5px;
 }
 
 %include ../../shared/popupnotification.inc.css
 
-.popup-notification-button:focus {
+.popup-notification-button:-moz-focusring {
   outline: 1px -moz-dialogtext dotted;
   outline-offset: -1px;
 }
 
 /* Override default icon size which is too small for this dropdown */
 .popup-notification-dropmarker > .button-box > .button-menu-dropmarker {
   width: 16px;
   height: 16px;