Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 03 Oct 2017 14:37:35 -0700
changeset 384248 c97190c389c4cfef20fe55b4bacade95a36ae6ef
parent 384178 65a5054a1f922b83929c80658062f441ca3da6a0 (current diff)
parent 384247 ba9aea79271889b8709bbf8dba89b78210652ef7 (diff)
child 384289 ec43659fe0c4abfca02a5d4c7d1b5d1d8b2d48c4
child 384339 6669a57218a0181ec34680fe23caceb2d6e678e7
push id32624
push userkwierso@gmail.com
push dateTue, 03 Oct 2017 21:37:49 +0000
treeherdermozilla-central@c97190c389c4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone58.0a1
first release with
nightly linux32
c97190c389c4 / 58.0a1 / 20171003220138 / files
nightly linux64
c97190c389c4 / 58.0a1 / 20171003220138 / files
nightly mac
c97190c389c4 / 58.0a1 / 20171003220138 / files
nightly win32
c97190c389c4 / 58.0a1 / 20171003220138 / files
nightly win64
c97190c389c4 / 58.0a1 / 20171003220138 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge MozReview-Commit-ID: I1ge4dvWtTB
devtools/client/inspector/rules/rules.js
devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-01.js
devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-02.js
devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-03.js
devtools/client/shared/test/browser_mdn-docs-01.js
devtools/client/shared/test/browser_mdn-docs-02.js
devtools/client/shared/test/browser_mdn-docs-03.js
devtools/client/shared/test/doc_mdn-css-basic-testing.html
devtools/client/shared/test/doc_mdn-css-no-summary-or-syntax.html
devtools/client/shared/test/doc_mdn-css-no-summary.html
devtools/client/shared/test/doc_mdn-css-no-syntax.html
devtools/client/shared/test/doc_mdn-css-syntax-old-style.html
devtools/client/shared/test/doc_mdn-docs-01.html
devtools/client/shared/widgets/MdnDocsWidget.js
devtools/client/shared/widgets/mdn-docs.css
devtools/client/shared/widgets/tooltip/CssDocsTooltip.js
devtools/shared/gcli/commands/mdn.js
dom/tests/mochitest/webcomponents/test_custom_element_clone_callbacks.html
testing/web-platform/meta/custom-elements/reaction-timing.html.ini
testing/web-platform/meta/custom-elements/reactions/Node.html.ini
testing/web-platform/meta/custom-elements/upgrading/Node-cloneNode.html.ini
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1091,18 +1091,17 @@ DocAccessible::ARIAActiveDescendantChang
       }
     }
   }
 }
 
 void
 DocAccessible::ContentAppended(nsIDocument* aDocument,
                                nsIContent* aContainer,
-                               nsIContent* aFirstNewContent,
-                               int32_t /* unused */)
+                               nsIContent* aFirstNewContent)
 {
 }
 
 void
 DocAccessible::ContentStateChanged(nsIDocument* aDocument,
                                    nsIContent* aContent,
                                    EventStates aStateMask)
 {
@@ -1158,24 +1157,24 @@ void
 DocAccessible::CharacterDataChanged(nsIDocument* aDocument,
                                     nsIContent* aContent,
                                     CharacterDataChangeInfo* aInfo)
 {
 }
 
 void
 DocAccessible::ContentInserted(nsIDocument* aDocument, nsIContent* aContainer,
-                               nsIContent* aChild, int32_t /* unused */)
+                               nsIContent* aChild)
 {
 }
 
 void
 DocAccessible::ContentRemoved(nsIDocument* aDocument,
                               nsIContent* aContainerNode,
-                              nsIContent* aChildNode, int32_t /* unused */,
+                              nsIContent* aChildNode,
                               nsIContent* aPreviousSiblingNode)
 {
 #ifdef A11Y_LOG
   if (logging::IsEnabled(logging::eTree)) {
     logging::MsgBegin("TREE", "DOM content removed; doc: %p", this);
     logging::Node("container node", aContainerNode);
     logging::Node("content node", aChildNode);
     logging::MsgEnd();
@@ -1531,23 +1530,25 @@ DocAccessible::DoInitialUpdate()
   // this reorder event is processed by parent document then events targeted to
   // this document may be fired prior to this reorder event. If this is
   // a problem then consider to keep event processing per tab document.
   if (!IsRoot()) {
     RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
     ParentDocument()->FireDelayedEvent(reorderEvent);
   }
 
-  TreeMutation mt(this);
-  uint32_t childCount = ChildCount();
-  for (uint32_t i = 0; i < childCount; i++) {
-    Accessible* child = GetChildAt(i);
-    mt.AfterInsertion(child);
+  if (IPCAccessibilityActive()) {
+    DocAccessibleChild* ipcDoc = IPCDoc();
+    MOZ_ASSERT(ipcDoc);
+    if (ipcDoc) {
+      for (auto idx = 0U; idx < mChildren.Length(); idx++) {
+        ipcDoc->InsertIntoIpcTree(this, mChildren.ElementAt(idx), idx);
+      }
+    }
   }
-  mt.Done();
 }
 
 void
 DocAccessible::ProcessLoad()
 {
   mLoadState |= eCompletelyLoaded;
 
 #ifdef A11Y_LOG
--- a/accessible/ipc/DocAccessibleChildBase.cpp
+++ b/accessible/ipc/DocAccessibleChildBase.cpp
@@ -76,22 +76,35 @@ DocAccessibleChildBase::SerializeTree(Ac
 #endif
 
   for (uint32_t i = 0; i < childCount; i++) {
     SerializeTree(aRoot->GetChildAt(i), aTree);
   }
 }
 
 void
+DocAccessibleChildBase::InsertIntoIpcTree(Accessible* aParent,
+                                          Accessible* aChild,
+                                          uint32_t aIdxInParent)
+{
+  uint64_t parentID = aParent->IsDoc() ?
+    0 : reinterpret_cast<uint64_t>(aParent->UniqueID());
+  nsTArray<AccessibleData> shownTree;
+  ShowEventData data(parentID, aIdxInParent, shownTree, true);
+  SerializeTree(aChild, data.NewTree());
+  MaybeSendShowEvent(data, false);
+}
+
+void
 DocAccessibleChildBase::ShowEvent(AccShowEvent* aShowEvent)
 {
   Accessible* parent = aShowEvent->Parent();
   uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast<uint64_t>(parent->UniqueID());
   uint32_t idxInParent = aShowEvent->GetAccessible()->IndexInParent();
   nsTArray<AccessibleData> shownTree;
-  ShowEventData data(parentID, idxInParent, shownTree);
+  ShowEventData data(parentID, idxInParent, shownTree, false);
   SerializeTree(aShowEvent->GetAccessible(), data.NewTree());
   MaybeSendShowEvent(data, aShowEvent->IsFromUserInput());
 }
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/ipc/DocAccessibleChildBase.h
+++ b/accessible/ipc/DocAccessibleChildBase.h
@@ -40,16 +40,21 @@ public:
   }
 
   virtual void Shutdown()
   {
     DetachDocument();
     SendShutdown();
   }
 
+  /**
+   * Serializes a shown tree and sends it to the chrome process.
+   */
+  void InsertIntoIpcTree(Accessible* aParent,
+                         Accessible* aChild, uint32_t aIdxInParent);
   void ShowEvent(AccShowEvent* aShowEvent);
 
   virtual void ActorDestroy(ActorDestroyReason) override
   {
     if (!mDoc) {
       return;
     }
 
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -74,16 +74,21 @@ DocAccessibleParent::RecvShowEvent(const
   for (uint32_t i = 0; i < consumed; i++) {
     uint64_t id = aData.NewTree()[i].ID();
     MOZ_ASSERT(mAccessibles.GetEntry(id));
   }
 #endif
 
   MOZ_ASSERT(CheckDocTree());
 
+  // Just update, no events.
+  if (aData.EventSuppressed()) {
+    return IPC_OK();
+  }
+
   ProxyAccessible* target = parent->ChildAt(newChildIdx);
   ProxyShowHideEvent(target, parent, true, aFromUser);
 
   if (!nsCoreUtils::AccEventObserversExist()) {
     return IPC_OK();
   }
 
   uint32_t type = nsIAccessibleEvent::EVENT_SHOW;
--- a/accessible/ipc/other/PDocAccessible.ipdl
+++ b/accessible/ipc/other/PDocAccessible.ipdl
@@ -24,16 +24,17 @@ struct AccessibleData
   uint32_t Interfaces;
 };
 
 struct ShowEventData
 {
   uint64_t ID;
   uint32_t Idx;
   AccessibleData[] NewTree;
+  bool EventSuppressed;
 };
 
 struct Attribute
 {
   nsCString Name;
   nsString Value;
 };
 
--- a/accessible/ipc/win/DocAccessibleChild.h
+++ b/accessible/ipc/win/DocAccessibleChild.h
@@ -106,17 +106,18 @@ private:
 
   void PushDeferredEvent(UniquePtr<DeferredEvent> aEvent);
 
   struct SerializedShow final : public DeferredEvent
   {
     SerializedShow(DocAccessibleChild* aTarget,
                    ShowEventData& aEventData, bool aFromUser)
       : DeferredEvent(aTarget)
-      , mEventData(aEventData.ID(), aEventData.Idx(), nsTArray<AccessibleData>())
+      , mEventData(aEventData.ID(), aEventData.Idx(),
+                   nsTArray<AccessibleData>(), aEventData.EventSuppressed())
       , mFromUser(aFromUser)
     {
       // Since IPDL doesn't generate a move constructor for ShowEventData,
       // we move NewTree manually (ugh). We still construct with an empty
       // NewTree above so that the compiler catches any changes made to the
       // ShowEventData structure in IPDL.
       mEventData.NewTree() = Move(aEventData.NewTree());
     }
--- a/accessible/ipc/win/PDocAccessible.ipdl
+++ b/accessible/ipc/win/PDocAccessible.ipdl
@@ -24,16 +24,17 @@ struct AccessibleData
   uint32_t Interfaces;
 };
 
 struct ShowEventData
 {
   uint64_t ID;
   uint32_t Idx;
   AccessibleData[] NewTree;
+  bool EventSuppressed;
 };
 
 struct Attribute
 {
   nsCString Name;
   nsString Value;
 };
 
--- a/accessible/tests/mochitest/treeupdate/test_ariaowns.html
+++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
@@ -519,19 +519,18 @@
 
       this.getID = function test8_getID() {
         return `Set ARIA owns on inaccessible span element that contains accessible children`;
       }
     }
 
     function test9_prepare() {
       this.eventSeq = [
-        new invokerChecker(EVENT_SHOW, () => {
-          let doc = getNode("t9_container").contentDocument;
-          return doc && doc.getElementById("container");
+        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, () => {
+          return getNode("t9_container").contentDocument;
         })
       ];
 
       this.invoke = () => {
         // The \ before the final /script avoids the script from being terminated
         // by the html parser.
         getNode("t9_container").src = `data:text/html,
           <html><body></body>
--- a/browser/components/migration/tests/unit/xpcshell.ini
+++ b/browser/components/migration/tests/unit/xpcshell.ini
@@ -13,15 +13,15 @@ skip-if = os != "win"
 [test_Chrome_cookies.js]
 skip-if = os != "mac" # Relies on ULibDir
 [test_Chrome_passwords.js]
 skip-if = os != "win"
 [test_Edge_db_migration.js]
 skip-if = os != "win"
 [test_fx_telemetry.js]
 [test_IE_bookmarks.js]
-skip-if = true # bug 1392396
+skip-if = !(os == "win" && bits == 64) # bug 1392396
 [test_IE_cookies.js]
 skip-if = os != "win"
 [test_IE7_passwords.js]
 skip-if = os != "win"
 [test_Safari_bookmarks.js]
 skip-if = os != "mac"
--- a/caps/moz.build
+++ b/caps/moz.build
@@ -52,16 +52,17 @@ UNIFIED_SOURCES += [
     'SystemPrincipal.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/docshell/base',
     '/dom/base',
     '/js/xpconnect/src',
     '/netwerk/base',
+    '/netwerk/cookie',
 ]
 
 if CONFIG['ENABLE_TESTS']:
     DIRS += ['tests/gtest']
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
--- a/devtools/client/framework/test/browser_source_map-reload.js
+++ b/devtools/client/framework/test/browser_source_map-reload.js
@@ -19,17 +19,16 @@ add_task(function* () {
   yield pushPref("devtools.debugger.new-debugger-frontend", true);
 
   // Start with the empty page, then navigate, so that we can properly
   // listen for new sources arriving.
   const toolbox = yield openNewTabAndToolbox(INITIAL_URL, "webconsole");
   const service = toolbox.sourceMapURLService;
   const tab = toolbox.target.tab;
 
-  debugger
   let sourceSeen = waitForSourceLoad(toolbox, JS_URL);
   tab.linkedBrowser.loadURI(PAGE_URL);
   yield sourceSeen;
 
   info(`checking original location for ${JS_URL}:${GENERATED_LINE}`);
   let newLoc = yield service.originalPositionFor(JS_URL, GENERATED_LINE);
   is(newLoc.sourceUrl, ORIGINAL_URL_1, "check mapped URL");
   is(newLoc.line, ORIGINAL_LINE, "check mapped line number");
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -35,18 +35,16 @@ const {debounce} = require("devtools/sha
 const EventEmitter = require("devtools/shared/old-event-emitter");
 const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
 const clipboardHelper = require("devtools/shared/platform/clipboard");
 const AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles";
 const PREF_DEFAULT_COLOR_UNIT = "devtools.defaultColorUnit";
-const PREF_ENABLE_MDN_DOCS_TOOLTIP =
-      "devtools.inspector.mdnDocsTooltip.enabled";
 const FILTER_CHANGED_TIMEOUT = 150;
 const PREF_ORIG_SOURCES = "devtools.source-map.client-service.enabled";
 
 // This is used to parse user input when filtering.
 const FILTER_PROP_RE = /\s*([^:\s]*)\s*:\s*(.*?)\s*;?$/;
 // This is used to parse the filter search value to see if the filter
 // should be strict or not
 const FILTER_STRICT_RE = /\s*`(.*?)`\s*$/;
@@ -160,21 +158,18 @@ function CssRuleView(inspector, document
 
   this._handlePrefChange = this._handlePrefChange.bind(this);
   this._onSourcePrefChanged = this._onSourcePrefChanged.bind(this);
 
   this._prefObserver = new PrefObserver("devtools.");
   this._prefObserver.on(PREF_ORIG_SOURCES, this._onSourcePrefChanged);
   this._prefObserver.on(PREF_UA_STYLES, this._handlePrefChange);
   this._prefObserver.on(PREF_DEFAULT_COLOR_UNIT, this._handlePrefChange);
-  this._prefObserver.on(PREF_ENABLE_MDN_DOCS_TOOLTIP, this._handlePrefChange);
 
   this.showUserAgentStyles = Services.prefs.getBoolPref(PREF_UA_STYLES);
-  this.enableMdnDocsTooltip =
-    Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP);
 
   // The popup will be attached to the toolbox document.
   this.popup = new AutocompletePopup(inspector._toolbox.doc, {
     autoSelect: true,
     theme: "auto"
   });
 
   this._showEmpty();
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -96,19 +96,16 @@ support-files =
 [browser_rules_computed-lists_01.js]
 [browser_rules_computed-lists_02.js]
 [browser_rules_completion-popup-hidden-after-navigation.js]
 [browser_rules_content_01.js]
 [browser_rules_content_02.js]
 [browser_rules_variables_01.js]
 [browser_rules_variables_02.js]
 skip-if = e10s && debug # Bug 1250058 - Docshell leak on debug e10s
-[browser_rules_context-menu-show-mdn-docs-01.js]
-[browser_rules_context-menu-show-mdn-docs-02.js]
-[browser_rules_context-menu-show-mdn-docs-03.js]
 [browser_rules_copy_styles.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_rules_cssom.js]
 [browser_rules_cubicbezier-appears-on-swatch-click.js]
 [browser_rules_cubicbezier-commit-on-ENTER.js]
 [browser_rules_cubicbezier-revert-on-ESC.js]
 [browser_rules_custom.js]
deleted file mode 100644
--- a/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-01.js
+++ /dev/null
@@ -1,144 +0,0 @@
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * This file tests the code that integrates the Style Inspector's rule view
- * with the MDN docs tooltip.
- *
- * If you display the context click on a property name in the rule view, you
- * should see a menu item "Show MDN Docs". If you click that item, the MDN
- * docs tooltip should be shown, containing docs from MDN for that property.
- *
- * This file tests that the context menu item is shown when it should be
- * shown and hidden when it should be hidden.
- */
-
-"use strict";
-
-/**
- * The test document tries to confuse the context menu
- * code by having a tag called "padding" and a property
- * value called "margin".
- */
-
-const { PrefObserver } = require("devtools/client/shared/prefs");
-const PREF_ENABLE_MDN_DOCS_TOOLTIP =
-  "devtools.inspector.mdnDocsTooltip.enabled";
-
-const TEST_URI = `
-  <html>
-    <head>
-      <style>
-        padding {font-family: margin;}
-      </style>
-    </head>
-
-    <body>
-      <padding>MDN tooltip testing</padding>
-    </body>
-  </html>
-`;
-
-add_task(function* () {
-  info("Ensure the pref is true to begin with");
-  let initial = Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP);
-  if (initial != true) {
-    yield setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, true);
-  }
-
-  yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_URI));
-  let {inspector, view} = yield openRuleView();
-  yield selectNode("padding", inspector);
-  yield testMdnContextMenuItemVisibility(view);
-
-  info("Ensure the pref is reset to its initial value");
-  let eventual = Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP);
-  if (eventual != initial) {
-    yield setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, initial);
-  }
-});
-
-/**
- * Set a boolean pref, and wait for the pref observer to
- * trigger, so that code listening for the pref change
- * has had a chance to update itself.
- *
- * @param pref {string} Name of the pref to change
- * @param state {boolean} Desired value of the pref.
- *
- * Note that if the pref already has the value in `state`,
- * then the prefObserver will not trigger. So you should only
- * call this function if you know the pref's current value is
- * not `state`.
- */
-function* setBooleanPref(pref, state) {
-  let prefObserver = new PrefObserver("devtools.");
-  let oncePrefChanged = new Promise(resolve => {
-    prefObserver.on(pref, onPrefChanged);
-
-    function onPrefChanged() {
-      prefObserver.off(pref, onPrefChanged);
-      resolve();
-    }
-  });
-
-  info("Set the pref " + pref + " to: " + state);
-  Services.prefs.setBoolPref(pref, state);
-
-  info("Wait for prefObserver to call back so the UI can update");
-  yield oncePrefChanged;
-}
-
-/**
- * Tests that the MDN context menu item is shown when it should be,
- * and hidden when it should be.
- *   - iterate through every node in the rule view
- *   - set that node as popupNode (the node that the context menu
- *   is shown for)
- *   - update the context menu's state
- *   - test that the MDN context menu item is hidden, or not,
- *   depending on popupNode
- */
-function* testMdnContextMenuItemVisibility(view) {
-  info("Test that MDN context menu item is shown only when it should be.");
-
-  let root = rootElement(view);
-  for (let node of iterateNodes(root)) {
-    info("Setting " + node + " as popupNode");
-    info("Creating context menu with " + node + " as popupNode");
-    let allMenuItems = openStyleContextMenuAndGetAllItems(view, node);
-    let menuitemShowMdnDocs = allMenuItems.find(item => item.label ===
-      STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.showMdnDocs"));
-
-    let isVisible = menuitemShowMdnDocs.visible;
-    let shouldBeVisible = isPropertyNameNode(node);
-    let message = shouldBeVisible ? "shown" : "hidden";
-    is(isVisible, shouldBeVisible,
-       "The MDN context menu item is " + message + " ; content : " +
-       node.textContent + " ; type : " + node.nodeType);
-  }
-}
-
-/**
- * Check if a node is a property name.
- */
-function isPropertyNameNode(node) {
-  return node.textContent === "font-family";
-}
-
-/**
- * A generator that iterates recursively through all child nodes of baseNode.
- */
-function* iterateNodes(baseNode) {
-  yield baseNode;
-
-  for (let child of baseNode.childNodes) {
-    yield* iterateNodes(child);
-  }
-}
-
-/**
- * Returns the root element for the rule view.
- */
-var rootElement = view => (view.element) ? view.element : view.styleDocument;
deleted file mode 100644
--- a/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-02.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * This file tests the code that integrates the Style Inspector's rule view
- * with the MDN docs tooltip.
- *
- * If you display the context click on a property name in the rule view, you
- * should see a menu item "Show MDN Docs". If you click that item, the MDN
- * docs tooltip should be shown, containing docs from MDN for that property.
- *
- * This file tests that:
- * - clicking the context menu item shows the tooltip
- * - the tooltip content matches the property name for which the context menu was opened
- */
-
-"use strict";
-
-const {setBaseCssDocsUrl} =
-  require("devtools/client/shared/widgets/MdnDocsWidget");
-
-const PROPERTYNAME = "color";
-
-const TEST_DOC = `
-  <html>
-    <body>
-      <div style="color: red">
-        Test "Show MDN Docs" context menu option
-      </div>
-    </body>
-  </html>
-`;
-
-add_task(function* () {
-  yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_DOC));
-  let {inspector, view} = yield openRuleView();
-  yield selectNode("div", inspector);
-
-  setBaseCssDocsUrl(URL_ROOT);
-
-  info("Setting the popupNode for the MDN docs tooltip");
-
-  let {nameSpan} = getRuleViewProperty(view, "element", PROPERTYNAME);
-
-  let allMenuItems = openStyleContextMenuAndGetAllItems(view, nameSpan.firstChild);
-  let menuitemShowMdnDocs = allMenuItems.find(item => item.label ===
-    STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.showMdnDocs"));
-
-  let cssDocs = view.tooltips.getTooltip("cssDocs");
-
-  info("Showing the MDN docs tooltip");
-  let onShown = cssDocs.tooltip.once("shown");
-  menuitemShowMdnDocs.click();
-  yield onShown;
-  ok(true, "The MDN docs tooltip was shown");
-
-  info("Quick check that the tooltip contents are set");
-  let h1 = cssDocs.tooltip.container.querySelector(".mdn-property-name");
-  is(h1.textContent, PROPERTYNAME, "The MDN docs tooltip h1 is correct");
-});
deleted file mode 100644
--- a/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-03.js
+++ /dev/null
@@ -1,123 +0,0 @@
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * This file tests the "devtools.inspector.mdnDocsTooltip.enabled" preference,
- * that we use to enable/disable the MDN tooltip in the Inspector.
- *
- * The desired behavior is:
- * - if the preference is true, show the "Show MDN Docs" context menu item
- * - if the preference is false, don't show the item
- * - listen for changes to the pref, so we can show/hide the item dynamically
- */
-
-"use strict";
-
-const { PrefObserver } = require("devtools/client/shared/prefs");
-const PREF_ENABLE_MDN_DOCS_TOOLTIP =
-  "devtools.inspector.mdnDocsTooltip.enabled";
-const PROPERTY_NAME_CLASS = "ruleview-propertyname";
-
-const TEST_DOC = `
-  <html>
-    <body>
-      <div style="color: red">
-        Test the pref to enable/disable the "Show MDN Docs" context menu option
-      </div>
-    </body>
-  </html>
-`;
-
-add_task(function* () {
-  info("Ensure the pref is true to begin with");
-  let initial = Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP);
-  if (initial != true) {
-    yield setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, true);
-  }
-
-  yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_DOC));
-
-  let {inspector, view} = yield openRuleView();
-  yield selectNode("div", inspector);
-  yield testMdnContextMenuItemVisibility(view, true);
-
-  yield setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, false);
-  yield testMdnContextMenuItemVisibility(view, false);
-
-  info("Close the Inspector");
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  yield gDevTools.closeToolbox(target);
-
-  ({inspector, view} = yield openRuleView());
-  yield selectNode("div", inspector);
-  yield testMdnContextMenuItemVisibility(view, false);
-
-  yield setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, true);
-  yield testMdnContextMenuItemVisibility(view, true);
-
-  info("Ensure the pref is reset to its initial value");
-  let eventual = Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP);
-  if (eventual != initial) {
-    yield setBooleanPref(PREF_ENABLE_MDN_DOCS_TOOLTIP, initial);
-  }
-});
-
-/**
- * Set a boolean pref, and wait for the pref observer to
- * trigger, so that code listening for the pref change
- * has had a chance to update itself.
- *
- * @param pref {string} Name of the pref to change
- * @param state {boolean} Desired value of the pref.
- *
- * Note that if the pref already has the value in `state`,
- * then the prefObserver will not trigger. So you should only
- * call this function if you know the pref's current value is
- * not `state`.
- */
-function* setBooleanPref(pref, state) {
-  let prefObserver = new PrefObserver("devtools.");
-  let oncePrefChanged = new Promise(resolve => {
-    prefObserver.on(pref, onPrefChanged);
-
-    function onPrefChanged() {
-      prefObserver.off(pref, onPrefChanged);
-      resolve();
-    }
-  });
-
-  info("Set the pref " + pref + " to: " + state);
-  Services.prefs.setBoolPref(pref, state);
-
-  info("Wait for prefObserver to call back so the UI can update");
-  yield oncePrefChanged;
-}
-
-/**
- * Test whether the MDN tooltip context menu item is visible when it should be.
- *
- * @param view The rule view
- * @param shouldBeVisible {boolean} Whether we expect the context
- * menu item to be visible or not.
- */
-function* testMdnContextMenuItemVisibility(view, shouldBeVisible) {
-  let message = shouldBeVisible ? "shown" : "hidden";
-  info("Test that MDN context menu item is " + message);
-
-  info("Set a CSS property name as popupNode");
-  let root = rootElement(view);
-  let node = root.querySelector("." + PROPERTY_NAME_CLASS).firstChild;
-  let allMenuItems = openStyleContextMenuAndGetAllItems(view, node);
-  let menuitemShowMdnDocs = allMenuItems.find(item => item.label ===
-    STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.showMdnDocs"));
-
-  let isVisible = menuitemShowMdnDocs.visible;
-  is(isVisible, shouldBeVisible,
-     "The MDN context menu item is " + message);
-}
-
-/**
- * Returns the root element for the rule view.
- */
-var rootElement = view => (view.element) ? view.element : view.styleDocument;
--- a/devtools/client/inspector/shared/style-inspector-menu.js
+++ b/devtools/client/inspector/shared/style-inspector-menu.js
@@ -20,18 +20,16 @@ const {
   VIEW_NODE_LOCATION_TYPE,
 } = require("devtools/client/inspector/shared/node-types");
 const clipboardHelper = require("devtools/shared/platform/clipboard");
 
 const STYLE_INSPECTOR_PROPERTIES = "devtools/shared/locales/styleinspector.properties";
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
 
-const PREF_ENABLE_MDN_DOCS_TOOLTIP =
-  "devtools.inspector.mdnDocsTooltip.enabled";
 const PREF_ORIG_SOURCES = "devtools.source-map.client-service.enabled";
 
 /**
  * Style inspector context menu
  *
  * @param {RuleView|ComputedView} view
  *        RuleView or ComputedView instance controlling this menu
  * @param {Object} options
@@ -52,17 +50,16 @@ function StyleInspectorMenu(view, option
   this._onCopyLocation = this._onCopyLocation.bind(this);
   this._onCopyPropertyDeclaration = this._onCopyPropertyDeclaration.bind(this);
   this._onCopyPropertyName = this._onCopyPropertyName.bind(this);
   this._onCopyPropertyValue = this._onCopyPropertyValue.bind(this);
   this._onCopyRule = this._onCopyRule.bind(this);
   this._onCopySelector = this._onCopySelector.bind(this);
   this._onCopyUrl = this._onCopyUrl.bind(this);
   this._onSelectAll = this._onSelectAll.bind(this);
-  this._onShowMdnDocs = this._onShowMdnDocs.bind(this);
   this._onToggleOrigSources = this._onToggleOrigSources.bind(this);
 }
 
 module.exports = StyleInspectorMenu;
 
 StyleInspectorMenu.prototype = {
   /**
    * Display the style inspector context menu
@@ -225,29 +222,16 @@ StyleInspectorMenu.prototype = {
         this._onAddNewRule();
       },
       visible: this.isRuleView,
       disabled: !this.isRuleView ||
                 this.inspector.selection.isAnonymousNode(),
     });
     menu.append(menuitemAddRule);
 
-    // Show MDN Docs
-    let mdnDocsAccessKey = "styleinspector.contextmenu.showMdnDocs.accessKey";
-    let menuitemShowMdnDocs = new MenuItem({
-      label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.showMdnDocs"),
-      accesskey: STYLE_INSPECTOR_L10N.getStr(mdnDocsAccessKey),
-      click: () => {
-        this._onShowMdnDocs();
-      },
-      visible: (Services.prefs.getBoolPref(PREF_ENABLE_MDN_DOCS_TOOLTIP) &&
-                                                    this._isPropertyName()),
-    });
-    menu.append(menuitemShowMdnDocs);
-
     // Show Original Sources
     let sourcesAccessKey = "styleinspector.contextmenu.toggleOrigSources.accessKey";
     let menuitemSources = new MenuItem({
       label: STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.toggleOrigSources"),
       accesskey: STYLE_INSPECTOR_L10N.getStr(sourcesAccessKey),
       click: () => {
         this._onToggleOrigSources();
       },
@@ -404,26 +388,16 @@ StyleInspectorMenu.prototype = {
       message =
         STYLE_INSPECTOR_L10N.getStr("styleinspector.copyImageDataUrlError");
     }
 
     clipboardHelper.copyString(message);
   }),
 
   /**
-   *  Show docs from MDN for a CSS property.
-   */
-  _onShowMdnDocs: function () {
-    let cssPropertyName = this.styleDocument.popupNode.textContent;
-    let anchor = this.styleDocument.popupNode.parentNode;
-    let cssDocsTooltip = this.view.tooltips.getTooltip("cssDocs");
-    cssDocsTooltip.show(anchor, cssPropertyName);
-  },
-
-  /**
    * Add a new rule to the current element.
    */
   _onAddNewRule: function () {
     this.view._onAddRule();
   },
 
   /**
    * Copy the rule source location of the current clicked node.
--- a/devtools/client/inspector/shared/tooltips-overlay.js
+++ b/devtools/client/inspector/shared/tooltips-overlay.js
@@ -113,21 +113,16 @@ TooltipsOverlay.prototype = {
         tooltip = new SwatchCubicBezierTooltip(doc);
         break;
       case "filterEditor":
         const SwatchFilterTooltip =
           require("devtools/client/shared/widgets/tooltip/SwatchFilterTooltip");
         tooltip = new SwatchFilterTooltip(doc,
           this._cssProperties.getValidityChecker(this.view.inspector.panelDoc));
         break;
-      case "cssDocs":
-        const CssDocsTooltip =
-          require("devtools/client/shared/widgets/tooltip/CssDocsTooltip");
-        tooltip = new CssDocsTooltip(doc);
-        break;
       case "previewTooltip":
         tooltip = new HTMLTooltip(doc, {
           type: "arrow",
           useXulWrapper: true
         });
         tooltip.startTogglingOnHover(this.view.element,
           this._onPreviewTooltipTargetHover.bind(this));
         break;
--- a/devtools/client/inspector/toolsidebar.js
+++ b/devtools/client/inspector/toolsidebar.js
@@ -82,66 +82,69 @@ ToolSidebar.prototype = {
     });
 
     this._tabbar = this.ReactDOM.render(sidebar, this._tabbox);
   },
 
   /**
    * Register a side-panel tab.
    *
-   * @param {string} tab uniq id
-   * @param {string} title tab title
+   * @param {String} tab uniq id
+   * @param {String} title tab title
    * @param {React.Component} panel component. See `InspectorPanelTab` as an example.
-   * @param {boolean} selected true if the panel should be selected
+   * @param {Boolean} selected true if the panel should be selected
+   * @param {Number} index the position where the tab should be inserted
    */
-  addTab: function (id, title, panel, selected) {
-    this._tabbar.addTab(id, title, selected, panel);
+  addTab: function (id, title, panel, selected, index) {
+    this._tabbar.addTab(id, title, selected, panel, null, index);
     this.emit("new-tab-registered", id);
   },
 
   /**
    * Helper API for adding side-panels that use existing DOM nodes
    * (defined within inspector.xhtml) as the content.
    *
-   * @param {string} tab uniq id
-   * @param {string} title tab title
-   * @param {boolean} selected true if the panel should be selected
+   * @param {String} tab uniq id
+   * @param {String} title tab title
+   * @param {Boolean} selected true if the panel should be selected
+   * @param {Number} index the position where the tab should be inserted
    */
-  addExistingTab: function (id, title, selected) {
+  addExistingTab: function (id, title, selected, index) {
     let panel = this.InspectorTabPanel({
       id: id,
       idPrefix: this.TABPANEL_ID_PREFIX,
       key: id,
       title: title,
     });
 
-    this.addTab(id, title, panel, selected);
+    this.addTab(id, title, panel, selected, index);
   },
 
   /**
    * Helper API for adding side-panels that use existing <iframe> nodes
    * (defined within inspector.xhtml) as the content.
    * The document must have a title, which will be used as the name of the tab.
    *
-   * @param {string} tab uniq id
-   * @param {string} title tab title
-   * @param {string} url
-   * @param {boolean} selected true if the panel should be selected
+   * @param {String} tab uniq id
+   * @param {String} title tab title
+   * @param {String} url
+   * @param {Boolean} selected true if the panel should be selected
+   * @param {Number} index the position where the tab should be inserted
    */
-  addFrameTab: function (id, title, url, selected) {
+  addFrameTab: function (id, title, url, selected, index) {
     let panel = this.InspectorTabPanel({
       id: id,
       idPrefix: this.TABPANEL_ID_PREFIX,
       key: id,
       title: title,
       url: url,
       onMount: this.onSidePanelMounted.bind(this),
     });
 
-    this.addTab(id, title, panel, selected);
+    this.addTab(id, title, panel, selected, index);
   },
 
   onSidePanelMounted: function (content, props) {
     let iframe = content.querySelector("iframe");
     if (!iframe || iframe.getAttribute("src")) {
       return;
     }
 
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -90,17 +90,16 @@ devtools.jar:
 *   content/framework/dev-edition-promo/dev-edition-promo.css (framework/dev-edition-promo/dev-edition-promo.css)
     content/framework/dev-edition-promo/dev-edition-logo.png (framework/dev-edition-promo/dev-edition-logo.png)
     content/inspector/inspector.xhtml (inspector/inspector.xhtml)
     content/framework/connect/connect.xhtml (framework/connect/connect.xhtml)
     content/framework/connect/connect.css (framework/connect/connect.css)
     content/framework/connect/connect.js (framework/connect/connect.js)
     content/shared/widgets/graphs-frame.xhtml (shared/widgets/graphs-frame.xhtml)
     content/shared/widgets/cubic-bezier.css (shared/widgets/cubic-bezier.css)
-    content/shared/widgets/mdn-docs.css (shared/widgets/mdn-docs.css)
     content/shared/widgets/filter-widget.css (shared/widgets/filter-widget.css)
     content/shared/widgets/color-widget.css (shared/widgets/color-widget.css)
     content/shared/widgets/spectrum.css (shared/widgets/spectrum.css)
     content/aboutdebugging/aboutdebugging.xhtml (aboutdebugging/aboutdebugging.xhtml)
     content/aboutdebugging/aboutdebugging.css (aboutdebugging/aboutdebugging.css)
     content/aboutdebugging/initializer.js (aboutdebugging/initializer.js)
     content/responsive.html/index.xhtml (responsive.html/index.xhtml)
     content/responsive.html/index.js (responsive.html/index.js)
--- a/devtools/client/locales/en-US/inspector.properties
+++ b/devtools/client/locales/en-US/inspector.properties
@@ -60,24 +60,16 @@ eventsTooltip.unknownLocation=Unknown lo
 eventsTooltip.unknownLocationExplanation=The original location of this listener cannot be detected. Maybe the code is transpiled by a utility such as Babel.
 
 #LOCALIZATION NOTE: Used in the tooltip for Bubbling
 eventsTooltip.Bubbling=Bubbling
 
 #LOCALIZATION NOTE: Used in the tooltip for Capturing
 eventsTooltip.Capturing=Capturing
 
-# LOCALIZATION NOTE (docsTooltip.visitMDN): Shown in the tooltip that displays
-# help from MDN. This is a link to the complete MDN documentation page.
-docsTooltip.visitMDN=Visit MDN page
-
-# LOCALIZATION NOTE (docsTooltip.visitMDN): Shown in the docs tooltip when the MDN page
-# could not be loaded (for example, because of a connectivity problem).
-docsTooltip.loadDocsError=Could not load docs page.
-
 # LOCALIZATION NOTE (inspector.searchResultsCount): This is the label that
 # will show up next to the inspector search box. %1$S is the current result
 # index and %2$S is the total number of search results. For example: "3 of 9".
 # This won't be visible until the search box is updated in Bug 835896.
 inspector.searchResultsCount2=%1$S of %2$S
 
 # LOCALIZATION NOTE (inspector.searchResultsNone): This is the label that
 # will show up next to the inspector search box when no matches were found
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -51,18 +51,16 @@ pref("devtools.inspector.remote", false)
 // Collapse pseudo-elements by default in the rule-view
 pref("devtools.inspector.show_pseudo_elements", false);
 // The default size for image preview tooltips in the rule-view/computed-view/markup-view
 pref("devtools.inspector.imagePreviewTooltipSize", 300);
 // Enable user agent style inspection in rule-view
 pref("devtools.inspector.showUserAgentStyles", false);
 // Show all native anonymous content (like controls in <video> tags)
 pref("devtools.inspector.showAllAnonymousContent", false);
-// Enable the MDN docs tooltip
-pref("devtools.inspector.mdnDocsTooltip.enabled", false);
 // Enable the new color widget
 pref("devtools.inspector.colorWidget.enabled", false);
 // Enable the CSS shapes highlighter
 pref("devtools.inspector.shapesHighlighter.enabled", false);
 
 // Enable the Font Inspector
 pref("devtools.fontinspector.enabled", true);
 
--- a/devtools/client/shared/components/tabs/TabBar.js
+++ b/devtools/client/shared/components/tabs/TabBar.js
@@ -73,26 +73,31 @@ let Tabbar = createClass({
           panel,
           title: panel.props.title,
         })
       );
   },
 
   // Public API
 
-  addTab: function (id, title, selected = false, panel, url) {
+  addTab: function (id, title, selected = false, panel, url, index = -1) {
     let tabs = this.state.tabs.slice();
-    tabs.push({id, title, panel, url});
+
+    if (index >= 0) {
+      tabs.splice(index, 0, {id, title, panel, url});
+    } else {
+      tabs.push({id, title, panel, url});
+    }
 
     let newState = Object.assign({}, this.state, {
       tabs: tabs,
     });
 
     if (selected) {
-      newState.activeTab = tabs.length - 1;
+      newState.activeTab = index >= 0 ? index : tabs.length - 1;
     }
 
     this.setState(newState, () => {
       if (this.props.onSelect && selected) {
         this.props.onSelect(id);
       }
     });
   },
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -13,22 +13,16 @@ support-files =
   doc_html_tooltip.xul
   doc_html_tooltip_arrow-01.xul
   doc_html_tooltip_arrow-02.xul
   doc_html_tooltip_hover.xul
   doc_html_tooltip_rtl.xul
   doc_inplace-editor_autocomplete_offset.xul
   doc_layoutHelpers-getBoxQuads.html
   doc_layoutHelpers.html
-  doc_mdn-css-basic-testing.html
-  doc_mdn-css-no-summary-or-syntax.html
-  doc_mdn-css-no-summary.html
-  doc_mdn-css-no-syntax.html
-  doc_mdn-css-syntax-old-style.html
-  doc_mdn-docs-01.html
   doc_options-view.xul
   doc_spectrum.html
   doc_tableWidget_basic.html
   doc_tableWidget_keyboard_interaction.xul
   doc_tableWidget_mouse_interaction.xul
   doc_templater_basic.html
   doc_toolbar_basic.html
   doc_toolbar_webconsole_errors_count.html
@@ -156,19 +150,16 @@ skip-if = e10s # Bug 1221911, bug 122228
 [browser_inplace-editor_autocomplete_offset.js]
 [browser_inplace-editor_maxwidth.js]
 [browser_keycodes.js]
 [browser_key_shortcuts.js]
 [browser_layoutHelpers.js]
 skip-if = e10s # Layouthelpers test should not run in a content page.
 [browser_layoutHelpers-getBoxQuads.js]
 skip-if = e10s # Layouthelpers test should not run in a content page.
-[browser_mdn-docs-01.js]
-[browser_mdn-docs-02.js]
-[browser_mdn-docs-03.js]
 [browser_num-l10n.js]
 [browser_options-view-01.js]
 [browser_outputparser.js]
 skip-if = e10s # Test intermittently fails with e10s. Bug 1124162.
 [browser_poller.js]
 [browser_prefs-01.js]
 [browser_prefs-02.js]
 [browser_require_raw.js]
deleted file mode 100644
--- a/devtools/client/shared/test/browser_mdn-docs-01.js
+++ /dev/null
@@ -1,168 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * This file tests the MdnDocsWidget object, and specifically its
- * loadCssDocs() function.
- *
- * The MdnDocsWidget is initialized with a document which has a specific
- * structure. You then call loadCssDocs(), passing in a CSS property name.
- * MdnDocsWidget then fetches docs for that property by making an XHR to
- * a docs page, and loads the results into the document. While the XHR is
- * still not resolved the document is put into an "initializing" state in
- * which the devtools throbber is displayed.
- *
- * In this file we test:
- * - the initial state of the document before the docs have loaded
- * - the state of the document after the docs have loaded
- */
-
-"use strict";
-
-const {setBaseCssDocsUrl, MdnDocsWidget} = require("devtools/client/shared/widgets/MdnDocsWidget");
-
-/**
- * Test properties
- *
- * In the real tooltip, a CSS property name is used to look up an MDN page
- * for that property.
- * In the test code, the names defined here is used to look up a page
- * served by the test server.
- */
-const BASIC_TESTING_PROPERTY = "doc_mdn-css-basic-testing.html";
-
-const BASIC_EXPECTED_SUMMARY = "A summary of the property.";
-const BASIC_EXPECTED_SYNTAX = [{type: "comment", text: "/* The part we want   */"},
-                               {type: "text", text: "\n"},
-                               {type: "property-name", text: "this"},
-                               {type: "text", text: ":"},
-                               {type: "text", text: " "},
-                               {type: "property-value", text: "is-the-part-we-want"},
-                               {type: "text", text: ";"}];
-
-const URI_PARAMS =
-  "?utm_source=mozilla&utm_medium=firefox-inspector&utm_campaign=default";
-
-add_task(function* () {
-  setBaseCssDocsUrl(TEST_URI_ROOT);
-
-  yield addTab("about:blank");
-  let [host, win] = yield createHost("bottom",
-                                     CHROME_URL_ROOT + "doc_mdn-docs-01.html");
-  let widget = new MdnDocsWidget(win.document.querySelector("div"));
-
-  yield testTheBasics(widget);
-
-  host.destroy();
-  gBrowser.removeCurrentTab();
-});
-
-/**
- * Test all the basics
- * - initial content, before docs have loaded, is as expected
- * - throbber is set before docs have loaded
- * - contents are as expected after docs have loaded
- * - throbber is gone after docs have loaded
- * - mdn link text is correct and onclick behavior is correct
- */
-function* testTheBasics(widget) {
-  info("Test all the basic functionality in the widget");
-
-  info("Get the widget state before docs have loaded");
-  let promise = widget.loadCssDocs(BASIC_TESTING_PROPERTY);
-
-  info("Check initial contents before docs have loaded");
-  checkTooltipContents(widget.elements, {
-    propertyName: BASIC_TESTING_PROPERTY,
-    summary: "",
-    syntax: ""
-  });
-
-  // throbber is set
-  ok(widget.elements.info.classList.contains("devtools-throbber"),
-     "Throbber is set");
-
-  info("Now let the widget finish loading");
-  yield promise;
-
-  info("Check contents after docs have loaded");
-  checkTooltipContents(widget.elements, {
-    propertyName: BASIC_TESTING_PROPERTY,
-    summary: BASIC_EXPECTED_SUMMARY,
-    syntax: BASIC_EXPECTED_SYNTAX
-  });
-
-  // throbber is gone
-  ok(!widget.elements.info.classList.contains("devtools-throbber"),
-     "Throbber is not set");
-
-  info("Check that MDN link text is correct and onclick behavior is correct");
-
-  let mdnLink = widget.elements.linkToMdn;
-  let expectedHref = TEST_URI_ROOT + BASIC_TESTING_PROPERTY + URI_PARAMS;
-  is(mdnLink.href, expectedHref, "MDN link href is correct");
-
-  let uri = yield checkLinkClick(mdnLink);
-  is(uri, expectedHref, "New tab opened with the expected URI");
-}
-
- /**
-  * Clicking the "Visit MDN Page" in the tooltip panel
-  * should open a new browser tab with the page loaded.
-  *
-  * To test this we'll listen for a new tab opening, and
-  * when it does, add a listener to that new tab to tell
-  * us when it has loaded.
-  *
-  * Then we click the link.
-  *
-  * In the tab's load listener, we'll resolve the promise
-  * with the URI, which is expected to match the href
-  * in the orginal link.
-  *
-  * One complexity is that when you open a new tab,
-  * "about:blank" is first loaded into the tab before the
-  * actual page. So we ignore that first load event, and keep
-  * listening until "load" is triggered for a different URI.
-  */
-function checkLinkClick(link) {
-  function loadListener(tab) {
-    let browser = getBrowser().getBrowserForTab(tab);
-    let uri = browser.currentURI.spec;
-
-    info("New browser tab has loaded");
-    gBrowser.removeTab(tab);
-    info("Resolve promise with new tab URI");
-    deferred.resolve(uri);
-  }
-
-  function newTabListener(e) {
-    gBrowser.tabContainer.removeEventListener("TabOpen", newTabListener);
-    let tab = e.target;
-    BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url => url != "about:blank")
-      .then(url => loadListener(tab));
-  }
-
-  let deferred = defer();
-  info("Check that clicking the link opens a new tab with the correct URI");
-  gBrowser.tabContainer.addEventListener("TabOpen", newTabListener);
-  info("Click the link to MDN");
-  link.click();
-  return deferred.promise;
-}
-
-/**
- * Utility function to check content of the tooltip.
- */
-function checkTooltipContents(doc, expected) {
-  is(doc.heading.textContent,
-     expected.propertyName,
-     "Property name is correct");
-
-  is(doc.summary.textContent,
-     expected.summary,
-     "Summary is correct");
-
-  checkCssSyntaxHighlighterOutput(expected.syntax, doc.syntax);
-}
deleted file mode 100644
--- a/devtools/client/shared/test/browser_mdn-docs-02.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * This file tests the MdnDocsWidget object, and specifically its
- * loadCssDocs() function.
- *
- * The MdnDocsWidget is initialized with a document which has a specific
- * structure. You then call loadCssDocs(), passing in a CSS property name.
- * MdnDocsWidget then fetches docs for that property by making an XHR to
- * a docs page, and loads the results into the document.
- *
- * In this file we test that the tooltip can properly handle the different
- * structures that the docs page might have, including variant structures and
- * error conditions like parts of the document being missing.
- *
- * We also test that the tooltip properly handles the case where the page
- * doesn't exist at all.
- */
-
-"use strict";
-
-const {
-  setBaseCssDocsUrl,
-  MdnDocsWidget
-} = require("devtools/client/shared/widgets/MdnDocsWidget");
-
-const BASIC_EXPECTED_SUMMARY = "A summary of the property.";
-const BASIC_EXPECTED_SYNTAX = [{type: "comment", text: "/* The part we want   */"},
-                               {type: "text", text: "\n"},
-                               {type: "property-name", text: "this"},
-                               {type: "text", text: ":"},
-                               {type: "text", text: " "},
-                               {type: "property-value", text: "is-the-part-we-want"},
-                               {type: "text", text: ";"}];
-
-const ERROR_MESSAGE = "Could not load docs page.";
-
-/**
- * Test properties
- *
- * In the real tooltip, a CSS property name is used to look up an MDN page
- * for that property.
- * In the test code, the names defined here are used to look up a page
- * served by the test server. We have different properties to test
- * different ways that the docs pages might be constructed, including errors
- * like pages that don't include docs where we expect.
- */
-const SYNTAX_OLD_STYLE = "doc_mdn-css-syntax-old-style.html";
-const NO_SUMMARY = "doc_mdn-css-no-summary.html";
-const NO_SYNTAX = "doc_mdn-css-no-syntax.html";
-const NO_SUMMARY_OR_SYNTAX = "doc_mdn-css-no-summary-or-syntax.html";
-
-const TEST_DATA = [{
-  desc: "Test a property for which we don't have a page",
-  docsPageUrl: "i-dont-exist.html",
-  expectedContents: {
-    propertyName: "i-dont-exist.html",
-    summary: ERROR_MESSAGE,
-    syntax: []
-  }
-}, {
-  desc: "Test a property whose syntax section is specified using an old-style page",
-  docsPageUrl: SYNTAX_OLD_STYLE,
-  expectedContents: {
-    propertyName: SYNTAX_OLD_STYLE,
-    summary: BASIC_EXPECTED_SUMMARY,
-    syntax: BASIC_EXPECTED_SYNTAX
-  }
-}, {
-  desc: "Test a property whose page doesn't have a summary",
-  docsPageUrl: NO_SUMMARY,
-  expectedContents: {
-    propertyName: NO_SUMMARY,
-    summary: "",
-    syntax: BASIC_EXPECTED_SYNTAX
-  }
-}, {
-  desc: "Test a property whose page doesn't have a syntax",
-  docsPageUrl: NO_SYNTAX,
-  expectedContents: {
-    propertyName: NO_SYNTAX,
-    summary: BASIC_EXPECTED_SUMMARY,
-    syntax: []
-  }
-}, {
-  desc: "Test a property whose page doesn't have a summary or a syntax",
-  docsPageUrl: NO_SUMMARY_OR_SYNTAX,
-  expectedContents: {
-    propertyName: NO_SUMMARY_OR_SYNTAX,
-    summary: ERROR_MESSAGE,
-    syntax: []
-  }
-}
-];
-
-add_task(function* () {
-  setBaseCssDocsUrl(TEST_URI_ROOT);
-
-  yield addTab("about:blank");
-  let [host, win] = yield createHost("bottom", "data:text/html," +
-    "<div class='mdn-container'></div>");
-  let widget = new MdnDocsWidget(win.document.querySelector("div"));
-
-  for (let {desc, docsPageUrl, expectedContents} of TEST_DATA) {
-    info(desc);
-    yield widget.loadCssDocs(docsPageUrl);
-    checkTooltipContents(widget.elements, expectedContents);
-  }
-  host.destroy();
-  gBrowser.removeCurrentTab();
-});
-
-/*
- * Utility function to check content of the tooltip.
- */
-function checkTooltipContents(doc, expected) {
-  is(doc.heading.textContent,
-     expected.propertyName,
-     "Property name is correct");
-
-  is(doc.summary.textContent,
-     expected.summary,
-     "Summary is correct");
-
-  checkCssSyntaxHighlighterOutput(expected.syntax, doc.syntax);
-}
deleted file mode 100644
--- a/devtools/client/shared/test/browser_mdn-docs-03.js
+++ /dev/null
@@ -1,277 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * This file tests the CSS syntax highlighter in the MdnDocsWidget object.
- *
- * The CSS syntax highlighter accepts:
- * - a string containing CSS
- * - a DOM node
- *
- * It parses the string and creates a collection of DOM nodes for different
- * CSS token types. These DOM nodes have CSS classes applied to them,
- * to apply the right style for that particular token type. The DOM nodes
- * are returned as children of the node that was passed to the function.
- *
- * This test code defines a number of different strings containing valid and
- * invalid CSS in various forms. For each string it defines the DOM nodes
- * that it expects to get from the syntax highlighter.
- *
- * It then calls the syntax highlighter, and checks that the resulting
- * collection of DOM nodes is what we expected.
- */
-
-"use strict";
-
-const {appendSyntaxHighlightedCSS} = require("devtools/client/shared/widgets/MdnDocsWidget");
-
-/**
- * An array containing the actual test cases.
- *
- * The test code tests every case in the array. If you want to add more
- * test cases, just add more items to the array.
- *
- * Each test case consists of:
- * - description: string describing the salient features of this test case
- * - example: the string to test
- * - expected: an array of objects, one for each DOM node we expect, that
- * captures the information about the node that we expect to test.
- */
-const TEST_DATA = [{
-  description: "Valid syntax, string value.",
-  example: "name: stringValue;",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, numeric value.",
-  example: "name: 1;",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "1"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, url value.",
-  example: "name: url(./name);",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "url(./name)"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, space before ':'.",
-  example: "name : stringValue;",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: " "},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, space before ';'.",
-  example: "name: stringValue ;",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: " "},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, trailing space.",
-  example: "name: stringValue; ",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: ";"},
-             {type: "text", text: " "}
-  ]}, {
-  description: "Valid syntax, leading space.",
-  example: " name: stringValue;",
-  expected: [{type: "text", text: " "},
-             {type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, two spaces.",
-  example: "name:  stringValue;",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: "  "},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, no spaces.",
-  example: "name:stringValue;",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, two-part value.",
-  example: "name: stringValue 1;",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "1"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, two declarations.",
-  example: "name: stringValue;\n" +
-         "name: 1;",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: ";"},
-             {type: "text", text: "\n"},
-             {type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "1"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, commented, numeric value.",
-  example: "/* comment */\n" +
-         "name: 1;",
-  expected: [{type: "comment", text: "/* comment */"},
-             {type: "text", text: "\n"},
-             {type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "1"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, multiline commented, string value.",
-  example: "/* multiline \n" +
-           "comment */\n" +
-           "name: stringValue;",
-  expected: [{type: "comment", text: "/* multiline \ncomment */"},
-             {type: "text", text: "\n"},
-             {type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, commented, two declarations.",
-  example: "/* comment 1 */\n" +
-           "name: 1;\n" +
-           "/* comment 2 */\n" +
-           "name: stringValue;",
-  expected: [{type: "comment", text: "/* comment 1 */"},
-             {type: "text", text: "\n"},
-             {type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "1"},
-             {type: "text", text: ";"},
-             {type: "text", text: "\n"},
-             {type: "comment", text: "/* comment 2 */"},
-             {type: "text", text: "\n"},
-             {type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, multiline.",
-  example: "name: \n" +
-           "stringValue;",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " \n"},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Valid syntax, multiline, two declarations.",
-  example: "name: \n" +
-           "stringValue \n" +
-           "stringValue2;",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " \n"},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: " \n"},
-             {type: "property-value", text: "stringValue2"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Invalid: not CSS at all.",
-  example: "not CSS at all",
-  expected: [{type: "property-name", text: "not"},
-             {type: "text", text: " "},
-             {type: "property-name", text: "CSS"},
-             {type: "text", text: " "},
-             {type: "property-name", text: "at"},
-             {type: "text", text: " "},
-             {type: "property-name", text: "all"}
-  ]}, {
-  description: "Invalid: switched ':' and ';'.",
-  example: "name; stringValue:",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ";"},
-             {type: "text", text: " "},
-             {type: "property-name", text: "stringValue"},
-             {type: "text", text: ":"}
-  ]}, {
-  description: "Invalid: unterminated comment.",
-  example: "/* unterminated comment\n" +
-           "name: stringValue;",
-  expected: [{type: "comment", text: "/* unterminated comment\nname: stringValue;"}
-  ]}, {
-  description: "Invalid: bad comment syntax.",
-  example: "// invalid comment\n" +
-           "name: stringValue;",
-  expected: [{type: "text", text: "/"},
-             {type: "text", text: "/"},
-             {type: "text", text: " "},
-             {type: "property-name", text: "invalid"},
-             {type: "text", text: " "},
-             {type: "property-name", text: "comment"},
-             {type: "text", text: "\n"},
-             {type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: ";"}
-  ]}, {
-  description: "Invalid: no trailing ';'.",
-  example: "name: stringValue\n" +
-           "name: stringValue2",
-  expected: [{type: "property-name", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "stringValue"},
-             {type: "text", text: "\n"},
-             {type: "property-value", text: "name"},
-             {type: "text", text: ":"},
-             {type: "text", text: " "},
-             {type: "property-value", text: "stringValue2"},
-  ]}
-];
-
-/**
- * Iterate through every test case, calling the syntax highlighter,
- * then calling a helper function to check the output.
- */
-add_task(function* () {
-  let doc = gBrowser.selectedTab.ownerDocument;
-  let parent = doc.createElement("div");
-  info("Testing all CSS syntax highlighter test cases");
-  for (let {description, example, expected} of TEST_DATA) {
-    info("Testing: " + description);
-    appendSyntaxHighlightedCSS(example, parent);
-    checkCssSyntaxHighlighterOutput(expected, parent);
-    while (parent.firstChild) {
-      parent.firstChild.remove();
-    }
-  }
-});
deleted file mode 100644
--- a/devtools/client/shared/test/doc_mdn-css-basic-testing.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<!doctype html>
-<html>
-<head>
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-</head>
-
-<body>
-
-  <h2 id="Summary">Summary</h2>
-
-  <p>A summary of the property.</p>
-
-  <h2 id="Syntax">Syntax</h2>
-
-  <pre>/* The part we want   */
-this: is-the-part-we-want;</pre>
-
-
-
-</body>
-</html>
deleted file mode 100644
--- a/devtools/client/shared/test/doc_mdn-css-no-summary-or-syntax.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!doctype html>
-<html>
-<head>
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-</head>
-
-<body>
-
-<p>This is not the summary or the syntax.</p>
-
-</body>
-</html>
deleted file mode 100644
--- a/devtools/client/shared/test/doc_mdn-css-no-summary.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<!doctype html>
-<html>
-<head>
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-</head>
-
-<body>
-
-  <p>This is not the summary.</p>
-
-  <h2 id="Syntax">Syntax</h2>
-
-  <pre>To be ignored.</pre>
-
-  <pre>/* The part we want   */
-this: is-the-part-we-want;</pre>
-
-
-
-</body>
-</html>
deleted file mode 100644
--- a/devtools/client/shared/test/doc_mdn-css-no-syntax.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<html>
-<head>
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-</head>
-
-<body>
-
-  <h2 id="Summary">Summary</h2>
-
-  <p>A summary of the property.</p>
-
-  <p>This is not the syntax.</p>
-
-
-</body>
-</html>
deleted file mode 100644
--- a/devtools/client/shared/test/doc_mdn-css-syntax-old-style.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!doctype html>
-<html>
-<head>
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-</head>
-
-<body>
-
-  <h2 id="Summary">Summary</h2>
-
-  <p>A summary of the property.</p>
-
-  <h2 id="Syntax">Syntax</h2>
-
-  <pre>The part we should ignore</pre>
-
-  <pre>/* The part we want   */
-this: is-the-part-we-want;</pre>
-
-
-
-</body>
-</html>
deleted file mode 100644
--- a/devtools/client/shared/test/doc_mdn-docs-01.html
+++ /dev/null
@@ -1,1 +0,0 @@
-<div class='mdn-container'></div>
--- a/devtools/client/shared/test/head.js
+++ b/devtools/client/shared/test/head.js
@@ -237,110 +237,8 @@ Task.async(function* (widget, name, valu
 
   onRender = widget.once("render");
   widget._savePreset({
     preventDefault: () => {}
   });
 
   yield onRender;
 });
-
-/**
- * Utility function for testing CSS code samples that have been
- * syntax-highlighted.
- *
- * The CSS syntax highlighter emits a collection of DOM nodes that have
- * CSS classes applied to them. This function checks that those nodes
- * are what we expect.
- *
- * @param {array} expectedNodes
- * A representation of the nodes we expect to see.
- * Each node is an object containing two properties:
- * - type: a string which can be one of:
- *   - text, comment, property-name, property-value
- * - text: the textContent of the node
- *
- * For example, given a string like this:
- * "<comment> The part we want   </comment>\n this: is-the-part-we-want;"
- *
- * we would represent the expected output like this:
- * [{type: "comment",        text: "<comment> The part we want   </comment>"},
- *  {type: "text",           text: "\n"},
- *  {type: "property-name",  text: "this"},
- *  {type: "text",           text: ":"},
- *  {type: "text",           text: " "},
- *  {type: "property-value", text: "is-the-part-we-want"},
- *  {type: "text",           text: ";"}];
- *
- * @param {Node} parent
- * The DOM node whose children are the output of the syntax highlighter.
- */
-function checkCssSyntaxHighlighterOutput(expectedNodes, parent) {
-  /**
-   * The classes applied to the output nodes by the syntax highlighter.
-   * These must be same as the definitions in MdnDocsWidget.js.
-   */
-  const PROPERTY_NAME_COLOR = "theme-fg-color5";
-  const PROPERTY_VALUE_COLOR = "theme-fg-color1";
-  const COMMENT_COLOR = "theme-comment";
-
-  /**
-   * Check the type and content of a single node.
-   */
-  function checkNode(expected, actual) {
-    ok(actual.textContent == expected.text,
-       "Check that node has the expected textContent");
-    info("Expected text content: [" + expected.text + "]");
-    info("Actual text content: [" + actual.textContent + "]");
-
-    info("Check that node has the expected type");
-    if (expected.type == "text") {
-      ok(actual.nodeType == 3, "Check that node is a text node");
-    } else {
-      ok(actual.tagName.toUpperCase() == "SPAN", "Check that node is a SPAN");
-    }
-
-    info("Check that node has the expected className");
-
-    let expectedClassName = null;
-    let actualClassName = null;
-
-    switch (expected.type) {
-      case "property-name":
-        expectedClassName = PROPERTY_NAME_COLOR;
-        break;
-      case "property-value":
-        expectedClassName = PROPERTY_VALUE_COLOR;
-        break;
-      case "comment":
-        expectedClassName = COMMENT_COLOR;
-        break;
-      default:
-        ok(!actual.classList, "No className expected");
-        return;
-    }
-
-    ok(actual.classList.length == 1, "One className expected");
-    actualClassName = actual.classList[0];
-
-    ok(expectedClassName == actualClassName, "Check className value");
-    info("Expected className: " + expectedClassName);
-    info("Actual className: " + actualClassName);
-  }
-
-  info("Logging the actual nodes we have:");
-  for (let j = 0; j < parent.childNodes.length; j++) {
-    let n = parent.childNodes[j];
-    info(j + " / " +
-         "nodeType: " + n.nodeType + " / " +
-         "textContent: " + n.textContent);
-  }
-
-  ok(parent.childNodes.length == parent.childNodes.length,
-    "Check we have the expected number of nodes");
-  info("Expected node count " + expectedNodes.length);
-  info("Actual node count " + expectedNodes.length);
-
-  for (let i = 0; i < expectedNodes.length; i++) {
-    info("Check node " + i);
-    checkNode(expectedNodes[i], parent.childNodes[i]);
-  }
-}
deleted file mode 100644
--- a/devtools/client/shared/widgets/MdnDocsWidget.js
+++ /dev/null
@@ -1,513 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/**
- * This file contains functions to retrieve docs content from
- * MDN (developer.mozilla.org) for particular items, and to display
- * the content in a tooltip.
- *
- * At the moment it only supports fetching content for CSS properties,
- * but it might support other types of content in the future
- * (Web APIs, for example).
- *
- * It's split into two parts:
- *
- * - functions like getCssDocs that just fetch content from MDN,
- * without any constraints on what to do with the content. If you
- * want to embed the content in some custom way, use this.
- *
- * - the MdnDocsWidget class, that manages and updates a tooltip
- * document whose content is taken from MDN. If you want to embed
- * the content in a tooltip, use this in conjunction with Tooltip.js.
- */
-
-"use strict";
-
-const Services = require("Services");
-const defer = require("devtools/shared/defer");
-const {getCSSLexer} = require("devtools/shared/css/lexer");
-const EventEmitter = require("devtools/shared/old-event-emitter");
-const {gDevTools} = require("devtools/client/framework/devtools");
-
-const {LocalizationHelper} = require("devtools/shared/l10n");
-const L10N = new LocalizationHelper("devtools/client/locales/inspector.properties");
-
-const XHTML_NS = "http://www.w3.org/1999/xhtml";
-
-// Parameters for the XHR request
-// see https://developer.mozilla.org/en-US/docs/MDN/Kuma/API#Document_parameters
-const XHR_PARAMS = "?raw&macros";
-// URL for the XHR request
-var XHR_CSS_URL = "https://developer.mozilla.org/en-US/docs/Web/CSS/";
-
-// Parameters for the link to MDN in the tooltip, so
-// so we know which MDN visits come from this feature
-const PAGE_LINK_PARAMS =
-  "?utm_source=mozilla&utm_medium=firefox-inspector&utm_campaign=default";
-// URL for the page link omits locale, so a locale-specific page will be loaded
-var PAGE_LINK_URL = "https://developer.mozilla.org/docs/Web/CSS/";
-exports.PAGE_LINK_URL = PAGE_LINK_URL;
-
-const PROPERTY_NAME_COLOR = "theme-fg-color5";
-const PROPERTY_VALUE_COLOR = "theme-fg-color1";
-const COMMENT_COLOR = "theme-comment";
-
-/**
- * Turns a string containing a series of CSS declarations into
- * a series of DOM nodes, with classes applied to provide syntax
- * highlighting.
- *
- * It uses the CSS tokenizer to generate a stream of CSS tokens.
- * https://dxr.mozilla.org/mozilla-central/source/dom/webidl/CSSLexer.webidl
- * lists all the token types.
- *
- * - "whitespace", "comment", and "symbol" tokens are appended as TEXT nodes,
- * and will inherit the default style for text.
- *
- * - "ident" tokens that we think are property names are considered to be
- * a property name, and are appended as SPAN nodes with a distinct color class.
- *
- * - "ident" nodes which we do not think are property names, and nodes
- * of all other types ("number", "url", "percentage", ...) are considered
- * to be part of a property value, and are appended as SPAN nodes with
- * a different color class.
- *
- * @param {Document} doc
- * Used to create nodes.
- *
- * @param {String} syntaxText
- * The CSS input. This is assumed to consist of a series of
- * CSS declarations, with trailing semicolons.
- *
- * @param {DOM node} syntaxSection
- * This is the parent for the output nodes. Generated nodes
- * are appended to this as children.
- */
-function appendSyntaxHighlightedCSS(cssText, parentElement) {
-  let doc = parentElement.ownerDocument;
-  let identClass = PROPERTY_NAME_COLOR;
-  let lexer = getCSSLexer(cssText);
-
-  /**
-   * Create a SPAN node with the given text content and class.
-   */
-  function createStyledNode(textContent, className) {
-    let newNode = doc.createElementNS(XHTML_NS, "span");
-    newNode.classList.add(className);
-    newNode.textContent = textContent;
-    return newNode;
-  }
-
-  /**
-   * If the symbol is ":", we will expect the next
-   * "ident" token to be part of a property value.
-   *
-   * If the symbol is ";", we will expect the next
-   * "ident" token to be a property name.
-   */
-  function updateIdentClass(tokenText) {
-    if (tokenText === ":") {
-      identClass = PROPERTY_VALUE_COLOR;
-    } else if (tokenText === ";") {
-      identClass = PROPERTY_NAME_COLOR;
-    }
-  }
-
-  /**
-   * Create the appropriate node for this token type.
-   *
-   * If this token is a symbol, also update our expectations
-   * for what the next "ident" token represents.
-   */
-  function tokenToNode(token, tokenText) {
-    switch (token.tokenType) {
-      case "ident":
-        return createStyledNode(tokenText, identClass);
-      case "symbol":
-        updateIdentClass(tokenText);
-        return doc.createTextNode(tokenText);
-      case "whitespace":
-        return doc.createTextNode(tokenText);
-      case "comment":
-        return createStyledNode(tokenText, COMMENT_COLOR);
-      default:
-        return createStyledNode(tokenText, PROPERTY_VALUE_COLOR);
-    }
-  }
-
-  let token = lexer.nextToken();
-  while (token) {
-    let tokenText = cssText.slice(token.startOffset, token.endOffset);
-    let newNode = tokenToNode(token, tokenText);
-    parentElement.appendChild(newNode);
-    token = lexer.nextToken();
-  }
-}
-
-exports.appendSyntaxHighlightedCSS = appendSyntaxHighlightedCSS;
-
-/**
- * Fetch an MDN page.
- *
- * @param {string} pageUrl
- * URL of the page to fetch.
- *
- * @return {promise}
- * The promise is resolved with the page as an XML document.
- *
- * The promise is rejected with an error message if
- * we could not load the page.
- */
-function getMdnPage(pageUrl) {
-  let deferred = defer();
-
-  let xhr = new XMLHttpRequest();
-
-  xhr.addEventListener("load", onLoaded);
-  xhr.addEventListener("error", onError);
-
-  xhr.open("GET", pageUrl);
-  xhr.responseType = "document";
-  xhr.send();
-
-  function onLoaded(e) {
-    if (xhr.status != 200) {
-      deferred.reject({page: pageUrl, status: xhr.status});
-    } else {
-      deferred.resolve(xhr.responseXML);
-    }
-  }
-
-  function onError(e) {
-    deferred.reject({page: pageUrl, status: xhr.status});
-  }
-
-  return deferred.promise;
-}
-
-/**
- * Gets some docs for the given CSS property.
- * Loads an MDN page for the property and gets some
- * information about the property.
- *
- * @param {string} cssProperty
- * The property for which we want docs.
- *
- * @return {promise}
- * The promise is resolved with an object containing:
- * - summary: a short summary of the property
- * - syntax: some example syntax
- *
- * The promise is rejected with an error message if
- * we could not load the page.
- */
-function getCssDocs(cssProperty) {
-  let deferred = defer();
-  let pageUrl = XHR_CSS_URL + cssProperty + XHR_PARAMS;
-
-  getMdnPage(pageUrl).then(parseDocsFromResponse, handleRejection);
-
-  function parseDocsFromResponse(responseDocument) {
-    let theDocs = {};
-    theDocs.summary = getSummary(responseDocument);
-    theDocs.syntax = getSyntax(responseDocument);
-    if (theDocs.summary || theDocs.syntax) {
-      deferred.resolve(theDocs);
-    } else {
-      deferred.reject("Couldn't find the docs in the page.");
-    }
-  }
-
-  function handleRejection(e) {
-    deferred.reject(e.status);
-  }
-
-  return deferred.promise;
-}
-
-exports.getCssDocs = getCssDocs;
-
-/**
- * The MdnDocsWidget is used by tooltip code that needs to display docs
- * from MDN in a tooltip.
- *
- * In the constructor, the widget does some general setup that's not
- * dependent on the particular item we need docs for.
- *
- * After that, when the tooltip code needs to display docs for an item, it
- * asks the widget to retrieve the docs and update the document with them.
- *
- * @param {Element} tooltipContainer
- * A DOM element where the MdnDocs widget markup should be created.
- */
-function MdnDocsWidget(tooltipContainer) {
-  EventEmitter.decorate(this);
-
-  tooltipContainer.innerHTML =
-    `<header>
-       <h1 class="mdn-property-name theme-fg-color5"></h1>
-     </header>
-     <div class="mdn-property-info">
-       <div class="mdn-summary"></div>
-       <pre class="mdn-syntax devtools-monospace"></pre>
-     </div>
-     <footer>
-       <a class="mdn-visit-page theme-link" href="#">Visit MDN (placeholder)</a>
-     </footer>`;
-
-  // fetch all the bits of the document that we will manipulate later
-  this.elements = {
-    heading: tooltipContainer.querySelector(".mdn-property-name"),
-    summary: tooltipContainer.querySelector(".mdn-summary"),
-    syntax: tooltipContainer.querySelector(".mdn-syntax"),
-    info: tooltipContainer.querySelector(".mdn-property-info"),
-    linkToMdn: tooltipContainer.querySelector(".mdn-visit-page")
-  };
-
-  // get the localized string for the link text
-  this.elements.linkToMdn.textContent = L10N.getStr("docsTooltip.visitMDN");
-
-  // force using LTR because we use the en-US version of MDN
-  tooltipContainer.setAttribute("dir", "ltr");
-
-  // listen for clicks and open in the browser window instead
-  let mainWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
-  this.elements.linkToMdn.addEventListener("click", (e) => {
-    e.stopPropagation();
-    e.preventDefault();
-    mainWindow.openUILinkIn(e.target.href, "tab");
-    this.emit("visitlink");
-  });
-}
-
-exports.MdnDocsWidget = MdnDocsWidget;
-
-MdnDocsWidget.prototype = {
-  /**
-   * This is called just before the tooltip is displayed, and is
-   * passed the CSS property for which we want to display help.
-   *
-   * Its job is to make sure the document contains the docs
-   * content for that CSS property.
-   *
-   * First, it initializes the document, setting the things it can
-   * set synchronously, resetting the things it needs to get
-   * asynchronously, and making sure the throbber is throbbing.
-   *
-   * Then it tries to get the content asynchronously, updating
-   * the document with the content or with an error message.
-   *
-   * It returns immediately, so the caller can display the tooltip
-   * without waiting for the asynch operation to complete.
-   *
-   * @param {string} propertyName
-   * The name of the CSS property for which we need to display help.
-   */
-  loadCssDocs: function (propertyName) {
-    /**
-     * Do all the setup we can do synchronously, and get the document in
-     * a state where it can be displayed while we are waiting for the
-     * MDN docs content to be retrieved.
-     */
-    function initializeDocument(propName) {
-      // set property name heading
-      elements.heading.textContent = propName;
-
-      // set link target
-      elements.linkToMdn.setAttribute("href",
-        PAGE_LINK_URL + propName + PAGE_LINK_PARAMS);
-
-      // clear docs summary and syntax
-      elements.summary.textContent = "";
-      while (elements.syntax.firstChild) {
-        elements.syntax.firstChild.remove();
-      }
-
-      // reset the scroll position
-      elements.info.scrollTop = 0;
-      elements.info.scrollLeft = 0;
-
-      // show the throbber
-      elements.info.classList.add("devtools-throbber");
-    }
-
-    /**
-     * This is called if we successfully got the docs content.
-     * Finishes setting up the tooltip content, and disables the throbber.
-     */
-    function finalizeDocument({summary, syntax}) {
-      // set docs summary and syntax
-      elements.summary.textContent = summary;
-      appendSyntaxHighlightedCSS(syntax, elements.syntax);
-
-      // hide the throbber
-      elements.info.classList.remove("devtools-throbber");
-
-      deferred.resolve(this);
-    }
-
-    /**
-     * This is called if we failed to get the docs content.
-     * Sets the content to contain an error message, and disables the throbber.
-     */
-    function gotError(error) {
-      // show error message
-      elements.summary.textContent = L10N.getStr("docsTooltip.loadDocsError");
-
-      // hide the throbber
-      elements.info.classList.remove("devtools-throbber");
-
-      // although gotError is called when there's an error, we have handled
-      // the error, so call resolve not reject.
-      deferred.resolve(this);
-    }
-
-    let deferred = defer();
-    let elements = this.elements;
-
-    initializeDocument(propertyName);
-    getCssDocs(propertyName).then(finalizeDocument, gotError);
-
-    return deferred.promise;
-  },
-
-  destroy: function () {
-    this.elements = null;
-  }
-};
-
-/**
- * Test whether a node is all whitespace.
- *
- * @return {boolean}
- * True if the node all whitespace, otherwise false.
- */
-function isAllWhitespace(node) {
-  return !(/[^\t\n\r ]/.test(node.textContent));
-}
-
-/**
- * Test whether a node is a comment or whitespace node.
- *
- * @return {boolean}
- * True if the node is a comment node or is all whitespace, otherwise false.
- */
-function isIgnorable(node) {
-  // Comment nodes (8), text nodes (3) or whitespace
-  return (node.nodeType == 8) ||
-         ((node.nodeType == 3) && isAllWhitespace(node));
-}
-
-/**
- * Get the next node, skipping comments and whitespace.
- *
- * @return {node}
- * The next sibling node that is not a comment or whitespace, or null if
- * there isn't one.
- */
-function nodeAfter(sib) {
-  while ((sib = sib.nextSibling)) {
-    if (!isIgnorable(sib)) {
-      return sib;
-    }
-  }
-  return null;
-}
-
-/**
- * Test whether the argument `node` is a node whose tag is `tagName`.
- *
- * @param {node} node
- * The code to test. May be null.
- *
- * @param {string} tagName
- * The tag name to test against.
- *
- * @return {boolean}
- * True if the node is not null and has the tag name `tagName`,
- * otherwise false.
- */
-function hasTagName(node, tagName) {
-  return node && node.tagName &&
-         node.tagName.toLowerCase() == tagName.toLowerCase();
-}
-
-/**
- * Given an MDN page, get the "summary" portion.
- *
- * This is the textContent of the first non-whitespace
- * element in the #Summary section of the document.
- *
- * It's expected to be a <P> element.
- *
- * @param {Document} mdnDocument
- * The document in which to look for the "summary" section.
- *
- * @return {string}
- * The summary section as a string, or null if it could not be found.
- */
-function getSummary(mdnDocument) {
-  let summary = mdnDocument.getElementById("Summary");
-  if (!hasTagName(summary, "H2")) {
-    return null;
-  }
-
-  let firstParagraph = nodeAfter(summary);
-  if (!hasTagName(firstParagraph, "P")) {
-    return null;
-  }
-
-  return firstParagraph.textContent;
-}
-
-/**
- * Given an MDN page, get the "syntax" portion.
- *
- * First we get the #Syntax section of the document. The syntax
- * section we want is somewhere inside there.
- *
- * If the page is in the old structure, then the *first two*
- * non-whitespace elements in the #Syntax section will be <PRE>
- * nodes, and the second of these will be the syntax section.
- *
- * If the page is in the new structure, then the only the *first*
- * non-whitespace element in the #Syntax section will be a <PRE>
- * node, and it will be the syntax section.
- *
- * @param {Document} mdnDocument
- * The document in which to look for the "syntax" section.
- *
- * @return {string}
- * The syntax section as a string, or null if it could not be found.
- */
-function getSyntax(mdnDocument) {
-  let syntax = mdnDocument.getElementById("Syntax");
-  if (!hasTagName(syntax, "H2")) {
-    return null;
-  }
-
-  let firstParagraph = nodeAfter(syntax);
-  if (!hasTagName(firstParagraph, "PRE")) {
-    return null;
-  }
-
-  let secondParagraph = nodeAfter(firstParagraph);
-  if (hasTagName(secondParagraph, "PRE")) {
-    return secondParagraph.textContent;
-  }
-  return firstParagraph.textContent;
-}
-
-/**
- * Use a different URL for CSS docs pages. Used only for testing.
- *
- * @param {string} baseUrl
- * The baseURL to use.
- */
-function setBaseCssDocsUrl(baseUrl) {
-  PAGE_LINK_URL = baseUrl;
-  XHR_CSS_URL = baseUrl;
-}
-
-exports.setBaseCssDocsUrl = setBaseCssDocsUrl;
deleted file mode 100644
--- a/devtools/client/shared/widgets/mdn-docs.css
+++ /dev/null
@@ -1,39 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-.mdn-container {
-  height: 300px;
-  margin: 4px;
-  overflow: auto;
-  box-sizing: border-box;
-  display: flex;
-  flex-direction: column;
-}
-
-.mdn-container header,
-.mdn-container footer {
-  flex: 1;
-  padding: 0 1em;
-}
-
-.mdn-property-info {
-  flex: 10;
-  padding: 0 1em;
-  overflow: auto;
-  transition: opacity 400ms ease-in;
-}
-
-.mdn-syntax {
-  margin-top: 1em;
-}
-
-.mdn-container .devtools-throbber {
-  align-self: center;
-  opacity: 0;
-}
-
-.mdn-visit-page {
-  display: inline-block;
-  padding: 1em 0;
-}
--- a/devtools/client/shared/widgets/moz.build
+++ b/devtools/client/shared/widgets/moz.build
@@ -17,17 +17,16 @@ DevToolsModules(
     'CubicBezierPresets.js',
     'CubicBezierWidget.js',
     'FastListWidget.js',
     'FilterWidget.js',
     'FlameGraph.js',
     'Graphs.js',
     'GraphsWorker.js',
     'LineGraphWidget.js',
-    'MdnDocsWidget.js',
     'MountainGraphWidget.js',
     'SideMenuWidget.jsm',
     'SimpleListWidget.jsm',
     'Spectrum.js',
     'TableWidget.js',
     'TreeWidget.js',
     'VariablesView.jsm',
     'VariablesViewController.jsm',
deleted file mode 100644
--- a/devtools/client/shared/widgets/tooltip/CssDocsTooltip.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
-const {MdnDocsWidget} = require("devtools/client/shared/widgets/MdnDocsWidget");
-const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
-const XHTML_NS = "http://www.w3.org/1999/xhtml";
-
-const TOOLTIP_WIDTH = 418;
-const TOOLTIP_HEIGHT = 308;
-
-/**
- * Tooltip for displaying docs for CSS properties from MDN.
- *
- * @param {Document} toolboxDoc
- *        The toolbox document to attach the CSS docs tooltip.
- */
-function CssDocsTooltip(toolboxDoc) {
-  this.tooltip = new HTMLTooltip(toolboxDoc, {
-    type: "arrow",
-    consumeOutsideClicks: true,
-    autofocus: true,
-    useXulWrapper: true
-  });
-  this.widget = this.setMdnDocsContent();
-  this._onVisitLink = this._onVisitLink.bind(this);
-  this.widget.on("visitlink", this._onVisitLink);
-
-  // Initialize keyboard shortcuts
-  this.shortcuts = new KeyShortcuts({ window: this.tooltip.topWindow });
-  this._onShortcut = this._onShortcut.bind(this);
-
-  this.shortcuts.on("Escape", this._onShortcut);
-}
-
-CssDocsTooltip.prototype = {
-  /**
-   * Reports if the tooltip is currently shown
-   *
-   * @return {Boolean} True if the tooltip is displayed.
-   */
-  isVisible: function () {
-    return this.tooltip.isVisible();
-  },
-
-  /**
-   * Load CSS docs for the given property,
-   * then display the tooltip.
-   */
-  show: function (anchor, propertyName) {
-    this.tooltip.once("shown", () => {
-      this.widget.loadCssDocs(propertyName);
-    });
-    this.tooltip.show(anchor);
-  },
-
-  hide: function () {
-    this.tooltip.hide();
-  },
-
-  revert: function () {},
-
-  _onShortcut: function (shortcut, event) {
-    if (!this.tooltip.isVisible()) {
-      return;
-    }
-    event.stopPropagation();
-    event.preventDefault();
-    this.hide();
-  },
-
-  _onVisitLink: function () {
-    this.hide();
-  },
-
-  /**
-   * Set the content of this tooltip to the MDN docs widget. This is called when the
-   * tooltip is first constructed.
-   * The caller can use the MdnDocsWidget to update the tooltip's  UI with new content
-   * each time the tooltip is shown.
-   *
-   * @return {MdnDocsWidget} the created MdnDocsWidget instance.
-   */
-  setMdnDocsContent: function () {
-    let container = this.tooltip.doc.createElementNS(XHTML_NS, "div");
-    container.setAttribute("class", "mdn-container theme-body");
-    this.tooltip.setContent(container, {width: TOOLTIP_WIDTH, height: TOOLTIP_HEIGHT});
-    return new MdnDocsWidget(container);
-  },
-
-  destroy: function () {
-    this.widget.off("visitlink", this._onVisitLink);
-    this.widget.destroy();
-
-    this.shortcuts.destroy();
-    this.tooltip.destroy();
-  }
-};
-
-module.exports = CssDocsTooltip;
--- a/devtools/client/shared/widgets/tooltip/moz.build
+++ b/devtools/client/shared/widgets/tooltip/moz.build
@@ -1,16 +1,15 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
-    'CssDocsTooltip.js',
     'EventTooltipHelper.js',
     'HTMLTooltip.js',
     'ImageTooltipHelper.js',
     'InlineTooltip.js',
     'SwatchBasedEditorTooltip.js',
     'SwatchColorPickerTooltip.js',
     'SwatchCubicBezierTooltip.js',
     'SwatchFilterTooltip.js',
--- a/devtools/client/themes/commandline.css
+++ b/devtools/client/themes/commandline.css
@@ -238,13 +238,8 @@
   list-style-type: none;
   padding-left: 0;
 }
 
 .gcli-appcache-detail {
   padding-left: 20px;
   padding-bottom: 10px;
 }
-
-.gcli-mdn-url {
-  text-decoration: underline;
-  cursor: pointer;
-}
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -2,17 +2,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/. */
 
 /* Import stylesheets for specific tooltip widgets */
 @import url(chrome://devtools/content/shared/widgets/color-widget.css);
 @import url(chrome://devtools/content/shared/widgets/cubic-bezier.css);
 @import url(chrome://devtools/content/shared/widgets/filter-widget.css);
-@import url(chrome://devtools/content/shared/widgets/mdn-docs.css);
 @import url(chrome://devtools/content/shared/widgets/spectrum.css);
 
 /* Tooltip specific theme variables */
 
 .theme-dark {
   --bezier-diagonal-color: #eee;
   --bezier-grid-color: rgba(0, 0, 0, 0.2);
 }
--- a/devtools/shared/gcli/commands/index.js
+++ b/devtools/shared/gcli/commands/index.js
@@ -60,17 +60,16 @@ exports.devtoolsModules = [
   "devtools/shared/gcli/commands/cmd",
   "devtools/shared/gcli/commands/cookie",
   "devtools/shared/gcli/commands/csscoverage",
   "devtools/shared/gcli/commands/folder",
   "devtools/shared/gcli/commands/highlight",
   "devtools/shared/gcli/commands/inject",
   "devtools/shared/gcli/commands/jsb",
   "devtools/shared/gcli/commands/listen",
-  "devtools/shared/gcli/commands/mdn",
   "devtools/shared/gcli/commands/measure",
   "devtools/shared/gcli/commands/media",
   "devtools/shared/gcli/commands/pagemod",
   "devtools/shared/gcli/commands/paintflashing",
   "devtools/shared/gcli/commands/qsa",
   "devtools/shared/gcli/commands/restart",
   "devtools/shared/gcli/commands/rulers",
   "devtools/shared/gcli/commands/screenshot",
deleted file mode 100644
--- a/devtools/shared/gcli/commands/mdn.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const l10n = require("gcli/l10n");
-
-var MdnDocsWidget;
-try {
-  MdnDocsWidget = require("devtools/client/shared/widgets/MdnDocsWidget");
-} catch (e) {
-  // DevTools MdnDocsWidget only available in Firefox Desktop
-}
-
-exports.items = [{
-  name: "mdn",
-  description: l10n.lookup("mdnDesc")
-}, {
-  item: "command",
-  runAt: "client",
-  name: "mdn css",
-  description: l10n.lookup("mdnCssDesc"),
-  returnType: "cssPropertyOutput",
-  params: [{
-    name: "property",
-    type: { name: "string" },
-    defaultValue: null,
-    description: l10n.lookup("mdnCssProp")
-  }],
-  exec: function (args) {
-    if (!MdnDocsWidget) {
-      return null;
-    }
-
-    return MdnDocsWidget.getCssDocs(args.property).then(result => {
-      return {
-        data: result,
-        url: MdnDocsWidget.PAGE_LINK_URL + args.property,
-        property: args.property
-      };
-    }, error => {
-      return { error, property: args.property };
-    });
-  }
-}, {
-  item: "converter",
-  from: "cssPropertyOutput",
-  to: "dom",
-  exec: function (result, context) {
-    let propertyName = result.property;
-
-    let document = context.document;
-    let root = document.createElement("div");
-
-    if (result.error) {
-      // The css property specified doesn't exist.
-      root.appendChild(document.createTextNode(
-        l10n.lookupFormat("mdnCssPropertyNotFound", [ propertyName ]) +
-        " (" + result.error + ")"));
-    } else {
-      let title = document.createElement("h2");
-      title.textContent = propertyName;
-      root.appendChild(title);
-
-      let link = document.createElement("p");
-      link.classList.add("gcli-mdn-url");
-      link.textContent = l10n.lookup("mdnCssVisitPage");
-      root.appendChild(link);
-
-      link.addEventListener("click", () => {
-        let mainWindow = context.environment.chromeWindow;
-        mainWindow.openUILinkIn(result.url, "tab");
-      });
-
-      let summary = document.createElement("p");
-      summary.textContent = result.data.summary;
-      root.appendChild(summary);
-    }
-
-    return root;
-  }
-}];
--- a/devtools/shared/gcli/commands/moz.build
+++ b/devtools/shared/gcli/commands/moz.build
@@ -12,17 +12,16 @@ DevToolsModules(
     'cookie.js',
     'csscoverage.js',
     'folder.js',
     'highlight.js',
     'index.js',
     'inject.js',
     'jsb.js',
     'listen.js',
-    'mdn.js',
     'measure.js',
     'media.js',
     'pagemod.js',
     'paintflashing.js',
     'qsa.js',
     'restart.js',
     'rulers.js',
     'screenshot.js',
--- a/devtools/shared/locales/en-US/gclicommands.properties
+++ b/devtools/shared/locales/en-US/gclicommands.properties
@@ -1464,34 +1464,16 @@ folderOpenProfileDesc=Open profile direc
 # of the 'folder open' command with an invalid folder path.
 folderInvalidPath=Please enter a valid path
 
 # LOCALIZATION NOTE (folderOpenDirResult) A very short string used to
 # describe the result of the 'folder open' command.
 # The argument (%1$S) is the folder path.
 folderOpenDirResult=Opened %1$S
 
-# LOCALIZATION NOTE (mdnDesc) A very short string used to describe the
-# use of 'mdn' command.
-mdnDesc=Retrieve documentation from MDN
-# LOCALIZATION NOTE (mdnCssDesc) A very short string used to describe the
-# result of the 'mdn css' command.
-mdnCssDesc=Retrieve documentation about a given CSS property name from MDN
-# LOCALIZATION NOTE (mdnCssProp) String used to describe the 'property name'
-# parameter used in the 'mdn css' command.
-mdnCssProp=Property name
-# LOCALIZATION NOTE (mdnCssPropertyNotFound) String used to display an error in
-# the result of the 'mdn css' command. Errors occur when a given CSS property
-# wasn't found on MDN. The %1$S parameter will be replaced with the name of the
-# CSS property.
-mdnCssPropertyNotFound=MDN documentation for the CSS property ā€˜%1$Sā€™ was not found.
-# LOCALIZATION NOTE (mdnCssVisitPage) String used as the label of a link to the
-# MDN page for a given CSS property.
-mdnCssVisitPage=Visit MDN page
-
 # LOCALIZATION NOTE (security)
 securityPrivacyDesc=Display supported security and privacy features
 securityManual=Commands to list and get suggestions about security features for the current domain.
 securityListDesc=Display security features
 securityListManual=Display a list of all relevant security features of the current page.
 # CSP specific
 securityCSPDesc=Display CSP specific security features
 securityCSPManual=Display feedback about the CSP applied to the current page.
--- a/devtools/shared/locales/en-US/styleinspector.properties
+++ b/devtools/shared/locales/en-US/styleinspector.properties
@@ -137,24 +137,16 @@ styleinspector.copyImageDataUrlError=Fai
 # LOCALIZATION NOTE (styleinspector.contextmenu.toggleOrigSources): Text displayed in the rule view
 # context menu.
 styleinspector.contextmenu.toggleOrigSources=Show Original Sources
 
 # LOCALIZATION NOTE (styleinspector.contextmenu.toggleOrigSources.accessKey): Access key for
 # the rule view context menu "Show original sources" entry.
 styleinspector.contextmenu.toggleOrigSources.accessKey=O
 
-# LOCALIZATION NOTE (styleinspector.contextmenu.showMdnDocs): Text displayed in the rule view
-# context menu to display docs from MDN for an item.
-styleinspector.contextmenu.showMdnDocs=Show MDN Docs
-
-# LOCALIZATION NOTE (styleinspector.contextmenu.showMdnDocs.accessKey): Access key for
-# the rule view context menu "Show MDN docs" entry.
-styleinspector.contextmenu.showMdnDocs.accessKey=D
-
 # LOCALIZATION NOTE (styleinspector.contextmenu.addNewRule): Text displayed in the
 # rule view context menu for adding a new rule to the element.
 # This should match inspector.addRule.tooltip in inspector.properties
 styleinspector.contextmenu.addNewRule=Add New Rule
 
 # LOCALIZATION NOTE (styleinspector.contextmenu.addRule.accessKey): Access key for
 # the rule view context menu "Add rule" entry.
 styleinspector.contextmenu.addNewRule.accessKey=R
--- a/docshell/shistory/nsSHEntryShared.cpp
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -296,36 +296,33 @@ nsSHEntryShared::AttributeChanged(nsIDoc
                                   const nsAttrValue* aOldValue)
 {
   RemoveFromBFCacheAsync();
 }
 
 void
 nsSHEntryShared::ContentAppended(nsIDocument* aDocument,
                                  nsIContent* aContainer,
-                                 nsIContent* aFirstNewContent,
-                                 int32_t /* unused */)
+                                 nsIContent* aFirstNewContent)
 {
   RemoveFromBFCacheAsync();
 }
 
 void
 nsSHEntryShared::ContentInserted(nsIDocument* aDocument,
                                  nsIContent* aContainer,
-                                 nsIContent* aChild,
-                                 int32_t /* unused */)
+                                 nsIContent* aChild)
 {
   RemoveFromBFCacheAsync();
 }
 
 void
 nsSHEntryShared::ContentRemoved(nsIDocument* aDocument,
                                 nsIContent* aContainer,
                                 nsIContent* aChild,
-                                int32_t aIndexInContainer,
                                 nsIContent* aPreviousSibling)
 {
   RemoveFromBFCacheAsync();
 }
 
 void
 nsSHEntryShared::ParentChainChanged(nsIContent* aContent)
 {
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -613,17 +613,17 @@ Navigator::CookieEnabled()
   }
 
   nsCOMPtr<nsICookiePermission> permMgr =
     do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
   NS_ENSURE_TRUE(permMgr, cookieEnabled);
 
   // Pass null for the channel, just like the cookie service does.
   nsCookieAccess access;
-  nsresult rv = permMgr->CanAccess(codebaseURI, nullptr, &access);
+  nsresult rv = permMgr->CanAccess(doc->NodePrincipal(), &access);
   NS_ENSURE_SUCCESS(rv, cookieEnabled);
 
   if (access != nsICookiePermission::ACCESS_DEFAULT) {
     cookieEnabled = access != nsICookiePermission::ACCESS_DENY;
   }
 
   return cookieEnabled;
 }
--- a/dom/base/NodeIterator.cpp
+++ b/dom/base/NodeIterator.cpp
@@ -267,17 +267,16 @@ NS_IMETHODIMP NodeIterator::GetPointerBe
 
 /*
  * nsIMutationObserver interface
  */
 
 void NodeIterator::ContentRemoved(nsIDocument *aDocument,
                                   nsIContent *aContainer,
                                   nsIContent *aChild,
-                                  int32_t aIndexInContainer,
                                   nsIContent *aPreviousSibling)
 {
     nsINode *container = NODE_FROM(aContainer, aDocument);
 
     mPointer.AdjustAfterRemoval(mRoot, container, aChild, aPreviousSibling);
     mWorkingPointer.AdjustAfterRemoval(mRoot, container, aChild, aPreviousSibling);
 }
 
--- a/dom/base/ShadowRoot.cpp
+++ b/dom/base/ShadowRoot.cpp
@@ -488,18 +488,17 @@ ShadowRoot::AttributeChanged(nsIDocument
   // Attributes may change insertion point matching, find its new distribution.
   RemoveDistributedNode(aElement);
   DistributeSingleNode(aElement);
 }
 
 void
 ShadowRoot::ContentAppended(nsIDocument* aDocument,
                             nsIContent* aContainer,
-                            nsIContent* aFirstNewContent,
-                            int32_t aNewIndexInContainer)
+                            nsIContent* aFirstNewContent)
 {
   if (mInsertionPointChanged) {
     DistributeAllNodes();
     mInsertionPointChanged = false;
     return;
   }
 
   // Watch for new nodes added to the pool because the node
@@ -520,18 +519,17 @@ ShadowRoot::ContentAppended(nsIDocument*
 
     currentChild = currentChild->GetNextSibling();
   }
 }
 
 void
 ShadowRoot::ContentInserted(nsIDocument* aDocument,
                             nsIContent* aContainer,
-                            nsIContent* aChild,
-                            int32_t aIndexInContainer)
+                            nsIContent* aChild)
 {
   if (mInsertionPointChanged) {
     DistributeAllNodes();
     mInsertionPointChanged = false;
     return;
   }
 
   // Watch for new nodes added to the pool because the node
@@ -548,17 +546,16 @@ ShadowRoot::ContentInserted(nsIDocument*
     DistributeSingleNode(aChild);
   }
 }
 
 void
 ShadowRoot::ContentRemoved(nsIDocument* aDocument,
                            nsIContent* aContainer,
                            nsIContent* aChild,
-                           int32_t aIndexInContainer,
                            nsIContent* aPreviousSibling)
 {
   if (mInsertionPointChanged) {
     DistributeAllNodes();
     mInsertionPointChanged = false;
     return;
   }
 
--- a/dom/base/nsContentList.cpp
+++ b/dom/base/nsContentList.cpp
@@ -738,18 +738,17 @@ nsContentList::AttributeChanged(nsIDocum
     // expensive one).  Either way, no change of mState is required
     // here.
     mElements.RemoveElement(aElement);
   }
 }
 
 void
 nsContentList::ContentAppended(nsIDocument* aDocument, nsIContent* aContainer,
-                               nsIContent* aFirstNewContent,
-                               int32_t aNewIndexInContainer)
+                               nsIContent* aFirstNewContent)
 {
   NS_PRECONDITION(aContainer, "Can't get at the new content if no container!");
 
   /*
    * If the state is LIST_DIRTY then we have no useful information in our list
    * and we want to put off doing work as much as possible.
    *
    * Also, if aContainer is anonymous from our point of view, we know that we
@@ -842,18 +841,17 @@ nsContentList::ContentAppended(nsIDocume
 
     ASSERT_IN_SYNC;
   }
 }
 
 void
 nsContentList::ContentInserted(nsIDocument *aDocument,
                                nsIContent* aContainer,
-                               nsIContent* aChild,
-                               int32_t aIndexInContainer)
+                               nsIContent* aChild)
 {
   // Note that aContainer can be null here if we are inserting into
   // the document itself; any attempted optimizations to this method
   // should deal with that.
   if (mState != LIST_DIRTY &&
       MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) &&
       nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
       MatchSelf(aChild)) {
@@ -862,17 +860,16 @@ nsContentList::ContentInserted(nsIDocume
 
   ASSERT_IN_SYNC;
 }
 
 void
 nsContentList::ContentRemoved(nsIDocument *aDocument,
                               nsIContent* aContainer,
                               nsIContent* aChild,
-                              int32_t aIndexInContainer,
                               nsIContent* aPreviousSibling)
 {
   // Note that aContainer can be null here if we are removing from
   // the document itself; any attempted optimizations to this method
   // should deal with that.
   if (mState != LIST_DIRTY &&
       MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) &&
       nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
@@ -1196,50 +1193,47 @@ nsLabelsNodeList::AttributeChanged(nsIDo
     SetDirty();
     return;
   }
 }
 
 void
 nsLabelsNodeList::ContentAppended(nsIDocument* aDocument,
                                   nsIContent* aContainer,
-                                  nsIContent* aFirstNewContent,
-                                  int32_t aNewIndexInContainer)
+                                  nsIContent* aFirstNewContent)
 {
   // If a labelable element is moved to outside or inside of
   // nested associated labels, we're gonna have to modify
   // the content list.
   if (mState != LIST_DIRTY ||
       nsContentUtils::IsInSameAnonymousTree(mRootNode, aContainer)) {
     SetDirty();
     return;
   }
 }
 
 void
 nsLabelsNodeList::ContentInserted(nsIDocument* aDocument,
                                   nsIContent* aContainer,
-                                  nsIContent* aChild,
-                                  int32_t aIndexInContainer)
+                                  nsIContent* aChild)
 {
   // If a labelable element is moved to outside or inside of
   // nested associated labels, we're gonna have to modify
   // the content list.
   if (mState != LIST_DIRTY ||
       nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild)) {
     SetDirty();
     return;
   }
 }
 
 void
 nsLabelsNodeList::ContentRemoved(nsIDocument* aDocument,
                                  nsIContent* aContainer,
                                  nsIContent* aChild,
-                                 int32_t aIndexInContainer,
                                  nsIContent* aPreviousSibling)
 {
   // If a labelable element is removed, we're gonna have to clean
   // the content list.
   if (mState != LIST_DIRTY ||
       nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild)) {
     SetDirty();
     return;
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -1305,18 +1305,17 @@ nsContentSink::NotifyAppend(nsIContent* 
   }
 
   mInNotification++;
 
   {
     // Scope so we call EndUpdate before we decrease mInNotification
     MOZ_AUTO_DOC_UPDATE(mDocument, UPDATE_CONTENT_MODEL, !mBeganUpdate);
     nsNodeUtils::ContentAppended(aContainer,
-                                 aContainer->GetChildAt(aStartIndex),
-                                 aStartIndex);
+                                 aContainer->GetChildAt(aStartIndex));
     mLastNotificationTime = PR_Now();
   }
 
   mInNotification--;
 }
 
 NS_IMETHODIMP
 nsContentSink::Notify(nsITimer *timer)
--- a/dom/base/nsDOMMutationObserver.cpp
+++ b/dom/base/nsDOMMutationObserver.cpp
@@ -217,18 +217,17 @@ nsMutationReceiver::CharacterDataWillCha
   if (CharacterDataOldValue() && m->mPrevValue.IsVoid()) {
     aContent->GetText()->AppendTo(m->mPrevValue);
   }
 }
 
 void
 nsMutationReceiver::ContentAppended(nsIDocument* aDocument,
                                     nsIContent* aContainer,
-                                    nsIContent* aFirstNewContent,
-                                    int32_t aNewIndexInContainer)
+                                    nsIContent* aFirstNewContent)
 {
   nsINode* parent = NODE_FROM(aContainer, aDocument);
   bool wantsChildList =
     ChildList() &&
     ((Subtree() && RegisterTarget()->SubtreeRoot() == parent->SubtreeRoot()) ||
      parent == Target());
   if (!wantsChildList || !IsObservable(aFirstNewContent)) {
     return;
@@ -258,18 +257,17 @@ nsMutationReceiver::ContentAppended(nsID
     n = n->GetNextSibling();
   }
   m->mPreviousSibling = aFirstNewContent->GetPreviousSibling();
 }
 
 void
 nsMutationReceiver::ContentInserted(nsIDocument* aDocument,
                                     nsIContent* aContainer,
-                                    nsIContent* aChild,
-                                    int32_t aIndexInContainer)
+                                    nsIContent* aChild)
 {
   nsINode* parent = NODE_FROM(aContainer, aDocument);
   bool wantsChildList =
     ChildList() &&
     ((Subtree() && RegisterTarget()->SubtreeRoot() == parent->SubtreeRoot()) ||
      parent == Target());
   if (!wantsChildList || !IsObservable(aChild)) {
     return;
@@ -294,17 +292,16 @@ nsMutationReceiver::ContentInserted(nsID
   m->mPreviousSibling = aChild->GetPreviousSibling();
   m->mNextSibling = aChild->GetNextSibling();
 }
 
 void
 nsMutationReceiver::ContentRemoved(nsIDocument* aDocument,
                                    nsIContent* aContainer,
                                    nsIContent* aChild,
-                                   int32_t aIndexInContainer,
                                    nsIContent* aPreviousSibling)
 {
   if (!IsObservable(aChild)) {
     return;
   }
 
   nsINode* parent = NODE_FROM(aContainer, aDocument);
   if (Subtree() && parent->SubtreeRoot() != RegisterTarget()->SubtreeRoot()) {
@@ -366,21 +363,24 @@ nsMutationReceiver::ContentRemoved(nsIDo
 
   if (ChildList() && (Subtree() || parent == Target())) {
     nsDOMMutationRecord* m =
       Observer()->CurrentRecord(nsGkAtoms::childList);
     if (m->mTarget) {
       // Already handled case.
       return;
     }
+    MOZ_ASSERT(parent);
+
     m->mTarget = parent;
     m->mRemovedNodes = new nsSimpleContentList(parent);
     m->mRemovedNodes->AppendElement(aChild);
     m->mPreviousSibling = aPreviousSibling;
-    m->mNextSibling = parent->GetChildAt(aIndexInContainer);
+    m->mNextSibling = aPreviousSibling ?
+      aPreviousSibling->GetNextSibling() : parent->GetFirstChild();
   }
   // We need to schedule always, so that after microtask mTransientReceivers
   // can be cleared correctly.
   Observer()->ScheduleForRun();
 }
 
 void nsMutationReceiver::NodeWillBeDestroyed(const nsINode *aNode)
 {
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2346,17 +2346,17 @@ nsDocument::ResetToURI(nsIURI *aURI, nsI
       }
       mChildren.RemoveChildAt(i);
       if (content == mCachedRootElement) {
         // Immediately clear mCachedRootElement, now that it's been removed
         // from mChildren, so that GetRootElement() will stop returning this
         // now-stale value.
         mCachedRootElement = nullptr;
       }
-      nsNodeUtils::ContentRemoved(this, content, i, previousSibling);
+      nsNodeUtils::ContentRemoved(this, content, previousSibling);
       content->UnbindFromTree();
     }
     MOZ_ASSERT(!mCachedRootElement,
                "After removing all children, there should be no root elem");
   }
   mInUnlinkOrDeletion = oldVal;
 
   // Reset our stylesheets
--- a/dom/base/nsIMutationObserver.h
+++ b/dom/base/nsIMutationObserver.h
@@ -229,29 +229,26 @@ public:
   /**
    * Notification that one or more content nodes have been appended to the
    * child list of another node in the tree.
    *
    * @param aDocument  The owner-document of aContent. Can be null.
    * @param aContainer The container that had new children appended. Is never
    *                   null.
    * @param aFirstNewContent the node at aIndexInContainer in aContainer.
-   * @param aNewIndexInContainer the index in the container of the first
-   *                   new child
    *
    * @note Callers of this method might not hold a strong reference to the
    *       observer.  The observer is responsible for making sure it stays
    *       alive for the duration of the call as needed.  The observer may
    *       assume that this call will happen when there are script blockers on
    *       the stack.
    */
   virtual void ContentAppended(nsIDocument *aDocument,
                                nsIContent* aContainer,
-                               nsIContent* aFirstNewContent,
-                               int32_t     aNewIndexInContainer) = 0;
+                               nsIContent* aFirstNewContent) = 0;
 
   /**
    * Notification that a content node has been inserted as child to another
    * node in the tree.
    *
    * @param aDocument  The owner-document of aContent, or, when aContainer
    *                   is null, the container that had the child inserted.
    *                   Can be null.
@@ -264,18 +261,17 @@ public:
    * @note Callers of this method might not hold a strong reference to the
    *       observer.  The observer is responsible for making sure it stays
    *       alive for the duration of the call as needed.  The observer may
    *       assume that this call will happen when there are script blockers on
    *       the stack.
    */
   virtual void ContentInserted(nsIDocument *aDocument,
                                nsIContent* aContainer,
-                               nsIContent* aChild,
-                               int32_t aIndexInContainer) = 0;
+                               nsIContent* aChild) = 0;
 
   /**
    * Notification that a content node has been removed from the child list of
    * another node in the tree.
    *
    * @param aDocument  The owner-document of aContent, or, when aContainer
    *                   is null, the container that had the child removed.
    *                   Can be null.
@@ -292,17 +288,16 @@ public:
    *       observer.  The observer is responsible for making sure it stays
    *       alive for the duration of the call as needed.  The observer may
    *       assume that this call will happen when there are script blockers on
    *       the stack.
    */
   virtual void ContentRemoved(nsIDocument *aDocument,
                               nsIContent* aContainer,
                               nsIContent* aChild,
-                              int32_t aIndexInContainer,
                               nsIContent* aPreviousSibling) = 0;
 
  /**
    * The node is in the process of being destroyed. Calling QI on the node is
    * not supported, however it is possible to get children and flags through
    * nsINode as well as calling IsNodeOfType(eCONTENT) and casting to
    * nsIContent to get attributes.
    *
@@ -369,30 +364,27 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIMutatio
                                   int32_t aNameSpaceID,                      \
                                   nsIAtom* aAttribute,                       \
                                   int32_t aModType,                          \
                                   const nsAttrValue* aOldValue) override;
 
 #define NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED                          \
     virtual void ContentAppended(nsIDocument* aDocument,                     \
                                  nsIContent* aContainer,                     \
-                                 nsIContent* aFirstNewContent,               \
-                                 int32_t aNewIndexInContainer) override;
+                                 nsIContent* aFirstNewContent) override;
 
 #define NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED                          \
     virtual void ContentInserted(nsIDocument* aDocument,                     \
                                  nsIContent* aContainer,                     \
-                                 nsIContent* aChild,                         \
-                                 int32_t aIndexInContainer) override;
+                                 nsIContent* aChild) override;
 
 #define NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED                           \
     virtual void ContentRemoved(nsIDocument* aDocument,                      \
                                 nsIContent* aContainer,                      \
                                 nsIContent* aChild,                          \
-                                int32_t aIndexInContainer,                   \
                                 nsIContent* aPreviousSibling) override;
 
 #define NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED                      \
     virtual void NodeWillBeDestroyed(const nsINode* aNode) override;
 
 #define NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED                       \
     virtual void ParentChainChanged(nsIContent *aContent) override;
 
@@ -449,32 +441,29 @@ void                                    
                          nsIAtom* aAttribute,                             \
                          int32_t aModType,                                \
                          const nsAttrValue* aOldValue)                    \
 {                                                                         \
 }                                                                         \
 void                                                                      \
 _class::ContentAppended(nsIDocument* aDocument,                           \
                         nsIContent* aContainer,                           \
-                        nsIContent* aFirstNewContent,                     \
-                        int32_t aNewIndexInContainer)                     \
+                        nsIContent* aFirstNewContent)                     \
 {                                                                         \
 }                                                                         \
 void                                                                      \
 _class::ContentInserted(nsIDocument* aDocument,                           \
                         nsIContent* aContainer,                           \
-                        nsIContent* aChild,                               \
-                        int32_t aIndexInContainer)                        \
+                        nsIContent* aChild)                               \
 {                                                                         \
 }                                                                         \
 void                                                                      \
 _class::ContentRemoved(nsIDocument* aDocument,                            \
                        nsIContent* aContainer,                            \
                        nsIContent* aChild,                                \
-                       int32_t aIndexInContainer,                         \
                        nsIContent* aPreviousSibling)                      \
 {                                                                         \
 }                                                                         \
 void                                                                      \
 _class::ParentChainChanged(nsIContent *aContent)                          \
 {                                                                         \
 }
 
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -1626,19 +1626,19 @@ nsINode::doInsertChildAt(nsIContent* aKi
 
   NS_ASSERTION(aKid->GetParentNode() == this,
                "Did we run script inappropriately?");
 
   if (aNotify) {
     // Note that we always want to call ContentInserted when things are added
     // as kids to documents
     if (parent && isAppend) {
-      nsNodeUtils::ContentAppended(parent, aKid, aIndex);
+      nsNodeUtils::ContentAppended(parent, aKid);
     } else {
-      nsNodeUtils::ContentInserted(this, aKid, aIndex);
+      nsNodeUtils::ContentInserted(this, aKid);
     }
 
     if (nsContentUtils::HasMutationListeners(aKid,
           NS_EVENT_BITS_MUTATION_NODEINSERTED, this)) {
       InternalMutationEvent mutation(true, eLegacyNodeInserted);
       mutation.mRelatedNode = do_QueryInterface(this);
 
       mozAutoSubtreeModified subtree(OwnerDoc(), this);
@@ -1934,17 +1934,17 @@ nsINode::doRemoveChildAt(uint32_t aIndex
   nsSlots* slots = GetExistingSlots();
   if (slots && slots->mChildNodes) {
     auto childNodes =
       static_cast<nsParentNodeChildContentList*>(slots->mChildNodes.get());
     childNodes->InvalidateCache();
   }
 
   if (aNotify) {
-    nsNodeUtils::ContentRemoved(this, aKid, aIndex, previousSibling);
+    nsNodeUtils::ContentRemoved(this, aKid, previousSibling);
   }
 
   aKid->UnbindFromTree();
 }
 
 // When replacing, aRefChild is the content being replaced; when
 // inserting it's the content before which we're inserting.  In the
 // latter case it may be null.
@@ -2470,45 +2470,43 @@ nsINode::ReplaceOrInsertBefore(bool aRep
 
     uint32_t count = fragChildren->Length();
     if (!count) {
       return result;
     }
 
     bool appending =
       !IsNodeOfType(eDOCUMENT) && uint32_t(insPos) == GetChildCount();
-    int32_t firstInsPos = insPos;
     nsIContent* firstInsertedContent = fragChildren->ElementAt(0);
 
     // Iterate through the fragment's children, and insert them in the new
     // parent
     for (uint32_t i = 0; i < count; ++i, ++insPos) {
       // XXXbz how come no reparenting here?  That seems odd...
       // Insert the child.
       aError = InsertChildAt(fragChildren->ElementAt(i), insPos,
                              !appending);
       if (aError.Failed()) {
         // Make sure to notify on any children that we did succeed to insert
         if (appending && i != 0) {
           nsNodeUtils::ContentAppended(static_cast<nsIContent*>(this),
-                                       firstInsertedContent,
-                                       firstInsPos);
+                                       firstInsertedContent);
         }
         return nullptr;
       }
     }
 
     if (mutationBatch && !appending) {
       mutationBatch->NodesAdded();
     }
 
     // Notify and fire mutation events when appending
     if (appending) {
       nsNodeUtils::ContentAppended(static_cast<nsIContent*>(this),
-                                   firstInsertedContent, firstInsPos);
+                                   firstInsertedContent);
       if (mutationBatch) {
         mutationBatch->NodesAdded();
       }
       // Optimize for the case when there are no listeners
       if (nsContentUtils::
             HasMutationListeners(doc, NS_EVENT_BITS_MUTATION_NODEINSERTED)) {
         Element::FireNodeInserted(doc, this, *fragChildren);
       }
--- a/dom/base/nsNodeUtils.cpp
+++ b/dom/base/nsNodeUtils.cpp
@@ -155,39 +155,36 @@ nsNodeUtils::AttributeSetToCurrentValue(
 {
   nsIDocument* doc = aElement->OwnerDoc();
   IMPL_MUTATION_NOTIFICATION(AttributeSetToCurrentValue, aElement,
                              (doc, aElement, aNameSpaceID, aAttribute));
 }
 
 void
 nsNodeUtils::ContentAppended(nsIContent* aContainer,
-                             nsIContent* aFirstNewContent,
-                             int32_t aNewIndexInContainer)
+                             nsIContent* aFirstNewContent)
 {
   nsIDocument* doc = aContainer->OwnerDoc();
 
   IMPL_MUTATION_NOTIFICATION(ContentAppended, aContainer,
-                             (doc, aContainer, aFirstNewContent,
-                              aNewIndexInContainer));
+                             (doc, aContainer, aFirstNewContent));
 }
 
 void
 nsNodeUtils::NativeAnonymousChildListChange(nsIContent* aContent,
                                             bool aIsRemove)
 {
   nsIDocument* doc = aContent->OwnerDoc();
   IMPL_MUTATION_NOTIFICATION(NativeAnonymousChildListChange, aContent,
                             (doc, aContent, aIsRemove));
 }
 
 void
 nsNodeUtils::ContentInserted(nsINode* aContainer,
-                             nsIContent* aChild,
-                             int32_t aIndexInContainer)
+                             nsIContent* aChild)
 {
   NS_PRECONDITION(aContainer->IsNodeOfType(nsINode::eCONTENT) ||
                   aContainer->IsNodeOfType(nsINode::eDOCUMENT),
                   "container must be an nsIContent or an nsIDocument");
   nsIContent* container;
   nsIDocument* doc = aContainer->OwnerDoc();
   nsIDocument* document;
   if (aContainer->IsNodeOfType(nsINode::eCONTENT)) {
@@ -195,23 +192,22 @@ nsNodeUtils::ContentInserted(nsINode* aC
     document = doc;
   }
   else {
     container = nullptr;
     document = static_cast<nsIDocument*>(aContainer);
   }
 
   IMPL_MUTATION_NOTIFICATION(ContentInserted, aContainer,
-                             (document, container, aChild, aIndexInContainer));
+                             (document, container, aChild));
 }
 
 void
 nsNodeUtils::ContentRemoved(nsINode* aContainer,
                             nsIContent* aChild,
-                            int32_t aIndexInContainer,
                             nsIContent* aPreviousSibling)
 {
   NS_PRECONDITION(aContainer->IsNodeOfType(nsINode::eCONTENT) ||
                   aContainer->IsNodeOfType(nsINode::eDOCUMENT),
                   "container must be an nsIContent or an nsIDocument");
   nsIContent* container;
   nsIDocument* doc = aContainer->OwnerDoc();
   nsIDocument* document;
@@ -220,18 +216,17 @@ nsNodeUtils::ContentRemoved(nsINode* aCo
     document = doc;
   }
   else {
     container = nullptr;
     document = static_cast<nsIDocument*>(aContainer);
   }
 
   IMPL_MUTATION_NOTIFICATION(ContentRemoved, aContainer,
-                             (document, container, aChild, aIndexInContainer,
-                              aPreviousSibling));
+                             (document, container, aChild, aPreviousSibling));
 }
 
 Maybe<NonOwningAnimationTarget>
 nsNodeUtils::GetTargetForAnimation(const Animation* aAnimation)
 {
   AnimationEffectReadOnly* effect = aAnimation->GetEffect();
   if (!effect || !effect->AsKeyframeEffect()) {
     return Nothing();
@@ -472,29 +467,47 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNod
   nsCOMPtr<nsINode> clone;
   if (aClone) {
     nsresult rv = aNode->Clone(nodeInfo, getter_AddRefs(clone), aDeep);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       aError.Throw(rv);
       return nullptr;
     }
 
-    if (clone->IsElement()) {
+    if (CustomElementRegistry::IsCustomElementEnabled() && clone->IsElement()) {
       // The cloned node may be a custom element that may require
-      // enqueing created callback and prototype swizzling.
+      // enqueing upgrade reaction.
       Element* elem = clone->AsElement();
-      if (nsContentUtils::IsCustomElementName(nodeInfo->NameAtom())) {
-        nsContentUtils::SetupCustomElement(elem);
+      CustomElementDefinition* definition = nullptr;
+      RefPtr<nsIAtom> tagAtom = nodeInfo->NameAtom();
+      if (nsContentUtils::IsCustomElementName(tagAtom)) {
+        definition =
+          nsContentUtils::LookupCustomElementDefinition(nodeInfo->GetDocument(),
+                                                        nodeInfo->LocalName(),
+                                                        nodeInfo->NamespaceID());
+        if (definition) {
+          elem->SetCustomElementData(new CustomElementData(tagAtom));
+          nsContentUtils::EnqueueUpgradeReaction(elem, definition);
+        }
       } else {
         // Check if node may be custom element by type extension.
         // ex. <button is="x-button">
         nsAutoString extension;
         if (elem->GetAttr(kNameSpaceID_None, nsGkAtoms::is, extension) &&
             !extension.IsEmpty()) {
-          nsContentUtils::SetupCustomElement(elem, &extension);
+          definition =
+            nsContentUtils::LookupCustomElementDefinition(nodeInfo->GetDocument(),
+                                                          nodeInfo->LocalName(),
+                                                          nodeInfo->NamespaceID(),
+                                                          &extension);
+          if (definition) {
+            RefPtr<nsIAtom> typeAtom = NS_Atomize(extension);
+            elem->SetCustomElementData(new CustomElementData(typeAtom));
+            nsContentUtils::EnqueueUpgradeReaction(elem, definition);
+          }
         }
       }
     }
 
     if (aParent) {
       // If we're cloning we need to insert the cloned children into the cloned
       // parent.
       rv = aParent->AppendChildTo(static_cast<nsIContent*>(clone.get()),
--- a/dom/base/nsNodeUtils.h
+++ b/dom/base/nsNodeUtils.h
@@ -87,52 +87,47 @@ public:
   static void AttributeSetToCurrentValue(mozilla::dom::Element* aElement,
                                          int32_t aNameSpaceID,
                                          nsIAtom* aAttribute);
 
   /**
    * Send ContentAppended notifications to nsIMutationObservers
    * @param aContainer           Node into which new child/children were added
    * @param aFirstNewContent     First new child
-   * @param aNewIndexInContainer Index of first new child
    * @see nsIMutationObserver::ContentAppended
    */
   static void ContentAppended(nsIContent* aContainer,
-                              nsIContent* aFirstNewContent,
-                              int32_t aNewIndexInContainer);
+                              nsIContent* aFirstNewContent);
 
   /**
    * Send NativeAnonymousChildList notifications to nsIMutationObservers
    * @param aContent             Anonymous node that's been added or removed
    * @param aIsRemove            True if it's a removal, false if an addition
    * @see nsIMutationObserver::NativeAnonymousChildListChange
    */
   static void NativeAnonymousChildListChange(nsIContent* aContent,
                                              bool aIsRemove);
 
   /**
    * Send ContentInserted notifications to nsIMutationObservers
    * @param aContainer        Node into which new child was inserted
    * @param aChild            Newly inserted child
-   * @param aIndexInContainer Index of new child
    * @see nsIMutationObserver::ContentInserted
    */
   static void ContentInserted(nsINode* aContainer,
-                              nsIContent* aChild,
-                              int32_t aIndexInContainer);
+                              nsIContent* aChild);
   /**
    * Send ContentRemoved notifications to nsIMutationObservers
    * @param aContainer        Node from which child was removed
    * @param aChild            Removed child
-   * @param aIndexInContainer Index of removed child
+   * @param aPreviousSibling  Previous sibling of the removed child
    * @see nsIMutationObserver::ContentRemoved
    */
   static void ContentRemoved(nsINode* aContainer,
                              nsIContent* aChild,
-                             int32_t aIndexInContainer,
                              nsIContent* aPreviousSibling);
   /**
    * Send ParentChainChanged notifications to nsIMutationObservers
    * @param aContent  The piece of content that had its parent changed.
    * @see nsIMutationObserver::ParentChainChanged
    */
   static inline void ParentChainChanged(nsIContent *aContent)
   {
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -633,18 +633,17 @@ nsRange::CharacterDataChanged(nsIDocumen
                newRoot ? newRoot : mRoot.get(),
                !newEnd.Container()->GetParentNode() || !newStart.Container()->GetParentNode());
   }
 }
 
 void
 nsRange::ContentAppended(nsIDocument* aDocument,
                          nsIContent*  aContainer,
-                         nsIContent*  aFirstNewContent,
-                         int32_t      aNewIndexInContainer)
+                         nsIContent*  aFirstNewContent)
 {
   NS_ASSERTION(mIsPositioned, "shouldn't be notified if not positioned");
 
   nsINode* container = NODE_FROM(aContainer, aDocument);
   MOZ_ASSERT(container);
   MOZ_ASSERT(aFirstNewContent->GetParentNode() == container);
   if (container->IsSelectionDescendant() && IsInSelection()) {
     nsINode* child = aFirstNewContent;
@@ -672,18 +671,17 @@ nsRange::ContentAppended(nsIDocument* aD
     }
     DoSetRange(mStart.AsRaw(), mEnd.AsRaw(), mRoot, true);
   }
 }
 
 void
 nsRange::ContentInserted(nsIDocument* aDocument,
                          nsIContent* aContainer,
-                         nsIContent* aChild,
-                         int32_t /* aIndexInContainer */)
+                         nsIContent* aChild)
 {
   MOZ_ASSERT(mIsPositioned, "shouldn't be notified if not positioned");
 
   bool updateBoundaries = false;
   nsINode* container = NODE_FROM(aContainer, aDocument);
   MOZ_ASSERT(container);
   RawRangeBoundary newStart(mStart);
   RawRangeBoundary newEnd(mEnd);
@@ -726,17 +724,16 @@ nsRange::ContentInserted(nsIDocument* aD
     DoSetRange(newStart, newEnd, mRoot);
   }
 }
 
 void
 nsRange::ContentRemoved(nsIDocument* aDocument,
                         nsIContent* aContainer,
                         nsIContent* aChild,
-                        int32_t /* aIndexInContainer */,
                         nsIContent* aPreviousSibling)
 {
   MOZ_ASSERT(mIsPositioned, "shouldn't be notified if not positioned");
   nsINode* container = NODE_FROM(aContainer, aDocument);
   RawRangeBoundary newStart;
   RawRangeBoundary newEnd;
   Maybe<bool> gravitateStart;
   bool gravitateEnd;
--- a/dom/browser-element/mochitest/browserElement_CookiesNotThirdParty.js
+++ b/dom/browser-element/mochitest/browserElement_CookiesNotThirdParty.js
@@ -42,11 +42,10 @@ function runTest() {
   // alert('failure:'), as appropriate.  Finally, the page will
   // alert('finish');
   iframe.src = innerPage;
   document.body.appendChild(iframe);
 }
 
 // Disable third-party cookies for this test.
 addEventListener('testready', function() {
-  SpecialPowers.pushPrefEnv({'set': [['network.cookie.cookieBehavior', 1],
-                                     ['network.cookie.ipc.sync', true]]}, runTest);
+  SpecialPowers.pushPrefEnv({'set': [['network.cookie.cookieBehavior', 1]]}, runTest);
 });
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -3197,44 +3197,30 @@ EventStateManager::PostHandleEvent(nsPre
               fm->SetFocusedWindow(mDocument->GetWindow());
             }
           }
         }
       }
       SetActiveManager(this, activeContent);
     }
     break;
-  case ePointerCancel: {
-    if(WidgetMouseEvent* pointerEvent = aEvent->AsPointerEvent()) {
-      // Implicitly releasing capture for given pointer. ePointerLostCapture
-      // should be send after ePointerUp or ePointerCancel.
-      PointerEventHandler::ImplicitlyReleasePointerCapture(pointerEvent);
-      GenerateMouseEnterExit(pointerEvent);
-      // After UP/Cancel Touch pointers become invalid so we can remove relevant
-      // helper from Table. Mouse/Pen pointers are valid all the time (not only
-      // between down/up)
-      if (pointerEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
-        mPointersEnterLeaveHelper.Remove(pointerEvent->pointerId);
-        GenerateMouseEnterExit(pointerEvent);
-      }
-    }
-    break;
-  }
+  case ePointerCancel:
   case ePointerUp: {
     WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
-
+    MOZ_ASSERT(pointerEvent);
     // Implicitly releasing capture for given pointer. ePointerLostCapture
     // should be send after ePointerUp or ePointerCancel.
     PointerEventHandler::ImplicitlyReleasePointerCapture(pointerEvent);
 
-    // After UP/Cancel Touch pointers become invalid so we can remove relevant helper from Table
-    // Mouse/Pen pointers are valid all the time (not only between down/up)
     if (pointerEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
+      // After UP/Cancel Touch pointers become invalid so we can remove relevant
+      // helper from Table Mouse/Pen pointers are valid all the time (not only
+      // between down/up)
+      GenerateMouseEnterExit(pointerEvent);
       mPointersEnterLeaveHelper.Remove(pointerEvent->pointerId);
-      GenerateMouseEnterExit(pointerEvent);
     }
     break;
   }
   case eMouseUp:
     {
       ClearGlobalActiveContent(this);
       WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
       if (mouseEvent && mouseEvent->IsReal()) {
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -1116,49 +1116,49 @@ IMEContentObserver::NotifyContentAdded(n
                       IsEditorHandlingEventForComposition(),
                       IsEditorComposing());
   MaybeNotifyIMEOfTextChange(data);
 }
 
 void
 IMEContentObserver::ContentAppended(nsIDocument* aDocument,
                                     nsIContent* aContainer,
-                                    nsIContent* aFirstNewContent,
-                                    int32_t /* unused */)
+                                    nsIContent* aFirstNewContent)
 {
   NotifyContentAdded(NODE_FROM(aContainer, aDocument),
                      aFirstNewContent, aContainer->GetLastChild());
 }
 
 void
 IMEContentObserver::ContentInserted(nsIDocument* aDocument,
                                     nsIContent* aContainer,
-                                    nsIContent* aChild,
-                                    int32_t /* unused */)
+                                    nsIContent* aChild)
 {
+  MOZ_ASSERT(aChild);
   NotifyContentAdded(NODE_FROM(aContainer, aDocument),
                      aChild, aChild);
 }
 
 void
 IMEContentObserver::ContentRemoved(nsIDocument* aDocument,
                                    nsIContent* aContainer,
                                    nsIContent* aChild,
-                                   int32_t /* unused */,
                                    nsIContent* aPreviousSibling)
 {
   if (!NeedsTextChangeNotification()) {
     return;
   }
 
   mEndOfAddedTextCache.Clear();
   MaybeNotifyIMEOfAddedTextDuringDocumentChange();
 
   nsINode* containerNode = NODE_FROM(aContainer, aDocument);
 
+  MOZ_ASSERT(containerNode);
+
   uint32_t offset = 0;
   nsresult rv = NS_OK;
   if (!mStartOfRemovingTextRangeCache.Match(containerNode, aPreviousSibling)) {
     // At removing a child node of aContainer, we need the line break caused
     // by open tag of aContainer.  Be careful when aPreviousSibling is nullptr.
 
     rv = ContentEventHandler::GetFlatTextLengthInRange(
                                 NodePosition(mRootContent, 0),
--- a/dom/events/test/pointerevents/mochitest.ini
+++ b/dom/events/test/pointerevents/mochitest.ini
@@ -9,16 +9,17 @@ support-files =
 [test_bug1285128.html]
 [test_bug1293174_implicit_pointer_capture_for_touch_1.html]
   support-files = bug1293174_implicit_pointer_capture_for_touch_1.html
 [test_bug1293174_implicit_pointer_capture_for_touch_2.html]
   support-files = bug1293174_implicit_pointer_capture_for_touch_2.html
 [test_bug1303704.html]
 [test_bug1315862.html]
 [test_bug1323158.html]
+[test_bug1403055.html]
 [test_empty_file.html]
   disabled = disabled # Bug 1150091 - Issue with support-files
 [test_pointerevent_attributes_hoverable_pointers-manual.html]
   support-files =
     pointerevent_attributes_hoverable_pointers-manual.html
     ./resources/pointerevent_attributes_hoverable_pointers-iframe.html
 [test_pointerevent_attributes_nohover_pointers-manual.html]
   support-files =
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/test_bug1403055.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1403055
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1403055</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1403055">Mozilla Bug 1403055</a>
+<p id="display"></p>
+<div id="target0" style="width: 50px; height: 50px; background: green"></div>
+<div id="target1" style="width: 50px; height: 50px; background: red"></div>
+<div id="done" style="width: 50px; height: 50px; background: black"></div>
+<script type="text/javascript">
+/** Test for Bug 1403055 **/
+SimpleTest.waitForExplicitFinish();
+
+function runTests() {
+  let target0 = window.document.getElementById("target0");
+  let target1 = window.document.getElementById("target1");
+  let done = window.document.getElementById("done");
+
+  let target0_events = ["pointerover", "pointerenter", "pointerdown", "pointermove"];
+  let target1_events = ["pointerover", "pointerenter", "pointermove", "pointercancel", "pointerout", "pointerleave"];
+
+  target0_events.forEach((elem, index, arr) => {
+    target0.addEventListener(elem, (event) => {
+      is(event.type, target0_events[0], "receive " + event.type + " on target0");
+      target0_events = target0_events.filter(item => item !== event.type);
+    }, { once: true });
+  });
+
+  target1_events.forEach((elem, index, arr) => {
+    target1.addEventListener(elem, (event) => {
+      is(event.type, target1_events[0], "receive " + event.type + " on target1");
+      target1_events = target1_events.filter(item => item !== event.type);
+    }, { once: true });
+  });
+
+  done.addEventListener("mouseup", () => {
+    ok(target0_events.length == 0, " should receive " + target0_events + " on target0");
+    ok(target1_events.length == 0, " should receive " + target1_events + " on target1");
+    SimpleTest.finish();
+  });
+
+  synthesizeTouch(target0, 5, 5, { type: "touchstart" });
+  synthesizeTouch(target0, 5, 5, { type: "touchmove" });
+  synthesizeTouch(target1, 5, 5, { type: "touchmove" });
+  synthesizeTouch(target1, 5, 5, { type: "touchcancel" });
+  synthesizeMouseAtCenter(done, { type: "mousedown" });
+  synthesizeMouseAtCenter(done, { type: "mouseup" });
+}
+
+SimpleTest.waitForFocus(() => {
+  SpecialPowers.pushPrefEnv({"set": [["dom.w3c_pointer_events.enabled", true]]}, runTests);
+});
+
+</script>
+</body>
+</html>
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1522,20 +1522,19 @@ NS_IMPL_URI_ATTR(HTMLMediaElement, Src, 
 NS_IMPL_BOOL_ATTR(HTMLMediaElement, Controls, controls)
 NS_IMPL_BOOL_ATTR(HTMLMediaElement, Autoplay, autoplay)
 NS_IMPL_BOOL_ATTR(HTMLMediaElement, Loop, loop)
 NS_IMPL_BOOL_ATTR(HTMLMediaElement, DefaultMuted, muted)
 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLMediaElement, Preload, preload, nullptr)
 
 void
 HTMLMediaElement::ContentRemoved(nsIDocument* aDocument,
-                                 nsIContent*  aContainer,
-                                 nsIContent*  aChild,
-                                 int32_t /* aIndexInContainer */,
-                                 nsIContent*  aPreviousSibling)
+                                 nsIContent* aContainer,
+                                 nsIContent* aChild,
+                                 nsIContent* aPreviousSibling)
 {
   if (aChild == mSourcePointer) {
     mSourcePointer = aPreviousSibling;
   }
 }
 
 NS_IMETHODIMP_(bool)
 HTMLMediaElement::IsVideo()
--- a/dom/html/HTMLOutputElement.cpp
+++ b/dom/html/HTMLOutputElement.cpp
@@ -188,34 +188,31 @@ void HTMLOutputElement::CharacterDataCha
                                              nsIContent* aContent,
                                              CharacterDataChangeInfo* aInfo)
 {
   DescendantsChanged();
 }
 
 void HTMLOutputElement::ContentAppended(nsIDocument* aDocument,
                                         nsIContent* aContainer,
-                                        nsIContent* aFirstNewContent,
-                                        int32_t aNewIndexInContainer)
+                                        nsIContent* aFirstNewContent)
 {
   DescendantsChanged();
 }
 
 void HTMLOutputElement::ContentInserted(nsIDocument* aDocument,
                                         nsIContent* aContainer,
-                                        nsIContent* aChild,
-                                        int32_t aIndexInContainer)
+                                        nsIContent* aChild)
 {
   DescendantsChanged();
 }
 
 void HTMLOutputElement::ContentRemoved(nsIDocument* aDocument,
                                        nsIContent* aContainer,
                                        nsIContent* aChild,
-                                       int32_t aIndexInContainer,
                                        nsIContent* aPreviousSibling)
 {
   DescendantsChanged();
 }
 
 JSObject*
 HTMLOutputElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
--- a/dom/html/HTMLStyleElement.cpp
+++ b/dom/html/HTMLStyleElement.cpp
@@ -70,36 +70,33 @@ HTMLStyleElement::CharacterDataChanged(n
                                        CharacterDataChangeInfo* aInfo)
 {
   ContentChanged(aContent);
 }
 
 void
 HTMLStyleElement::ContentAppended(nsIDocument* aDocument,
                                   nsIContent* aContainer,
-                                  nsIContent* aFirstNewContent,
-                                  int32_t aNewIndexInContainer)
+                                  nsIContent* aFirstNewContent)
 {
   ContentChanged(aContainer);
 }
 
 void
 HTMLStyleElement::ContentInserted(nsIDocument* aDocument,
                                   nsIContent* aContainer,
-                                  nsIContent* aChild,
-                                  int32_t aIndexInContainer)
+                                  nsIContent* aChild)
 {
   ContentChanged(aChild);
 }
 
 void
 HTMLStyleElement::ContentRemoved(nsIDocument* aDocument,
                                  nsIContent* aContainer,
                                  nsIContent* aChild,
-                                 int32_t aIndexInContainer,
                                  nsIContent* aPreviousSibling)
 {
   ContentChanged(aChild);
 }
 
 void
 HTMLStyleElement::ContentChanged(nsIContent* aContent)
 {
--- a/dom/html/HTMLTableElement.cpp
+++ b/dom/html/HTMLTableElement.cpp
@@ -474,18 +474,17 @@ TableRowsCollection::HandleInsert(nsICon
   return index + 1;
 }
 
 // nsIMutationObserver
 
 void
 TableRowsCollection::ContentAppended(nsIDocument* aDocument,
                                      nsIContent* aContainer,
-                                     nsIContent* aFirstNewContent,
-                                     int32_t aNewIndexInContainer)
+                                     nsIContent* aFirstNewContent)
 {
   if (!nsContentUtils::IsInSameAnonymousTree(mParent, aFirstNewContent) ||
       !InterestingContainer(aContainer)) {
     return;
   }
 
   // We usually can't guess where we need to start inserting, unless we're
   // appending into mParent, in which case we can provide the guess that we
@@ -499,32 +498,30 @@ TableRowsCollection::ContentAppended(nsI
        content; content = content->GetNextSibling()) {
     indexGuess = HandleInsert(aContainer, content, indexGuess);
   }
 }
 
 void
 TableRowsCollection::ContentInserted(nsIDocument* aDocument,
                                      nsIContent* aContainer,
-                                     nsIContent* aChild,
-                                     int32_t aIndexInContainer)
+                                     nsIContent* aChild)
 {
   if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild) ||
       !InterestingContainer(aContainer)) {
     return;
   }
 
   HandleInsert(aContainer, aChild);
 }
 
 void
 TableRowsCollection::ContentRemoved(nsIDocument* aDocument,
                                     nsIContent* aContainer,
                                     nsIContent* aChild,
-                                    int32_t aIndexInContainer,
                                     nsIContent* aPreviousSibling)
 {
   if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild) ||
       !InterestingContainer(aContainer)) {
     return;
   }
 
   // If the element being removed is a `tr`, we can just remove it from our
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -1030,36 +1030,33 @@ HTMLTextAreaElement::CharacterDataChange
                                           CharacterDataChangeInfo* aInfo)
 {
   ContentChanged(aContent);
 }
 
 void
 HTMLTextAreaElement::ContentAppended(nsIDocument* aDocument,
                                      nsIContent* aContainer,
-                                     nsIContent* aFirstNewContent,
-                                     int32_t /* unused */)
+                                     nsIContent* aFirstNewContent)
 {
   ContentChanged(aFirstNewContent);
 }
 
 void
 HTMLTextAreaElement::ContentInserted(nsIDocument* aDocument,
                                      nsIContent* aContainer,
-                                     nsIContent* aChild,
-                                     int32_t /* unused */)
+                                     nsIContent* aChild)
 {
   ContentChanged(aChild);
 }
 
 void
 HTMLTextAreaElement::ContentRemoved(nsIDocument* aDocument,
                                     nsIContent* aContainer,
                                     nsIContent* aChild,
-                                    int32_t aIndexInContainer,
                                     nsIContent* aPreviousSibling)
 {
   ContentChanged(aChild);
 }
 
 void
 HTMLTextAreaElement::ContentChanged(nsIContent* aContent)
 {
--- a/dom/html/HTMLTitleElement.cpp
+++ b/dom/html/HTMLTitleElement.cpp
@@ -59,36 +59,33 @@ HTMLTitleElement::CharacterDataChanged(n
                                        CharacterDataChangeInfo *aInfo)
 {
   SendTitleChangeEvent(false);
 }
 
 void
 HTMLTitleElement::ContentAppended(nsIDocument *aDocument,
                                   nsIContent *aContainer,
-                                  nsIContent *aFirstNewContent,
-                                  int32_t aNewIndexInContainer)
+                                  nsIContent *aFirstNewContent)
 {
   SendTitleChangeEvent(false);
 }
 
 void
 HTMLTitleElement::ContentInserted(nsIDocument *aDocument,
                                   nsIContent *aContainer,
-                                  nsIContent *aChild,
-                                  int32_t aIndexInContainer)
+                                  nsIContent *aChild)
 {
   SendTitleChangeEvent(false);
 }
 
 void
 HTMLTitleElement::ContentRemoved(nsIDocument *aDocument,
                                  nsIContent *aContainer,
                                  nsIContent *aChild,
-                                 int32_t aIndexInContainer,
                                  nsIContent *aPreviousSibling)
 {
   SendTitleChangeEvent(false);
 }
 
 nsresult
 HTMLTitleElement::BindToTree(nsIDocument *aDocument,
                              nsIContent *aParent,
--- a/dom/html/nsHTMLContentSink.cpp
+++ b/dom/html/nsHTMLContentSink.cpp
@@ -1086,17 +1086,17 @@ HTMLContentSink::NotifyInsert(nsIContent
   }
 
   mInNotification++;
 
   {
     // Scope so we call EndUpdate before we decrease mInNotification
     MOZ_AUTO_DOC_UPDATE(mDocument, UPDATE_CONTENT_MODEL, !mBeganUpdate);
     nsNodeUtils::ContentInserted(NODE_FROM(aContent, mDocument),
-                                 aChildContent, aIndexInContainer);
+                                 aChildContent);
     mLastNotificationTime = PR_Now();
   }
 
   mInNotification--;
 }
 
 void
 HTMLContentSink::NotifyRootInsertion()
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -475,17 +475,17 @@ skip-if = toolkit == 'android' # just co
 tags = openwindow
 [test_iframe_sandbox_inheritance.html]
 tags = openwindow
 [test_iframe_sandbox_navigation.html]
 tags = openwindow
 [test_iframe_sandbox_navigation2.html]
 tags = openwindow
 [test_iframe_sandbox_plugins.html]
-skip-if = toolkit == 'android' # plugins not supported
+skip-if = (toolkit == 'android') || debug # plugins not supported on android, bug 1388764 for debug
 [test_iframe_sandbox_popups.html]
 tags = openwindow
 [test_iframe_sandbox_popups_inheritance.html]
 tags = openwindow
 skip-if = toolkit == 'android' # bug 939642
 [test_iframe_sandbox_redirect.html]
 [test_iframe_sandbox_refresh.html]
 [test_iframe_sandbox_same_origin.html]
@@ -497,17 +497,17 @@ skip-if = toolkit == 'android' # bug 939
 [test_link_sizes.html]
 [test_map_attributes_reflection.html]
 [test_meta_attributes_reflection.html]
 [test_mod_attributes_reflection.html]
 [test_named_options.html]
 [test_nested_invalid_fieldsets.html]
 [test_object_attributes_reflection.html]
 [test_object_plugin_nav.html]
-skip-if = toolkit == 'android' # plugins not supported
+skip-if = (toolkit == 'android') || (os == 'win' && debug) # plugins not supported on android, bug 1388764 for debug
 [test_ol_attributes_reflection.html]
 [test_option_defaultSelected.html]
 [test_option_selected_state.html]
 [test_param_attributes_reflection.html]
 [test_q_attributes_reflection.html]
 [test_restore_from_parser_fragment.html]
 [test_rowscollection.html]
 [test_srcdoc-2.html]
--- a/dom/script/ScriptElement.cpp
+++ b/dom/script/ScriptElement.cpp
@@ -94,27 +94,25 @@ ScriptElement::AttributeChanged(nsIDocum
                                 const nsAttrValue* aOldValue)
 {
   MaybeProcessScript();
 }
 
 void
 ScriptElement::ContentAppended(nsIDocument* aDocument,
                                nsIContent* aContainer,
-                               nsIContent* aFirstNewContent,
-                               int32_t aNewIndexInContainer)
+                               nsIContent* aFirstNewContent)
 {
   MaybeProcessScript();
 }
 
 void
 ScriptElement::ContentInserted(nsIDocument* aDocument,
                                nsIContent* aContainer,
-                               nsIContent* aChild,
-                               int32_t aIndexInContainer)
+                               nsIContent* aChild)
 {
   MaybeProcessScript();
 }
 
 bool
 ScriptElement::MaybeProcessScript()
 {
   nsCOMPtr<nsIContent> cont =
--- a/dom/svg/SVGStyleElement.cpp
+++ b/dom/svg/SVGStyleElement.cpp
@@ -155,36 +155,33 @@ SVGStyleElement::CharacterDataChanged(ns
                                       CharacterDataChangeInfo* aInfo)
 {
   ContentChanged(aContent);
 }
 
 void
 SVGStyleElement::ContentAppended(nsIDocument* aDocument,
                                  nsIContent* aContainer,
-                                 nsIContent* aFirstNewContent,
-                                 int32_t aNewIndexInContainer)
+                                 nsIContent* aFirstNewContent)
 {
   ContentChanged(aContainer);
 }
 
 void
 SVGStyleElement::ContentInserted(nsIDocument* aDocument,
                                  nsIContent* aContainer,
-                                 nsIContent* aChild,
-                                 int32_t aIndexInContainer)
+                                 nsIContent* aChild)
 {
   ContentChanged(aChild);
 }
 
 void
 SVGStyleElement::ContentRemoved(nsIDocument* aDocument,
                                 nsIContent* aContainer,
                                 nsIContent* aChild,
-                                int32_t aIndexInContainer,
                                 nsIContent* aPreviousSibling)
 {
   ContentChanged(aChild);
 }
 
 void
 SVGStyleElement::ContentChanged(nsIContent* aContent)
 {
--- a/dom/svg/SVGTitleElement.cpp
+++ b/dom/svg/SVGTitleElement.cpp
@@ -45,36 +45,33 @@ SVGTitleElement::CharacterDataChanged(ns
                                       CharacterDataChangeInfo *aInfo)
 {
   SendTitleChangeEvent(false);
 }
 
 void
 SVGTitleElement::ContentAppended(nsIDocument *aDocument,
                                  nsIContent *aContainer,
-                                 nsIContent *aFirstNewContent,
-                                 int32_t aNewIndexInContainer)
+                                 nsIContent *aFirstNewContent)
 {
   SendTitleChangeEvent(false);
 }
 
 void
 SVGTitleElement::ContentInserted(nsIDocument *aDocument,
                                  nsIContent *aContainer,
-                                 nsIContent *aChild,
-                                 int32_t aIndexInContainer)
+                                 nsIContent *aChild)
 {
   SendTitleChangeEvent(false);
 }
 
 void
 SVGTitleElement::ContentRemoved(nsIDocument *aDocument,
                                 nsIContent *aContainer,
                                 nsIContent *aChild,
-                                int32_t aIndexInContainer,
                                 nsIContent *aPreviousSibling)
 {
   SendTitleChangeEvent(false);
 }
 
 nsresult
 SVGTitleElement::BindToTree(nsIDocument *aDocument,
                              nsIContent *aParent,
--- a/dom/svg/SVGUseElement.cpp
+++ b/dom/svg/SVGUseElement.cpp
@@ -165,40 +165,37 @@ SVGUseElement::AttributeChanged(nsIDocum
   if (nsContentUtils::IsInSameAnonymousTree(this, aElement)) {
     TriggerReclone();
   }
 }
 
 void
 SVGUseElement::ContentAppended(nsIDocument *aDocument,
                                nsIContent *aContainer,
-                               nsIContent *aFirstNewContent,
-                               int32_t aNewIndexInContainer)
+                               nsIContent *aFirstNewContent)
 {
   if (nsContentUtils::IsInSameAnonymousTree(this, aContainer)) {
     TriggerReclone();
   }
 }
 
 void
 SVGUseElement::ContentInserted(nsIDocument *aDocument,
                                nsIContent *aContainer,
-                               nsIContent *aChild,
-                               int32_t aIndexInContainer)
+                               nsIContent *aChild)
 {
   if (nsContentUtils::IsInSameAnonymousTree(this, aChild)) {
     TriggerReclone();
   }
 }
 
 void
 SVGUseElement::ContentRemoved(nsIDocument *aDocument,
                               nsIContent *aContainer,
                               nsIContent *aChild,
-                              int32_t aIndexInContainer,
                               nsIContent *aPreviousSibling)
 {
   if (nsContentUtils::IsInSameAnonymousTree(this, aChild)) {
     TriggerReclone();
   }
 }
 
 void
--- a/dom/tests/mochitest/webcomponents/mochitest.ini
+++ b/dom/tests/mochitest/webcomponents/mochitest.ini
@@ -7,17 +7,16 @@ support-files =
 [test_bug1017896.html]
 [test_bug1176757.html]
 skip-if = stylo # bug 1293844
 [test_bug1276240.html]
 [test_content_element.html]
 skip-if = stylo # bug 1293844
 [test_custom_element_adopt_callbacks.html]
 [test_custom_element_callback_innerhtml.html]
-[test_custom_element_clone_callbacks.html]
 [test_custom_element_clone_callbacks_extended.html]
 [test_custom_element_htmlconstructor.html]
 skip-if = os == 'android' # bug 1323645
 support-files =
   htmlconstructor_autonomous_tests.js
   htmlconstructor_builtin_tests.js
 [test_custom_element_import_node_created_callback.html]
 [test_custom_element_in_shadow.html]
deleted file mode 100644
--- a/dom/tests/mochitest/webcomponents/test_custom_element_clone_callbacks.html
+++ /dev/null
@@ -1,54 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1081039
--->
-<head>
-  <title>Test callbacks for cloned custom elements.</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1081039">Bug 1081039</a>
-<script>
-
-SimpleTest.waitForExplicitFinish();
-
-// Test to make sure created callback is called on clones that are upgraded and clones
-// created after registering the custom element.
-
-var callbackCalledOnUpgrade = false;
-var callbackCalledOnClone = false;
-
-var foo = document.createElement("x-foo");
-var fooClone = foo.cloneNode(true);
-
-var p = Object.create(HTMLElement.prototype);
-p.createdCallback = function() {
-  is(this.__proto__, p, "Correct prototype should be set on custom elements.");
-
-  if (this == fooClone) {
-    // Callback called for the element created before registering the custom element.
-    // Should be called on element upgrade.
-    is(callbackCalledOnUpgrade, false, "Upgrade should only be called once per clone.");
-    callbackCalledOnUpgrade = true;
-  } else if (this != foo) {
-    // Callback called for the element created after registering the custom element.
-    is(callbackCalledOnClone, false, "Upgrade should only be called once per clone.");
-    callbackCalledOnClone = true;
-  }
-
-  if (callbackCalledOnUpgrade && callbackCalledOnClone) {
-    SimpleTest.finish();
-  }
-};
-
-document.registerElement("x-foo", { prototype: p });
-
-var anotherFooClone = foo.cloneNode(true);
-
-SimpleTest.waitForExplicitFinish();
-
-</script>
-</body>
-</html>
--- a/dom/xbl/nsBindingManager.cpp
+++ b/dom/xbl/nsBindingManager.cpp
@@ -833,23 +833,18 @@ InsertAppendedContent(XBLChildrenElement
        currentChild = currentChild->GetNextSibling()) {
     aPoint->InsertInsertedChildAt(currentChild, insertionIndex++);
   }
 }
 
 void
 nsBindingManager::ContentAppended(nsIDocument* aDocument,
                                   nsIContent* aContainer,
-                                  nsIContent* aFirstNewContent,
-                                  int32_t     aNewIndexInContainer)
+                                  nsIContent* aFirstNewContent)
 {
-  if (aNewIndexInContainer == -1) {
-    return;
-  }
-
   // Try to find insertion points for all the new kids.
   XBLChildrenElement* point = nullptr;
   nsIContent* parent = aContainer;
 
   // Handle appending of default content.
   if (parent && parent->IsActiveChildrenElement()) {
     XBLChildrenElement* childrenEl = static_cast<XBLChildrenElement*>(parent);
     if (childrenEl->HasInsertedChildren()) {
@@ -869,21 +864,19 @@ nsBindingManager::ContentAppended(nsIDoc
     }
 
     if (binding->HasFilteredInsertionPoints()) {
       // There are filtered insertion points involved, handle each child
       // separately.
       // We could optimize this in the case when we've nested a few levels
       // deep already, without hitting bindings that have filtered insertion
       // points.
-      int32_t currentIndex = aNewIndexInContainer;
       for (nsIContent* currentChild = aFirstNewContent; currentChild;
            currentChild = currentChild->GetNextSibling()) {
-        HandleChildInsertion(aContainer, currentChild,
-                             currentIndex++, true);
+        HandleChildInsertion(aContainer, currentChild, true);
       }
 
       return;
     }
 
     point = binding->GetDefaultInsertionPoint();
     if (!point) {
       break;
@@ -908,31 +901,25 @@ nsBindingManager::ContentAppended(nsIDoc
     }
     parent = newParent;
   } while (parent);
 }
 
 void
 nsBindingManager::ContentInserted(nsIDocument* aDocument,
                                   nsIContent* aContainer,
-                                  nsIContent* aChild,
-                                  int32_t aIndexInContainer)
+                                  nsIContent* aChild)
 {
-  if (aIndexInContainer == -1) {
-    return;
-  }
-
-  HandleChildInsertion(aContainer, aChild, aIndexInContainer, false);
+  HandleChildInsertion(aContainer, aChild, false);
 }
 
 void
 nsBindingManager::ContentRemoved(nsIDocument* aDocument,
                                  nsIContent* aContainer,
                                  nsIContent* aChild,
-                                 int32_t aIndexInContainer,
                                  nsIContent* aPreviousSibling)
 {
   aChild->SetXBLInsertionParent(nullptr);
 
   XBLChildrenElement* point = nullptr;
   nsIContent* parent = aContainer;
 
   // Handle appending of default content.
@@ -1034,23 +1021,19 @@ nsBindingManager::Traverse(nsIContent *a
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "[via binding manager] mWrapperTable value");
     cb.NoteXPCOMChild(value);
   }
 }
 
 void
 nsBindingManager::HandleChildInsertion(nsIContent* aContainer,
                                        nsIContent* aChild,
-                                       uint32_t aIndexInContainer,
                                        bool aAppend)
 {
-  NS_PRECONDITION(aChild, "Must have child");
-  NS_PRECONDITION(!aContainer ||
-                  uint32_t(aContainer->IndexOf(aChild)) == aIndexInContainer,
-                  "Child not at the right index?");
+  MOZ_ASSERT(aChild, "Must have child");
 
   XBLChildrenElement* point = nullptr;
   nsIContent* parent = aContainer;
 
   // Handle insertion of default content.
   if (parent && parent->IsActiveChildrenElement()) {
     XBLChildrenElement* childrenEl = static_cast<XBLChildrenElement*>(parent);
     if (childrenEl->HasInsertedChildren()) {
--- a/dom/xbl/nsBindingManager.h
+++ b/dom/xbl/nsBindingManager.h
@@ -176,20 +176,19 @@ public:
   nsIContent* FindNestedSingleInsertionPoint(nsIContent* aContainer, bool* aMulti);
 
 protected:
   nsIXPConnectWrappedJS* GetWrappedJS(nsIContent* aContent);
   nsresult SetWrappedJS(nsIContent* aContent, nsIXPConnectWrappedJS* aResult);
 
   // Called by ContentAppended and ContentInserted to handle a single child
   // insertion.  aChild must not be null.  aContainer may be null.
-  // aIndexInContainer is the index of the child in the parent.  aAppend is
-  // true if this child is being appended, not inserted.
+  // aAppend is true if this child is being appended, not inserted.
   void HandleChildInsertion(nsIContent* aContainer, nsIContent* aChild,
-                            uint32_t aIndexInContainer, bool aAppend);
+                            bool aAppend);
 
   // Same as ProcessAttachedQueue, but also nulls out
   // mProcessAttachedQueueEvent
   void DoProcessAttachedQueue();
 
   // Post an event to process the attached queue.
   void PostProcessAttachedQueueEvent();
 
--- a/dom/xml/nsXMLContentSink.cpp
+++ b/dom/xml/nsXMLContentSink.cpp
@@ -396,18 +396,17 @@ nsXMLContentSink::OnTransformDone(nsresu
   // into the document.
   // XXX do we need to notify for things like PIs?  Or just the
   // documentElement?
   nsIContent *rootElement = mDocument->GetRootElement();
   if (rootElement) {
     NS_ASSERTION(mDocument->IndexOf(rootElement) != -1,
                  "rootElement not in doc?");
     mDocument->BeginUpdate(UPDATE_CONTENT_MODEL);
-    nsNodeUtils::ContentInserted(mDocument, rootElement,
-                                 mDocument->IndexOf(rootElement));
+    nsNodeUtils::ContentInserted(mDocument, rootElement);
     mDocument->EndUpdate(UPDATE_CONTENT_MODEL);
   }
 
   // Start the layout process
   StartLayout(false);
 
   ScrollToRef();
 
--- a/dom/xml/nsXMLPrettyPrinter.cpp
+++ b/dom/xml/nsXMLPrettyPrinter.cpp
@@ -222,36 +222,33 @@ nsXMLPrettyPrinter::AttributeChanged(nsI
                                      const nsAttrValue* aOldValue)
 {
     MaybeUnhook(aElement);
 }
 
 void
 nsXMLPrettyPrinter::ContentAppended(nsIDocument* aDocument,
                                     nsIContent* aContainer,
-                                    nsIContent* aFirstNewContent,
-                                    int32_t aNewIndexInContainer)
+                                    nsIContent* aFirstNewContent)
 {
     MaybeUnhook(aContainer);
 }
 
 void
 nsXMLPrettyPrinter::ContentInserted(nsIDocument* aDocument,
                                     nsIContent* aContainer,
-                                    nsIContent* aChild,
-                                    int32_t aIndexInContainer)
+                                    nsIContent* aChild)
 {
     MaybeUnhook(aContainer);
 }
 
 void
 nsXMLPrettyPrinter::ContentRemoved(nsIDocument* aDocument,
                                    nsIContent* aContainer,
                                    nsIContent* aChild,
-                                   int32_t aIndexInContainer,
                                    nsIContent* aPreviousSibling)
 {
     MaybeUnhook(aContainer);
 }
 
 void
 nsXMLPrettyPrinter::NodeWillBeDestroyed(const nsINode* aNode)
 {
--- a/dom/xslt/xpath/XPathResult.cpp
+++ b/dom/xslt/xpath/XPathResult.cpp
@@ -139,36 +139,33 @@ XPathResult::AttributeChanged(nsIDocumen
                               const nsAttrValue* aOldValue)
 {
     Invalidate(aElement);
 }
 
 void
 XPathResult::ContentAppended(nsIDocument* aDocument,
                              nsIContent* aContainer,
-                             nsIContent* aFirstNewContent,
-                             int32_t aNewIndexInContainer)
+                             nsIContent* aFirstNewContent)
 {
     Invalidate(aContainer);
 }
 
 void
 XPathResult::ContentInserted(nsIDocument* aDocument,
                              nsIContent* aContainer,
-                             nsIContent* aChild,
-                             int32_t aIndexInContainer)
+                             nsIContent* aChild)
 {
     Invalidate(aContainer);
 }
 
 void
 XPathResult::ContentRemoved(nsIDocument* aDocument,
                             nsIContent* aContainer,
                             nsIContent* aChild,
-                            int32_t aIndexInContainer,
                             nsIContent* aPreviousSibling)
 {
     Invalidate(aContainer);
 }
 
 nsresult
 XPathResult::SetExprResult(txAExprResult* aExprResult, uint16_t aResultType,
                            nsINode* aContextNode)
--- a/dom/xslt/xslt/txMozillaXSLTProcessor.cpp
+++ b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp
@@ -1227,36 +1227,33 @@ txMozillaXSLTProcessor::AttributeChanged
                                          const nsAttrValue* aOldValue)
 {
     mStylesheet = nullptr;
 }
 
 void
 txMozillaXSLTProcessor::ContentAppended(nsIDocument* aDocument,
                                         nsIContent* aContainer,
-                                        nsIContent* aFirstNewContent,
-                                        int32_t /* unused */)
+                                        nsIContent* aFirstNewContent)
 {
     mStylesheet = nullptr;
 }
 
 void
 txMozillaXSLTProcessor::ContentInserted(nsIDocument* aDocument,
                                         nsIContent* aContainer,
-                                        nsIContent* aChild,
-                                        int32_t /* unused */)
+                                        nsIContent* aChild)
 {
     mStylesheet = nullptr;
 }
 
 void
 txMozillaXSLTProcessor::ContentRemoved(nsIDocument* aDocument,
                                        nsIContent* aContainer,
                                        nsIContent* aChild,
-                                       int32_t aIndexInContainer,
                                        nsIContent* aPreviousSibling)
 {
     mStylesheet = nullptr;
 }
 
 /* virtual */ JSObject*
 txMozillaXSLTProcessor::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -1044,18 +1044,17 @@ XULDocument::AttributeChanged(nsIDocumen
           kNameSpaceID_None,
           aAttribute));
     }
 }
 
 void
 XULDocument::ContentAppended(nsIDocument* aDocument,
                              nsIContent* aContainer,
-                             nsIContent* aFirstNewContent,
-                             int32_t aNewIndexInContainer)
+                             nsIContent* aFirstNewContent)
 {
     NS_ASSERTION(aDocument == this, "unexpected doc");
 
     // Might not need this, but be safe for now.
     nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
 
     // Update our element map
     nsresult rv = NS_OK;
@@ -1063,32 +1062,30 @@ XULDocument::ContentAppended(nsIDocument
          cur = cur->GetNextSibling()) {
         rv = AddSubtreeToDocument(cur);
     }
 }
 
 void
 XULDocument::ContentInserted(nsIDocument* aDocument,
                              nsIContent* aContainer,
-                             nsIContent* aChild,
-                             int32_t aIndexInContainer)
+                             nsIContent* aChild)
 {
     NS_ASSERTION(aDocument == this, "unexpected doc");
 
     // Might not need this, but be safe for now.
     nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
 
     AddSubtreeToDocument(aChild);
 }
 
 void
 XULDocument::ContentRemoved(nsIDocument* aDocument,
                             nsIContent* aContainer,
                             nsIContent* aChild,
-                            int32_t aIndexInContainer,
                             nsIContent* aPreviousSibling)
 {
     NS_ASSERTION(aDocument == this, "unexpected doc");
 
     // Might not need this, but be safe for now.
     nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
 
     RemoveSubtreeFromDocument(aChild);
--- a/dom/xul/templates/nsXULContentBuilder.cpp
+++ b/dom/xul/templates/nsXULContentBuilder.cpp
@@ -1035,18 +1035,17 @@ nsXULContentBuilder::CreateContainerCont
         CreateContainerContentsForQuerySet(aElement, aResult, aNotify, queryset,
                                            &container, &newIndexInContainer);
     }
 
     if (aNotifyAtEnd && container) {
         MOZ_AUTO_DOC_UPDATE(container->GetUncomposedDoc(), UPDATE_CONTENT_MODEL,
                             true);
         nsNodeUtils::ContentAppended(container,
-                                     container->GetChildAt(newIndexInContainer),
-                                     newIndexInContainer);
+                                     container->GetChildAt(newIndexInContainer));
     }
 
     NS_IF_RELEASE(container);
 
     return NS_OK;
 }
 
 nsresult
--- a/dom/xul/templates/nsXULTemplateBuilder.cpp
+++ b/dom/xul/templates/nsXULTemplateBuilder.cpp
@@ -1208,17 +1208,16 @@ nsXULTemplateBuilder::AttributeChanged(n
         }
     }
 }
 
 void
 nsXULTemplateBuilder::ContentRemoved(nsIDocument* aDocument,
                                      nsIContent* aContainer,
                                      nsIContent* aChild,
-                                     int32_t aIndexInContainer,
                                      nsIContent* aPreviousSibling)
 {
     if (mRoot && nsContentUtils::ContentIsDescendantOf(mRoot, aChild)) {
         RefPtr<nsXULTemplateBuilder> kungFuDeathGrip(this);
 
         if (mQueryProcessor)
             mQueryProcessor->Done();
 
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -2426,38 +2426,39 @@ EditorBase::FindBetterInsertionPoint(nsC
       aOffset = 0;
       return;
     }
 
     // In some other cases, aNode is the anonymous DIV, and offset points to the
     // terminating mozBR.  In that case, we'll adjust aInOutNode and
     // aInOutOffset to the preceding text node, if any.
     if (offset) {
-      if (offset == static_cast<int32_t>(node->GetChildCount())) {
-        // If offset points to the last child, use a fast path that avoids calling
-        // GetChildAt() which may perform a linear search.
+      if (AsHTMLEditor()) {
+        // Fall back to a slow path that uses GetChildAt() for Thunderbird's
+        // plaintext editor.
+        nsIContent* child = node->GetChildAt(offset - 1);
+        if (child && child->IsNodeOfType(nsINode::eTEXT)) {
+          NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
+          aNode = child;
+          aOffset = static_cast<int32_t>(aNode->Length());
+          return;
+        }
+      } else {
+        // If we're in a real plaintext editor, use a fast path that avoids
+        // calling GetChildAt() which may perform a linear search.
         nsIContent* child = node->GetLastChild();
         while (child) {
           if (child->IsNodeOfType(nsINode::eTEXT)) {
             NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
             aNode = child;
             aOffset = static_cast<int32_t>(aNode->Length());
             return;
           }
           child = child->GetPreviousSibling();
         }
-      } else {
-        // Fall back to a slow path that uses GetChildAt().
-        nsIContent* child = node->GetChildAt(offset - 1);
-        if (child && child->IsNodeOfType(nsINode::eTEXT)) {
-          NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
-          aNode = child;
-          aOffset = static_cast<int32_t>(aNode->Length());
-          return;
-        }
       }
     }
   }
 
   // Sometimes, aNode is the mozBR element itself.  In that case, we'll adjust
   // the insertion point to the previous text node, if one exists, or to the
   // parent anonymous DIV.
   if (TextEditUtils::IsMozBR(node) && !offset) {
--- a/editor/libeditor/HTMLAnonymousNodeEditor.cpp
+++ b/editor/libeditor/HTMLAnonymousNodeEditor.cpp
@@ -302,21 +302,18 @@ HTMLEditor::DeleteRefToAnonymousNode(Man
     if (docObserver) {
       // Call BeginUpdate() so that the nsCSSFrameConstructor/PresShell
       // knows we're messing with the frame tree.
       nsCOMPtr<nsIDocument> document = GetDocument();
       if (document) {
         docObserver->BeginUpdate(document, UPDATE_CONTENT_MODEL);
       }
 
-      // XXX This is wrong (bug 439258).  Once it's fixed, the NS_WARNING
-      // in RestyleManager::RestyleForRemove should be changed back
-      // to an assertion.
       docObserver->ContentRemoved(aContent->GetComposedDoc(),
-                                  parentContent, aContent, -1,
+                                  parentContent, aContent,
                                   aContent->GetPreviousSibling());
       if (document) {
         docObserver->EndUpdate(document, UPDATE_CONTENT_MODEL);
       }
     }
   }
 
   // The ManualNACPtr destructor will invoke UnbindFromTree.
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -3128,31 +3128,27 @@ HTMLEditor::InsertTextImpl(const nsAStri
 
   return EditorBase::InsertTextImpl(aStringToInsert, aInOutNode, aInOutOffset,
                                     aDoc);
 }
 
 void
 HTMLEditor::ContentAppended(nsIDocument* aDocument,
                             nsIContent* aContainer,
-                            nsIContent* aFirstNewContent,
-                            int32_t aIndexInContainer)
+                            nsIContent* aFirstNewContent)
 {
-  DoContentInserted(aDocument, aContainer, aFirstNewContent, aIndexInContainer,
-                    eAppended);
+  DoContentInserted(aDocument, aContainer, aFirstNewContent, eAppended);
 }
 
 void
 HTMLEditor::ContentInserted(nsIDocument* aDocument,
                             nsIContent* aContainer,
-                            nsIContent* aChild,
-                            int32_t aIndexInContainer)
+                            nsIContent* aChild)
 {
-  DoContentInserted(aDocument, aContainer, aChild, aIndexInContainer,
-                    eInserted);
+  DoContentInserted(aDocument, aContainer, aChild, eInserted);
 }
 
 bool
 HTMLEditor::IsInObservedSubtree(nsIDocument* aDocument,
                                 nsIContent* aContainer,
                                 nsIContent* aChild)
 {
   if (!aChild) {
@@ -3170,17 +3166,16 @@ HTMLEditor::IsInObservedSubtree(nsIDocum
 
   return !aChild->ChromeOnlyAccess() && !aChild->GetBindingParent();
 }
 
 void
 HTMLEditor::DoContentInserted(nsIDocument* aDocument,
                               nsIContent* aContainer,
                               nsIContent* aChild,
-                              int32_t /* aIndexInContainer */,
                               InsertedOrAppended aInsertedOrAppended)
 {
   MOZ_ASSERT(aChild);
   nsINode* container = NODE_FROM(aContainer, aDocument);
   MOZ_ASSERT(container);
 
   if (!IsInObservedSubtree(aDocument, aContainer, aChild)) {
     return;
@@ -3221,17 +3216,16 @@ HTMLEditor::DoContentInserted(nsIDocumen
     }
   }
 }
 
 void
 HTMLEditor::ContentRemoved(nsIDocument* aDocument,
                            nsIContent* aContainer,
                            nsIContent* aChild,
-                           int32_t aIndexInContainer,
                            nsIContent* aPreviousSibling)
 {
   if (!IsInObservedSubtree(aDocument, aContainer, aChild)) {
     return;
   }
 
   // XXX Why do we need to do this?  This method is a mutation observer's
   //     method.  Therefore, the caller should guarantee that this won't be
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -1071,17 +1071,17 @@ private:
                               const nsAString* aAttribute,
                               const nsAString* aValue);
   nsresult SetInlinePropertyOnNodeImpl(nsIContent& aNode,
                                        nsIAtom& aProperty,
                                        const nsAString* aAttribute,
                                        const nsAString& aValue);
   typedef enum { eInserted, eAppended } InsertedOrAppended;
   void DoContentInserted(nsIDocument* aDocument, nsIContent* aContainer,
-                         nsIContent* aChild, int32_t aIndexInContainer,
+                         nsIContent* aChild,
                          InsertedOrAppended aInsertedOrAppended);
   already_AddRefed<Element> GetElementOrParentByTagName(
                               const nsAString& aTagName, nsINode* aNode);
   already_AddRefed<Element> CreateElementWithDefaults(
                               const nsAString& aTagName);
   /**
    * Returns an anonymous Element of type aTag,
    * child of aParentContent. If aIsCreatedHidden is true, the class
--- a/extensions/cookie/nsCookiePermission.cpp
+++ b/extensions/cookie/nsCookiePermission.cpp
@@ -134,36 +134,37 @@ nsCookiePermission::SetAccess(nsIURI    
   //       the permission codes used by nsIPermissionManager.
   //       this is nice because it avoids conversion code.
   //
   return mPermMgr->Add(aURI, kPermissionType, aAccess,
                        nsIPermissionManager::EXPIRE_NEVER, 0);
 }
 
 NS_IMETHODIMP
-nsCookiePermission::CanAccess(nsIURI         *aURI,
-                              nsIChannel     *aChannel,
+nsCookiePermission::CanAccess(nsIPrincipal   *aPrincipal,
                               nsCookieAccess *aResult)
 {
   // Check this protocol doesn't allow cookies
   bool hasFlags;
+  nsCOMPtr<nsIURI> uri;
+  aPrincipal->GetURI(getter_AddRefs(uri));
   nsresult rv =
-    NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS,
+    NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS,
                         &hasFlags);
   if (NS_FAILED(rv) || hasFlags) {
     *aResult = ACCESS_DENY;
     return NS_OK;
   }
 
   // Lazily initialize ourselves
   if (!EnsureInitialized())
     return NS_ERROR_UNEXPECTED;
 
   // finally, check with permission manager...
-  rv = mPermMgr->TestPermission(aURI, kPermissionType, (uint32_t *) aResult);
+  rv = mPermMgr->TestPermissionFromPrincipal(aPrincipal, kPermissionType, (uint32_t *) aResult);
   if (NS_SUCCEEDED(rv)) {
     if (*aResult == nsICookiePermission::ACCESS_SESSION) {
       *aResult = nsICookiePermission::ACCESS_ALLOW;
     }
   }
 
   return rv;
 }
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -621,28 +621,32 @@ public:
       , mMode(aMode)
       , mTransport(aTransport)
       , mMyPid(aMyPid)
       , mOtherPid(aOtherPid)
     {}
 
     Endpoint(Endpoint&& aOther)
       : mValid(aOther.mValid)
-      , mMode(aOther.mMode)
       , mTransport(aOther.mTransport)
       , mMyPid(aOther.mMyPid)
       , mOtherPid(aOther.mOtherPid)
     {
+        if (aOther.mValid) {
+            mMode = aOther.mMode;
+        }
         aOther.mValid = false;
     }
 
     Endpoint& operator=(Endpoint&& aOther)
     {
         mValid = aOther.mValid;
-        mMode = aOther.mMode;
+        if (aOther.mValid) {
+            mMode = aOther.mMode;
+        }
         mTransport = aOther.mTransport;
         mMyPid = aOther.mMyPid;
         mOtherPid = aOther.mOtherPid;
 
         aOther.mValid = false;
         return *this;
     }
 
--- a/js/public/ProfilingFrameIterator.h
+++ b/js/public/ProfilingFrameIterator.h
@@ -41,19 +41,27 @@ struct ForEachTrackedOptimizationTypeInf
 // (via EnableRuntimeProfilingStack) before executing the callstack being
 // unwound.
 //
 // Note that the caller must not do anything that could cause GC to happen while
 // the iterator is alive, since this could invalidate Ion code and cause its
 // contents to become out of date.
 class MOZ_NON_PARAM JS_PUBLIC_API(ProfilingFrameIterator)
 {
+  public:
+    enum class Kind : bool {
+        JSJit,
+        Wasm
+    };
+
+  private:
     JSContext* cx_;
     uint32_t sampleBufferGen_;
     js::Activation* activation_;
+    Kind kind_;
 
     static const unsigned StorageSpace = 8 * sizeof(void*);
     alignas(void*) unsigned char storage_[StorageSpace];
 
     void* storage() { return storage_; }
     const void* storage() const { return storage_; }
 
     js::wasm::ProfilingFrameIterator& wasmIter() {
@@ -74,16 +82,17 @@ class MOZ_NON_PARAM JS_PUBLIC_API(Profil
     }
 
     const js::jit::JitProfilingFrameIterator& jitIter() const {
         MOZ_ASSERT(!done());
         MOZ_ASSERT(isJit());
         return *static_cast<const js::jit::JitProfilingFrameIterator*>(storage());
     }
 
+    void settleFrames();
     void settle();
 
     bool hasSampleBufferGen() const {
         return sampleBufferGen_ != UINT32_MAX;
     }
 
   public:
     struct RegisterState
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -2365,18 +2365,24 @@ function Intl_NumberFormat_format_get() 
 
     // Step 5.
     return internals.boundFormat;
 }
 _SetCanonicalName(Intl_NumberFormat_format_get, "get format");
 
 
 function Intl_NumberFormat_formatToParts(value) {
-    // Steps 1-3.
-    var nf = UnwrapNumberFormat(this, "formatToParts");
+    // Step 1.
+    var nf = this;
+
+    // Steps 2-3.
+    if (!IsObject(nf) || !IsNumberFormat(nf)) {
+        ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "NumberFormat", "formatToParts",
+                       "NumberFormat");
+    }
 
     // Ensure the NumberFormat internals are resolved.
     getNumberFormatInternals(nf);
 
     // Step 4.
     var x = ToNumber(value);
 
     // Step 5.
@@ -3066,18 +3072,24 @@ function Intl_DateTimeFormat_format_get(
 }
 _SetCanonicalName(Intl_DateTimeFormat_format_get, "get format");
 
 
 /**
  * Intl.DateTimeFormat.prototype.formatToParts ( date )
  */
 function Intl_DateTimeFormat_formatToParts(date) {
-    // Steps 1-3.
-    var dtf = UnwrapDateTimeFormat(this, "formatToParts");
+    // Step 1.
+    var dtf = this;
+
+    // Steps 2-3.
+    if (!IsObject(dtf) || !IsDateTimeFormat(dtf)) {
+        ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "DateTimeFormat", "formatToParts",
+                       "DateTimeFormat");
+    }
 
     // Ensure the DateTimeFormat internals are resolved.
     getDateTimeFormatInternals(dtf);
 
     // Steps 4-5.
     var x = (date === undefined) ? std_Date_now() : ToNumber(date);
 
     // Step 6.
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -1201,16 +1201,35 @@ js::regexp_test_no_statics(JSContext* cx
     RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, 0,
                                            nullptr, &ignored, DontUpdateRegExpStatics);
     args.rval().setBoolean(status == RegExpRunStatus_Success);
     return status != RegExpRunStatus_Error;
 }
 
 using CapturesVector = GCVector<Value, 4>;
 
+struct JSSubString
+{
+    JSLinearString* base;
+    size_t          offset;
+    size_t          length;
+
+    JSSubString() { mozilla::PodZero(this); }
+
+    void initEmpty(JSLinearString* base) {
+        this->base = base;
+        offset = length = 0;
+    }
+    void init(JSLinearString* base, size_t offset, size_t length) {
+        this->base = base;
+        this->offset = offset;
+        this->length = length;
+    }
+};
+
 static void
 GetParen(JSLinearString* matched, const JS::Value& capture, JSSubString* out)
 {
     if (capture.isUndefined()) {
         out->initEmpty(matched);
         return;
     }
     JSLinearString& captureLinear = capture.toString()->asLinear();
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -882,17 +882,17 @@ Statistics::beginGC(JSGCInvocationKind k
 }
 
 void
 Statistics::endGC()
 {
     TimeDuration sccTotal, sccLongest;
     sccDurations(&sccTotal, &sccLongest);
 
-    runtime->addTelemetry(JS_TELEMETRY_GC_IS_ZONE_GC, !zoneStats.isCollectingAllZones());
+    runtime->addTelemetry(JS_TELEMETRY_GC_IS_ZONE_GC, !zoneStats.isFullCollection());
     TimeDuration markTotal = SumPhase(PhaseKind::MARK, phaseTimes);
     TimeDuration markRootsTotal = SumPhase(PhaseKind::MARK_ROOTS, phaseTimes);
     runtime->addTelemetry(JS_TELEMETRY_GC_MARK_MS, t(markTotal));
     runtime->addTelemetry(JS_TELEMETRY_GC_SWEEP_MS, t(phaseTimes[Phase::SWEEP]));
     if (runtime->gc.isCompactingGc()) {
         runtime->addTelemetry(JS_TELEMETRY_GC_COMPACT_MS,
                               t(phaseTimes[Phase::COMPACT]));
     }
@@ -961,17 +961,17 @@ Statistics::beginSlice(const ZoneGCStats
         // If we are OOM, set a flag to indicate we have missing slice data.
         aborted = true;
         return;
     }
 
     runtime->addTelemetry(JS_TELEMETRY_GC_REASON, reason);
 
     // Slice callbacks should only fire for the outermost level.
-    bool wasFullGC = zoneStats.isCollectingAllZones();
+    bool wasFullGC = zoneStats.isFullCollection();
     if (sliceCallback) {
         JSContext* cx = TlsContext.get();
         JS::GCDescription desc(!wasFullGC, false, gckind, reason);
         if (first)
             (*sliceCallback)(cx, JS::GC_CYCLE_BEGIN, desc);
         (*sliceCallback)(cx, JS::GC_SLICE_BEGIN, desc);
     }
 }
@@ -1032,17 +1032,17 @@ Statistics::endSlice()
             endGC();
     }
 
     if (enableProfiling_ && !aborted && slices_.back().duration() >= profileThreshold_)
         printSliceProfile();
 
     // Slice callbacks should only fire for the outermost level.
     if (!aborted) {
-        bool wasFullGC = zoneStats.isCollectingAllZones();
+        bool wasFullGC = zoneStats.isFullCollection();
         if (sliceCallback) {
             JSContext* cx = TlsContext.get();
             JS::GCDescription desc(!wasFullGC, last, gckind, slices_.back().reason);
             (*sliceCallback)(cx, JS::GC_SLICE_END, desc);
             if (last)
                 (*sliceCallback)(cx, JS::GC_CYCLE_END, desc);
         }
     }
@@ -1314,17 +1314,17 @@ Statistics::maybePrintProfileHeaders()
 }
 
 void
 Statistics::printProfileHeader()
 {
     if (!enableProfiling_)
         return;
 
-    fprintf(stderr, "MajorGC:               Reason States SRN  ");
+    fprintf(stderr, "MajorGC:               Reason States FSNR ");
     fprintf(stderr, " %6s", "budget");
     fprintf(stderr, " %6s", "total");
 #define PRINT_PROFILE_HEADER(name, text, phase)                               \
     fprintf(stderr, " %6s", text);
 FOR_EACH_GC_PROFILE_TIME(PRINT_PROFILE_HEADER)
 #undef PRINT_PROFILE_HEADER
     fprintf(stderr, "\n");
 }
@@ -1342,23 +1342,25 @@ Statistics::printSliceProfile()
 {
     const SliceData& slice = slices_.back();
 
     maybePrintProfileHeaders();
 
     bool shrinking = gckind == GC_SHRINK;
     bool reset = slice.resetReason != AbortReason::None;
     bool nonIncremental = nonincrementalReason_ != AbortReason::None;
+    bool full = zoneStats.isFullCollection();
 
-    fprintf(stderr, "MajorGC: %20s %1d -> %1d %1s%1s%1s  ",
+    fprintf(stderr, "MajorGC: %20s %1d -> %1d %1s%1s%1s%1s ",
             ExplainReason(slice.reason),
             int(slice.initialState), int(slice.finalState),
+            full ? "F": "",
             shrinking ? "S" : "",
-            reset ? "R" : "",
-            nonIncremental ? "N" : "");
+            nonIncremental ? "N" : "",
+            reset ? "R" : "");
 
     if (!nonIncremental && !slice.budget.isUnlimited() && slice.budget.isTimeBudget())
         fprintf(stderr, " %6" PRIi64, static_cast<int64_t>(slice.budget.timeBudget.budget));
     else
         fprintf(stderr, "       ");
 
     ProfileDurations times;
     times[ProfileKey::Total] = slice.duration();
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -50,37 +50,41 @@ enum Stat {
     STAT_LIMIT
 };
 
 struct ZoneGCStats
 {
     /* Number of zones collected in this GC. */
     int collectedZoneCount;
 
+    /* Number of zones that could have been collected in this GC. */
+    int collectableZoneCount;
+
     /* Total number of zones in the Runtime at the start of this GC. */
     int zoneCount;
 
     /* Number of zones swept in this GC. */
     int sweptZoneCount;
 
     /* Total number of compartments in all zones collected. */
     int collectedCompartmentCount;
 
     /* Total number of compartments in the Runtime at the start of this GC. */
     int compartmentCount;
 
     /* Total number of compartments swept by this GC. */
     int sweptCompartmentCount;
 
-    bool isCollectingAllZones() const { return collectedZoneCount == zoneCount; }
+    bool isFullCollection() const {
+        return collectedZoneCount == collectableZoneCount;
+    }
 
-    ZoneGCStats()
-      : collectedZoneCount(0), zoneCount(0), sweptZoneCount(0),
-        collectedCompartmentCount(0), compartmentCount(0), sweptCompartmentCount(0)
-    {}
+    ZoneGCStats() {
+        mozilla::PodZero(this);
+    }
 };
 
 #define FOR_EACH_GC_PROFILE_TIME(_)                                           \
     _(BeginCallback, "bgnCB",  PhaseKind::GC_BEGIN)                           \
     _(MinorForMajor, "evct4m", PhaseKind::EVICT_NURSERY_FOR_MAJOR_GC)         \
     _(WaitBgThread,  "waitBG", PhaseKind::WAIT_BACKGROUND_THREAD)             \
     _(Prepare,       "prep",   PhaseKind::PREPARE)                            \
     _(Mark,          "mark",   PhaseKind::MARK)                               \
--- a/js/src/gdb/mozilla/unwind.py
+++ b/js/src/gdb/mozilla/unwind.py
@@ -35,17 +35,18 @@ def debug(something):
     pass
 
 # Maps frametype enum base names to corresponding class.
 SizeOfFramePrefix = {
     'JitFrame_IonJS': 'ExitFrameLayout',
     'JitFrame_BaselineJS': 'JitFrameLayout',
     'JitFrame_BaselineStub': 'BaselineStubFrameLayout',
     'JitFrame_IonStub': 'JitStubFrameLayout',
-    'JitFrame_Entry': 'JitFrameLayout',
+    'JitFrame_CppToJSJit': 'JitFrameLayout',
+    'JitFrame_WasmToJSJit': 'JitFrameLayout',
     'JitFrame_Rectifier': 'RectifierFrameLayout',
     'JitFrame_IonAccessorIC': 'IonAccessorICFrameLayout',
     'JitFrame_IonICCall': 'IonICCallFrameLayout',
     'JitFrame_Exit': 'ExitFrameLayout',
     'JitFrame_Bailout': 'JitFrameLayout',
 }
 
 # All types and symbols that we need are attached to an object that we
@@ -362,17 +363,17 @@ class UnwinderState(object):
     # representing the previous frame's type.
     def unpack_descriptor(self, common):
         value = long(common['descriptor_'])
         local_size = value >> self.typecache.FRAMESIZE_SHIFT
         header_size = ((value >> self.typecache.FRAME_HEADER_SIZE_SHIFT) &
                        self.typecache.FRAME_HEADER_SIZE_MASK)
         header_size = header_size * self.typecache.void_starstar.sizeof
         frame_type = long(value & self.typecache.FRAMETYPE_MASK)
-        if frame_type == self.typecache.JitFrame_Entry:
+        if frame_type == self.typecache.JitFrame_CppToJSJit:
             # Trampoline-x64.cpp pushes a JitFrameLayout object, but
             # the stack pointer is actually adjusted as if a
             # CommonFrameLayout object was pushed.
             header_size = self.typecache.typeCommonFrameLayout.sizeof
         return (local_size, header_size, frame_type)
 
     # Create a new frame for gdb.  This makes a new unwind info object
     # and fills it in, then returns it.  It also registers any
@@ -433,30 +434,30 @@ class UnwinderState(object):
             # Reached the end of the list.
             return None
         elif self.activation is None:
             cx = self.get_tls_context()
             self.activation = cx['jitActivation']
         else:
             self.activation = self.activation['prevJitActivation_']
 
-        exitFP = self.activation['exitFP_']
-        if exitFP == 0:
+        packedExitFP = self.activation['packedExitFP_']
+        if packedExitFP == 0:
             return None
 
         exit_sp = pending_frame.read_register(self.SP_REGISTER)
         frame_type = self.typecache.JitFrame_Exit
-        return self.create_frame(pc, exit_sp, exitFP, frame_type, pending_frame)
+        return self.create_frame(pc, exit_sp, packedExitFP, frame_type, pending_frame)
 
     # A wrapper for unwind_entry_frame_registers that handles
     # architecture-independent boilerplate.
     def unwind_entry_frame(self, pc, pending_frame):
         sp = self.next_sp
         # Notify the frame filter.
-        self.add_frame(sp, name = 'JitFrame_Entry')
+        self.add_frame(sp, name = 'JitFrame_CppToJSJit')
         # Make an unwind_info for the per-architecture code to fill in.
         frame_id = SpiderMonkeyFrameId(sp, pc)
         unwind_info = pending_frame.create_unwind_info(frame_id)
         self.unwind_entry_frame_registers(sp, unwind_info)
         self.next_sp = None
         self.next_type = None
         return unwind_info
 
@@ -467,17 +468,17 @@ class UnwinderState(object):
         pc = pending_frame.read_register(self.PC_REGISTER)
 
         # If the jit does not claim this address, bail.  GDB defers to our
         # unwinder by default, but we don't really want that kind of power.
         if not self.is_jit_address(long(pc)):
             return None
 
         if self.next_sp is not None:
-            if self.next_type == self.typecache.JitFrame_Entry:
+            if self.next_type == self.typecache.JitFrame_CppToJSJit:
                 return self.unwind_entry_frame(pc, pending_frame)
             return self.unwind_ordinary(pc, pending_frame)
         # Maybe we've found an exit frame.  FIXME I currently don't
         # know how to identify these precisely, so we'll just hope for
         # the time being.
         return self.unwind_exit_frame(pc, pending_frame)
 
 # The UnwinderState subclass for x86-64.
--- a/js/src/gdb/tests/test-unwind.py
+++ b/js/src/gdb/tests/test-unwind.py
@@ -22,17 +22,17 @@ def do_unwinder_test():
     frames = list(gdb.frames.execute_frame_filters(gdb.newest_frame(), 0, -1))
     for frame in frames:
         print("examining " + frame.function())
         if first:
             assert_eq(frame.function().startswith("Something"), True)
             first = False
         elif frame.function() == "<<JitFrame_Exit>>":
             found_exit = True
-        elif frame.function() == "<<JitFrame_Entry>>":
+        elif frame.function() == "<<JitFrame_CppToJSJit>>":
             found_entry = True
         elif frame.function() == "main":
             found_main = True
         elif "unwindFunctionInner" in frame.function():
             found_inner = True
         elif "unwindFunctionOuter" in frame.function():
             found_outer = True
 
--- a/js/src/jit-test/tests/asm.js/testFFI.js
+++ b/js/src/jit-test/tests/asm.js/testFFI.js
@@ -122,39 +122,50 @@ for (var i = 0; i < 15; i++)
 
 // Stack and registers for x64 and ARM, stack for x86
 function ffiDoubleMany(a,b,c,d,e,f,g,h,i,j) { return j+1 }
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function f(i) { i=+i; return +ffi(i,i+1.0,i+2.0,i+3.0,i+4.0,i+5.0,i+6.0,i+7.0,i+8.0,i+9.0) } return f'), null, {ffi:ffiDoubleMany});
 for (var i = 0; i < 15; i++)
     assertEq(f(i), i+10);
 
 // Test the throw path
-function ffiThrow(n) { if (n == 14) throw 'yolo'; }
+function ffiThrow(n) { if (n == 14) throw new Error('yolo'); }
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function f(i) { i=i|0; ffi(i >> 0); } return f'), null, {ffi:ffiThrow});
 var i = 0;
 try {
     for (; i < 15; i++)
         f(i);
     throw 'assume unreachable';
 } catch (e) {
-    assertEq(e, 'yolo');
+    assertEq(e.message, 'yolo');
     assertEq(i, 14);
 }
 
 // OOL conversion paths
 var INT32_MAX = Math.pow(2, 31) - 1;
 function ffiOOLConvertInt(n) { if (n == 40) return valueToConvert; return 42; }
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function f(i) { i=i|0; return ffi(i >> 0) | 0; } return f'), null, {ffi:ffiOOLConvertInt});
 for (var i = 0; i < 40; i++)
     assertEq(f(i), 42);
 valueToConvert = INT32_MAX + 1;
 assertEq(f(40), INT32_MAX + 1 | 0);
 function testBadConversions(f) {
+    valueToConvert = {valueOf: function () { throw new Error("FAIL"); }};
+
+    var errMsg;
+    try {
+        f(40);
+    } catch(e) {
+        errMsg = e.message;
+    }
+    assertEq(errMsg, "FAIL");
+
     valueToConvert = {valueOf: function () { throw "FAIL"; }};
     assertThrowsValue(() => f(40), "FAIL");
+
     valueToConvert = Symbol();
     assertThrowsInstanceOf(() => f(40), TypeError);
 }
 testBadConversions(f);
 
 function ffiOOLConvertDouble(n) { if (n == 40) return valueToConvert; return 42.5; }
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function f(i) { i=i|0; return +ffi(i >> 0); } return f'), null, {ffi:ffiOOLConvertDouble});
 for (var i = 0; i < 40; i++)
--- a/js/src/jit-test/tests/asm.js/testProfiling.js
+++ b/js/src/jit-test/tests/asm.js/testProfiling.js
@@ -195,22 +195,25 @@ var {f1,f2} = asmLink(asmCompile('g','ff
 // Interpreter FFI exit
 enableSingleStepProfiling();
 assertEq(f1(), 32);
 var stacks = disableSingleStepProfiling();
 assertStackContainsSeq(stacks, ">,f1,>,<,f1,>,>,<,f1,>,f2,>,<,f1,>,<,f2,>,<,f1,>,f2,>,<,f1,>,>,<,f1,>,<,f1,>,f1,>,>");
 
 
 // Ion FFI exit
-for (var i = 0; i < 20; i++)
+var jitOptions = getJitCompilerOptions();
+if (jitOptions['baseline.enable']) {
+    for (var i = 0; i < 20; i++)
+        assertEq(f1(), 32);
+    enableSingleStepProfiling();
     assertEq(f1(), 32);
-enableSingleStepProfiling();
-assertEq(f1(), 32);
-var stacks = disableSingleStepProfiling();
-assertStackContainsSeq(stacks, ">,f1,>,<,f1,>,>,<,f1,>,f2,>,<,f1,>,<,f2,>,<,f1,>,f2,>,<,f1,>,>,<,f1,>,<,f1,>,f1,>,>");
+    var stacks = disableSingleStepProfiling();
+    assertStackContainsSeq(stacks, ">,f1,>,<,f1,>,>,<,f1,>,f2,>,<,f1,>,<,f2,>,<,f1,>,f2,>,<,f1,>,>,<,f1,>,<,f1,>,f1,>,>");
+}
 
 
 if (isSimdAvailable() && typeof SIMD !== 'undefined') {
     // SIMD out-of-bounds exit
     var buf = new ArrayBuffer(0x10000);
     var f = asmLink(asmCompile('g','ffi','buf', USE_ASM + 'var f4=g.SIMD.float32x4; var f4l=f4.load; var u8=new g.Uint8Array(buf); function f(i) { i=i|0; return f4l(u8, 0xFFFF + i | 0); } return f'), this, {}, buf);
     enableSingleStepProfiling();
     assertThrowsInstanceOf(() => f(4), RangeError);
--- a/js/src/jit-test/tests/wasm/basic.js
+++ b/js/src/jit-test/tests/wasm/basic.js
@@ -1,9 +1,9 @@
-const LinkError = WebAssembly.LinkError;
+const { LinkError } = WebAssembly;
 
 // ----------------------------------------------------------------------------
 // exports
 
 var o = wasmEvalText('(module)').exports;
 assertEq(Object.getOwnPropertyNames(o).length, 0);
 
 var o = wasmEvalText('(module (func))').exports;
--- a/js/src/jit-test/tests/wasm/import-export.js
+++ b/js/src/jit-test/tests/wasm/import-export.js
@@ -603,8 +603,51 @@ var m = new Module(wasmTextToBinary(`(mo
                 (i32.load (i32.const 0))
                 (call $next))))
     (export "call" $call)
 )`));
 var e = {call:() => 1000};
 for (var i = 0; i < 10; i++)
     e = new Instance(m, {a:{val:i, next:e.call}}).exports;
 assertEq(e.call(), 1090);
+
+(function testImportJitExit() {
+    let options = getJitCompilerOptions();
+    if (!options['baseline.enable'])
+        return;
+
+    let baselineTrigger = options['baseline.warmup.trigger'];
+
+    let valueToConvert = 0;
+    function ffi(n) { if (n == 1337) { return valueToConvert }; return 42; }
+
+    // Baseline compile ffi.
+    for (let i = baselineTrigger + 1; i --> 0;)
+        ffi(i);
+
+    let imports = { a: { ffi }};
+
+    i = wasmEvalText(`(module
+        (import $ffi "a" "ffi" (param i32) (result i32))
+        (func $foo (export "foo") (param i32) (result i32)
+         get_local 0
+         call $ffi)
+    )`, imports).exports;
+
+    // Enable the jit exit.
+    assertEq(i.foo(0), 42);
+
+    // Test the jit exit.
+    assertEq(i.foo(0), 42);
+    assertEq(i.foo(1337), 0);
+
+    // Test OOL coercion.
+    valueToConvert = 2**31;
+    assertEq(i.foo(1337), -(2**31));
+
+    // Test OOL error path.
+    valueToConvert = { valueOf() { throw new Error('make ffi great again'); } }
+    assertErrorMessage(() => i.foo(1337), Error, "make ffi great again");
+
+    valueToConvert = { toString() { throw new Error('a FFI to believe in'); } }
+    assertErrorMessage(() => i.foo(1337), Error, "a FFI to believe in");
+})();
+
--- a/js/src/jit-test/tests/wasm/profiling.js
+++ b/js/src/jit-test/tests/wasm/profiling.js
@@ -171,50 +171,54 @@ for (let type of ['f32', 'f64']) {
                 ${type}.${func}
             )
         )`,
         this,
         ["", ">", "0,>", "<,0,>", `${type}.${func},0,>`, "<,0,>", "0,>", ">", ""]);
     }
 }
 
-function testError(code, error, expect)
-{
-    enableGeckoProfiling();
-    var f = wasmEvalText(code).exports[""];
-    enableSingleStepProfiling();
-    assertThrowsInstanceOf(f, error);
-    assertEqStacks(disableSingleStepProfiling(), expect);
-    disableGeckoProfiling();
-}
+(function() {
+    // Error handling.
+    function testError(code, error, expect)
+    {
+        enableGeckoProfiling();
+        var f = wasmEvalText(code).exports[""];
+        enableSingleStepProfiling();
+        assertThrowsInstanceOf(f, error);
+        assertEqStacks(disableSingleStepProfiling(), expect);
+        disableGeckoProfiling();
+    }
 
-testError(
-`(module
-    (func $foo (unreachable))
-    (func (export "") (call $foo))
-)`,
-WebAssembly.RuntimeError,
-["", ">", "1,>", "0,1,>", "interstitial,0,1,>", "trap handling,0,1,>", "", ">", ""]);
+    testError(
+    `(module
+        (func $foo (unreachable))
+        (func (export "") (call $foo))
+    )`,
+    WebAssembly.RuntimeError,
+    ["", ">", "1,>", "0,1,>", "interstitial,0,1,>", "trap handling,0,1,>", "", ">", ""]);
 
-testError(
-`(module
-    (type $good (func))
-    (type $bad (func (param i32)))
-    (func $foo (call_indirect $bad (i32.const 1) (i32.const 0)))
-    (func $bar (type $good))
-    (table anyfunc (elem $bar))
-    (export "" $foo)
-)`,
-WebAssembly.RuntimeError,
-// Technically we have this one *one-instruction* interval where
-// the caller is lost (the stack with "1,>"). It's annoying to fix and shouldn't
-// mess up profiles in practice so we ignore it.
-["", ">", "0,>", "1,0,>", "1,>", "trap handling,0,>", "", ">", ""]);
+    testError(
+    `(module
+        (type $good (func))
+        (type $bad (func (param i32)))
+        (func $foo (call_indirect $bad (i32.const 1) (i32.const 0)))
+        (func $bar (type $good))
+        (table anyfunc (elem $bar))
+        (export "" $foo)
+    )`,
+    WebAssembly.RuntimeError,
+    // Technically we have this one *one-instruction* interval where
+    // the caller is lost (the stack with "1,>"). It's annoying to fix and shouldn't
+    // mess up profiles in practice so we ignore it.
+    ["", ">", "0,>", "1,0,>", "1,>", "trap handling,0,>", "", ">", ""]);
+})();
 
 (function() {
+    // Tables fun.
     var e = wasmEvalText(`
     (module
         (func $foo (result i32) (i32.const 42))
         (export "foo" $foo)
         (func $bar (result i32) (i32.const 13))
         (table 10 anyfunc)
         (elem (i32.const 0) $foo $bar)
         (export "tbl" table)
@@ -275,16 +279,17 @@ WebAssembly.RuntimeError,
     enableGeckoProfiling();
     enableSingleStepProfiling();
     assertEq(e2.baz(2), 99);
     assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", "0,1,>", "1,>", ">", ""]);
     disableGeckoProfiling();
 })();
 
 (function() {
+    // Optimized wasm->wasm import.
     var m1 = new Module(wasmTextToBinary(`(module
         (func $foo (result i32) (i32.const 42))
         (export "foo" $foo)
     )`));
     var m2 = new Module(wasmTextToBinary(`(module
         (import $foo "a" "foo" (result i32))
         (func $bar (result i32) (call $foo))
         (export "bar" $bar)
@@ -305,8 +310,79 @@ WebAssembly.RuntimeError,
     var e3 = new Instance(m1).exports;
     var e4 = new Instance(m2, {a:e3}).exports;
     enableSingleStepProfiling();
     assertEq(e4.bar(), 42);
     assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", "0,1,>", "1,>", ">", ""]);
     disableGeckoProfiling();
     assertEq(e4.bar(), 42);
 })();
+
+(function() {
+    // FFIs test.
+    let prevOptions = getJitCompilerOptions();
+
+    // Skip tests if baseline isn't enabled, since the stacks might differ by
+    // a few instructions.
+    if (prevOptions['baseline.enable'] === 0)
+        return;
+
+    setJitCompilerOption("baseline.warmup.trigger", 10);
+
+    enableGeckoProfiling();
+
+    var m = new Module(wasmTextToBinary(`(module
+        (import $ffi "a" "ffi" (param i32) (result i32))
+        (func $foo (export "foo") (param i32) (result i32)
+         get_local 0
+         call $ffi)
+    )`));
+
+    var valueToConvert = 0;
+    function ffi(n) {
+        new Error().stack; // enter VM to clobber FP register.
+        if (n == 1337) { return valueToConvert };
+        return 42;
+    }
+
+    // Baseline compile ffi.
+    for (var i = 20; i --> 0;)
+        ffi(i);
+
+    var imports = { a: { ffi }};
+
+    var i = new Instance(m, imports).exports;
+
+    // Enable the jit exit.
+    assertEq(i.foo(0), 42);
+
+    enableSingleStepProfiling();
+    assertEq(i.foo(0), 42);
+    assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", "<,1,>",
+        // Losing stack information while the JIT func prologue sets profiler
+        // virtual FP.
+        "",
+        // Callee time.
+        "<,1,>",
+        // Losing stack information while we're exiting JIT func epilogue and
+        // recovering wasm FP.
+        "",
+        // Back into the jit exit (frame info has been recovered).
+        "<,1,>",
+        // Normal unwinding.
+        "1,>", ">", ""]);
+
+    // Test OOL coercion path.
+    valueToConvert = 2**31;
+
+    enableSingleStepProfiling();
+    assertEq(i.foo(1337), -(2**31));
+    assertEqStacks(disableSingleStepProfiling(), ["", ">", "1,>", "<,1,>", "", "<,1,>", "",
+        // Back into the jit exit (frame info has been recovered).
+        // Inline conversion fails, we skip to the OOL path, call from there
+        // and get back to the jit exit.
+        "<,1,>",
+        // Normal unwinding.
+        "1,>", ">", ""]);
+
+    disableGeckoProfiling();
+    setJitCompilerOption("baseline.warmup.trigger", prevOptions["baseline.warmup.trigger"]);
+})();
--- a/js/src/jit/Bailouts.cpp
+++ b/js/src/jit/Bailouts.cpp
@@ -32,17 +32,17 @@ jit::Bailout(BailoutStack* sp, BaselineB
     JSContext* cx = TlsContext.get();
     MOZ_ASSERT(bailoutInfo);
 
     // We don't have an exit frame.
     MOZ_ASSERT(IsInRange(FAKE_EXITFP_FOR_BAILOUT, 0, 0x1000) &&
                IsInRange(FAKE_EXITFP_FOR_BAILOUT + sizeof(CommonFrameLayout), 0, 0x1000),
                "Fake exitfp pointer should be within the first page.");
 
-    cx->activation()->asJit()->setExitFP(FAKE_EXITFP_FOR_BAILOUT);
+    cx->activation()->asJit()->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT);
 
     JitActivationIterator jitActivations(cx);
     BailoutFrameInfo bailoutData(jitActivations, sp);
     JSJitFrameIter frame(jitActivations->asJit());
     MOZ_ASSERT(!frame.ionScript()->invalidated());
     CommonFrameLayout* currentFramePtr = frame.current();
 
     TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
@@ -104,17 +104,17 @@ uint32_t
 jit::InvalidationBailout(InvalidationBailoutStack* sp, size_t* frameSizeOut,
                          BaselineBailoutInfo** bailoutInfo)
 {
     sp->checkInvariants();
 
     JSContext* cx = TlsContext.get();
 
     // We don't have an exit frame.
-    cx->activation()->asJit()->setExitFP(FAKE_EXITFP_FOR_BAILOUT);
+    cx->activation()->asJit()->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT);
 
     JitActivationIterator jitActivations(cx);
     BailoutFrameInfo bailoutData(jitActivations, sp);
     JSJitFrameIter frame(jitActivations->asJit());
     CommonFrameLayout* currentFramePtr = frame.current();
 
     TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
     TraceLogTimestamp(logger, TraceLogger_Invalidation);
@@ -190,19 +190,19 @@ jit::ExceptionHandlerBailout(JSContext* 
                              bool* overrecursed)
 {
     // We can be propagating debug mode exceptions without there being an
     // actual exception pending. For instance, when we return false from an
     // operation callback like a timeout handler.
     MOZ_ASSERT_IF(!excInfo.propagatingIonExceptionForDebugMode(), cx->isExceptionPending());
 
     JitActivation* act = cx->activation()->asJit();
-    uint8_t* prevExitFP = act->exitFP();
-    auto restoreExitFP = mozilla::MakeScopeExit([&]() { act->setExitFP(prevExitFP); });
-    act->setExitFP(FAKE_EXITFP_FOR_BAILOUT);
+    uint8_t* prevExitFP = act->jsExitFP();
+    auto restoreExitFP = mozilla::MakeScopeExit([&]() { act->setJSExitFP(prevExitFP); });
+    act->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT);
 
     gc::AutoSuppressGC suppress(cx);
 
     JitActivationIterator jitActivations(cx);
     BailoutFrameInfo bailoutData(jitActivations, frame.frame());
     JSJitFrameIter frameView(jitActivations->asJit());
     CommonFrameLayout* currentFramePtr = frameView.current();
 
@@ -301,17 +301,17 @@ jit::CheckFrequentBailouts(JSContext* cx
             Invalidate(cx, script);
         }
     }
 }
 
 void
 BailoutFrameInfo::attachOnJitActivation(const JitActivationIterator& jitActivations)
 {
-    MOZ_ASSERT(jitActivations.exitFP() == FAKE_EXITFP_FOR_BAILOUT);
+    MOZ_ASSERT(jitActivations->asJit()->jsExitFP() == FAKE_EXITFP_FOR_BAILOUT);
     activation_ = jitActivations->asJit();
     activation_->setBailoutData(this);
 }
 
 BailoutFrameInfo::~BailoutFrameInfo()
 {
     activation_->cleanBailoutData();
 }
--- a/js/src/jit/Bailouts.h
+++ b/js/src/jit/Bailouts.h
@@ -97,17 +97,22 @@ static const uint32_t BAILOUT_TABLE_SIZE
 // Bailout return codes.
 // N.B. the relative order of these values is hard-coded into ::GenerateBailoutThunk.
 static const uint32_t BAILOUT_RETURN_OK = 0;
 static const uint32_t BAILOUT_RETURN_FATAL_ERROR = 1;
 static const uint32_t BAILOUT_RETURN_OVERRECURSED = 2;
 
 // This address is a magic number made to cause crashes while indicating that we
 // are making an attempt to mark the stack during a bailout.
-static uint8_t* const FAKE_EXITFP_FOR_BAILOUT = reinterpret_cast<uint8_t*>(0xba1);
+static const uint32_t FAKE_EXITFP_FOR_BAILOUT_ADDR = 0xba2;
+static uint8_t* const FAKE_EXITFP_FOR_BAILOUT =
+    reinterpret_cast<uint8_t*>(FAKE_EXITFP_FOR_BAILOUT_ADDR);
+
+static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & JitActivation::ExitFpWasmBit),
+              "FAKE_EXITFP_FOR_BAILOUT could be mistaken as a low-bit tagged wasm exit fp");
 
 // BailoutStack is an architecture specific pointer to the stack, given by the
 // bailout handler.
 class BailoutStack;
 class InvalidationBailoutStack;
 
 // Must be implemented by each architecture.
 
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -357,17 +357,17 @@ struct BaselineStackBuilder
         // Get the incoming frame.
         BufferPointer<JitFrameLayout> topFrame = topFrameAddress();
         FrameType type = topFrame->prevType();
 
         // For IonJS, IonICCall and Entry frames, the "saved" frame pointer
         // in the baseline frame is meaningless, since Ion saves all registers
         // before calling other ion frames, and the entry frame saves all
         // registers too.
-        if (type == JitFrame_IonJS || type == JitFrame_Entry || type == JitFrame_IonICCall)
+        if (JSJitFrameIter::isEntry(type) || type == JitFrame_IonJS || type == JitFrame_IonICCall)
             return nullptr;
 
         // BaselineStub - Baseline calling into Ion.
         //  PrevFramePtr needs to point to the BaselineStubFrame's saved frame pointer.
         //      STACK_START_ADDR + JitFrameLayout::Size() + PREV_FRAME_SIZE
         //                      - BaselineStubFrameLayout::reverseOffsetOfSavedFramePtr()
         if (type == JitFrame_BaselineStub) {
             size_t offset = JitFrameLayout::Size() + topFrame->prevFrameLocalSize() +
@@ -1510,24 +1510,24 @@ jit::BailoutIonToBaseline(JSContext* cx,
     // ensure that its Debugger.Frame entry is cleaned up.
     auto guardRemoveRematerializedFramesFromDebugger = mozilla::MakeScopeExit([&] {
         activation->removeRematerializedFramesFromDebugger(cx, iter.fp());
     });
 
     // The caller of the top frame must be one of the following:
     //      IonJS - Ion calling into Ion.
     //      BaselineStub - Baseline calling into Ion.
-    //      Entry - Interpreter or other calling into Ion.
+    //      Entry / WasmToJSJit - Interpreter or other (wasm) calling into Ion.
     //      Rectifier - Arguments rectifier calling into Ion.
     MOZ_ASSERT(iter.isBailoutJS());
 #if defined(DEBUG) || defined(JS_JITSPEW)
     FrameType prevFrameType = iter.prevType();
-    MOZ_ASSERT(prevFrameType == JitFrame_IonJS ||
+    MOZ_ASSERT(JSJitFrameIter::isEntry(prevFrameType) ||
+               prevFrameType == JitFrame_IonJS ||
                prevFrameType == JitFrame_BaselineStub ||
-               prevFrameType == JitFrame_Entry ||
                prevFrameType == JitFrame_Rectifier ||
                prevFrameType == JitFrame_IonICCall);
 #endif
 
     // All incoming frames are going to look like this:
     //
     //      +---------------+
     //      |     ...       |
--- a/js/src/jit/BaselineDebugModeOSR.h
+++ b/js/src/jit/BaselineDebugModeOSR.h
@@ -88,17 +88,17 @@ class DebugModeOSRVolatileStub
 
 class DebugModeOSRVolatileJitFrameIter : public JitFrameIter
 {
     DebugModeOSRVolatileJitFrameIter** stack;
     DebugModeOSRVolatileJitFrameIter* prev;
 
   public:
     explicit DebugModeOSRVolatileJitFrameIter(JSContext* cx)
-      : JitFrameIter(cx->activation())
+      : JitFrameIter(cx->activation()->asJit())
     {
         stack = &cx->liveVolatileJitFrameIter_.ref();
         prev = *stack;
         *stack = this;
     }
 
     ~DebugModeOSRVolatileJitFrameIter() {
         MOZ_ASSERT(*stack == this);
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -3000,19 +3000,22 @@ InvalidateActivation(FreeOp* fop, const 
             JitSpew(JitSpew_IonInvalidate, "#%zu baseline stub frame @ %p", frameno, frame.fp());
             break;
           case JitFrame_Rectifier:
             JitSpew(JitSpew_IonInvalidate, "#%zu rectifier frame @ %p", frameno, frame.fp());
             break;
           case JitFrame_IonICCall:
             JitSpew(JitSpew_IonInvalidate, "#%zu ion IC call frame @ %p", frameno, frame.fp());
             break;
-          case JitFrame_Entry:
+          case JitFrame_CppToJSJit:
             JitSpew(JitSpew_IonInvalidate, "#%zu entry frame @ %p", frameno, frame.fp());
             break;
+          case JitFrame_WasmToJSJit:
+            JitSpew(JitSpew_IonInvalidate, "#%zu wasm frames @ %p", frameno, frame.fp());
+            break;
         }
 #endif // JS_JITSPEW
 
         if (!frame.isIonScripted())
             continue;
 
         bool calledFromLinkStub = false;
         JitCode* lazyLinkStub = fop->runtime()->jitRuntime()->lazyLinkStub();
--- a/js/src/jit/JSJitFrameIter.cpp
+++ b/js/src/jit/JSJitFrameIter.cpp
@@ -9,17 +9,17 @@
 #include "jit/BaselineIC.h"
 #include "jit/JitcodeMap.h"
 #include "jit/JitFrames.h"
 
 using namespace js;
 using namespace js::jit;
 
 JSJitFrameIter::JSJitFrameIter(const JitActivation* activation)
-  : current_(activation->exitFP()),
+  : current_(activation->jsExitFP()),
     type_(JitFrame_Exit),
     returnAddressToFp_(nullptr),
     frameSize_(0),
     cachedSafepointIndex_(nullptr),
     activation_(activation)
 {
     if (activation_->bailoutData()) {
         current_ = activation_->bailoutData()->fp();
@@ -148,25 +148,25 @@ uint8_t*
 JSJitFrameIter::prevFp() const
 {
     return current_ + current()->prevFrameLocalSize() + current()->headerSize();
 }
 
 void
 JSJitFrameIter::operator++()
 {
-    MOZ_ASSERT(type_ != JitFrame_Entry);
+    MOZ_ASSERT(!isEntry());
 
     frameSize_ = prevFrameLocalSize();
     cachedSafepointIndex_ = nullptr;
 
     // If the next frame is the entry frame, just exit. Don't update current_,
     // since the entry and first frames overlap.
-    if (current()->prevType() == JitFrame_Entry) {
-        type_ = JitFrame_Entry;
+    if (isEntry(current()->prevType())) {
+        type_ = current()->prevType();
         return;
     }
 
     type_ = current()->prevType();
     returnAddressToFp_ = current()->returnAddress();
     current_ = prevFp();
 }
 
@@ -332,17 +332,17 @@ JSJitFrameIter::dumpBaseline() const
 #endif
     }
 }
 
 void
 JSJitFrameIter::dump() const
 {
     switch (type_) {
-      case JitFrame_Entry:
+      case JitFrame_CppToJSJit:
         fprintf(stderr, " Entry frame\n");
         fprintf(stderr, "  Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
         break;
       case JitFrame_BaselineJS:
         dumpBaseline();
         break;
       case JitFrame_BaselineStub:
         fprintf(stderr, " Baseline stub frame\n");
@@ -363,16 +363,20 @@ JSJitFrameIter::dump() const
       case JitFrame_Rectifier:
         fprintf(stderr, " Rectifier frame\n");
         fprintf(stderr, "  Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
         break;
       case JitFrame_IonICCall:
         fprintf(stderr, " Ion IC call\n");
         fprintf(stderr, "  Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
         break;
+      case JitFrame_WasmToJSJit:
+        fprintf(stderr, " Fast wasm-to-JS entry frame\n");
+        fprintf(stderr, "  Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
+        break;
       case JitFrame_Exit:
         fprintf(stderr, " Exit frame\n");
         break;
     };
     fputc('\n', stderr);
 }
 
 #ifdef DEBUG
--- a/js/src/jit/JSJitFrameIter.h
+++ b/js/src/jit/JSJitFrameIter.h
@@ -31,17 +31,17 @@ enum FrameType
     JitFrame_BaselineJS,
 
     // Frame pushed by Baseline stubs that make non-tail calls, so that the
     // return address -> ICEntry mapping works.
     JitFrame_BaselineStub,
 
     // The entry frame is the initial prologue block transitioning from the VM
     // into the Ion world.
-    JitFrame_Entry,
+    JitFrame_CppToJSJit,
 
     // A rectifier frame sits in between two JS frames, adapting argc != nargs
     // mismatches in calls.
     JitFrame_Rectifier,
 
     // Ion IC calling a scripted getter/setter or a VMFunction.
     JitFrame_IonICCall,
 
@@ -50,16 +50,21 @@ enum FrameType
     // JitActivation.
     JitFrame_Exit,
 
     // A bailout frame is a special IonJS jit frame after a bailout, and before
     // the reconstruction of the BaselineJS frame. From within C++, a bailout
     // frame is always the last frame in a JitActivation iff the bailout frame
     // information is recorded on the JitActivation.
     JitFrame_Bailout,
+
+    // A wasm to JS frame is constructed during fast calls from wasm to the JS
+    // jits, used as a marker to interleave JS jit and wasm frames. From the
+    // point of view of JS JITs, this is just another kind of entry frame.
+    JitFrame_WasmToJSJit,
 };
 
 enum ReadFrameArgsBehavior {
     // Only read formals (i.e. [0 ... callee()->nargs]
     ReadFrame_Formals,
 
     // Only read overflown args (i.e. [callee()->nargs ... numActuals()]
     ReadFrame_Overflown,
@@ -168,19 +173,23 @@ class JSJitFrameIter
         return type_ == JitFrame_BaselineStub;
     }
     bool isRectifier() const {
         return type_ == JitFrame_Rectifier;
     }
     bool isBareExit() const;
     template <typename T> bool isExitFrameLayout() const;
 
+    static bool isEntry(FrameType type) {
+        return type == JitFrame_CppToJSJit || type == JitFrame_WasmToJSJit;
+    }
     bool isEntry() const {
-        return type_ == JitFrame_Entry;
+        return isEntry(type_);
     }
+
     bool isFunctionFrame() const;
 
     bool isConstructing() const;
 
     void* calleeToken() const;
     JSFunction* callee() const;
     JSFunction* maybeCallee() const;
     unsigned numActualArgs() const;
@@ -201,20 +210,20 @@ class JSJitFrameIter
 
     // Returns the stack space used by the current frame, in bytes. This does
     // not include the size of its fixed header.
     size_t frameSize() const {
         MOZ_ASSERT(!isExitFrame());
         return frameSize_;
     }
 
-    // Functions used to iterate on frames. When prevType is JitFrame_Entry,
-    // the current frame is the last frame.
+    // Functions used to iterate on frames. When prevType is an entry,
+    // the current frame is the last JS Jit frame.
     bool done() const {
-        return type_ == JitFrame_Entry;
+        return isEntry();
     }
     void operator++();
 
     // Returns the IonScript associated with this JS frame.
     IonScript* ionScript() const;
 
     // Returns the IonScript associated with this JS frame; the frame must
     // not be invalidated.
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/JitFrames-inl.h"
 
-
 #include "jsfun.h"
 #include "jsobj.h"
 #include "jsscript.h"
 #include "jsutil.h"
 
 #include "gc/Marking.h"
 #include "jit/BaselineDebugModeOSR.h"
 #include "jit/BaselineFrame.h"
@@ -28,16 +27,17 @@
 #include "jit/Snapshots.h"
 #include "jit/VMFunctions.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/Debugger.h"
 #include "vm/GeckoProfiler.h"
 #include "vm/Interpreter.h"
 #include "vm/TraceLogging.h"
 #include "vm/TypeInference.h"
+#include "wasm/WasmBuiltins.h"
 
 #include "jsscriptinlines.h"
 #include "gc/Nursery-inl.h"
 #include "jit/JSJitFrameIter-inl.h"
 #include "vm/Debugger-inl.h"
 #include "vm/Probes-inl.h"
 #include "vm/TypeInference-inl.h"
 
@@ -569,16 +569,17 @@ struct AutoResetLastProfilerFrameOnRetur
 
         void* lastProfilingFrame = getLastProfilingFrame();
         cx->jitActivation->setLastProfilingFrame(lastProfilingFrame);
     }
 
     void* getLastProfilingFrame() {
         switch (rfe->kind) {
           case ResumeFromException::RESUME_ENTRY_FRAME:
+          case ResumeFromException::RESUME_WASM:
             return nullptr;
 
           // The following all return into baseline frames.
           case ResumeFromException::RESUME_CATCH:
           case ResumeFromException::RESUME_FINALLY:
           case ResumeFromException::RESUME_FORCED_RETURN:
             return rfe->framePointer + BaselineFrame::FramePointerOffset;
 
@@ -589,16 +590,29 @@ struct AutoResetLastProfilerFrameOnRetur
         }
 
         MOZ_CRASH("Invalid ResumeFromException type!");
         return nullptr;
     }
 };
 
 void
+HandleExceptionWasm(JSContext* cx, wasm::WasmFrameIter* iter, ResumeFromException* rfe)
+{
+    // Maintain the wasm invariant that we have wasm frames when unwinding.
+    JitActivation* act = cx->activation()->asJit();
+    act->setWasmExitFP((const wasm::Frame*) act->jsExitFP());
+
+    rfe->kind = ResumeFromException::RESUME_WASM;
+    rfe->framePointer = (uint8_t*) wasm::FailFP;
+    rfe->stackPointer = (uint8_t*) wasm::HandleThrow(cx, *iter);
+    MOZ_ASSERT(iter->done());
+}
+
+void
 HandleException(ResumeFromException* rfe)
 {
     JSContext* cx = TlsContext.get();
     TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
 
     AutoResetLastProfilerFrameOnReturnFromException profFrameReset(cx, rfe);
 
     rfe->kind = ResumeFromException::RESUME_ENTRY_FRAME;
@@ -621,19 +635,24 @@ HandleException(ResumeFromException* rfe
 
     // The Debugger onExceptionUnwind hook (reachable via
     // HandleExceptionBaseline below) may cause on-stack recompilation of
     // baseline scripts, which may patch return addresses on the stack. Since
     // JSJitFrameIter cache the previous frame's return address when
     // iterating, we need a variant here that is automatically updated should
     // on-stack recompilation occur.
     DebugModeOSRVolatileJitFrameIter iter(cx);
-    while (!iter.isJSJit() || !iter.asJSJit().isEntry()) {
-        while (!iter.isJSJit())
-            ++iter;
+    while (!iter.done()) {
+        if (iter.isWasm()) {
+            HandleExceptionWasm(cx, &iter.asWasm(), rfe);
+            if (!iter.done())
+                ++iter;
+            continue;
+        }
+
         const JSJitFrameIter& frame = iter.asJSJit();
 
         bool overrecursed = false;
         if (frame.isIonJS()) {
             // Search each inlined frame for live iterator objects, and close
             // them.
             InlineFrameIterator frames(cx, &frame);
 
@@ -723,59 +742,61 @@ HandleException(ResumeFromException* rfe
                 return;
         }
 
         JitFrameLayout* current = frame.isScripted() ? frame.jsFrame() : nullptr;
 
         ++iter;
 
         if (current) {
-            // Unwind the frame by updating exitFP. This is necessary so that
-            // (1) debugger exception unwind and leave frame hooks don't see this
-            // frame when they use ScriptFrameIter, and (2) ScriptFrameIter does
-            // not crash when accessing an IonScript that's destroyed by the
-            // ionScript->decref call.
+            // Unwind the frame by updating packedExitFP. This is necessary so
+            // that (1) debugger exception unwind and leave frame hooks don't
+            // see this frame when they use ScriptFrameIter, and (2)
+            // ScriptFrameIter does not crash when accessing an IonScript
+            // that's destroyed by the ionScript->decref call.
             EnsureBareExitFrame(cx, current);
         }
 
         if (overrecursed) {
             // We hit an overrecursion error during bailout. Report it now.
             ReportOverRecursed(cx);
         }
     }
 
-    rfe->stackPointer = iter.asJSJit().fp();
+    // Wasm sets its own value of SP in HandleExceptionWasm.
+    if (iter.isJSJit())
+        rfe->stackPointer = iter.asJSJit().fp();
 }
 
 // Turns a JitFrameLayout into an ExitFrameLayout. Note that it has to be a
 // bare exit frame so it's ignored by TraceJitExitFrame.
 void
 EnsureBareExitFrame(JSContext* cx, JitFrameLayout* frame)
 {
     ExitFrameLayout* exitFrame = reinterpret_cast<ExitFrameLayout*>(frame);
 
-    if (cx->activation()->asJit()->exitFP() == (uint8_t*)frame) {
+    if (cx->activation()->asJit()->jsExitFP() == (uint8_t*)frame) {
         // If we already called this function for the current frame, do
         // nothing.
         MOZ_ASSERT(exitFrame->isBareExit());
         return;
     }
 
 #ifdef DEBUG
     JSJitFrameIter iter(cx);
     while (!iter.isScripted())
         ++iter;
     MOZ_ASSERT(iter.current() == frame, "|frame| must be the top JS frame");
 
-    MOZ_ASSERT(!!cx->activation()->asJit()->exitFP());
-    MOZ_ASSERT((uint8_t*)exitFrame->footer() >= cx->activation()->asJit()->exitFP(),
-               "Must have space for ExitFooterFrame before exitFP");
+    MOZ_ASSERT(!!cx->activation()->asJit()->jsExitFP());
+    MOZ_ASSERT((uint8_t*)exitFrame->footer() >= cx->activation()->asJit()->jsExitFP(),
+               "Must have space for ExitFooterFrame before jsExitFP");
 #endif
 
-    cx->activation()->asJit()->setExitFP((uint8_t*)frame);
+    cx->activation()->asJit()->setJSExitFP((uint8_t*)frame);
     *exitFrame->footer()->addressOfJitCode() = ExitFrameLayout::BareToken();
     MOZ_ASSERT(exitFrame->isBareExit());
 }
 
 CalleeToken
 TraceCalleeToken(JSTracer* trc, CalleeToken token)
 {
     switch (CalleeTokenTag tag = GetCalleeTokenTag(token)) {
@@ -1270,61 +1291,60 @@ TraceJitActivation(JSTracer* trc, JitAct
         // when a GC happens.
         activation->setCheckRegs(false);
     }
 #endif
 
     activation->traceRematerializedFrames(trc);
     activation->traceIonRecovery(trc);
 
-    for (JSJitFrameIter frames(activation); !frames.done(); ++frames) {
-        switch (frames.type()) {
-          case JitFrame_Exit:
-            TraceJitExitFrame(trc, frames);
-            break;
-          case JitFrame_BaselineJS:
-            frames.baselineFrame()->trace(trc, frames);
-            break;
-          case JitFrame_IonJS:
-            TraceIonJSFrame(trc, frames);
-            break;
-          case JitFrame_BaselineStub:
-            TraceBaselineStubFrame(trc, frames);
-            break;
-          case JitFrame_Bailout:
-            TraceBailoutFrame(trc, frames);
-            break;
-          case JitFrame_Rectifier:
-            TraceRectifierFrame(trc, frames);
-            break;
-          case JitFrame_IonICCall:
-            TraceIonICCallFrame(trc, frames);
-            break;
-          default:
-            MOZ_CRASH("unexpected frame type");
+    for (JitFrameIter frames(activation); !frames.done(); ++frames) {
+        if (frames.isJSJit()) {
+            const JSJitFrameIter& jitFrame = frames.asJSJit();
+            switch (jitFrame.type()) {
+              case JitFrame_Exit:
+                TraceJitExitFrame(trc, jitFrame);
+                break;
+              case JitFrame_BaselineJS:
+                jitFrame.baselineFrame()->trace(trc, jitFrame);
+                break;
+              case JitFrame_IonJS:
+                TraceIonJSFrame(trc, jitFrame);
+                break;
+              case JitFrame_BaselineStub:
+                TraceBaselineStubFrame(trc, jitFrame);
+                break;
+              case JitFrame_Bailout:
+                TraceBailoutFrame(trc, jitFrame);
+                break;
+              case JitFrame_Rectifier:
+                TraceRectifierFrame(trc, jitFrame);
+                break;
+              case JitFrame_IonICCall:
+                TraceIonICCallFrame(trc, jitFrame);
+                break;
+              case JitFrame_WasmToJSJit:
+                // Ignore: this is a marked used to let the JitFrameIter the
+                // frame above is a wasm frame, handled in the next iteration.
+                break;
+              default:
+                MOZ_CRASH("unexpected frame type");
+            }
+        } else {
+            MOZ_ASSERT(frames.isWasm());
+            frames.asWasm().instance()->trace(trc);
         }
     }
 }
 
-static void
-TraceWasmActivation(JSTracer* trc, WasmActivation* act)
-{
-    for (wasm::WasmFrameIter iter(act); !iter.done(); ++iter)
-        iter.instance()->trace(trc);
-}
-
 void
 TraceJitActivations(JSContext* cx, const CooperatingContext& target, JSTracer* trc)
 {
-    for (ActivationIterator activations(cx, target); !activations.done(); ++activations) {
-        if (activations->isJit())
-            TraceJitActivation(trc, activations->asJit());
-        if (activations->isWasm())
-            TraceWasmActivation(trc, activations->asWasm());
-    }
+    for (JitActivationIterator activations(cx, target); !activations.done(); ++activations)
+        TraceJitActivation(trc, activations->asJit());
 }
 
 void
 UpdateJitActivationsForMinorGC(JSRuntime* rt, JSTracer* trc)
 {
     MOZ_ASSERT(JS::CurrentThreadIsHeapMinorCollecting());
     JSContext* cx = TlsContext.get();
     for (const CooperatingContext& target : rt->cooperatingContexts()) {
@@ -2390,31 +2410,31 @@ InlineFrameIterator::dump() const
 }
 
 JitProfilingFrameIterator::JitProfilingFrameIterator(
         JSContext* cx, const JS::ProfilingFrameIterator::RegisterState& state)
 {
     // If no profilingActivation is live, initialize directly to
     // end-of-iteration state.
     if (!cx->profilingActivation()) {
-        type_ = JitFrame_Entry;
+        type_ = JitFrame_CppToJSJit;
         fp_ = nullptr;
         returnAddressToFp_ = nullptr;
         return;
     }
 
     MOZ_ASSERT(cx->profilingActivation()->isJit());
 
     JitActivation* act = cx->profilingActivation()->asJit();
 
     // If the top JitActivation has a null lastProfilingFrame, assume that
     // it's a trivially empty activation, and initialize directly
     // to end-of-iteration state.
     if (!act->lastProfilingFrame()) {
-        type_ = JitFrame_Entry;
+        type_ = JitFrame_CppToJSJit;
         fp_ = nullptr;
         returnAddressToFp_ = nullptr;
         return;
     }
 
     // Get the fp from the current profilingActivation
     fp_ = (uint8_t*) act->lastProfilingFrame();
     void* lastCallSite = act->lastProfilingCallSite();
@@ -2499,17 +2519,17 @@ JitProfilingFrameIterator::tryInitWithTa
         return false;
 
     JSScript* callee = frameScript();
 
     MOZ_ASSERT(entry->isIon() || entry->isBaseline() || entry->isIonCache() || entry->isDummy());
 
     // Treat dummy lookups as an empty frame sequence.
     if (entry->isDummy()) {
-        type_ = JitFrame_Entry;
+        type_ = JitFrame_CppToJSJit;
         fp_ = nullptr;
         returnAddressToFp_ = nullptr;
         return true;
     }
 
     if (entry->isIon()) {
         // If looked-up callee doesn't match frame callee, don't accept lastProfilingCallSite
         if (entry->ionEntry().getScript(0) != callee)
@@ -2587,16 +2607,18 @@ JitProfilingFrameIterator::moveToNextFra
      *
      * <Baseline-Or-Ion>
      * ^
      * |
      * ^--- Ion
      * |
      * ^--- Baseline Stub <---- Baseline
      * |
+     * ^--- WasmToJSJit <---- (other wasm frames, not handled by this iterator)
+     * |
      * ^--- Argument Rectifier
      * |    ^
      * |    |
      * |    ^--- Ion
      * |    |
      * |    ^--- Baseline Stub <---- Baseline
      * |
      * ^--- Entry Frame (From C++)
@@ -2668,22 +2690,31 @@ JitProfilingFrameIterator::moveToNextFra
         MOZ_ASSERT(callFrame->prevType() == JitFrame_IonJS);
 
         returnAddressToFp_ = callFrame->returnAddress();
         fp_ = GetPreviousRawFrame<uint8_t*>(callFrame);
         type_ = JitFrame_IonJS;
         return;
     }
 
-    if (prevType == JitFrame_Entry) {
-        // No previous frame, set to null to indicate that OnlyJSJitFrameIter is
-        // done().
+    if (prevType == JitFrame_WasmToJSJit) {
+        // No previous js jit frame, this is a transition frame, used to pass
+        // a wasm iterator the correct value of FP.
+        returnAddressToFp_ = nullptr;
+        fp_ = GetPreviousRawFrame<uint8_t*>(frame);
+        type_ = JitFrame_WasmToJSJit;
+        return;
+    }
+
+    if (prevType == JitFrame_CppToJSJit) {
+        // No previous frame, set to null to indicate that
+        // JitProfilingFrameIterator is done().
         returnAddressToFp_ = nullptr;
         fp_ = nullptr;
-        type_ = JitFrame_Entry;
+        type_ = JitFrame_CppToJSJit;
         return;
     }
 
     MOZ_CRASH("Bad frame type.");
 }
 
 JitFrameLayout*
 InvalidationBailoutStack::fp() const
@@ -2705,78 +2736,85 @@ InvalidationBailoutStack::checkInvariant
     MOZ_ASSERT(rawBase <= osiPoint && osiPoint <= rawLimit);
 #endif
 }
 
 void
 AssertJitStackInvariants(JSContext* cx)
 {
     for (JitActivationIterator activations(cx); !activations.done(); ++activations) {
-        JitFrameIter iter(activations.activation());
-        size_t prevFrameSize = 0;
-        size_t frameSize = 0;
-        bool isScriptedCallee = false;
-        for (; !iter.done(); ++iter) {
-            const JSJitFrameIter& frames = iter.asJSJit();
-            size_t calleeFp = reinterpret_cast<size_t>(frames.fp());
-            size_t callerFp = reinterpret_cast<size_t>(frames.prevFp());
-            MOZ_ASSERT(callerFp >= calleeFp);
-            prevFrameSize = frameSize;
-            frameSize = callerFp - calleeFp;
-
-            if (frames.prevType() == JitFrame_Rectifier) {
-                MOZ_RELEASE_ASSERT(frameSize % JitStackAlignment == 0,
-                  "The rectifier frame should keep the alignment");
-
-                size_t expectedFrameSize = 0
+        JitFrameIter iter(activations->asJit());
+        if (iter.isJSJit()) {
+            JSJitFrameIter& frames = iter.asJSJit();
+            size_t prevFrameSize = 0;
+            size_t frameSize = 0;
+            bool isScriptedCallee = false;
+            for (; !frames.done(); ++frames) {
+                size_t calleeFp = reinterpret_cast<size_t>(frames.fp());
+                size_t callerFp = reinterpret_cast<size_t>(frames.prevFp());
+                MOZ_ASSERT(callerFp >= calleeFp);
+                prevFrameSize = frameSize;
+                frameSize = callerFp - calleeFp;
+
+                if (frames.prevType() == JitFrame_Rectifier) {
+                    MOZ_RELEASE_ASSERT(frameSize % JitStackAlignment == 0,
+                      "The rectifier frame should keep the alignment");
+
+                    size_t expectedFrameSize = 0
 #if defined(JS_CODEGEN_X86)
-                    + sizeof(void*) /* frame pointer */
+                        + sizeof(void*) /* frame pointer */
 #endif
-                    + sizeof(Value) * (frames.callee()->nargs() +
-                                       1 /* |this| argument */ +
-                                       frames.isConstructing() /* new.target */)
-                    + sizeof(JitFrameLayout);
-                MOZ_RELEASE_ASSERT(frameSize >= expectedFrameSize,
-                  "The frame is large enough to hold all arguments");
-                MOZ_RELEASE_ASSERT(expectedFrameSize + JitStackAlignment > frameSize,
-                  "The frame size is optimal");
+                        + sizeof(Value) * (frames.callee()->nargs() +
+                                           1 /* |this| argument */ +
+                                           frames.isConstructing() /* new.target */)
+                        + sizeof(JitFrameLayout);
+                    MOZ_RELEASE_ASSERT(frameSize >= expectedFrameSize,
+                      "The frame is large enough to hold all arguments");
+                    MOZ_RELEASE_ASSERT(expectedFrameSize + JitStackAlignment > frameSize,
+                      "The frame size is optimal");
+                }
+
+                if (frames.isExitFrame()) {
+                    // For the moment, we do not keep the JitStackAlignment
+                    // alignment for exit frames.
+                    frameSize -= ExitFrameLayout::Size();
+                }
+
+                if (frames.isIonJS()) {
+                    // Ideally, we should not have such requirement, but keep the
+                    // alignment-delta as part of the Safepoint such that we can pad
+                    // accordingly when making out-of-line calls.  In the mean time,
+                    // let us have check-points where we can garantee that
+                    // everything can properly be aligned before adding complexity.
+                    MOZ_RELEASE_ASSERT(frames.ionScript()->frameSize() % JitStackAlignment == 0,
+                      "Ensure that if the Ion frame is aligned, then the spill base is also aligned");
+
+                    if (isScriptedCallee) {
+                        MOZ_RELEASE_ASSERT(prevFrameSize % JitStackAlignment == 0,
+                          "The ion frame should keep the alignment");
+                    }
+                }
+
+                // The stack is dynamically aligned by baseline stubs before calling
+                // any jitted code.
+                if (frames.prevType() == JitFrame_BaselineStub && isScriptedCallee) {
+                    MOZ_RELEASE_ASSERT(calleeFp % JitStackAlignment == 0,
+                        "The baseline stub restores the stack alignment");
+                }
+
+                isScriptedCallee = frames.isScripted() || frames.type() == JitFrame_Rectifier;
             }
 
-            if (frames.isExitFrame()) {
-                // For the moment, we do not keep the JitStackAlignment
-                // alignment for exit frames.
-                frameSize -= ExitFrameLayout::Size();
-            }
-
-            if (frames.isIonJS()) {
-                // Ideally, we should not have such requirement, but keep the
-                // alignment-delta as part of the Safepoint such that we can pad
-                // accordingly when making out-of-line calls.  In the mean time,
-                // let us have check-points where we can garantee that
-                // everything can properly be aligned before adding complexity.
-                MOZ_RELEASE_ASSERT(frames.ionScript()->frameSize() % JitStackAlignment == 0,
-                  "Ensure that if the Ion frame is aligned, then the spill base is also aligned");
-
-                if (isScriptedCallee) {
-                    MOZ_RELEASE_ASSERT(prevFrameSize % JitStackAlignment == 0,
-                      "The ion frame should keep the alignment");
-                }
-            }
-
-            // The stack is dynamically aligned by baseline stubs before calling
-            // any jitted code.
-            if (frames.prevType() == JitFrame_BaselineStub && isScriptedCallee) {
-                MOZ_RELEASE_ASSERT(calleeFp % JitStackAlignment == 0,
-                    "The baseline stub restores the stack alignment");
-            }
-
-            isScriptedCallee = frames.isScripted() || frames.type() == JitFrame_Rectifier;
+            MOZ_RELEASE_ASSERT(JSJitFrameIter::isEntry(frames.type()),
+              "The first frame of a Jit activation should be an entry frame");
+            MOZ_RELEASE_ASSERT(reinterpret_cast<size_t>(frames.fp()) % JitStackAlignment == 0,
+              "The entry frame should be properly aligned");
+        } else {
+            MOZ_ASSERT(iter.isWasm());
+            wasm::WasmFrameIter& frames = iter.asWasm();
+            while (!frames.done())
+                ++frames;
         }
-
-        MOZ_RELEASE_ASSERT(iter.asJSJit().type() == JitFrame_Entry,
-          "The first frame of a Jit activation should be an entry frame");
-        MOZ_RELEASE_ASSERT(reinterpret_cast<size_t>(iter.asJSJit().fp()) % JitStackAlignment == 0,
-          "The entry frame should be properly aligned");
     }
 }
 
 } // namespace jit
 } // namespace js
--- a/js/src/jit/JitFrames.h
+++ b/js/src/jit/JitFrames.h
@@ -264,16 +264,17 @@ struct BaselineBailoutInfo;
 // Data needed to recover from an exception.
 struct ResumeFromException
 {
     static const uint32_t RESUME_ENTRY_FRAME = 0;
     static const uint32_t RESUME_CATCH = 1;
     static const uint32_t RESUME_FINALLY = 2;
     static const uint32_t RESUME_FORCED_RETURN = 3;
     static const uint32_t RESUME_BAILOUT = 4;
+    static const uint32_t RESUME_WASM = 5;
 
     uint8_t* framePointer;
     uint8_t* stackPointer;
     uint8_t* target;
     uint32_t kind;
 
     // Value to push when resuming into a |finally| block.
     Value exception;
@@ -439,16 +440,24 @@ class JitFrameLayout : public CommonFram
 class RectifierFrameLayout : public JitFrameLayout
 {
   public:
     static inline size_t Size() {
         return sizeof(RectifierFrameLayout);
     }
 };
 
+class WasmFrameLayout : public JitFrameLayout
+{
+  public:
+    static inline size_t Size() {
+        return sizeof(WasmFrameLayout);
+    }
+};
+
 class IonICCallFrameLayout : public CommonFrameLayout
 {
   protected:
     // Pointer to root the stub's JitCode.
     JitCode* stubCode_;
 
   public:
     JitCode** stubCode() {
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -2856,17 +2856,17 @@ MacroAssembler::callWithABI(wasm::Byteco
 
 // ===============================================================
 // Exit frame footer.
 
 void
 MacroAssembler::linkExitFrame(Register cxreg, Register scratch)
 {
     loadPtr(Address(cxreg, JSContext::offsetOfActivation()), scratch);
-    storeStackPtr(Address(scratch, JitActivation::offsetOfExitFP()));
+    storeStackPtr(Address(scratch, JitActivation::offsetOfPackedExitFP()));
 }
 
 void
 MacroAssembler::linkSelfReference(JitCode* code)
 {
     // If this code can transition to C++ code and witness a GC, then we need to store
     // the JitCode onto the stack in order to GC it correctly.  exitCodePatch should
     // be unset if the code never needed to push its JitCode*.
@@ -3110,29 +3110,16 @@ MacroAssembler::wasmEmitTrapOutOfLineCod
     // within this function's CodeRange which is necessary for the stack
     // iterator to find the right CodeRange while walking the stack.
     breakpoint();
 
     trapSites().clear();
 }
 
 void
-MacroAssembler::wasmAssertNonExitInvariants(Register activation)
-{
-#ifdef DEBUG
-    // WasmActivation.exitFP should be null when outside any exit frame.
-    Label ok;
-    Address exitFP(activation, WasmActivation::offsetOfExitFP());
-    branchPtr(Assembler::Equal, exitFP, ImmWord(0), &ok);
-    breakpoint();
-    bind(&ok);
-#endif
-}
-
-void
 MacroAssembler::wasmEmitStackCheck(Register sp, Register scratch, Label* onOverflow)
 {
     loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, addressOfContext)), scratch);
     loadPtr(Address(scratch, 0), scratch);
     branchPtr(Assembler::AboveOrEqual,
               Address(scratch, offsetof(JSContext, jitStackLimitNoInterrupt)),
               sp,
               onOverflow);
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -719,18 +719,18 @@ class MacroAssembler : public MacroAssem
 
     // Push an exit frame token for a native call.
     inline void enterFakeExitFrameForNative(Register cxreg, Register scratch, bool isConstructing);
 
     // Pop ExitFrame footer in addition to the extra frame.
     inline void leaveExitFrame(size_t extraFrame = 0);
 
   private:
-    // Save the top of the stack into JitActivation::exitFP of the current
-    // thread, which should be the location of the latest exit frame.
+    // Save the top of the stack into JitActivation::packedExitFP of the
+    // current thread, which should be the location of the latest exit frame.
     void linkExitFrame(Register cxreg, Register scratch);
 
     // Patch the value of PushStubCode with the pointer to the finalized code.
     void linkSelfReference(JitCode* code);
 
     // If the JitCode that created this assembler needs to transition into the VM,
     // we want to store the JitCode on the stack in order to mark it during a GC.
     // This is a reference to a patch location where the JitCode* will be written.
@@ -1498,19 +1498,16 @@ class MacroAssembler : public MacroAssem
     void wasmCallBuiltinInstanceMethod(const wasm::CallSiteDesc& desc, const ABIArg& instanceArg,
                                        wasm::SymbolicAddress builtin);
 
     // Emit the out-of-line trap code to which trapping jumps/branches are
     // bound. This should be called once per function after all other codegen,
     // including "normal" OutOfLineCode.
     void wasmEmitTrapOutOfLineCode();
 
-    // Assert invariants that should be true within any non-exit-stub wasm code.
-    void wasmAssertNonExitInvariants(Register activation);
-
     // Perform a stack-overflow test, branching to the given Label on overflow.
     void wasmEmitStackCheck(Register sp, Register scratch, Label* onOverflow);
 
   public:
     // ========================================================================
     // Clamping functions.
 
     inline void clampIntToUint8(Register reg) PER_SHARED_ARCH;
@@ -2321,17 +2318,17 @@ static inline MIRType
 ToMIRType(MIRType t)
 {
     return t;
 }
 
 static inline MIRType
 ToMIRType(ABIArgType argType)
 {
-    switch (argType & ArgType_Mask) {
+    switch (argType) {
       case ArgType_General: return MIRType::Int32;
       case ArgType_Double:  return MIRType::Double;
       case ArgType_Float32: return MIRType::Float32;
       case ArgType_Int64:   return MIRType::Int64;
       default: break;
     }
     MOZ_CRASH("unexpected argType");
 }
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -817,18 +817,18 @@ DebugPrologue(JSContext* cx, BaselineFra
         MOZ_CRASH("bad Debugger::onEnterFrame status");
     }
 }
 
 bool
 DebugEpilogueOnBaselineReturn(JSContext* cx, BaselineFrame* frame, jsbytecode* pc)
 {
     if (!DebugEpilogue(cx, frame, pc, true)) {
-        // DebugEpilogue popped the frame by updating exitFP, so run the stop
-        // event here before we enter the exception handler.
+        // DebugEpilogue popped the frame by updating packedExitFP, so run the
+        // stop event here before we enter the exception handler.
         TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
         TraceLogStopEvent(logger, TraceLogger_Baseline);
         TraceLogStopEvent(logger, TraceLogger_Scripts);
         return false;
     }
 
     return true;
 }
@@ -844,18 +844,18 @@ DebugEpilogue(JSContext* cx, BaselineFra
     // Unwind to the outermost environment and set pc to the end of the
     // script, regardless of error.
     EnvironmentIter ei(cx, frame, pc);
     UnwindAllEnvironmentsInFrame(cx, ei);
     JSScript* script = frame->script();
     frame->setOverridePc(script->lastPC());
 
     if (!ok) {
-        // Pop this frame by updating exitFP, so that the exception handling
-        // code will start at the previous frame.
+        // Pop this frame by updating packedExitFP, so that the exception
+        // handling code will start at the previous frame.
         JitFrameLayout* prefix = frame->framePrefix();
         EnsureBareExitFrame(cx, prefix);
         return false;
     }
 
     // Clear the override pc. This is not necessary for correctness: the frame
     // will return immediately, but this simplifies the check we emit in debug
     // builds after each callVM, to ensure this flag is not set.
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -104,22 +104,30 @@ class ABIArgGenerator
     }
     ABIArg next(MIRType argType);
     ABIArg& current() { return current_; }
     uint32_t stackBytesConsumedSoFar() const { return stackOffset_; }
 };
 
 bool IsUnaligned(const wasm::MemoryAccessDesc& access);
 
+// These registers may be volatile or nonvolatile.
 static constexpr Register ABINonArgReg0 = r4;
 static constexpr Register ABINonArgReg1 = r5;
 static constexpr Register ABINonArgReg2 = r6;
+
+// These registers may be volatile or nonvolatile.
+// Note: these three registers are all guaranteed to be different
 static constexpr Register ABINonArgReturnReg0 = r4;
 static constexpr Register ABINonArgReturnReg1 = r5;
 
+// This register is guaranteed to be clobberable during the prologue of an ABI
+// call which must preserve both ABI argument and non-volatile registers.
+static constexpr Register NativeABIPrologueClobberable = ScratchRegister;
+
 // TLS pointer argument register for WebAssembly functions. This must not alias
 // any other register used for passing function arguments or return values.
 // Preserved by WebAssembly functions.
 static constexpr Register WasmTlsReg = r9;
 
 // Registers used for wasm table calls. These registers must be disjoint
 // from the ABI argument registers, WasmTlsReg and each other.
 static constexpr Register WasmTableCallScratchReg = ABINonArgReg0;
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -3600,29 +3600,31 @@ MacroAssemblerARMCompat::handleFailureWi
     asMasm().passABIArg(r0);
     asMasm().callWithABI(handler, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     Label entryFrame;
     Label catch_;
     Label finally;
     Label return_;
     Label bailout;
+    Label wasm;
 
     {
         ScratchRegisterScope scratch(asMasm());
         ma_ldr(Address(sp, offsetof(ResumeFromException, kind)), r0, scratch);
     }
 
     asMasm().branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_ENTRY_FRAME),
                       &entryFrame);
     asMasm().branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_CATCH), &catch_);
     asMasm().branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_FINALLY), &finally);
     asMasm().branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_FORCED_RETURN),
                       &return_);
     asMasm().branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_BAILOUT), &bailout);
+    asMasm().branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_WASM), &wasm);
 
     breakpoint(); // Invalid kind.
 
     // No exception handler. Load the error value, load the new stack pointer
     // and return from the entry frame.
     bind(&entryFrame);
     asMasm().moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
     {
@@ -3692,16 +3694,27 @@ MacroAssemblerARMCompat::handleFailureWi
     bind(&bailout);
     {
         ScratchRegisterScope scratch(asMasm());
         ma_ldr(Address(sp, offsetof(ResumeFromException, bailoutInfo)), r2, scratch);
         ma_mov(Imm32(BAILOUT_RETURN_OK), r0);
         ma_ldr(Address(sp, offsetof(ResumeFromException, target)), r1, scratch);
     }
     jump(r1);
+
+    // If we are throwing and the innermost frame was a wasm frame, reset SP and
+    // FP; SP is pointing to the unwound return address to the wasm entry, so
+    // we can just ret().
+    bind(&wasm);
+    {
+        ScratchRegisterScope scratch(asMasm());
+        ma_ldr(Address(sp, offsetof(ResumeFromException, framePointer)), r11, scratch);
+        ma_ldr(Address(sp, offsetof(ResumeFromException, stackPointer)), sp, scratch);
+    }
+    as_dtr(IsLoad, 32, PostIndex, pc, DTRAddr(sp, DtrOffImm(4)));
 }
 
 Assembler::Condition
 MacroAssemblerARMCompat::testStringTruthy(bool truthy, const ValueOperand& value)
 {
     Register string = value.payloadReg();
     ScratchRegisterScope scratch(asMasm());
     SecondScratchRegisterScope scratch2(asMasm());
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -1542,24 +1542,24 @@ Simulator::exclusiveMonitorGetAndClear(b
 
 void
 Simulator::exclusiveMonitorClear()
 {
     exclusiveMonitorHeld_ = false;
 }
 
 void
-Simulator::startInterrupt(WasmActivation* activation)
+Simulator::startWasmInterrupt(JitActivation* activation)
 {
     JS::ProfilingFrameIterator::RegisterState state;
     state.pc = (void*) get_pc();
     state.fp = (void*) get_register(fp);
     state.sp = (void*) get_register(sp);
     state.lr = (void*) get_register(lr);
-    activation->startInterrupt(state);
+    activation->startWasmInterrupt(state);
 }
 
 // The signal handler only redirects the PC to the interrupt stub when the PC is
 // in function code. However, this guard is racy for the ARM simulator since the
 // signal handler samples PC in the middle of simulating an instruction and thus
 // the current PC may have advanced once since the signal handler's guard. So we
 // re-check here.
 void
@@ -1571,51 +1571,48 @@ Simulator::handleWasmInterrupt()
     const wasm::CodeSegment* cs = nullptr;
     if (!wasm::InInterruptibleCode(cx_, pc, &cs))
         return;
 
     // fp can be null during the prologue/epilogue of the entry function.
     if (!fp)
         return;
 
-    startInterrupt(wasm::ActivationIfInnermost(cx_));
+    startWasmInterrupt(cx_->activation()->asJit());
     set_pc(int32_t(cs->interruptCode()));
 }
 
 // WebAssembly memories contain an extra region of guard pages (see
 // WasmArrayRawBuffer comment). The guard pages catch out-of-bounds accesses
 // using a signal handler that redirects PC to a stub that safely reports an
 // error. However, if the handler is hit by the simulator, the PC is in C++ code
 // and cannot be redirected. Therefore, we must avoid hitting the handler by
 // redirecting in the simulator before the real handler would have been hit.
 bool
 Simulator::handleWasmFault(int32_t addr, unsigned numBytes)
 {
-    WasmActivation* act = wasm::ActivationIfInnermost(cx_);
-    if (!act)
+    if (!cx_->activation() || !cx_->activation()->isJit())
         return false;
+    JitActivation* act = cx_->activation()->asJit();
 
     void* pc = reinterpret_cast<void*>(get_pc());
     uint8_t* fp = reinterpret_cast<uint8_t*>(get_register(r11));
 
-    // Cache the wasm::Code to avoid lookup on every load/store.
-    if (!wasm_code_ || !wasm_code_->containsCodePC(pc))
-        wasm_code_ = act->compartment()->wasm.lookupCode(pc);
-    if (!wasm_code_)
+    const wasm::CodeSegment* segment = act->compartment()->wasm.lookupCodeSegment(pc);
+    if (!segment)
         return false;
 
-    wasm::Instance* instance = wasm::LookupFaultingInstance(*wasm_code_, pc, fp);
+    wasm::Instance* instance = wasm::LookupFaultingInstance(*segment, pc, fp);
     if (!instance || !instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
         return false;
 
-    const wasm::CodeSegment* segment;
-    const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc, &segment);
+    const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
     if (!memoryAccess) {
-        startInterrupt(act);
-        if (!instance->code().containsCodePC(pc, &segment))
+        startWasmInterrupt(act);
+        if (!instance->code().containsCodePC(pc))
             MOZ_CRASH("Cannot map PC to trap handler");
         set_pc(int32_t(segment->outOfBoundsCode()));
         return true;
     }
 
     MOZ_ASSERT(memoryAccess->hasTrapOutOfLineCode());
     set_pc(int32_t(memoryAccess->trapOutOfLineCode(segment->base())));
     return true;
--- a/js/src/jit/arm/Simulator-arm.h
+++ b/js/src/jit/arm/Simulator-arm.h
@@ -36,21 +36,19 @@
 #include "jit/arm/Architecture-arm.h"
 #include "jit/arm/disasm/Disasm-arm.h"
 #include "jit/IonTypes.h"
 #include "threading/Thread.h"
 #include "vm/MutexIDs.h"
 #include "wasm/WasmCode.h"
 
 namespace js {
-
-class WasmActivation;
-
 namespace jit {
 
+class JitActivation;
 class Simulator;
 class Redirection;
 class CachePage;
 class AutoLockSimulator;
 
 // When the SingleStepCallback is called, the simulator is about to execute
 // sim->get_pc() and the current machine state represents the completed
 // execution of the previous pc.
@@ -288,17 +286,17 @@ class Simulator
     inline bool isEnabledStop(uint32_t bkpt_code);
     inline void enableStop(uint32_t bkpt_code);
     inline void disableStop(uint32_t bkpt_code);
     inline void increaseStopCounter(uint32_t bkpt_code);
     void printStopInfo(uint32_t code);
 
     // Handle a wasm interrupt triggered by an async signal handler.
     void handleWasmInterrupt();
-    void startInterrupt(WasmActivation* act);
+    void startWasmInterrupt(JitActivation* act);
 
     // Handle any wasm faults, returning true if the fault was handled.
     bool handleWasmFault(int32_t addr, unsigned numBytes);
 
     // Read and write memory.
     inline uint8_t readBU(int32_t addr);
     inline int8_t readB(int32_t addr);
     inline void writeB(int32_t addr, uint8_t value);
@@ -421,17 +419,16 @@ class Simulator
     // Simulator support.
     char* stack_;
     uintptr_t stackLimit_;
     bool pc_modified_;
     int64_t icount_;
 
     // wasm async interrupt / fault support
     bool wasm_interrupt_;
-    wasm::SharedCode wasm_code_;
 
     // Debugger input.
     char* lastDebuggerInput_;
 
     // Registered breakpoints.
     SimInstruction* break_pc_;
     Instr break_instr_;
 
--- a/js/src/jit/arm/Trampoline-arm.cpp
+++ b/js/src/jit/arm/Trampoline-arm.cpp
@@ -200,17 +200,17 @@ JitRuntime::generateEnterJIT(JSContext* 
         // BIG FAT WARNING: this loads both r6 and r7.
         aasm->as_extdtr(IsLoad,  64, true, PostIndex, r6, EDtrAddr(r2, EDtrOffImm(8)));
         aasm->as_extdtr(IsStore, 64, true, PostIndex, r6, EDtrAddr(r4, EDtrOffImm(8)));
         aasm->as_b(&header, Assembler::NonZero);
         masm.bind(&footer);
     }
 
     masm.ma_sub(r8, sp, r8);
-    masm.makeFrameDescriptor(r8, JitFrame_Entry, JitFrameLayout::Size());
+    masm.makeFrameDescriptor(r8, JitFrame_CppToJSJit, JitFrameLayout::Size());
 
     masm.startDataTransferM(IsStore, sp, IB, NoWriteBack);
                            // [sp]    = return address (written later)
     masm.transferReg(r8);  // [sp',4] = descriptor, argc*8+20
     masm.transferReg(r9);  // [sp',8]  = callee token
     masm.transferReg(r10); // [sp',12]  = actual arguments
     masm.finishDataTransfer();
 
@@ -1200,17 +1200,20 @@ JitRuntime::generateProfilerExitFrameTai
     Label handle_Entry;
     Label end;
 
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonJS), &handle_IonJS);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonICCall), &handle_IonICCall);
-    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry);
+    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_CppToJSJit), &handle_Entry);
+
+    // The WasmToJSJit is just another kind of entry.
+    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_WasmToJSJit), &handle_Entry);
 
     masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame.");
 
     //
     // JitFrame_IonJS
     //
     // Stack layout:
     //                  ...
@@ -1419,19 +1422,21 @@ JitRuntime::generateProfilerExitFrameTai
         //                       IonICCallFrameLayout::Size()
         masm.ma_add(scratch2, scratch3, scratch1);
         masm.addPtr(Imm32(IonICCallFrameLayout::Size()), scratch1);
         masm.storePtr(scratch1, lastProfilingFrame);
         masm.ret();
     }
 
     //
-    // JitFrame_Entry
+    // JitFrame_CppToJSJit / JitFrame_WasmJSToJit
     //
     // If at an entry frame, store null into both fields.
+    // A fast-path wasm->jit transition frame is an entry frame from the point
+    // of view of the JIT.
     //
     masm.bind(&handle_Entry);
     {
         masm.movePtr(ImmPtr(nullptr), scratch1);
         masm.storePtr(scratch1, lastProfilingCallSite);
         masm.storePtr(scratch1, lastProfilingFrame);
         masm.ret();
     }
--- a/js/src/jit/arm64/Assembler-arm64.h
+++ b/js/src/jit/arm64/Assembler-arm64.h
@@ -438,22 +438,30 @@ class ABIArgGenerator
 
   protected:
     unsigned intRegIndex_;
     unsigned floatRegIndex_;
     uint32_t stackOffset_;
     ABIArg current_;
 };
 
+// These registers may be volatile or nonvolatile.
 static constexpr Register ABINonArgReg0 = r8;
 static constexpr Register ABINonArgReg1 = r9;
 static constexpr Register ABINonArgReg2 = r10;
+
+// These registers may be volatile or nonvolatile.
+// Note: these three registers are all guaranteed to be different
 static constexpr Register ABINonArgReturnReg0 = r8;
 static constexpr Register ABINonArgReturnReg1 = r9;
 
+// This register is guaranteed to be clobberable during the prologue of an ABI
+// call which must preserve both ABI argument and non-volatile registers.
+static constexpr Register NativeABIPrologueClobberable = lr;
+
 // TLS pointer argument register for WebAssembly functions. This must not alias
 // any other register used for passing function arguments or return values.
 // Preserved by WebAssembly functions.
 static constexpr Register WasmTlsReg { Registers::x17 };
 
 // Registers used for wasm table calls. These registers must be disjoint
 // from the ABI argument registers, WasmTlsReg and each other.
 static constexpr Register WasmTableCallScratchReg = ABINonArgReg0;
--- a/js/src/jit/arm64/Trampoline-arm64.cpp
+++ b/js/src/jit/arm64/Trampoline-arm64.cpp
@@ -158,17 +158,17 @@ JitRuntime::generateEnterJIT(JSContext* 
     masm.unboxInt32(Address(reg_vp, 0x0), ip0);
     masm.push(ip0, reg_callee);
     masm.checkStackAlignment();
 
     // Calculate the number of bytes pushed so far.
     masm.subStackPtrFrom(r19);
 
     // Push the frameDescriptor.
-    masm.makeFrameDescriptor(r19, JitFrame_Entry, JitFrameLayout::Size());
+    masm.makeFrameDescriptor(r19, JitFrame_CppToJSJit, JitFrameLayout::Size());
     masm.Push(r19);
 
     Label osrReturnPoint;
     if (type == EnterJitBaseline) {
         // Check for OSR.
         Label notOsr;
         masm.branchTestPtr(Assembler::Zero, OsrFrameReg, OsrFrameReg, &notOsr);
 
@@ -984,17 +984,20 @@ JitRuntime::generateProfilerExitFrameTai
     Label handle_Entry;
     Label end;
 
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonJS), &handle_IonJS);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonICCall), &handle_IonICCall);
-    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry);
+    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_CppToJSJit), &handle_Entry);
+
+    // The WasmToJSJit is just another kind of entry.
+    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_WasmToJSJit), &handle_Entry);
 
     masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame.");
 
     //
     // JitFrame_IonJS
     //
     // Stack layout:
     //                  ...
@@ -1208,19 +1211,21 @@ JitRuntime::generateProfilerExitFrameTai
         //                       IonICCallFrameLayout::Size()
         masm.addPtr(scratch2, scratch3, scratch1);
         masm.addPtr(Imm32(IonICCallFrameLayout::Size()), scratch1);
         masm.storePtr(scratch1, lastProfilingFrame);
         masm.ret();
     }
 
     //
-    // JitFrame_Entry
+    // JitFrame_CppToJSJit / JitFrame_WasmJSToJit
     //
     // If at an entry frame, store null into both fields.
+    // A fast-path wasm->jit transition frame is an entry frame from the point
+    // of view of the JIT.
     //
     masm.bind(&handle_Entry);
     {
         masm.movePtr(ImmPtr(nullptr), scratch1);
         masm.storePtr(scratch1, lastProfilingCallSite);
         masm.storePtr(scratch1, lastProfilingFrame);
         masm.ret();
     }
--- a/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
@@ -252,17 +252,17 @@ void Simulator::handle_wasm_interrupt() 
   if (!fp)
       return;
 
   JS::ProfilingFrameIterator::RegisterState state;
   state.pc = pc;
   state.fp = fp;
   state.lr = (uint8_t*) xreg(30);
   state.sp = (uint8_t*) xreg(31);
-  js::wasm::ActivationIfInnermost(cx_)->startInterrupt(state);
+  cx_->activation_->asJit()->startWasmInterrupt(state);
 
   set_pc((Instruction*)cs->interruptCode());
 }
 
 
 int64_t Simulator::call(uint8_t* entry, int argument_count, ...) {
   va_list parameters;
   va_start(parameters, argument_count);
--- a/js/src/jit/mips32/Assembler-mips32.h
+++ b/js/src/jit/mips32/Assembler-mips32.h
@@ -41,22 +41,30 @@ class ABIArgGenerator
     uint32_t stackBytesConsumedSoFar() const {
         if (usedArgSlots_ <= 4)
             return ShadowStackSpace;
 
         return usedArgSlots_ * sizeof(intptr_t);
     }
 };
 
+// These registers may be volatile or nonvolatile.
 static constexpr Register ABINonArgReg0 = t0;
 static constexpr Register ABINonArgReg1 = t1;
 static constexpr Register ABINonArgReg2 = t2;
+
+// These registers may be volatile or nonvolatile.
+// Note: these three registers are all guaranteed to be different
 static constexpr Register ABINonArgReturnReg0 = t0;
 static constexpr Register ABINonArgReturnReg1 = t1;
 
+// This register is guaranteed to be clobberable during the prologue of an ABI
+// call which must preserve both ABI argument and non-volatile registers.
+static constexpr Register NativeABIPrologueClobberable = t0;
+
 // TLS pointer argument register for WebAssembly functions. This must not alias
 // any other register used for passing function arguments or return values.
 // Preserved by WebAssembly functions.
 static constexpr Register WasmTlsReg = s5;
 
 // Registers used for asm.js/wasm table calls. These registers must be disjoint
 // from the ABI argument registers, WasmTlsReg and each other.
 static constexpr Register WasmTableCallScratchReg = ABINonArgReg0;
--- a/js/src/jit/mips32/Simulator-mips32.cpp
+++ b/js/src/jit/mips32/Simulator-mips32.cpp
@@ -1329,18 +1329,18 @@ class Redirection
     // sim's lock must already be held.
     Redirection(void* nativeFunction, ABIFunctionType type)
       : nativeFunction_(nativeFunction),
         swiInstruction_(kCallRedirInstr),
         type_(type),
         next_(nullptr)
     {
         next_ = SimulatorProcess::redirection();
-	if (!SimulatorProcess::ICacheCheckingDisableCount) {
-	    FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(),
+        if (!SimulatorProcess::ICacheCheckingDisableCount) {
+            FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(),
                               SimInstruction::kInstrSize);
         }
         SimulatorProcess::setRedirection(this);
     }
 
   public:
     void* addressOfSwiInstruction() { return &swiInstruction_; }
     void* nativeFunction() const { return nativeFunction_; }
@@ -1614,41 +1614,40 @@ Simulator::has_bad_pc() const
 // Raw access to the PC register without the special adjustment when reading.
 int32_t
 Simulator::get_pc() const
 {
     return registers_[pc];
 }
 
 void
-Simulator::startInterrupt(WasmActivation* activation)
+Simulator::startInterrupt(JitActivation* activation)
 {
     JS::ProfilingFrameIterator::RegisterState state;
     state.pc = (void*) get_pc();
     state.fp = (void*) getRegister(fp);
     state.sp = (void*) getRegister(sp);
     state.lr = (void*) getRegister(ra);
-    activation->startInterrupt(state);
+    activation->startWasmInterrupt(state);
 }
 
 // The signal handler only redirects the PC to the interrupt stub when the PC is
 // in function code. However, this guard is racy for the simulator since the
 // signal handler samples PC in the middle of simulating an instruction and thus
 // the current PC may have advanced once since the signal handler's guard. So we
 // re-check here.
 void
 Simulator::handleWasmInterrupt()
 {
     void* pc = (void*)get_pc();
     void* fp = (void*)getRegister(Register::fp);
 
-    WasmActivation* activation = wasm::ActivationIfInnermost(TlsContext.get());
-    const wasm::CodeSegment* segment;
-    const wasm::Code* code = activation->compartment()->wasm.lookupCode(pc, &segment);
-    if (!code || !segment->containsFunctionPC(pc))
+    JitActivation* activation = TlsContext.get()->activation()->asJit();
+    const wasm::CodeSegment* segment = activation->compartment()->wasm.lookupCodeSegment(pc);
+    if (!segment || !segment->containsCodePC(pc))
         return;
 
     // fp can be null during the prologue/epilogue of the entry function.
     if (!fp)
         return;
 
     startInterrupt(activation);
     set_pc(int32_t(segment->interruptCode()));
@@ -1660,40 +1659,37 @@ Simulator::handleWasmInterrupt()
 // using a signal handler that redirects PC to a stub that safely reports an
 // error. However, if the handler is hit by the simulator, the PC is in C++ code
 // and cannot be redirected. Therefore, we must avoid hitting the handler by
 // redirecting in the simulator before the real handler would have been hit.
 bool
 Simulator::handleWasmFault(int32_t addr, unsigned numBytes)
 {
     JSContext* cx = TlsContext.get();
-    WasmActivation* act = wasm::ActivationIfInnermost(cx);
-    if (!act)
+    if (!cx->activation() || !cx->activation()->isJit())
         return false;
+    JitActivation* act = cx->activation()->asJit();
 
     void* pc = reinterpret_cast<void*>(get_pc());
     uint8_t* fp = reinterpret_cast<uint8_t*>(getRegister(Register::fp));
 
-    // Cache the wasm::Code to avoid lookup on every load/store.
-    if (!wasm_code_ || !wasm_code_->containsCodePC(pc))
-        wasm_code_ = act->compartment()->wasm.lookupCode(pc);
-    if (!wasm_code_)
+    const wasm::CodeSegment* segment = act->compartment()->wasm.lookupCodeSegment(pc);
+    if (!segment)
         return false;
 
-    wasm::Instance* instance = wasm::LookupFaultingInstance(*wasm_code_, pc, fp);
+    wasm::Instance* instance = wasm::LookupFaultingInstance(*segment, pc, fp);
     if (!instance || !instance->memoryAccessInGuardRegion((uint8_t*)addr, numBytes))
         return false;
 
     LLBit_ = false;
 
-    const wasm::CodeSegment* segment;
-    const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc, &segment);
+    const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
     if (!memoryAccess) {
         startInterrupt(act);
-        if (!instance->code().containsCodePC(pc, &segment))
+        if (!instance->code().containsCodePC(pc))
             MOZ_CRASH("Cannot map PC to trap handler");
         set_pc(int32_t(segment->outOfBoundsCode()));
         return true;
     }
 
     MOZ_ASSERT(memoryAccess->hasTrapOutOfLineCode());
     set_pc(int32_t(memoryAccess->trapOutOfLineCode(segment->base())));
     return true;
--- a/js/src/jit/mips32/Simulator-mips32.h
+++ b/js/src/jit/mips32/Simulator-mips32.h
@@ -35,19 +35,19 @@
 
 #include "jit/IonTypes.h"
 #include "threading/Thread.h"
 #include "vm/MutexIDs.h"
 #include "wasm/WasmCode.h"
 
 namespace js {
 
-class WasmActivation;
+namespace jit {
 
-namespace jit {
+class JitActivation;
 
 class Simulator;
 class Redirection;
 class CachePage;
 class AutoLockSimulator;
 
 const intptr_t kPointerAlignment = 4;
 const intptr_t kPointerAlignmentMask = kPointerAlignment - 1;
@@ -291,17 +291,17 @@ class Simulator {
     bool isEnabledStop(uint32_t code);
     void enableStop(uint32_t code);
     void disableStop(uint32_t code);
     void increaseStopCounter(uint32_t code);
     void printStopInfo(uint32_t code);
 
     // Handle a wasm interrupt triggered by an async signal handler.
     void handleWasmInterrupt();
-    void startInterrupt(WasmActivation* act);
+    void startInterrupt(JitActivation* act);
 
     // Handle any wasm faults, returning true if the fault was handled.
     bool handleWasmFault(int32_t addr, unsigned numBytes);
 
     // Executes one instruction.
     void instructionDecode(SimInstruction* instr);
     // Execute one instruction placed in a branch delay slot.
     void branchDelayInstructionDecode(SimInstruction* instr);
@@ -351,17 +351,16 @@ class Simulator {
     char* stack_;
     uintptr_t stackLimit_;
     bool pc_modified_;
     int icount_;
     int break_count_;
 
     // wasm async interrupt / fault support
     bool wasm_interrupt_;
-    wasm::SharedCode wasm_code_;
 
     // Debugger input.
     char* lastDebuggerInput_;
 
     // Registered breakpoints.
     SimInstruction* break_pc_;
     Instr break_instr_;
 
--- a/js/src/jit/mips32/Trampoline-mips32.cpp
+++ b/js/src/jit/mips32/Trampoline-mips32.cpp
@@ -195,17 +195,17 @@ JitRuntime::generateEnterJIT(JSContext* 
     }
     masm.bind(&footer);
 
     masm.subPtr(Imm32(2 * sizeof(uintptr_t)), StackPointer);
     masm.storePtr(s3, Address(StackPointer, sizeof(uintptr_t))); // actual arguments
     masm.storePtr(s2, Address(StackPointer, 0)); // callee token
 
     masm.subPtr(StackPointer, s4);
-    masm.makeFrameDescriptor(s4, JitFrame_Entry, JitFrameLayout::Size());
+    masm.makeFrameDescriptor(s4, JitFrame_CppToJSJit, JitFrameLayout::Size());
     masm.push(s4); // descriptor
 
     CodeLabel returnLabel;
     CodeLabel oomReturnLabel;
     if (type == EnterJitBaseline) {
         // Handle OSR.
         AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
         regs.take(OsrFrameReg);
@@ -1176,17 +1176,20 @@ JitRuntime::generateProfilerExitFrameTai
     Label handle_Entry;
     Label end;
 
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonJS), &handle_IonJS);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonICCall), &handle_IonICCall);
-    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry);
+    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_CppToJSJit), &handle_Entry);
+
+    // The WasmToJSJit is just another kind of entry.
+    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_WasmToJSJit), &handle_Entry);
 
     masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame.");
 
     //
     // JitFrame_IonJS
     //
     // Stack layout:
     //                  ...
@@ -1395,19 +1398,21 @@ JitRuntime::generateProfilerExitFrameTai
         //                       IonICCallFrameLayout::Size()
         masm.as_addu(scratch1, scratch2, scratch3);
         masm.addPtr(Imm32(IonICCallFrameLayout::Size()), scratch1);
         masm.storePtr(scratch1, lastProfilingFrame);
         masm.ret();
     }
 
     //
-    // JitFrame_Entry
+    // JitFrame_CppToJSJit / JitFrame_WasmJSToJit
     //
     // If at an entry frame, store null into both fields.
+    // A fast-path wasm->jit transition frame is an entry frame from the point
+    // of view of the JIT.
     //
     masm.bind(&handle_Entry);
     {
         masm.movePtr(ImmPtr(nullptr), scratch1);
         masm.storePtr(scratch1, lastProfilingCallSite);
         masm.storePtr(scratch1, lastProfilingFrame);
         masm.ret();
     }
--- a/js/src/jit/mips64/Assembler-mips64.h
+++ b/js/src/jit/mips64/Assembler-mips64.h
@@ -34,22 +34,30 @@ class ABIArgGenerator
     uint32_t stackBytesConsumedSoFar() const {
         if (usedArgSlots_ <= 8)
             return 0;
 
         return (usedArgSlots_ - 8) * sizeof(int64_t);
     }
 };
 
+// These registers may be volatile or nonvolatile.
 static constexpr Register ABINonArgReg0 = t0;
 static constexpr Register ABINonArgReg1 = t1;
 static constexpr Register ABINonArgReg2 = t2;
+
+// These registers may be volatile or nonvolatile.
+// Note: these three registers are all guaranteed to be different
 static constexpr Register ABINonArgReturnReg0 = t0;
 static constexpr Register ABINonArgReturnReg1 = t1;
 
+// This register is guaranteed to be clobberable during the prologue of an ABI
+// call which must preserve both ABI argument and non-volatile registers.
+static constexpr Register NativeABIPrologueClobberable = t0;
+
 // TLS pointer argument register for WebAssembly functions. This must not alias
 // any other register used for passing function arguments or return values.
 // Preserved by WebAssembly functions.
 static constexpr Register WasmTlsReg = s5;
 
 // Registers used for wasm table calls. These registers must be disjoint
 // from the ABI argument registers, WasmTlsReg and each other.
 static constexpr Register WasmTableCallScratchReg = ABINonArgReg0;
--- a/js/src/jit/mips64/Simulator-mips64.cpp
+++ b/js/src/jit/mips64/Simulator-mips64.cpp
@@ -1608,17 +1608,17 @@ Simulator::has_bad_pc() const
 // Raw access to the PC register without the special adjustment when reading.
 int64_t
 Simulator::get_pc() const
 {
     return registers_[pc];
 }
 
 void
-Simulator::startInterrupt(WasmActivation* activation)
+Simulator::startInterrupt(JitActivation* activation)
 {
     MOZ_CRASH("NIY");
 }
 
 void
 Simulator::handleWasmInterrupt()
 {
     MOZ_CRASH("NIY");
--- a/js/src/jit/mips64/Simulator-mips64.h
+++ b/js/src/jit/mips64/Simulator-mips64.h
@@ -35,19 +35,19 @@
 #include "mozilla/Atomics.h"
 
 #include "jit/IonTypes.h"
 #include "threading/Thread.h"
 #include "vm/MutexIDs.h"
 
 namespace js {
 
-class WasmActivation;
+namespace jit {
 
-namespace jit {
+class JitActivation;
 
 class Simulator;
 class Redirection;
 class CachePage;
 class AutoLockSimulator;
 
 // When the SingleStepCallback is called, the simulator is about to execute
 // sim->get_pc() and the current machine state represents the completed
@@ -300,17 +300,17 @@ class Simulator {
     bool isEnabledStop(uint32_t code);
     void enableStop(uint32_t code);
     void disableStop(uint32_t code);
     void increaseStopCounter(uint32_t code);
     void printStopInfo(uint32_t code);
 
     // Handle a wasm interrupt triggered by an async signal handler.
     void handleWasmInterrupt();
-    void startInterrupt(WasmActivation* act);
+    void startInterrupt(JitActivation* act);
 
     // Executes one instruction.
     void instructionDecode(SimInstruction* instr);
     // Execute one instruction placed in a branch delay slot.
     void branchDelayInstructionDecode(SimInstruction* instr);
 
   public:
     static int64_t StopSimAt;
--- a/js/src/jit/mips64/Trampoline-mips64.cpp
+++ b/js/src/jit/mips64/Trampoline-mips64.cpp
@@ -217,17 +217,17 @@ JitRuntime::generateEnterJIT(JSContext* 
     }
     masm.bind(&footer);
 
     masm.subPtr(Imm32(2 * sizeof(uintptr_t)), StackPointer);
     masm.storePtr(s3, Address(StackPointer, sizeof(uintptr_t))); // actual arguments
     masm.storePtr(reg_token, Address(StackPointer, 0)); // callee token
 
     masm.subPtr(StackPointer, s4);
-    masm.makeFrameDescriptor(s4, JitFrame_Entry, JitFrameLayout::Size());
+    masm.makeFrameDescriptor(s4, JitFrame_CppToJSJit, JitFrameLayout::Size());
     masm.push(s4); // descriptor
 
     CodeLabel returnLabel;
     CodeLabel oomReturnLabel;
     if (type == EnterJitBaseline) {
         // Handle OSR.
         AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
         regs.take(OsrFrameReg);
@@ -1121,17 +1121,20 @@ JitRuntime::generateProfilerExitFrameTai
     Label handle_Entry;
     Label end;
 
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonJS), &handle_IonJS);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonICCall), &handle_IonICCall);
-    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry);
+    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_CppToJSJit), &handle_Entry);
+
+    // The WasmToJSJit is just another kind of entry.
+    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_WasmToJSJit), &handle_Entry);
 
     masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame.");
 
     //
     // JitFrame_IonJS
     //
     // Stack layout:
     //                  ...
@@ -1340,19 +1343,21 @@ JitRuntime::generateProfilerExitFrameTai
         //                       IonICCallFrameLayout::Size()
         masm.as_daddu(scratch1, scratch2, scratch3);
         masm.addPtr(Imm32(IonICCallFrameLayout::Size()), scratch1);
         masm.storePtr(scratch1, lastProfilingFrame);
         masm.ret();
     }
 
     //
-    // JitFrame_Entry
+    // JitFrame_CppToJSJit / JitFrame_WasmJSToJit
     //
     // If at an entry frame, store null into both fields.
+    // A fast-path wasm->jit transition frame is an entry frame from the point
+    // of view of the JIT.
     //
     masm.bind(&handle_Entry);
     {
         masm.movePtr(ImmPtr(nullptr), scratch1);
         masm.storePtr(scratch1, lastProfilingCallSite);
         masm.storePtr(scratch1, lastProfilingFrame);
         masm.ret();
     }
--- a/js/src/jit/x64/Assembler-x64.h
+++ b/js/src/jit/x64/Assembler-x64.h
@@ -185,26 +185,32 @@ class ABIArgGenerator
 
   public:
     ABIArgGenerator();
     ABIArg next(MIRType argType);
     ABIArg& current() { return current_; }
     uint32_t stackBytesConsumedSoFar() const { return stackOffset_; }
 };
 
+// These registers may be volatile or nonvolatile.
 // Avoid r11, which is the MacroAssembler's ScratchReg.
 static constexpr Register ABINonArgReg0 = rax;
 static constexpr Register ABINonArgReg1 = rbx;
 static constexpr Register ABINonArgReg2 = r10;
 
+// These registers may be volatile or nonvolatile.
 // Note: these three registers are all guaranteed to be different
 static constexpr Register ABINonArgReturnReg0 = r10;
 static constexpr Register ABINonArgReturnReg1 = r12;
 static constexpr Register ABINonVolatileReg = r13;
 
+// This register is guaranteed to be clobberable during the prologue of an ABI
+// call which must preserve both ABI argument and non-volatile registers.
+static constexpr Register NativeABIPrologueClobberable = rax;
+
 // TLS pointer argument register for WebAssembly functions. This must not alias
 // any other register used for passing function arguments or return values.
 // Preserved by WebAssembly functions.
 static constexpr Register WasmTlsReg = r14;
 
 // Registers used for asm.js/wasm table calls. These registers must be disjoint
 // from the ABI argument registers, WasmTlsReg and each other.
 static constexpr Register WasmTableCallScratchReg = ABINonArgReg0;
--- a/js/src/jit/x64/MacroAssembler-x64.cpp
+++ b/js/src/jit/x64/MacroAssembler-x64.cpp
@@ -309,23 +309,25 @@ MacroAssemblerX64::handleFailureWithHand
     asMasm().passABIArg(rax);
     asMasm().callWithABI(handler, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     Label entryFrame;
     Label catch_;
     Label finally;
     Label return_;
     Label bailout;
+    Label wasm;
 
     load32(Address(rsp, offsetof(ResumeFromException, kind)), rax);
     asMasm().branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_ENTRY_FRAME), &entryFrame);
     asMasm().branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_CATCH), &catch_);
     asMasm().branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_FINALLY), &finally);
     asMasm().branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_FORCED_RETURN), &return_);
     asMasm().branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_BAILOUT), &bailout);
+    asMasm().branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_WASM), &wasm);
 
     breakpoint(); // Invalid kind.
 
     // No exception handler. Load the error value, load the new stack pointer
     // and return from the entry frame.
     bind(&entryFrame);
     asMasm().moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
     loadPtr(Address(rsp, offsetof(ResumeFromException, stackPointer)), rsp);
@@ -375,16 +377,24 @@ MacroAssemblerX64::handleFailureWithHand
     ret();
 
     // If we are bailing out to baseline to handle an exception, jump to
     // the bailout tail stub.
     bind(&bailout);
     loadPtr(Address(esp, offsetof(ResumeFromException, bailoutInfo)), r9);
     mov(ImmWord(BAILOUT_RETURN_OK), rax);
     jmp(Operand(rsp, offsetof(ResumeFromException, target)));
+
+    // If we are throwing and the innermost frame was a wasm frame, reset SP and
+    // FP; SP is pointing to the unwound return address to the wasm entry, so
+    // we can just ret().
+    bind(&wasm);
+    loadPtr(Address(rsp, offsetof(ResumeFromException, framePointer)), rbp);
+    loadPtr(Address(rsp, offsetof(ResumeFromException, stackPointer)), rsp);
+    masm.ret();
 }
 
 void
 MacroAssemblerX64::profilerEnterFrame(Register framePtr, Register scratch)
 {
     asMasm().loadJSContext(scratch);
     loadPtr(Address(scratch, offsetof(JSContext, profilingActivation_)), scratch);
     storePtr(framePtr, Address(scratch, JitActivation::offsetOfLastProfilingFrame()));
--- a/js/src/jit/x64/Trampoline-x64.cpp
+++ b/js/src/jit/x64/Trampoline-x64.cpp
@@ -156,17 +156,17 @@ JitRuntime::generateEnterJIT(JSContext* 
     masm.push(token);
 
     /*****************************************************************
     Push the number of bytes we've pushed so far on the stack and call
     *****************************************************************/
     masm.subq(rsp, r14);
 
     // Create a frame descriptor.
-    masm.makeFrameDescriptor(r14, JitFrame_Entry, JitFrameLayout::Size());
+    masm.makeFrameDescriptor(r14, JitFrame_CppToJSJit, JitFrameLayout::Size());
     masm.push(r14);
 
     CodeLabel returnLabel;
     CodeLabel oomReturnLabel;
     if (type == EnterJitBaseline) {
         // Handle OSR.
         AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
         regs.takeUnchecked(OsrFrameReg);
@@ -1097,17 +1097,20 @@ JitRuntime::generateProfilerExitFrameTai
     Label handle_Entry;
     Label end;
 
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonJS), &handle_IonJS);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonICCall), &handle_IonICCall);
-    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry);
+    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_CppToJSJit), &handle_Entry);
+
+    // The WasmToJSJit is just another kind of entry.
+    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_WasmToJSJit), &handle_Entry);
 
     masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame.");
 
     //
     // JitFrame_IonJS
     //
     // Stack layout:
     //                  ...
@@ -1309,19 +1312,21 @@ JitRuntime::generateProfilerExitFrameTai
         // lastProfilingFrame := ICCallFrame + ICCallFrame-Descriptor.Size +
         //                       IonICCallFrameLayout::Size()
         masm.lea(Operand(scratch2, scratch3, TimesOne, IonICCallFrameLayout::Size()), scratch1);
         masm.storePtr(scratch1, lastProfilingFrame);
         masm.ret();
     }
 
     //
-    // JitFrame_Entry
+    // JitFrame_CppToJSJit / JitFrame_WasmJSToJit
     //
     // If at an entry frame, store null into both fields.
+    // A fast-path wasm->jit transition frame is an entry frame from the point
+    // of view of the JIT.
     //
     masm.bind(&handle_Entry);
     {
         masm.movePtr(ImmPtr(nullptr), scratch1);
         masm.storePtr(scratch1, lastProfilingCallSite);
         masm.storePtr(scratch1, lastProfilingFrame);
         masm.ret();
     }
--- a/js/src/jit/x86/Assembler-x86.h
+++ b/js/src/jit/x86/Assembler-x86.h
@@ -76,25 +76,31 @@ class ABIArgGenerator
   public:
     ABIArgGenerator();
     ABIArg next(MIRType argType);
     ABIArg& current() { return current_; }
     uint32_t stackBytesConsumedSoFar() const { return stackOffset_; }
 
 };
 
+// These registers may be volatile or nonvolatile.
 static constexpr Register ABINonArgReg0 = eax;
 static constexpr Register ABINonArgReg1 = ebx;
 static constexpr Register ABINonArgReg2 = ecx;
 
+// These registers may be volatile or nonvolatile.
 // Note: these three registers are all guaranteed to be different
 static constexpr Register ABINonArgReturnReg0 = ecx;
 static constexpr Register ABINonArgReturnReg1 = edx;
 static constexpr Register ABINonVolatileReg = ebx;
 
+// This register is guaranteed to be clobberable during the prologue of an ABI
+// call which must preserve both ABI argument and non-volatile registers.
+static constexpr Register NativeABIPrologueClobberable = eax;
+
 // TLS pointer argument register for WebAssembly functions. This must not alias
 // any other register used for passing function arguments or return values.
 // Preserved by WebAssembly functions.
 static constexpr Register WasmTlsReg = esi;
 
 // Registers used for asm.js/wasm table calls. These registers must be disjoint
 // from the ABI argument registers, WasmTlsReg and each other.
 static constexpr Register WasmTableCallScratchReg = ABINonArgReg0;
--- a/js/src/jit/x86/MacroAssembler-x86.cpp
+++ b/js/src/jit/x86/MacroAssembler-x86.cpp
@@ -208,25 +208,27 @@ MacroAssemblerX86::handleFailureWithHand
     asMasm().passABIArg(eax);
     asMasm().callWithABI(handler, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     Label entryFrame;
     Label catch_;
     Label finally;
     Label return_;
     Label bailout;
+    Label wasm;
 
     loadPtr(Address(esp, offsetof(ResumeFromException, kind)), eax);
     asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_ENTRY_FRAME),
                       &entryFrame);
     asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_CATCH), &catch_);
     asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_FINALLY), &finally);
     asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_FORCED_RETURN),
                       &return_);
     asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_BAILOUT), &bailout);
+    asMasm().branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_WASM), &wasm);
 
     breakpoint(); // Invalid kind.
 
     // No exception handler. Load the error value, load the new stack pointer
     // and return from the entry frame.
     bind(&entryFrame);
     asMasm().moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
     loadPtr(Address(esp, offsetof(ResumeFromException, stackPointer)), esp);
@@ -278,16 +280,24 @@ MacroAssemblerX86::handleFailureWithHand
     ret();
 
     // If we are bailing out to baseline to handle an exception, jump to
     // the bailout tail stub.
     bind(&bailout);
     loadPtr(Address(esp, offsetof(ResumeFromException, bailoutInfo)), ecx);
     movl(Imm32(BAILOUT_RETURN_OK), eax);
     jmp(Operand(esp, offsetof(ResumeFromException, target)));
+
+    // If we are throwing and the innermost frame was a wasm frame, reset SP and
+    // FP; SP is pointing to the unwound return address to the wasm entry, so
+    // we can just ret().
+    bind(&wasm);
+    loadPtr(Address(esp, offsetof(ResumeFromException, framePointer)), ebp);
+    loadPtr(Address(esp, offsetof(ResumeFromException, stackPointer)), esp);
+    masm.ret();
 }
 
 void
 MacroAssemblerX86::profilerEnterFrame(Register framePtr, Register scratch)
 {
     asMasm().loadJSContext(scratch);
     loadPtr(Address(scratch, offsetof(JSContext, profilingActivation_)), scratch);
     storePtr(framePtr, Address(scratch, JitActivation::offsetOfLastProfilingFrame()));
--- a/js/src/jit/x86/Trampoline-x86.cpp
+++ b/js/src/jit/x86/Trampoline-x86.cpp
@@ -152,17 +152,17 @@ JitRuntime::generateEnterJIT(JSContext* 
     // This address is also used for setting the constructing bit on all paths.
     masm.loadPtr(Address(ebp, ARG_STACKFRAME), OsrFrameReg);
 
     /*****************************************************************
     Push the number of bytes we've pushed so far on the stack and call
     *****************************************************************/
     // Create a frame descriptor.
     masm.subl(esp, esi);
-    masm.makeFrameDescriptor(esi, JitFrame_Entry, JitFrameLayout::Size());
+    masm.makeFrameDescriptor(esi, JitFrame_CppToJSJit, JitFrameLayout::Size());
     masm.push(esi);
 
     CodeLabel returnLabel;
     CodeLabel oomReturnLabel;
     if (type == EnterJitBaseline) {
         // Handle OSR.
         AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
         regs.take(JSReturnOperand);
@@ -1128,17 +1128,20 @@ JitRuntime::generateProfilerExitFrameTai
     Label handle_Entry;
     Label end;
 
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonJS), &handle_IonJS);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier);
     masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonICCall), &handle_IonICCall);
-    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry);
+    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_CppToJSJit), &handle_Entry);
+
+    // The WasmToJSJit is just another kind of entry.
+    masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_WasmToJSJit), &handle_Entry);
 
     masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame.");
 
     //
     // JitFrame_IonJS
     //
     // Stack layout:
     //                  ...
@@ -1342,19 +1345,21 @@ JitRuntime::generateProfilerExitFrameTai
         // lastProfilingFrame := ICCallFrame + ICCallFrame-Descriptor.Size +
         //                       IonICCallFrameLayout::Size()
         masm.lea(Operand(scratch2, scratch3, TimesOne, IonICCallFrameLayout::Size()), scratch1);
         masm.storePtr(scratch1, lastProfilingFrame);
         masm.ret();
     }
 
     //
-    // JitFrame_Entry
+    // JitFrame_CppToJSJit / JitFrame_WasmJSToJit
     //
     // If at an entry frame, store null into both fields.
+    // A fast-path wasm->jit transition frame is an entry frame from the point
+    // of view of the JIT.
     //
     masm.bind(&handle_Entry);
     {
         masm.movePtr(ImmPtr(nullptr), scratch1);
         masm.storePtr(scratch1, lastProfilingCallSite);
         masm.storePtr(scratch1, lastProfilingFrame);
         masm.ret();
     }
--- a/js/src/jsapi-tests/testParseJSON.cpp
+++ b/js/src/jsapi-tests/testParseJSON.cpp
@@ -23,24 +23,24 @@ class AutoInflatedString {
   public:
     explicit AutoInflatedString(JSContext* cx) : cx(cx), chars_(nullptr), length_(0) { }
     ~AutoInflatedString() {
         JS_free(cx, chars_);
     }
 
     template<size_t N> void operator=(const char (&str)[N]) {
         length_ = N - 1;
-        chars_ = InflateString(cx, str, &length_);
+        chars_ = InflateString(cx, str, length_);
         if (!chars_)
             abort();
     }
 
     void operator=(const char* str) {
         length_ = strlen(str);
-        chars_ = InflateString(cx, str, &length_);
+        chars_ = InflateString(cx, str, length_);
         if (!chars_)
             abort();
     }
 
     const char16_t* chars() const { return chars_; }
     size_t length() const { return length_; }
 };
 
--- a/js/src/jsapi-tests/testSharedImmutableStringsCache.cpp
+++ b/js/src/jsapi-tests/testSharedImmutableStringsCache.cpp
@@ -39,17 +39,17 @@ getString(CacheAndIndex* cacheAndIndex)
     for (int i = 0; i < NUM_ITERATIONS; i++) {
         auto str = STRINGS[cacheAndIndex->index % NUM_STRINGS];
 
         auto dupe = js::DuplicateString(str);
         MOZ_RELEASE_ASSERT(dupe);
 
         auto deduped = cacheAndIndex->cache->getOrCreate(mozilla::Move(dupe), js_strlen(str));
         MOZ_RELEASE_ASSERT(deduped.isSome());
-        MOZ_RELEASE_ASSERT(js_strcmp(str, deduped->chars()) == 0);
+        MOZ_RELEASE_ASSERT(js::EqualChars(str, deduped->chars(), js_strlen(str) + 1));
 
         {
             auto cloned = deduped->clone();
             // We should be de-duplicating and giving back the same string.
             MOZ_RELEASE_ASSERT(deduped->chars() == cloned.chars());
         }
     }
 
--- a/js/src/jsapi-tests/testUbiNode.cpp
+++ b/js/src/jsapi-tests/testUbiNode.cpp
@@ -152,17 +152,17 @@ BEGIN_TEST(test_ubiNodeJSObjectConstruct
 {
     JS::RootedValue val(cx);
     EVAL("this.Ctor = function Ctor() {}; new Ctor", &val);
     CHECK(val.isObject());
 
     UniqueTwoByteChars ctorName;
     CHECK(JS::ubi::Node(&val.toObject()).jsObjectConstructorName(cx, ctorName));
     CHECK(ctorName);
-    CHECK(js_strcmp(ctorName.get(), u"Ctor") == 0);
+    CHECK(EqualChars(ctorName.get(), u"Ctor", js_strlen(u"Ctor") + 1));
 
     return true;
 }
 END_TEST(test_ubiNodeJSObjectConstructorName)
 
 template <typename F, typename G>
 static bool
 checkString(const char* expected, F fillBufferFunction, G stringGetterFunction)
--- a/js/src/jsapi-tests/testXDR.cpp
+++ b/js/src/jsapi-tests/testXDR.cpp
@@ -136,18 +136,18 @@ BEGIN_TEST(testXDR_sourceMap)
     JS::RootedScript script(cx);
     for (const char** sm = sourceMaps; *sm; sm++) {
         JS::CompileOptions options(cx);
         options.setFileAndLine(__FILE__, __LINE__);
         CHECK(JS_CompileScript(cx, "", 0, options, &script));
         CHECK(script);
 
         size_t len = strlen(*sm);
-        JS::UniqueTwoByteChars expected_wrapper(js::InflateString(cx, *sm, &len));
-        char16_t *expected = expected_wrapper.get();
+        JS::UniqueTwoByteChars expected_wrapper(js::InflateString(cx, *sm, len));
+        char16_t* expected = expected_wrapper.get();
         CHECK(expected);
 
         // The script source takes responsibility of free'ing |expected|.
         CHECK(script->scriptSource()->setSourceMapURL(cx, expected));
         script = FreezeThaw(cx, script);
         CHECK(script);
         CHECK(script->scriptSource());
         CHECK(script->scriptSource()->hasSourceMapURL());
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4103,17 +4103,17 @@ Compile(JSContext* cx, const ReadOnlyCom
 static bool
 Compile(JSContext* cx, const ReadOnlyCompileOptions& options, ScopeKind scopeKind,
         const char* bytes, size_t length, MutableHandleScript script)
 {
     UniqueTwoByteChars chars;
     if (options.utf8)
         chars.reset(UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(bytes, length), &length).get());
     else
-        chars.reset(InflateString(cx, bytes, &length));
+        chars.reset(InflateString(cx, bytes, length));
     if (!chars)
         return false;
 
     return ::Compile(cx, options, scopeKind, chars.get(), length, script);
 }
 
 static bool
 Compile(JSContext* cx, const ReadOnlyCompileOptions& options, ScopeKind scopeKind,
@@ -4606,17 +4606,17 @@ JS::CompileFunction(JSContext* cx, AutoO
                     const ReadOnlyCompileOptions& options,
                     const char* name, unsigned nargs, const char* const* argnames,
                     const char* bytes, size_t length, MutableHandleFunction fun)
 {
     UniqueTwoByteChars chars;
     if (options.utf8)
         chars.reset(UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(bytes, length), &length).get());
     else
-        chars.reset(InflateString(cx, bytes, &length));
+        chars.reset(InflateString(cx, bytes, length));
     if (!chars)
         return false;
 
     return CompileFunction(cx, envChain, options, name, nargs, argnames,
                            chars.get(), length, fun);
 }
 
 JS_PUBLIC_API(JSString*)
@@ -4787,17 +4787,17 @@ Evaluate(JSContext* cx, const ReadOnlyCo
 extern JS_PUBLIC_API(bool)
 JS::Evaluate(JSContext* cx, const ReadOnlyCompileOptions& options,
              const char* bytes, size_t length, MutableHandleValue rval)
 {
     char16_t* chars;
     if (options.utf8)
         chars = UTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(bytes, length), &length).get();
     else
-        chars = InflateString(cx, bytes, &length);
+        chars = InflateString(cx, bytes, length);
     if (!chars)
         return false;
 
     SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::GiveOwnership);
     RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
     bool ok = ::Evaluate(cx, ScopeKind::Global, globalLexical, options, srcBuf, rval);
     return ok;
 }
@@ -6604,17 +6604,17 @@ JS_ObjectIsDate(JSContext* cx, HandleObj
 /*
  * Regular Expressions.
  */
 JS_PUBLIC_API(JSObject*)
 JS_NewRegExpObject(JSContext* cx, const char* bytes, size_t length, unsigned flags)
 {
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
-    ScopedJSFreePtr<char16_t> chars(InflateString(cx, bytes, &length));
+    ScopedJSFreePtr<char16_t> chars(InflateString(cx, bytes, length));
     if (!chars)
         return nullptr;
 
     RegExpObject* reobj = RegExpObject::create(cx, chars, length, RegExpFlag(flags),
                                                nullptr, nullptr, cx->tempLifoAlloc(),
                                                GenericObject);
     return reobj;
 }
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -371,17 +371,21 @@ struct JSContext : public JS::RootingCon
         return offsetof(JSContext, activation_);
     }
 
     js::Activation* profilingActivation() const {
         return profilingActivation_;
     }
     static size_t offsetOfProfilingActivation() {
         return offsetof(JSContext, profilingActivation_);
-     }
+    }
+
+    static size_t offsetOfJitActivation() {
+        return offsetof(JSContext, jitActivation);
+    }
 
 #ifdef DEBUG
     static size_t offsetOfInUnsafeCallWithABI() {
         return offsetof(JSContext, inUnsafeCallWithABI);
     }
 #endif
 
   private:
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -553,37 +553,33 @@ JSContext::leaveZoneGroup(js::ZoneGroup*
 inline JSScript*
 JSContext::currentScript(jsbytecode** ppc,
                          MaybeAllowCrossCompartment allowCrossCompartment) const
 {
     if (ppc)
         *ppc = nullptr;
 
     js::Activation* act = activation();
-    while (act && act->isJit() && !act->asJit()->isActive())
-        act = act->prev();
-
     if (!act)
         return nullptr;
 
     MOZ_ASSERT(act->cx() == this);
 
     if (!allowCrossCompartment && act->compartment() != compartment())
         return nullptr;
 
     if (act->isJit()) {
+        if (act->hasWasmExitFP())
+            return nullptr;
         JSScript* script = nullptr;
         js::jit::GetPcScript(const_cast<JSContext*>(this), &script, ppc);
         MOZ_ASSERT(allowCrossCompartment || script->compartment() == compartment());
         return script;
     }
 
-    if (act->isWasm())
-        return nullptr;
-
     MOZ_ASSERT(act->isInterpreter());
 
     js::InterpreterFrame* fp = act->asInterpreter()->current();
     MOZ_ASSERT(!fp->runningInJit());
 
     JSScript* script = fp->script();
     MOZ_ASSERT(allowCrossCompartment || script->compartment() == compartment());
 
--- a/js/src/jsexn.h
+++ b/js/src/jsexn.h
@@ -92,17 +92,17 @@ ExnTypeFromProtoKey(JSProtoKey key)
     MOZ_ASSERT(type >= JSEXN_ERR);
     MOZ_ASSERT(type < JSEXN_ERROR_LIMIT);
     return type;
 }
 
 static inline bool
 IsErrorProtoKey(JSProtoKey key)
 {
-    JSExnType type = static_cast<JSExnType>(key - JSProto_Error);
+    int type = key - JSProto_Error;
     return type >= JSEXN_ERR && type < JSEXN_ERROR_LIMIT;
 }
 
 class AutoClearPendingException
 {
     JSContext* cx;
 
   public:
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -848,17 +848,17 @@ CreateFunctionPrototype(JSContext* cx, J
         return nullptr;
 
     RootedFunction functionProto(cx, &functionProto_->as<JSFunction>());
 
     const char* rawSource = "function () {\n}";
     size_t sourceLen = strlen(rawSource);
     size_t begin = 9;
     MOZ_ASSERT(rawSource[begin] == '(');
-    mozilla::UniquePtr<char16_t[], JS::FreePolicy> source(InflateString(cx, rawSource, &sourceLen));
+    mozilla::UniquePtr<char16_t[], JS::FreePolicy> source(InflateString(cx, rawSource, sourceLen));
     if (!source)
         return nullptr;
 
     ScriptSource* ss = cx->new_<ScriptSource>();
     if (!ss)
         return nullptr;
     ScriptSourceHolder ssHolder(ss);
     if (!ss->setSource(cx, mozilla::Move(source), sourceLen))
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -7179,25 +7179,25 @@ IsDeterministicGCReason(JS::gcreason::Re
 #endif
 
 gcstats::ZoneGCStats
 GCRuntime::scanZonesBeforeGC()
 {
     gcstats::ZoneGCStats zoneStats;
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         zoneStats.zoneCount++;
+        zoneStats.compartmentCount += zone->compartments().length();
+        if (zone->canCollect())
+            zoneStats.collectableZoneCount++;
         if (zone->isGCScheduled()) {
             zoneStats.collectedZoneCount++;
             zoneStats.collectedCompartmentCount += zone->compartments().length();
         }
     }
 
-    for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next())
-        zoneStats.compartmentCount++;
-
     return zoneStats;
 }
 
 // The GC can only clean up scheduledForDestruction compartments that were
 // marked live by a barrier (e.g. by RemapWrappers from a navigation event).
 // It is also common to have compartments held live because they are part of a
 // cycle in gecko, e.g. involving the HTMLDocument wrapper. In this case, we
 // need to run the CycleCollector in order to remove these edges before the
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -553,20 +553,31 @@ HasNativeMethodPure(JSObject* obj, Prope
 
     return IsNativeFunction(v, native);
 }
 
 // Return whether 'obj' definitely has no @@toPrimitive method.
 static MOZ_ALWAYS_INLINE bool
 HasNoToPrimitiveMethodPure(JSObject* obj, JSContext* cx)
 {
-    jsid id = SYMBOL_TO_JSID(cx->wellKnownSymbols().toPrimitive);
+    Symbol* toPrimitive = cx->wellKnownSymbols().toPrimitive;
+    JSObject* holder;
+    if (!MaybeHasInterestingSymbolProperty(cx, obj, toPrimitive, &holder)) {
+#ifdef DEBUG
+        JSObject* pobj;
+        PropertyResult prop;
+        MOZ_ASSERT(LookupPropertyPure(cx, obj, SYMBOL_TO_JSID(toPrimitive), &pobj, &prop));
+        MOZ_ASSERT(!prop);
+#endif
+        return true;
+    }
+
     JSObject* pobj;
     PropertyResult prop;
-    if (!LookupPropertyPure(cx, obj, id, &pobj, &prop))
+    if (!LookupPropertyPure(cx, holder, SYMBOL_TO_JSID(toPrimitive), &pobj, &prop))
         return false;
 
     return !prop;
 }
 
 extern bool
 ToPropertyKeySlow(JSContext* cx, HandleValue argument, MutableHandleId result);
 
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -41,16 +41,17 @@
 #include "unicode/unorm2.h"
 #endif
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/Opcodes.h"
 #include "vm/Printer.h"
 #include "vm/RegExpObject.h"
 #include "vm/RegExpStatics.h"
+#include "vm/SelfHosting.h"
 #include "vm/StringBuffer.h"
 #include "vm/Unicode.h"
 
 #include "vm/Interpreter-inl.h"
 #include "vm/String-inl.h"
 #include "vm/StringObject-inl.h"
 #include "vm/TypeInference-inl.h"
 
@@ -70,26 +71,25 @@ using mozilla::IsSame;
 using mozilla::Move;
 using mozilla::PodCopy;
 using mozilla::PodEqual;
 using mozilla::RangedPtr;
 
 using JS::AutoCheckCannotGC;
 
 static JSLinearString*
-ArgToRootedString(JSContext* cx, const CallArgs& args, unsigned argno)
+ArgToLinearString(JSContext* cx, const CallArgs& args, unsigned argno)
 {
     if (argno >= args.length())
         return cx->names().undefined;
 
     JSString* str = ToString<CanGC>(cx, args[argno]);
     if (!str)
         return nullptr;
 
-    args[argno].setString(str);
     return str->ensureLinear(cx);
 }
 
 template <typename CharT> struct MaximumInlineLength;
 
 template<> struct MaximumInlineLength<Latin1Char> {
     static constexpr size_t value = JSFatInlineString::MAX_LENGTH_LATIN1;
 };
@@ -319,17 +319,17 @@ Escape(JSContext* cx, const CharT* chars
     return true;
 }
 
 static bool
 str_escape(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    JSLinearString* str = ArgToRootedString(cx, args, 0);
+    RootedLinearString str(cx, ArgToLinearString(cx, args, 0));
     if (!str)
         return false;
 
     InlineCharBuffer<Latin1Char> newChars;
     uint32_t newLength = 0;  // initialize to silence GCC warning
     if (str->hasLatin1Chars()) {
         AutoCheckCannotGC nogc;
         if (!Escape(cx, str->latin1Chars(nogc), str->length(), newChars, &newLength))
@@ -449,17 +449,17 @@ Unescape(StringBuffer& sb, const mozilla
 // ES2018 draft rev f83aa38282c2a60c6916ebc410bfdf105a0f6a54
 // B.2.1.2 unescape ( string )
 static bool
 str_unescape(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
-    RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
+    RootedLinearString str(cx, ArgToLinearString(cx, args, 0));
     if (!str)
         return false;
 
     // Step 3.
     StringBuffer sb(cx);
     if (str->hasTwoByteChars() && !sb.ensureTwoByteChars())
         return false;
 
@@ -1434,17 +1434,17 @@ js::str_normalize(JSContext* cx, unsigne
     };
 
     NormalizationForm form;
     if (!args.hasDefined(0)) {
         // Step 3.
         form = NFC;
     } else {
         // Step 4.
-        RootedLinearString formStr(cx, ArgToRootedString(cx, args, 0));
+        JSLinearString* formStr = ArgToLinearString(cx, args, 0);
         if (!formStr)
             return false;
 
         // Step 5.
         if (EqualStrings(formStr, cx->names().NFC)) {
             form = NFC;
         } else if (EqualStrings(formStr, cx->names().NFD)) {
             form = NFD;
@@ -1587,17 +1587,16 @@ js::str_charAt(JSContext* cx, unsigned a
   out_of_range:
     args.rval().setString(cx->runtime()->emptyString);
     return true;
 }
 
 bool
 js::str_charCodeAt_impl(JSContext* cx, HandleString string, HandleValue index, MutableHandleValue res)
 {
-    RootedString str(cx);
     size_t i;
     if (index.isInt32()) {
         i = index.toInt32();
         if (i >= string->length())
             goto out_of_range;
     } else {
         double d = 0.0;
         if (!ToInteger(cx, index, &d))
@@ -1824,26 +1823,20 @@ StringMatch(const TextChar* text, uint32
         if (index != sBMHBadPattern)
             return index;
     }
 
     /*
      * For big patterns with large potential overlap we want the SIMD-optimized
      * speed of memcmp. For small patterns, a simple loop is faster. We also can't
      * use memcmp if one of the strings is TwoByte and the other is Latin-1.
-     *
-     * FIXME: Linux memcmp performance is sad and the manual loop is faster.
      */
-    return
-#if !defined(__linux__)
-        (patLen > 128 && IsSame<TextChar, PatChar>::value)
-            ? Matcher<MemCmp<TextChar, PatChar>, TextChar, PatChar>(text, textLen, pat, patLen)
-            :
-#endif
-              Matcher<ManualCmp<TextChar, PatChar>, TextChar, PatChar>(text, textLen, pat, patLen);
+    return (patLen > 128 && IsSame<TextChar, PatChar>::value)
+           ? Matcher<MemCmp<TextChar, PatChar>, TextChar, PatChar>(text, textLen, pat, patLen)
+           : Matcher<ManualCmp<TextChar, PatChar>, TextChar, PatChar>(text, textLen, pat, patLen);
 }
 
 static int32_t
 StringMatch(JSLinearString* text, JSLinearString* pat, uint32_t start = 0)
 {
     MOZ_ASSERT(start <= text->length());
     uint32_t textLen = text->length() - start;
     uint32_t patLen = pat->length();
@@ -2054,65 +2047,78 @@ RopeMatch(JSContext* cx, JSRope* text, J
             *match = RopeMatchImpl<char16_t>(nogc, strings, pat->latin1Chars(nogc), patLen);
         else
             *match = RopeMatchImpl<char16_t>(nogc, strings, pat->twoByteChars(nogc), patLen);
     }
 
     return true;
 }
 
-/* ES6 draft rc4 21.1.3.7. */
-bool
-js::str_includes(JSContext* cx, unsigned argc, Value* vp)
+static MOZ_ALWAYS_INLINE bool
+ReportErrorIfFirstArgIsRegExp(JSContext* cx, const CallArgs& args)
 {
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    // Steps 1, 2, and 3
-    RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
-    if (!str)
+    // Only call IsRegExp if the first argument is definitely an object, so we
+    // don't pay the cost of an additional function call in the common case.
+    if (args.length() == 0 || !args[0].isObject())
+        return true;
+
+    bool isRegExp;
+    if (!IsRegExp(cx, args[0], &isRegExp))
         return false;
 
-    // Steps 4 and 5
-    bool isRegExp;
-    if (!IsRegExp(cx, args.get(0), &isRegExp))
-        return false;
-
-    // Step 6
     if (isRegExp) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
                                   "first", "", "Regular Expression");
         return false;
     }
-
-    // Steps 7 and 8
-    RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
+    return true;
+}
+
+// ES2018 draft rev de77aaeffce115deaf948ed30c7dbe4c60983c0c
+// 21.1.3.7 String.prototype.includes ( searchString [ , position ] )
+bool
+js::str_includes(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Steps 1-2.
+    RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
+    if (!str)
+        return false;
+
+    // Steps 3-4.
+    if (!ReportErrorIfFirstArgIsRegExp(cx, args))
+        return false;
+
+    // Step 5.
+    RootedLinearString searchStr(cx, ArgToLinearString(cx, args, 0));
     if (!searchStr)
         return false;
 
-    // Steps 9 and 10
+    // Step 6.
     uint32_t pos = 0;
     if (args.hasDefined(1)) {
         if (args[1].isInt32()) {
             int i = args[1].toInt32();
             pos = (i < 0) ? 0U : uint32_t(i);
         } else {
             double d;
             if (!ToInteger(cx, args[1], &d))
                 return false;
             pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
         }
     }
 
-    // Step 11
+    // Step 7.
     uint32_t textLen = str->length();
 
-    // Step 12
+    // Step 8.
     uint32_t start = Min(Max(pos, 0U), textLen);
 
-    // Steps 13 and 14
+    // Steps 9-10.
     JSLinearString* text = str->ensureLinear(cx);
     if (!text)
         return false;
 
     args.rval().setBoolean(StringMatch(text, searchStr, start) != -1);
     return true;
 }
 
@@ -2123,17 +2129,17 @@ js::str_indexOf(JSContext* cx, unsigned 
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Steps 1, 2, and 3
     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
     if (!str)
         return false;
 
     // Steps 4 and 5
-    RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
+    RootedLinearString searchStr(cx, ArgToLinearString(cx, args, 0));
     if (!searchStr)
         return false;
 
     // Steps 6 and 7
     uint32_t pos = 0;
     if (args.hasDefined(1)) {
         if (args[1].isInt32()) {
             int i = args[1].toInt32();
@@ -2205,17 +2211,17 @@ js::str_lastIndexOf(JSContext* cx, unsig
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Steps 1-2.
     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
     if (!str)
         return false;
 
     // Step 3.
-    RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
+    RootedLinearString searchStr(cx, ArgToLinearString(cx, args, 0));
     if (!searchStr)
         return false;
 
     // Step 6.
     size_t len = str->length();
 
     // Step 8.
     size_t searchLen = searchStr->length();
@@ -2302,143 +2308,129 @@ js::HasSubstringAt(JSLinearString* text,
 
     const char16_t* textChars = text->twoByteChars(nogc) + start;
     if (pat->hasTwoByteChars())
         return PodEqual(textChars, pat->twoByteChars(nogc), patLen);
 
     return EqualChars(pat->latin1Chars(nogc), textChars, patLen);
 }
 
-/* ES6 draft rc3 21.1.3.18. */
+// ES2018 draft rev de77aaeffce115deaf948ed30c7dbe4c60983c0c
+// 21.1.3.20 String.prototype.startsWith ( searchString [ , position ] )
 bool
 js::str_startsWith(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    // Steps 1, 2, and 3
+    // Steps 1-2.
     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
     if (!str)
         return false;
 
-    // Steps 4 and 5
-    bool isRegExp;
-    if (!IsRegExp(cx, args.get(0), &isRegExp))
+    // Steps 3-4.
+    if (!ReportErrorIfFirstArgIsRegExp(cx, args))
         return false;
 
-    // Step 6
-    if (isRegExp) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
-                                  "first", "", "Regular Expression");
-        return false;
-    }
-
-    // Steps 7 and 8
-    RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
+    // Step 5.
+    RootedLinearString searchStr(cx, ArgToLinearString(cx, args, 0));
     if (!searchStr)
         return false;
 
-    // Steps 9 and 10
+    // Step 6.
     uint32_t pos = 0;
     if (args.hasDefined(1)) {
         if (args[1].isInt32()) {
             int i = args[1].toInt32();
             pos = (i < 0) ? 0U : uint32_t(i);
         } else {
             double d;
             if (!ToInteger(cx, args[1], &d))
                 return false;
             pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
         }
     }
 
-    // Step 11
+    // Step 7.
     uint32_t textLen = str->length();
 
-    // Step 12
+    // Step 8.
     uint32_t start = Min(Max(pos, 0U), textLen);
 
-    // Step 13
+    // Step 9.
     uint32_t searchLen = searchStr->length();
 
-    // Step 14
+    // Step 10.
     if (searchLen + start < searchLen || searchLen + start > textLen) {
         args.rval().setBoolean(false);
         return true;
     }
 
-    // Steps 15 and 16
+    // Steps 11-12.
     JSLinearString* text = str->ensureLinear(cx);
     if (!text)
         return false;
 
     args.rval().setBoolean(HasSubstringAt(text, searchStr, start));
     return true;
 }
 
-/* ES6 draft rc3 21.1.3.6. */
+// ES2018 draft rev de77aaeffce115deaf948ed30c7dbe4c60983c0c
+// 21.1.3.6 String.prototype.endsWith ( searchString [ , endPosition ] )
 bool
 js::str_endsWith(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    // Steps 1, 2, and 3
+    // Steps 1-2.
     RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
     if (!str)
         return false;
 
-    // Steps 4 and 5
-    bool isRegExp;
-    if (!IsRegExp(cx, args.get(0), &isRegExp))
+    // Steps 3-4.
+    if (!ReportErrorIfFirstArgIsRegExp(cx, args))
         return false;
 
-    // Step 6
-    if (isRegExp) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE,
-                                  "first", "", "Regular Expression");
-        return false;
-    }
-
-    // Steps 7 and 8
-    RootedLinearString searchStr(cx, ArgToRootedString(cx, args, 0));
+    // Step 5.
+    RootedLinearString searchStr(cx, ArgToLinearString(cx, args, 0));
     if (!searchStr)
         return false;
 
-    // Step 9
+    // Step 6.
     uint32_t textLen = str->length();
 
-    // Steps 10 and 11
+    // Step 7.
     uint32_t pos = textLen;
     if (args.hasDefined(1)) {
         if (args[1].isInt32()) {
             int i = args[1].toInt32();
             pos = (i < 0) ? 0U : uint32_t(i);
         } else {
             double d;
             if (!ToInteger(cx, args[1], &d))
                 return false;
             pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX)));
         }
     }
 
-    // Step 12
+    // Step 8.
     uint32_t end = Min(Max(pos, 0U), textLen);
 
-    // Step 13
+    // Step 9.
     uint32_t searchLen = searchStr->length();
 
-    // Step 15 (reordered)
+    // Step 11 (reordered).
     if (searchLen > end) {
         args.rval().setBoolean(false);
         return true;
     }
 
-    // Step 14
+    // Step 10.
     uint32_t start = end - searchLen;
 
-    // Steps 16 and 17
+    // Steps 12-13.
     JSLinearString* text = str->ensureLinear(cx);
     if (!text)
         return false;
 
     args.rval().setBoolean(HasSubstringAt(text, searchStr, start));
     return true;
 }
 
@@ -2461,17 +2453,17 @@ TrimString(const CharT* chars, bool trim
 
     *pBegin = begin;
     *pEnd = end;
 }
 
 static bool
 TrimString(JSContext* cx, const CallArgs& args, bool trimLeft, bool trimRight)
 {
-    RootedString str(cx, ToStringForStringFunction(cx, args.thisv()));
+    JSString* str = ToStringForStringFunction(cx, args.thisv());
     if (!str)
         return false;
 
     JSLinearString* linear = str->ensureLinear(cx);
     if (!linear)
         return false;
 
     size_t length = linear->length();
@@ -2479,21 +2471,21 @@ TrimString(JSContext* cx, const CallArgs
     if (linear->hasLatin1Chars()) {
         AutoCheckCannotGC nogc;
         TrimString(linear->latin1Chars(nogc), trimLeft, trimRight, length, &begin, &end);
     } else {
         AutoCheckCannotGC nogc;
         TrimString(linear->twoByteChars(nogc), trimLeft, trimRight, length, &begin, &end);
     }
 
-    str = NewDependentString(cx, str, begin, end - begin);
-    if (!str)
+    JSLinearString* result = NewDependentString(cx, linear, begin, end - begin);
+    if (!result)
         return false;
 
-    args.rval().setString(str);
+    args.rval().setString(result);
     return true;
 }
 
 bool
 js::str_trim(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return TrimString(cx, args, true, true);
@@ -2509,17 +2501,18 @@ js::str_trimLeft(JSContext* cx, unsigned
 bool
 js::str_trimRight(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return TrimString(cx, args, false, true);
 }
 
 // Utility for building a rope (lazy concatenation) of strings.
-class RopeBuilder {
+class RopeBuilder
+{
     JSContext* cx;
     RootedString res;
 
     RopeBuilder(const RopeBuilder& other) = delete;
     void operator=(const RopeBuilder& other) = delete;
 
   public:
     explicit RopeBuilder(JSContext* cx)
@@ -2547,90 +2540,103 @@ FindDollarIndex(const CharT* chars, size
         MOZ_ASSERT(dollarIndex < length);
         return dollarIndex;
     }
     return UINT32_MAX;
 }
 
 } /* anonymous namespace */
 
+/*
+ * Constructs a result string that looks like:
+ *
+ *      newstring = string[:matchStart] + repstr + string[matchEnd:]
+ */
 static JSString*
-BuildFlatReplacement(JSContext* cx, HandleString textstr, HandleString repstr,
-                     size_t match, size_t patternLength)
+BuildFlatReplacement(JSContext* cx, HandleString textstr, HandleLinearString repstr,
+                     size_t matchStart, size_t patternLength)
 {
-    RopeBuilder builder(cx);
+    size_t matchEnd = matchStart + patternLength;
+
+    RootedString resultStr(cx, NewDependentString(cx, textstr, 0, matchStart));
+    if (!resultStr)
+        return nullptr;
+
+    resultStr = ConcatStrings<CanGC>(cx, resultStr, repstr);
+    if (!resultStr)
+        return nullptr;
+
+    MOZ_ASSERT(textstr->length() >= matchEnd);
+    RootedString rest(cx, NewDependentString(cx, textstr, matchEnd, textstr->length() - matchEnd));
+    if (!rest)
+        return nullptr;
+
+    return ConcatStrings<CanGC>(cx, resultStr, rest);
+}
+
+static JSString*
+BuildFlatRopeReplacement(JSContext* cx, HandleString textstr, HandleLinearString repstr,
+                         size_t match, size_t patternLength)
+{
+    MOZ_ASSERT(textstr->isRope());
+
     size_t matchEnd = match + patternLength;
 
-    if (textstr->isRope()) {
-        /*
-         * If we are replacing over a rope, avoid flattening it by iterating
-         * through it, building a new rope.
-         */
-        StringSegmentRange r(cx);
-        if (!r.init(textstr))
-            return nullptr;
-
-        size_t pos = 0;
-        while (!r.empty()) {
-            RootedString str(cx, r.front());
-            size_t len = str->length();
-            size_t strEnd = pos + len;
-            if (pos < matchEnd && strEnd > match) {
+    /*
+     * If we are replacing over a rope, avoid flattening it by iterating
+     * through it, building a new rope.
+     */
+    StringSegmentRange r(cx);
+    if (!r.init(textstr))
+        return nullptr;
+
+    RopeBuilder builder(cx);
+    size_t pos = 0;
+    while (!r.empty()) {
+        RootedString str(cx, r.front());
+        size_t len = str->length();
+        size_t strEnd = pos + len;
+        if (pos < matchEnd && strEnd > match) {
+            /*
+             * We need to special-case any part of the rope that overlaps
+             * with the replacement string.
+             */
+            if (match >= pos) {
                 /*
-                 * We need to special-case any part of the rope that overlaps
-                 * with the replacement string.
+                 * If this part of the rope overlaps with the left side of
+                 * the pattern, then it must be the only one to overlap with
+                 * the first character in the pattern, so we include the
+                 * replacement string here.
                  */
-                if (match >= pos) {
-                    /*
-                     * If this part of the rope overlaps with the left side of
-                     * the pattern, then it must be the only one to overlap with
-                     * the first character in the pattern, so we include the
-                     * replacement string here.
-                     */
-                    RootedString leftSide(cx, NewDependentString(cx, str, 0, match - pos));
-                    if (!leftSide ||
-                        !builder.append(leftSide) ||
-                        !builder.append(repstr))
-                    {
-                        return nullptr;
-                    }
+                RootedString leftSide(cx, NewDependentString(cx, str, 0, match - pos));
+                if (!leftSide ||
+                    !builder.append(leftSide) ||
+                    !builder.append(repstr))
+                {
+                    return nullptr;
                 }
-
-                /*
-                 * If str runs off the end of the matched string, append the
-                 * last part of str.
-                 */
-                if (strEnd > matchEnd) {
-                    RootedString rightSide(cx, NewDependentString(cx, str, matchEnd - pos,
-                                                                  strEnd - matchEnd));
-                    if (!rightSide || !builder.append(rightSide))
-                        return nullptr;
-                }
-            } else {
-                if (!builder.append(str))
+            }
+
+            /*
+             * If str runs off the end of the matched string, append the
+             * last part of str.
+             */
+            if (strEnd > matchEnd) {
+                RootedString rightSide(cx, NewDependentString(cx, str, matchEnd - pos,
+                                                              strEnd - matchEnd));
+                if (!rightSide || !builder.append(rightSide))
                     return nullptr;
             }
-            pos += str->length();
-            if (!r.popFront())
+        } else {
+            if (!builder.append(str))
                 return nullptr;
         }
-    } else {
-        RootedString leftSide(cx, NewDependentString(cx, textstr, 0, match));
-        if (!leftSide)
+        pos += str->length();
+        if (!r.popFront())
             return nullptr;
-        RootedString rightSide(cx);
-        rightSide = NewDependentString(cx, textstr, match + patternLength,
-                                       textstr->length() - match - patternLength);
-        if (!rightSide ||
-            !builder.append(leftSide) ||
-            !builder.append(repstr) ||
-            !builder.append(rightSide))
-        {
-            return nullptr;
-        }
     }
 
     return builder.result();
 }
 
 template <typename CharT>
 static bool
 AppendDollarReplacement(StringBuffer& newReplaceChars, size_t firstDollarIndex,
@@ -2675,24 +2681,21 @@ AppendDollarReplacement(StringBuffer& ne
         }
         ++it; /* We always eat an extra char in the above switch. */
     }
 
     return true;
 }
 
 /*
- * Perform a linear-scan dollar substitution on the replacement text,
- * constructing a result string that looks like:
- *
- *      newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:]
+ * Perform a linear-scan dollar substitution on the replacement text.
  */
-static JSString*
-BuildDollarReplacement(JSContext* cx, JSString* textstrArg, JSLinearString* repstr,
-                       uint32_t firstDollarIndex, size_t matchStart, size_t patternLength)
+static JSLinearString*
+InterpretDollarReplacement(JSContext* cx, HandleString textstrArg, HandleLinearString repstr,
+                           uint32_t firstDollarIndex, size_t matchStart, size_t patternLength)
 {
     RootedLinearString textstr(cx, textstrArg->ensureLinear(cx));
     if (!textstr)
         return nullptr;
 
     size_t matchLimit = matchStart + patternLength;
 
     /*
@@ -2717,47 +2720,29 @@ BuildDollarReplacement(JSContext* cx, JS
     } else {
         AutoCheckCannotGC nogc;
         res = AppendDollarReplacement(newReplaceChars, firstDollarIndex, matchStart, matchLimit,
                                       textstr, repstr->twoByteChars(nogc), repstr->length());
     }
     if (!res)
         return nullptr;
 
-    RootedString leftSide(cx, NewDependentString(cx, textstr, 0, matchStart));
-    if (!leftSide)
-        return nullptr;
-
-    RootedString newReplace(cx, newReplaceChars.finishString());
-    if (!newReplace)
-        return nullptr;
-
-    MOZ_ASSERT(textstr->length() >= matchLimit);
-    RootedString rightSide(cx, NewDependentString(cx, textstr, matchLimit,
-                                                  textstr->length() - matchLimit));
-    if (!rightSide)
-        return nullptr;
-
-    RopeBuilder builder(cx);
-    if (!builder.append(leftSide) || !builder.append(newReplace) || !builder.append(rightSide))
-        return nullptr;
-
-    return builder.result();
+    return newReplaceChars.finishString();
 }
 
 template <typename StrChar, typename RepChar>
 static bool
-StrFlatReplaceGlobal(JSContext *cx, JSLinearString *str, JSLinearString *pat, JSLinearString *rep,
-                     StringBuffer &sb)
+StrFlatReplaceGlobal(JSContext* cx, JSLinearString* str, JSLinearString* pat, JSLinearString* rep,
+                     StringBuffer& sb)
 {
     MOZ_ASSERT(str->length() > 0);
 
     AutoCheckCannotGC nogc;
-    const StrChar *strChars = str->chars<StrChar>(nogc);
-    const RepChar *repChars = rep->chars<RepChar>(nogc);
+    const StrChar* strChars = str->chars<StrChar>(nogc);
+    const RepChar* repChars = rep->chars<RepChar>(nogc);
 
     // The pattern is empty, so we interleave the replacement string in-between
     // each character.
     if (!pat->length()) {
         CheckedInt<uint32_t> strLength(str->length());
         CheckedInt<uint32_t> repLength(rep->length());
         CheckedInt<uint32_t> length = repLength * (strLength - 1) + strLength;
         if (!length.isValid()) {
@@ -2798,18 +2783,18 @@ StrFlatReplaceGlobal(JSContext *cx, JSLi
     if (!sb.append(strChars + start, str->length() - start))
         return false;
 
     return true;
 }
 
 // This is identical to "str.split(pattern).join(replacement)" except that we
 // do some deforestation optimization in Ion.
-JSString *
-js::str_flat_replace_string(JSContext *cx, HandleString string, HandleString pattern,
+JSString*
+js::str_flat_replace_string(JSContext* cx, HandleString string, HandleString pattern,
                             HandleString replacement)
 {
     MOZ_ASSERT(string);
     MOZ_ASSERT(pattern);
     MOZ_ASSERT(replacement);
 
     if (!string->length())
         return string;
@@ -2844,32 +2829,28 @@ js::str_flat_replace_string(JSContext *c
             if (!StrFlatReplaceGlobal<Latin1Char, char16_t>(cx, linearStr, linearPat, linearRepl, sb))
                 return nullptr;
         } else {
             if (!StrFlatReplaceGlobal<Latin1Char, Latin1Char>(cx, linearStr, linearPat, linearRepl, sb))
                 return nullptr;
         }
     }
 
-    JSString *str = sb.finishString();
-    if (!str)
-        return nullptr;
-
-    return str;
+    return sb.finishString();
 }
 
 JSString*
 js::str_replace_string_raw(JSContext* cx, HandleString string, HandleString pattern,
                            HandleString replacement)
 {
     RootedLinearString repl(cx, replacement->ensureLinear(cx));
     if (!repl)
         return nullptr;
 
-    RootedAtom pat(cx, AtomizeString(cx, pattern));
+    RootedLinearString pat(cx, pattern->ensureLinear(cx));
     if (!pat)
         return nullptr;
 
     size_t patternLength = pat->length();
     int32_t match;
     uint32_t dollarIndex;
 
     {
@@ -2888,18 +2869,23 @@ js::str_replace_string_raw(JSContext* cx
             return nullptr;
     } else {
         match = StringMatch(&string->asLinear(), pat, 0);
     }
 
     if (match < 0)
         return string;
 
-    if (dollarIndex != UINT32_MAX)
-        return BuildDollarReplacement(cx, string, repl, dollarIndex, match, patternLength);
+    if (dollarIndex != UINT32_MAX) {
+        repl = InterpretDollarReplacement(cx, string, repl, dollarIndex, match, patternLength);
+        if (!repl)
+            return nullptr;
+    } else if (string->isRope()) {
+        return BuildFlatRopeReplacement(cx, string, repl, match, patternLength);
+    }
     return BuildFlatReplacement(cx, string, repl, match, patternLength);
 }
 
 // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
 static ArrayObject*
 SplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleLinearString sep,
             HandleObjectGroup group)
 {
@@ -3060,35 +3046,35 @@ SplitSingleCharHelper(JSContext* cx, Han
     if (!sub || !splits.append(StringValue(sub)))
         return nullptr;
 
     return NewCopiedArrayTryUseGroup(cx, group, splits.begin(), splits.length());
 }
 
 // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
 static ArrayObject*
-SplitSingleCharHelper(JSContext* cx, HandleLinearString str, char16_t ch, HandleObjectGroup group) {
-
+SplitSingleCharHelper(JSContext* cx, HandleLinearString str, char16_t ch, HandleObjectGroup group)
+{
     // Step 12.
     size_t strLength = str->length();
 
     AutoStableStringChars linearChars(cx);
     if (!linearChars.init(cx, str))
         return nullptr;
 
     if (linearChars.isLatin1())
         return SplitSingleCharHelper(cx, str, linearChars.latin1Chars(), strLength, ch, group);
 
     return SplitSingleCharHelper(cx, str, linearChars.twoByteChars(), strLength, ch, group);
 }
 
 // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
 ArrayObject*
-js::str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep, uint32_t limit)
-
+js::str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep,
+                     uint32_t limit)
 {
     RootedLinearString linearStr(cx, str->ensureLinear(cx));
     if (!linearStr)
         return nullptr;
 
     RootedLinearString linearSep(cx, sep->ensureLinear(cx));
     if (!linearSep)
         return nullptr;
@@ -3806,38 +3792,16 @@ js::StringEqualsAscii(JSLinearString* st
     const Latin1Char* latin1 = reinterpret_cast<const Latin1Char*>(asciiBytes);
 
     AutoCheckCannotGC nogc;
     return str->hasLatin1Chars()
            ? PodEqual(latin1, str->latin1Chars(nogc), length)
            : EqualChars(latin1, str->twoByteChars(nogc), length);
 }
 
-size_t
-js_strlen(const char16_t* s)
-{
-    const char16_t* t;
-
-    for (t = s; *t != 0; t++)
-        continue;
-    return (size_t)(t - s);
-}
-
-int32_t
-js_strcmp(const char16_t* lhs, const char16_t* rhs)
-{
-    while (true) {
-        if (*lhs != *rhs)
-            return int32_t(*lhs) - int32_t(*rhs);
-        if (*lhs == 0)
-            return 0;
-        ++lhs; ++rhs;
-    }
-}
-
 int32_t
 js_fputs(const char16_t* s, FILE* f)
 {
     while (*s != 0) {
         if (fputwc(wchar_t(*s), f) == WEOF)
             return WEOF;
         s++;
     }
@@ -3914,37 +3878,24 @@ js_strchr_limit(const CharT* s, char16_t
 
 template const Latin1Char*
 js_strchr_limit(const Latin1Char* s, char16_t c, const Latin1Char* limit);
 
 template const char16_t*
 js_strchr_limit(const char16_t* s, char16_t c, const char16_t* limit);
 
 char16_t*
-js::InflateString(JSContext* cx, const char* bytes, size_t* lengthp)
+js::InflateString(JSContext* cx, const char* bytes, size_t length)
 {
-    size_t nchars;
-    char16_t* chars;
-    size_t nbytes = *lengthp;
-
-    nchars = nbytes;
-    chars = cx->pod_malloc<char16_t>(nchars + 1);
+    char16_t* chars = cx->pod_malloc<char16_t>(length + 1);
     if (!chars)
-        goto bad;
-    for (size_t i = 0; i < nchars; i++)
-        chars[i] = (unsigned char) bytes[i];
-    *lengthp = nchars;
-    chars[nchars] = 0;
+        return nullptr;
+    CopyAndInflateChars(chars, bytes, length);
+    chars[length] = 0;
     return chars;
-
-  bad:
-    // For compatibility with callers of JS_DecodeBytes we must zero lengthp
-    // on errors.
-    *lengthp = 0;
-    return nullptr;
 }
 
 template <typename CharT>
 bool
 js::DeflateStringToBuffer(JSContext* maybecx, const CharT* src, size_t srclen,
                           char* dst, size_t* dstlenp)
 {
     size_t dstlen = *dstlenp;
@@ -3970,83 +3921,16 @@ js::DeflateStringToBuffer(JSContext* may
 
 template bool
 js::DeflateStringToBuffer(JSContext* maybecx, const char16_t* src, size_t srclen,
                           char* dst, size_t* dstlenp);
 
 #define ____ false
 
 /*
- * Identifier start chars:
- * -      36:    $
- * -  65..90: A..Z
- * -      95:    _
- * - 97..122: a..z
- */
-const bool js_isidstart[] = {
-/*       0     1     2     3     4     5     6     7     8     9  */
-/*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
-/*  4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
-/*  7 */ true, true, true, true, true, true, true, true, true, true,
-/*  8 */ true, true, true, true, true, true, true, true, true, true,
-/*  9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
-/* 10 */ true, true, true, true, true, true, true, true, true, true,
-/* 11 */ true, true, true, true, true, true, true, true, true, true,
-/* 12 */ true, true, true, ____, ____, ____, ____, ____
-};
-
-/*
- * Identifier chars:
- * -      36:    $
- * -  48..57: 0..9
- * -  65..90: A..Z
- * -      95:    _
- * - 97..122: a..z
- */
-const bool js_isident[] = {
-/*       0     1     2     3     4     5     6     7     8     9  */
-/*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____,
-/*  4 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true,
-/*  5 */ true, true, true, true, true, true, true, true, ____, ____,
-/*  6 */ ____, ____, ____, ____, ____, true, true, true, true, true,
-/*  7 */ true, true, true, true, true, true, true, true, true, true,
-/*  8 */ true, true, true, true, true, true, true, true, true, true,
-/*  9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
-/* 10 */ true, true, true, true, true, true, true, true, true, true,
-/* 11 */ true, true, true, true, true, true, true, true, true, true,
-/* 12 */ true, true, true, ____, ____, ____, ____, ____
-};
-
-/* Whitespace chars: '\t', '\n', '\v', '\f', '\r', ' '. */
-const bool js_isspace[] = {
-/*       0     1     2     3     4     5     6     7     8     9  */
-/*  0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, true,
-/*  1 */ true, true, true, true, ____, ____, ____, ____, ____, ____,
-/*  2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  3 */ ____, ____, true, ____, ____, ____, ____, ____, ____, ____,
-/*  4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  6 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/*  9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
-/* 12 */ ____, ____, ____, ____, ____, ____, ____, ____
-};
-
-/*
  * Uri reserved chars + #:
  * - 35: #
  * - 36: $
  * - 38: &
  * - 43: +
  * - 44: ,
  * - 47: /
  * - 58: :
@@ -4101,18 +3985,16 @@ static const bool js_isUriUnescaped[] = 
 /*  9 */ true, ____, ____, ____, ____, true, ____, true, true, true,
 /* 10 */ true, true, true, true, true, true, true, true, true, true,
 /* 11 */ true, true, true, true, true, true, true, true, true, true,
 /* 12 */ true, true, true, ____, ____, ____, true, ____
 };
 
 #undef ____
 
-#define URI_CHUNK 64U
-
 static inline bool
 TransferBufferToString(StringBuffer& sb, MutableHandleValue rval)
 {
     JSString* str = sb.finishString();
     if (!str)
         return false;
     rval.setString(str);
     return true;
@@ -4122,85 +4004,101 @@ TransferBufferToString(StringBuffer& sb,
  * ECMA 3, 15.1.3 URI Handling Function Properties
  *
  * The following are implementations of the algorithms
  * given in the ECMA specification for the hidden functions
  * 'Encode' and 'Decode'.
  */
 enum EncodeResult { Encode_Failure, Encode_BadUri, Encode_Success };
 
+// Bug 1403318: GCC sometimes inlines this Encode function rather than the
+// caller Encode function. Annotate both functions with MOZ_NEVER_INLINE resp.
+// MOZ_ALWAYS_INLINE to ensure we get the desired inlining behavior.
 template <typename CharT>
-static EncodeResult
-Encode(StringBuffer& sb, const CharT* chars, size_t length,
-       const bool* unescapedSet, const bool* unescapedSet2)
+static MOZ_NEVER_INLINE EncodeResult
+Encode(StringBuffer& sb, const CharT* chars, size_t length, const bool* unescapedSet)
 {
-    static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */
-
-    char16_t hexBuf[4];
+    Latin1Char hexBuf[4];
     hexBuf[0] = '%';
     hexBuf[3] = 0;
 
+    auto appendEncoded = [&sb, &hexBuf](Latin1Char c) {
+        static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */
+
+        hexBuf[1] = HexDigits[c >> 4];
+        hexBuf[2] = HexDigits[c & 0xf];
+        return sb.append(hexBuf, 3);
+    };
+
     for (size_t k = 0; k < length; k++) {
-        char16_t c = chars[k];
-        if (c < 128 && (unescapedSet[c] || (unescapedSet2 && unescapedSet2[c]))) {
-            if (!sb.append(c))
+        CharT c = chars[k];
+        if (c < 128 && (js_isUriUnescaped[c] || (unescapedSet && unescapedSet[c]))) {
+            if (!sb.append(Latin1Char(c)))
                 return Encode_Failure;
         } else {
-            if (unicode::IsTrailSurrogate(c))
-                return Encode_BadUri;
-
-            uint32_t v;
-            if (!unicode::IsLeadSurrogate(c)) {
-                v = c;
+            if (mozilla::IsSame<CharT, Latin1Char>::value) {
+                if (c < 0x80) {
+                    if (!appendEncoded(c))
+                        return Encode_Failure;
+                } else {
+                    if (!appendEncoded(0xC0 | (c >> 6)) || !appendEncoded(0x80 | (c & 0x3F)))
+                        return Encode_Failure;
+                }
             } else {
-                k++;
-                if (k == length)
+                if (unicode::IsTrailSurrogate(c))
                     return Encode_BadUri;
 
-                char16_t c2 = chars[k];
-                if (!unicode::IsTrailSurrogate(c2))
-                    return Encode_BadUri;
-
-                v = unicode::UTF16Decode(c, c2);
-            }
-            uint8_t utf8buf[4];
-            size_t L = OneUcs4ToUtf8Char(utf8buf, v);
-            for (size_t j = 0; j < L; j++) {
-                hexBuf[1] = HexDigits[utf8buf[j] >> 4];
-                hexBuf[2] = HexDigits[utf8buf[j] & 0xf];
-                if (!sb.append(hexBuf, 3))
-                    return Encode_Failure;
+                uint32_t v;
+                if (!unicode::IsLeadSurrogate(c)) {
+                    v = c;
+                } else {
+                    k++;
+                    if (k == length)
+                        return Encode_BadUri;
+
+                    char16_t c2 = chars[k];
+                    if (!unicode::IsTrailSurrogate(c2))
+                        return Encode_BadUri;
+
+                    v = unicode::UTF16Decode(c, c2);
+                }
+
+                uint8_t utf8buf[4];
+                size_t L = OneUcs4ToUtf8Char(utf8buf, v);
+                for (size_t j = 0; j < L; j++) {
+                    if (!appendEncoded(utf8buf[j]))
+                        return Encode_Failure;
+                }
             }
         }
     }
 
     return Encode_Success;
 }
 
-static bool
-Encode(JSContext* cx, HandleLinearString str, const bool* unescapedSet,
-       const bool* unescapedSet2, MutableHandleValue rval)
+static MOZ_ALWAYS_INLINE bool
+Encode(JSContext* cx, HandleLinearString str, const bool* unescapedSet, MutableHandleValue rval)
 {
     size_t length = str->length();
     if (length == 0) {
         rval.setString(cx->runtime()->emptyString);
         return true;
     }
 
     StringBuffer sb(cx);
     if (!sb.reserve(length))
         return false;
 
     EncodeResult res;
     if (str->hasLatin1Chars()) {
         AutoCheckCannotGC nogc;
-        res = Encode(sb, str->latin1Chars(nogc), str->length(), unescapedSet, unescapedSet2);
+        res = Encode(sb, str->latin1Chars(nogc), str->length(), unescapedSet);
     } else {
         AutoCheckCannotGC nogc;
-        res = Encode(sb, str->twoByteChars(nogc), str->length(), unescapedSet, unescapedSet2);
+        res = Encode(sb, str->twoByteChars(nogc), str->length(), unescapedSet);
     }
 
     if (res == Encode_Failure)
         return false;
 
     if (res == Encode_BadUri) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_URI);
         return false;
@@ -4212,29 +4110,36 @@ Encode(JSContext* cx, HandleLinearString
 
 enum DecodeResult { Decode_Failure, Decode_BadUri, Decode_Success };
 
 template <typename CharT>
 static DecodeResult
 Decode(StringBuffer& sb, const CharT* chars, size_t length, const bool* reservedSet)
 {
     for (size_t k = 0; k < length; k++) {
-        char16_t c = chars[k];
+        CharT c = chars[k];
         if (c == '%') {
             size_t start = k;
             if ((k + 2) >= length)
                 return Decode_BadUri;
 
             if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
                 return Decode_BadUri;
 
             uint32_t B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
             k += 2;
-            if (!(B & 0x80)) {
-                c = char16_t(B);
+            if (B < 128) {
+                c = CharT(B);
+                if (reservedSet && reservedSet[c]) {
+                    if (!sb.append(chars + start, k - start + 1))
+                        return Decode_Failure;
+                } else {
+                    if (!sb.append(c))
+                        return Decode_Failure;
+                }
             } else {
                 int n = 1;
                 while (B & (0x80 >> n))
                     n++;
 
                 if (n == 1 || n > 4)
                     return Decode_BadUri;
 
@@ -4253,36 +4158,32 @@ Decode(StringBuffer& sb, const CharT* ch
 
                     B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
                     if ((B & 0xC0) != 0x80)
                         return Decode_BadUri;
 
                     k += 2;
                     octets[j] = char(B);
                 }
+
                 uint32_t v = JS::Utf8ToOneUcs4Char(octets, n);
+                MOZ_ASSERT(v >= 128);
                 if (v >= unicode::NonBMPMin) {
                     if (v > unicode::NonBMPMax)
                         return Decode_BadUri;
 
-                    char16_t H = unicode::LeadSurrogate(v);
-                    if (!sb.append(H))
+                    if (!sb.append(unicode::LeadSurrogate(v)))
+                        return Decode_Failure;
+                    if (!sb.append(unicode::TrailSurrogate(v)))
                         return Decode_Failure;
-                    c = unicode::TrailSurrogate(v);
                 } else {
-                    c = char16_t(v);
+                    if (!sb.append(char16_t(v)))
+                        return Decode_Failure;
                 }
             }
-            if (c < 128 && reservedSet && reservedSet[c]) {
-                if (!sb.append(chars + start, k - start + 1))
-                    return Decode_Failure;
-            } else {
-                if (!sb.append(c))
-                    return Decode_Failure;
-            }
         } else {
             if (!sb.append(c))
                 return Decode_Failure;
         }
     }
 
     return Decode_Success;
 }
@@ -4318,60 +4219,61 @@ Decode(JSContext* cx, HandleLinearString
     MOZ_ASSERT(res == Decode_Success);
     return TransferBufferToString(sb, rval);
 }
 
 static bool
 str_decodeURI(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
+    RootedLinearString str(cx, ArgToLinearString(cx, args, 0));
     if (!str)
         return false;
 
     return Decode(cx, str, js_isUriReservedPlusPound, args.rval());
 }
 
 static bool
 str_decodeURI_Component(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
+    RootedLinearString str(cx, ArgToLinearString(cx, args, 0));
     if (!str)
         return false;
 
     return Decode(cx, str, nullptr, args.rval());
 }
 
 static bool
 str_encodeURI(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
+    RootedLinearString str(cx, ArgToLinearString(cx, args, 0));
     if (!str)
         return false;
 
-    return Encode(cx, str, js_isUriUnescaped, js_isUriReservedPlusPound, args.rval());
+    return Encode(cx, str, js_isUriReservedPlusPound, args.rval());
 }
 
 static bool
 str_encodeURI_Component(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    RootedLinearString str(cx, ArgToRootedString(cx, args, 0));
+    RootedLinearString str(cx, ArgToLinearString(cx, args, 0));
     if (!str)
         return false;
 
-    return Encode(cx, str, js_isUriUnescaped, nullptr, args.rval());
+    return Encode(cx, str, nullptr, args.rval());
 }
 
 bool
 js::EncodeURI(JSContext* cx, StringBuffer& sb, const char* chars, size_t length)
 {
-    EncodeResult result = Encode(sb, chars, length, js_isUriUnescaped, js_isUriReservedPlusPound);
+    EncodeResult result = Encode(sb, reinterpret_cast<const Latin1Char*>(chars), length,
+                                 js_isUriReservedPlusPound);
     if (result == EncodeResult::Encode_Failure)
         return false;
     if (result == EncodeResult::Encode_BadUri) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_URI);
         return false;
     }
     return true;
 }
@@ -4619,29 +4521,20 @@ BuildFlatMatchArray(JSContext* cx, Handl
     rval.setObject(*arr);
     return true;
 }
 
 #ifdef DEBUG
 static bool
 CallIsStringOptimizable(JSContext* cx, const char* name, bool* result)
 {
-    JSAtom* atom = Atomize(cx, name, strlen(name));
-    if (!atom)
-        return false;
-    RootedPropertyName propName(cx, atom->asPropertyName());
-
-    RootedValue funcVal(cx);
-    if (!GlobalObject::getSelfHostedFunction(cx, cx->global(), propName, propName, 0, &funcVal))
-        return false;
-
     FixedInvokeArgs<0> args(cx);
 
     RootedValue rval(cx);
-    if (!Call(cx, funcVal, UndefinedHandleValue, args, &rval))
+    if (!CallSelfHostedFunction(cx, name, UndefinedHandleValue, args, &rval))
         return false;
 
     *result = rval.toBoolean();
     return true;
 }
 #endif
 
 bool
@@ -4653,17 +4546,17 @@ js::FlatStringMatch(JSContext* cx, unsig
     MOZ_ASSERT(args[1].isString());
 #ifdef DEBUG
     bool isOptimizable = false;
     if (!CallIsStringOptimizable(cx, "IsStringMatchOptimizable", &isOptimizable))
         return false;
     MOZ_ASSERT(isOptimizable);
 #endif
 
-    RootedString str(cx,args[0].toString());
+    RootedString str(cx, args[0].toString());
     RootedString pattern(cx, args[1].toString());
 
     bool isFlat = false;
     int32_t match = 0;
     if (!FlatStringMatchHelper(cx, str, pattern, &isFlat, &match))
         return false;
 
     if (!isFlat) {
@@ -4683,17 +4576,17 @@ js::FlatStringSearch(JSContext* cx, unsi
     MOZ_ASSERT(args[1].isString());
 #ifdef DEBUG
     bool isOptimizable = false;
     if (!CallIsStringOptimizable(cx, "IsStringSearchOptimizable", &isOptimizable))
         return false;
     MOZ_ASSERT(isOptimizable);
 #endif
 
-    RootedString str(cx,args[0].toString());
+    RootedString str(cx, args[0].toString());
     RootedString pattern(cx, args[1].toString());
 
     bool isFlat = false;
     int32_t match = 0;
     if (!FlatStringMatchHelper(cx, str, pattern, &isFlat, &match))
         return false;
 
     if (!isFlat) {
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -6,16 +6,17 @@
 
 #ifndef jsstr_h
 #define jsstr_h
 
 #include "mozilla/HashFunctions.h"
 #include "mozilla/PodOperations.h"
 
 #include <stdio.h>
+#include <string.h>
 
 #include "jsutil.h"
 #include "NamespaceImports.h"
 
 #include "gc/Rooting.h"
 #include "js/RootingAPI.h"
 #include "js/UniquePtr.h"
 #include "vm/Printer.h"
@@ -62,63 +63,39 @@ CompareChars(const Char1* s1, size_t len
     return int32_t(len1 - len2);
 }
 
 extern int32_t
 CompareChars(const char16_t* s1, size_t len1, JSLinearString* s2);
 
 }  /* namespace js */
 
-struct JSSubString {
-    JSLinearString* base;
-    size_t          offset;
-    size_t          length;
-
-    JSSubString() { mozilla::PodZero(this); }
-
-    void initEmpty(JSLinearString* base) {
-        this->base = base;
-        offset = length = 0;
-    }
-    void init(JSLinearString* base, size_t offset, size_t length) {
-        this->base = base;
-        this->offset = offset;
-        this->length = length;
-    }
-};
-
 /*
  * Shorthands for ASCII (7-bit) decimal and hex conversion.
  * Manually inline isdigit and isxdigit for performance; MSVC doesn't do this for us.
  */
 #define JS7_ISDEC(c)    ((((unsigned)(c)) - '0') <= 9)
 #define JS7_ISA2F(c)    ((((((unsigned)(c)) - 'a') <= 5) || (((unsigned)(c)) - 'A') <= 5))
 #define JS7_UNDEC(c)    ((c) - '0')
 #define JS7_ISOCT(c)    ((((unsigned)(c)) - '0') <= 7)
 #define JS7_UNOCT(c)    (JS7_UNDEC(c))
 #define JS7_ISHEX(c)    ((c) < 128 && (JS7_ISDEC(c) || JS7_ISA2F(c)))
 #define JS7_UNHEX(c)    (unsigned)(JS7_ISDEC(c) ? (c) - '0' : 10 + tolower(c) - 'a')
 #define JS7_ISLET(c)    ((c) < 128 && isalpha(c))
 
-extern size_t
-js_strlen(const char16_t* s);
-
-extern int32_t
-js_strcmp(const char16_t* lhs, const char16_t* rhs);
+static MOZ_ALWAYS_INLINE size_t
+js_strlen(const char16_t* s)
+{
+    return std::char_traits<char16_t>::length(s);
+}
 
 template <typename CharT>
 extern const CharT*
 js_strchr_limit(const CharT* s, char16_t c, const CharT* limit);
 
-static MOZ_ALWAYS_INLINE void
-js_strncpy(char16_t* dst, const char16_t* src, size_t nelem)
-{
-    return mozilla::PodCopy(dst, src, nelem);
-}
-
 extern int32_t
 js_fputs(const char16_t* s, FILE* f);
 
 namespace js {
 
 /* Initialize the String class, returning its prototype object. */
 extern JSObject*
 InitStringClass(JSContext* cx, HandleObject obj);
@@ -268,22 +245,21 @@ EqualChars(const Char1* s1, const Char2*
  * Negative, overlarge, swapped, etc. |beginInt| and |lengthInt| are forbidden
  * and constitute API misuse.
  */
 JSString*
 SubstringKernel(JSContext* cx, HandleString str, int32_t beginInt, int32_t lengthInt);
 
 /*
  * Inflate bytes in ASCII encoding to char16_t code units. Return null on error,
- * otherwise return the char16_t buffer that was malloc'ed. length is updated to
- * the length of the new string (in char16_t code units). A null char is
- * appended, but it is not included in the length.
+ * otherwise return the char16_t buffer that was malloc'ed. A null char is
+ * appended.
  */
 extern char16_t*
-InflateString(JSContext* cx, const char* bytes, size_t* length);
+InflateString(JSContext* cx, const char* bytes, size_t length);
 
 /*
  * Inflate bytes to JS chars in an existing buffer. 'dst' must be large
  * enough for 'srclen' char16_t code units. The buffer is NOT null-terminated.
  */
 inline void
 CopyAndInflateChars(char16_t* dst, const char* src, size_t srclen)
 {
--- a/js/src/proxy/DeadObjectProxy.cpp
+++ b/js/src/proxy/DeadObjectProxy.cpp
@@ -9,296 +9,181 @@
 #include "jsapi.h"
 #include "jsfun.h" // XXXefaust Bug 1064662
 
 #include "vm/ProxyObject.h"
 
 using namespace js;
 using namespace js::gc;
 
+const DeadObjectProxy DeadObjectProxy::singleton;
+const char DeadObjectProxy::family = 0;
+
 static void
 ReportDead(JSContext *cx)
 {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
-                                                  MutableHandle<PropertyDescriptor> desc) const
+DeadObjectProxy::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
+                                          MutableHandle<PropertyDescriptor> desc) const
 {
     ReportDead(cx);
     return false;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id,
-                                        Handle<PropertyDescriptor> desc,
-                                        ObjectOpResult& result) const
+DeadObjectProxy::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id,
+                                Handle<PropertyDescriptor> desc,
+                                ObjectOpResult& result) const
 {
     ReportDead(cx);
     return false;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::ownPropertyKeys(JSContext* cx, HandleObject wrapper,
-                                         AutoIdVector& props) const
+DeadObjectProxy::ownPropertyKeys(JSContext* cx, HandleObject wrapper,
+                                 AutoIdVector& props) const
 {
     ReportDead(cx);
     return false;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::delete_(JSContext* cx, HandleObject wrapper, HandleId id,
-                                 ObjectOpResult& result) const
+DeadObjectProxy::delete_(JSContext* cx, HandleObject wrapper, HandleId id,
+                         ObjectOpResult& result) const
 {
     ReportDead(cx);
     return false;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::getPrototype(JSContext* cx, HandleObject proxy,
-                                      MutableHandleObject protop) const
+DeadObjectProxy::getPrototype(JSContext* cx, HandleObject proxy,
+                              MutableHandleObject protop) const
 {
     protop.set(nullptr);
     return true;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
-                                                MutableHandleObject protop) const
+DeadObjectProxy::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
+                                        MutableHandleObject protop) const
 {
     *isOrdinary = false;
     return true;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::preventExtensions(JSContext* cx, HandleObject proxy,
-                                           ObjectOpResult& result) const
+DeadObjectProxy::preventExtensions(JSContext* cx, HandleObject proxy,
+                                   ObjectOpResult& result) const
 {
     ReportDead(cx);
     return false;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const
+DeadObjectProxy::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const
 {
     // This is kind of meaningless, but dead-object semantics aside,
     // [[Extensible]] always being true is consistent with other proxy types.
     *extensible = true;
     return true;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::call(JSContext* cx, HandleObject wrapper, const CallArgs& args) const
+DeadObjectProxy::call(JSContext* cx, HandleObject wrapper, const CallArgs& args) const
 {
     ReportDead(cx);
     return false;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::construct(JSContext* cx, HandleObject wrapper, const CallArgs& args) const
+DeadObjectProxy::construct(JSContext* cx, HandleObject wrapper, const CallArgs& args) const
 {
     ReportDead(cx);
     return false;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
-                                    const CallArgs& args) const
-{
-    ReportDead(cx);
-    return false;
-}
-
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
-bool
-DeadObjectProxy<CC, BF>::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
-                                     bool* bp) const
+DeadObjectProxy::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+                            const CallArgs& args) const
 {
     ReportDead(cx);
     return false;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
+DeadObjectProxy::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
+                             bool* bp) const
 {
     ReportDead(cx);
     return false;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 bool
-DeadObjectProxy<CC, BF>::isArray(JSContext* cx, HandleObject obj, JS::IsArrayAnswer* answer) const
+DeadObjectProxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
 {
     ReportDead(cx);
     return false;
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
+bool
+DeadObjectProxy::isArray(JSContext* cx, HandleObject obj, JS::IsArrayAnswer* answer) const
+{
+    ReportDead(cx);
+    return false;
+}
+
 const char*
-DeadObjectProxy<CC, BF>::className(JSContext* cx, HandleObject wrapper) const
+DeadObjectProxy::className(JSContext* cx, HandleObject wrapper) const
 {
     return "DeadObject";
 }
 
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
 JSString*
-DeadObjectProxy<CC, BF>::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const
-{
-    ReportDead(cx);
-    return nullptr;
-}
-
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BF>
-RegExpShared*
-DeadObjectProxy<CC, BF>::regexp_toShared(JSContext* cx, HandleObject proxy) const
+DeadObjectProxy::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const
 {
     ReportDead(cx);
     return nullptr;
 }
 
-template <>
-const char DeadObjectProxy<DeadProxyNotCallableNotConstructor,
-                           DeadProxyBackgroundFinalized::Yes>::family = 0;
-template <>
-const char DeadObjectProxy<DeadProxyNotCallableNotConstructor,
-                           DeadProxyBackgroundFinalized::No>::family = 0;
-template <>
-const char DeadObjectProxy<DeadProxyNotCallableIsConstructor,
-                           DeadProxyBackgroundFinalized::Yes>::family = 0;
-template <>
-const char DeadObjectProxy<DeadProxyNotCallableIsConstructor,
-                           DeadProxyBackgroundFinalized::No>::family = 0;
-template <>
-const char DeadObjectProxy<DeadProxyIsCallableNotConstructor,
-                           DeadProxyBackgroundFinalized::Yes>::family = 0;
-template <>
-const char DeadObjectProxy<DeadProxyIsCallableNotConstructor,
-                           DeadProxyBackgroundFinalized::No>::family = 0;
-template <>
-const char DeadObjectProxy<DeadProxyIsCallableIsConstructor,
-                           DeadProxyBackgroundFinalized::Yes>::family = 0;
-template <>
-const char DeadObjectProxy<DeadProxyIsCallableIsConstructor,
-                           DeadProxyBackgroundFinalized::No>::family = 0;
+RegExpShared*
+DeadObjectProxy::regexp_toShared(JSContext* cx, HandleObject proxy) const
+{
+    ReportDead(cx);
+    return nullptr;
+}
 
 bool
 js::IsDeadProxyObject(JSObject* obj)
 {
-    return
-        IsDerivedProxyObject(obj,
-          DeadObjectProxy<DeadProxyNotCallableNotConstructor,
-                          DeadProxyBackgroundFinalized::Yes>::singleton()) ||
-        IsDerivedProxyObject(obj,
-          DeadObjectProxy<DeadProxyNotCallableNotConstructor,
-                          DeadProxyBackgroundFinalized::No>::singleton()) ||
-        IsDerivedProxyObject(obj,
-          DeadObjectProxy<DeadProxyIsCallableIsConstructor,
-                          DeadProxyBackgroundFinalized::Yes>::singleton()) ||
-        IsDerivedProxyObject(obj,
-          DeadObjectProxy<DeadProxyIsCallableIsConstructor,
-                          DeadProxyBackgroundFinalized::No>::singleton()) ||
-        IsDerivedProxyObject(obj,
-          DeadObjectProxy<DeadProxyIsCallableNotConstructor,
-                          DeadProxyBackgroundFinalized::Yes>::singleton()) ||
-        IsDerivedProxyObject(obj,
-          DeadObjectProxy<DeadProxyIsCallableNotConstructor,
-                          DeadProxyBackgroundFinalized::No>::singleton()) ||
-        IsDerivedProxyObject(obj,
-          DeadObjectProxy<DeadProxyNotCallableIsConstructor,
-                          DeadProxyBackgroundFinalized::Yes>::singleton()) ||
-        IsDerivedProxyObject(obj,
-          DeadObjectProxy<DeadProxyNotCallableIsConstructor,
-                          DeadProxyBackgroundFinalized::No>::singleton());
+    return IsDerivedProxyObject(obj, &DeadObjectProxy::singleton);
 }
 
-
-const BaseProxyHandler*
-js::SelectDeadProxyHandler(ProxyObject* obj)
+Value
+js::DeadProxyTargetValue(ProxyObject* obj)
 {
     // When nuking scripted proxies, isCallable and isConstructor values for
     // the proxy needs to be preserved.  So does background-finalization status.
-    uint32_t callable = obj->handler()->isCallable(obj);
-    uint32_t constructor = obj->handler()->isConstructor(obj);
-    bool finalizeInBackground = obj->handler()->finalizeInBackground(obj->private_());
-
-    if (callable) {
-        if (constructor) {
-            if (finalizeInBackground) {
-                return DeadObjectProxy<DeadProxyIsCallableIsConstructor,
-                                       DeadProxyBackgroundFinalized::Yes>::singleton();
-            } else {
-                return DeadObjectProxy<DeadProxyIsCallableIsConstructor,
-                                       DeadProxyBackgroundFinalized::No>::singleton();
-            }
-        }
-
-        if (finalizeInBackground) {
-            return DeadObjectProxy<DeadProxyIsCallableNotConstructor,
-                                   DeadProxyBackgroundFinalized::Yes>::singleton();
-        }
-
-        return DeadObjectProxy<DeadProxyIsCallableNotConstructor,
-                               DeadProxyBackgroundFinalized::No>::singleton();
-    }
-
-    if (constructor) {
-        if (finalizeInBackground) {
-            return DeadObjectProxy<DeadProxyNotCallableIsConstructor,
-                                   DeadProxyBackgroundFinalized::Yes>::singleton();
-        }
-
-        return DeadObjectProxy<DeadProxyNotCallableIsConstructor,
-                               DeadProxyBackgroundFinalized::No>::singleton();
-    }
-
-    if (finalizeInBackground) {
-        return DeadObjectProxy<DeadProxyNotCallableNotConstructor,
-                               DeadProxyBackgroundFinalized::Yes>::singleton();
-    }
-
-    return DeadObjectProxy<DeadProxyNotCallableNotConstructor,
-                           DeadProxyBackgroundFinalized::No>::singleton();
+    int32_t flags = 0;
+    if (obj->handler()->isCallable(obj))
+        flags |= DeadObjectProxyIsCallable;
+    if (obj->handler()->isConstructor(obj))
+         flags |= DeadObjectProxyIsConstructor;
+    if (obj->handler()->finalizeInBackground(obj->private_()))
+         flags |= DeadObjectProxyIsBackgroundFinalized;
+    return Int32Value(flags);
 }
 
 JSObject*
 js::NewDeadProxyObject(JSContext* cx, JSObject* origObj)
 {
     MOZ_ASSERT_IF(origObj, origObj->is<ProxyObject>());
 
-    const BaseProxyHandler* handler;
+    RootedValue target(cx);
     if (origObj && origObj->is<ProxyObject>())
-        handler = SelectDeadProxyHandler(&origObj->as<ProxyObject>());
+        target = DeadProxyTargetValue(&origObj->as<ProxyObject>());
     else
-        handler = DeadObjectProxy<DeadProxyNotCallableNotConstructor,
-                                  DeadProxyBackgroundFinalized::Yes>::singleton();
+        target = Int32Value(DeadObjectProxyIsBackgroundFinalized);
 
-    return NewProxyObject(cx, handler, NullHandleValue, nullptr, ProxyOptions());
+    return NewProxyObject(cx, &DeadObjectProxy::singleton, target, nullptr, ProxyOptions());
 }
--- a/js/src/proxy/DeadObjectProxy.h
+++ b/js/src/proxy/DeadObjectProxy.h
@@ -8,32 +8,23 @@
 #define proxy_DeadObjectProxy_h
 
 #include "js/Proxy.h"
 
 namespace js {
 
 class ProxyObject;
 
-enum DeadProxyIsCallableIsConstructorOption
+enum DeadObjectProxyFlags
 {
-    DeadProxyNotCallableNotConstructor,
-    DeadProxyNotCallableIsConstructor,
-    DeadProxyIsCallableNotConstructor,
-    DeadProxyIsCallableIsConstructor
+    DeadObjectProxyIsCallable            = 1 << 0,
+    DeadObjectProxyIsConstructor         = 1 << 1,
+    DeadObjectProxyIsBackgroundFinalized = 1 << 2
 };
 
-enum class DeadProxyBackgroundFinalized
-{
-    Yes,
-    No
-};
-
-template <DeadProxyIsCallableIsConstructorOption CC,
-          DeadProxyBackgroundFinalized BackgroundFinalized>
 class DeadObjectProxy : public BaseProxyHandler
 {
   public:
     explicit constexpr DeadObjectProxy()
       : BaseProxyHandler(&family)
     { }
 
     /* Standard internal methods. */
@@ -66,38 +57,39 @@ class DeadObjectProxy : public BaseProxy
     virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy,
                                    bool isToSource) const override;
     virtual RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy) const override;
 
     virtual bool isCallable(JSObject* obj) const override {
-        return CC == DeadProxyIsCallableIsConstructor || CC == DeadProxyIsCallableNotConstructor;
+        return flags(obj) & DeadObjectProxyIsCallable;
     }
     virtual bool isConstructor(JSObject* obj) const override {
-        return CC == DeadProxyIsCallableIsConstructor || CC == DeadProxyNotCallableIsConstructor;
+        return flags(obj) & DeadObjectProxyIsConstructor;
     }
 
     virtual bool finalizeInBackground(const JS::Value& priv) const override {
-        return BackgroundFinalized == DeadProxyBackgroundFinalized::Yes;
+        return priv.toInt32() & DeadObjectProxyIsBackgroundFinalized;
     }