Merge m-c to b2ginbound a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 27 Mar 2015 17:18:16 -0700
changeset 266624 21cfa00b88ddfd6d3247d80f01b63fad742f6e49
parent 266623 7b56591efd6872447f05294ba7c49ff5e3ea5482 (current diff)
parent 266497 ad587ca628cfc317941aa2c61b447226fbbd214a (diff)
child 266625 39b8d7178234d22933f9476e82f79ab395eefbf3
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2ginbound a=merge
--- a/accessible/atk/nsMaiInterfaceDocument.cpp
+++ b/accessible/atk/nsMaiInterfaceDocument.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "InterfaceInitFuncs.h"
 
 #include "Accessible-inl.h"
 #include "AccessibleWrap.h"
 #include "DocAccessible.h"
 #include "nsMai.h"
+#include "ProxyAccessible.h"
 #include "mozilla/Likely.h"
 
 using namespace mozilla::a11y;
 
 static const char* const kDocTypeName = "W3C-doctype";
 static const char* const kDocUrlName = "DocURL";
 static const char* const kMimeTypeName = "MimeType";
 
@@ -41,22 +42,24 @@ documentInterfaceInitCB(AtkDocumentIface
     aIface->get_document_attributes = getDocumentAttributesCB;
     aIface->get_document_attribute_value = getDocumentAttributeValueCB;
     aIface->get_document_locale = getDocumentLocaleCB;
 }
 
 const gchar *
 getDocumentLocaleCB(AtkDocument *aDocument)
 {
+  nsAutoString locale;
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aDocument));
-  if (!accWrap)
-    return nullptr;
+  if (accWrap) {
+    accWrap->Language(locale);
+  } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aDocument))) {
+    proxy->Language(locale);
+  }
 
-  nsAutoString locale;
-  accWrap->Language(locale);
   return locale.IsEmpty() ? nullptr : AccessibleWrap::ReturnString(locale);
 }
 
 static inline GSList *
 prependToList(GSList *aList, const char *const aName, const nsAutoString &aValue)
 {
   if (aValue.IsEmpty())
     return aList;
@@ -66,52 +69,82 @@ prependToList(GSList *aList, const char 
     atkAttr->name = g_strdup(aName);
     atkAttr->value = g_strdup(NS_ConvertUTF16toUTF8(aValue).get());
     return g_slist_prepend(aList, atkAttr);
 }
 
 AtkAttributeSet *
 getDocumentAttributesCB(AtkDocument *aDocument)
 {
+  nsAutoString url;
+  nsAutoString w3cDocType;
+  nsAutoString mimeType;
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aDocument));
-  if (!accWrap || !accWrap->IsDoc())
+  if (accWrap) {
+    if (!accWrap->IsDoc()) {
+      return nullptr;
+    }
+
+    DocAccessible* document = accWrap->AsDoc();
+    document->URL(url);
+    document->DocType(w3cDocType);
+    document->MimeType(mimeType);
+  } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aDocument))) {
+    proxy->URLDocTypeMimeType(url, w3cDocType, mimeType);
+  } else {
     return nullptr;
+  }
 
   // according to atkobject.h, AtkAttributeSet is a GSList
   GSList* attributes = nullptr;
-  DocAccessible* document = accWrap->AsDoc();
-  nsAutoString aURL;
-  document->URL(aURL);
-  attributes = prependToList(attributes, kDocUrlName, aURL);
-
-  nsAutoString aW3CDocType;
-  document->DocType(aW3CDocType);
-  attributes = prependToList(attributes, kDocTypeName, aW3CDocType);
-
-  nsAutoString aMimeType;
-  document->MimeType(aMimeType);
-  attributes = prependToList(attributes, kMimeTypeName, aMimeType);
+  attributes = prependToList(attributes, kDocUrlName, url);
+  attributes = prependToList(attributes, kDocTypeName, w3cDocType);
+  attributes = prependToList(attributes, kMimeTypeName, mimeType);
 
   return attributes;
 }
 
 const gchar *
 getDocumentAttributeValueCB(AtkDocument *aDocument,
                             const gchar *aAttrName)
 {
+  ProxyAccessible* proxy = nullptr;
+  DocAccessible* document = nullptr;
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aDocument));
-  if (!accWrap || !accWrap->IsDoc())
-    return nullptr;
+  if (accWrap) {
+    if (!accWrap->IsDoc()) {
+      return nullptr;
+    }
 
-  DocAccessible* document = accWrap->AsDoc();
+    document = accWrap->AsDoc();
+  } else {
+    proxy = GetProxy(ATK_OBJECT(aDocument));
+    if (!proxy) {
+      return nullptr;
+    }
+  }
+
   nsAutoString attrValue;
-  if (!strcasecmp(aAttrName, kDocTypeName))
-    document->DocType(attrValue);
-  else if (!strcasecmp(aAttrName, kDocUrlName))
-    document->URL(attrValue);
-  else if (!strcasecmp(aAttrName, kMimeTypeName))
-    document->MimeType(attrValue);
-  else
+  if (!strcasecmp(aAttrName, kDocTypeName)) {
+    if (document) {
+      document->DocType(attrValue);
+    } else {
+      proxy->DocType(attrValue);
+    }
+  } else if (!strcasecmp(aAttrName, kDocUrlName)) {
+    if (document) {
+      document->URL(attrValue);
+    } else {
+      proxy->URL(attrValue);
+    }
+  } else if (!strcasecmp(aAttrName, kMimeTypeName)) {
+    if (document) {
+      document->MimeType(attrValue);
+    } else {
+      proxy->MimeType(attrValue);
+    }
+  } else {
     return nullptr;
+  }
 
   return attrValue.IsEmpty() ? nullptr : AccessibleWrap::ReturnString(attrValue);
 }
 }
--- a/accessible/ipc/DocAccessibleChild.cpp
+++ b/accessible/ipc/DocAccessibleChild.cpp
@@ -1626,10 +1626,75 @@ DocAccessibleChild::RecvBounds(const uin
   Accessible* acc = IdToAccessible(aID);
   if (acc && !acc->IsDefunct()) {
     *aRect = acc->Bounds();
   }
 
   return false;
 }
 
+bool
+DocAccessibleChild::RecvLanguage(const uint64_t& aID,
+                                 nsString* aLocale)
+{
+  Accessible* acc = IdToAccessible(aID);
+  if (acc) {
+    acc->Language(*aLocale);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvDocType(const uint64_t& aID,
+                                nsString* aType)
+{
+  Accessible* acc = IdToAccessible(aID);
+  if (acc && acc->IsDoc()) {
+    acc->AsDoc()->DocType(*aType);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvURL(const uint64_t& aID,
+                            nsString* aURL)
+{
+  Accessible* acc = IdToAccessible(aID);
+  if (acc && acc->IsDoc()) {
+    acc->AsDoc()->URL(*aURL);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvMimeType(const uint64_t& aID,
+                                 nsString* aMime)
+{
+  Accessible* acc = IdToAccessible(aID);
+  if (acc && acc->IsDoc()) {
+    acc->AsDoc()->MimeType(*aMime);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvURLDocTypeMimeType(const uint64_t& aID,
+                                           nsString* aURL,
+                                           nsString* aDocType,
+                                           nsString* aMimeType)
+{
+  Accessible* acc = IdToAccessible(aID);
+  if (acc && acc->IsDoc()) {
+    DocAccessible* doc = acc->AsDoc();
+    doc->URL(*aURL);
+    doc->DocType(*aDocType);
+    doc->MimeType(*aMimeType);
+  }
+
+  return true;
+}
+
 }
 }
--- a/accessible/ipc/DocAccessibleChild.h
+++ b/accessible/ipc/DocAccessibleChild.h
@@ -396,16 +396,25 @@ public:
   virtual bool RecvChildAtPoint(const uint64_t& aID,
                                 const int32_t& aX,
                                 const int32_t& aY,
                                 const uint32_t& aWhich,
                                 uint64_t* aChild,
                                 bool* aOk) override;
 
   virtual bool RecvBounds(const uint64_t& aID, nsIntRect* aRect) override;
+
+  virtual bool RecvLanguage(const uint64_t& aID, nsString* aLocale) override;
+  virtual bool RecvDocType(const uint64_t& aID, nsString* aType) override;
+  virtual bool RecvURL(const uint64_t& aID, nsString* aURL) override;
+  virtual bool RecvMimeType(const uint64_t& aID, nsString* aMime) override;
+  virtual bool RecvURLDocTypeMimeType(const uint64_t& aID,
+                                      nsString* aURL,
+                                      nsString* aDocType,
+                                      nsString* aMimeType) override;
 private:
 
   Accessible* IdToAccessible(const uint64_t& aID) const;
   Accessible* IdToAccessibleLink(const uint64_t& aID) const;
   Accessible* IdToAccessibleSelect(const uint64_t& aID) const;
   HyperTextAccessible* IdToHyperTextAccessible(const uint64_t& aID) const;
   ImageAccessible* IdToImageAccessible(const uint64_t& aID) const;
   TableCellAccessible* IdToTableCellAccessible(const uint64_t& aID) const;
--- a/accessible/ipc/PDocAccessible.ipdl
+++ b/accessible/ipc/PDocAccessible.ipdl
@@ -204,12 +204,18 @@ child:
   prio(high) sync MinValue(uint64_t aID) returns(double aValue);
   prio(high) sync MaxValue(uint64_t aID) returns(double aValue);
   prio(high) sync Step(uint64_t aID) returns(double aStep);
 
   prio(high) sync TakeFocus(uint64_t aID);
   prio(high) sync ChildAtPoint(uint64_t aID, int32_t aX, int32_t aY, uint32_t aWhich)
     returns(uint64_t aChild, bool aOk);
   prio(high) sync Bounds(uint64_t aID) returns(nsIntRect aRect);
+
+  prio(high) sync Language(uint64_t aID) returns(nsString aLocale);
+  prio(high) sync DocType(uint64_t aID) returns(nsString aType);
+  prio(high) sync URL(uint64_t aID) returns(nsString aURL);
+  prio(high) sync MimeType(uint64_t aID) returns(nsString aMime);
+  prio(high) sync URLDocTypeMimeType(uint64_t aID) returns(nsString aURL, nsString aDocType, nsString aMimeType);
 };
 
 }
 }
--- a/accessible/ipc/ProxyAccessible.cpp
+++ b/accessible/ipc/ProxyAccessible.cpp
@@ -906,10 +906,41 @@ ProxyAccessible::ChildAtPoint(int32_t aX
 nsIntRect
 ProxyAccessible::Bounds()
 {
   nsIntRect rect;
   unused << mDoc->SendBounds(mID, &rect);
   return rect;
 }
 
+void
+ProxyAccessible::Language(nsString& aLocale)
+{
+  unused << mDoc->SendLanguage(mID, &aLocale);
+}
+
+void
+ProxyAccessible::DocType(nsString& aType)
+{
+  unused << mDoc->SendDocType(mID, &aType);
+}
+
+void
+ProxyAccessible::URL(nsString& aURL)
+{
+  unused << mDoc->SendURL(mID, &aURL);
+}
+
+void
+ProxyAccessible::MimeType(nsString aMime)
+{
+  unused << mDoc->SendMimeType(mID, &aMime);
+}
+
+void
+ProxyAccessible::URLDocTypeMimeType(nsString& aURL, nsString& aDocType,
+                                    nsString& aMimeType)
+{
+  unused << mDoc->SendURLDocTypeMimeType(mID, &aURL, &aDocType, &aMimeType);
+}
+
 }
 }
--- a/accessible/ipc/ProxyAccessible.h
+++ b/accessible/ipc/ProxyAccessible.h
@@ -271,16 +271,23 @@ public:
   double MaxValue();
   double Step();
 
   void TakeFocus();
   ProxyAccessible* ChildAtPoint(int32_t aX, int32_t aY,
                                 Accessible::EWhichChildAtPoint aWhichChild);
   nsIntRect Bounds();
 
+  void Language(nsString& aLocale);
+  void DocType(nsString& aType);
+  void URL(nsString& aURL);
+  void MimeType(nsString aMime);
+  void URLDocTypeMimeType(nsString& aURL, nsString& aDocType,
+                          nsString& aMimeType);
+
   /**
    * Allow the platform to store a pointers worth of data on us.
    */
   uintptr_t GetWrapper() const { return mWrapper; }
   void SetWrapper(uintptr_t aWrapper) { mWrapper = aWrapper; }
 
   /*
    * Return the ID of the accessible being proxied.
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -535,18 +535,17 @@ struct RoleDescrComparator
 }
 
 - (id)value
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   nsAutoString value;
   mGeckoAccessible->Value(value);
-  return value.IsEmpty() ? nil : [NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.BeginReading())
-                                                         length:value.Length()];
+  return nsCocoaUtils::ToNSString(value);
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (void)valueDidChange
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
@@ -580,18 +579,17 @@ struct RoleDescrComparator
 }
 
 - (NSString*)help
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   nsAutoString helpText;
   mGeckoAccessible->Help(helpText);
-  return helpText.IsEmpty() ? nil : [NSString stringWithCharacters:reinterpret_cast<const unichar*>(helpText.BeginReading())
-                                                            length:helpText.Length()];
+  return nsCocoaUtils::ToNSString(helpText);
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 // objc-style description (from NSObject); not to be confused with the accessible description above.
 - (NSString*)description
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -41,16 +41,22 @@
 #main-window[customize-entered] {
   min-width: -moz-fit-content;
 }
 
 searchbar {
   -moz-binding: url("chrome://browser/content/search/search.xml#searchbar");
 }
 
+/* Prevent shrinking the page content to 0 height and width */
+.browserStack > browser {
+  min-height: 25px;
+  min-width: 25px;
+}
+
 .browserStack > browser {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-browser");
 }
 
 .browserStack > browser[remote="true"] {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-remote-browser");
 }
 
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -656,17 +656,17 @@
 
       <tabs id="tabbrowser-tabs"
             class="tabbrowser-tabs"
             tabbrowser="content"
             flex="1"
             setfocus="false"
             tooltip="tabbrowser-tab-tooltip"
             stopwatchid="FX_TAB_CLICK_MS">
-        <tab class="tabbrowser-tab" selected="true" fadein="true"/>
+        <tab class="tabbrowser-tab" selected="true" visuallyselected="true" fadein="true"/>
       </tabs>
 
       <toolbarbutton id="new-tab-button"
                      class="toolbarbutton-1 chromeclass-toolbar-additional"
                      label="&tabCmd.label;"
                      command="cmd_newNavigatorTab"
                      onclick="checkForMiddleClick(this, event);"
                      tooltip="dynamic-shortcut-tooltip"
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1198,28 +1198,36 @@
                   findBar._findField.getAttribute("focused") == "true");
               }
 
               // If focus is in the tab bar, retain it there.
               if (document.activeElement == oldTab) {
                 // We need to explicitly focus the new tab, because
                 // tabbox.xml does this only in some cases.
                 this.mCurrentTab.focus();
-              } else if (gMultiProcessBrowser) {
+              } else if (gMultiProcessBrowser && document.activeElement !== newBrowser) {
                 // Clear focus so that _adjustFocusAfterTabSwitch can detect if
                 // some element has been focused and respect that.
                 document.activeElement.blur();
               }
 
               if (!gMultiProcessBrowser)
                 this._adjustFocusAfterTabSwitch(this.mCurrentTab);
             }
 
             this.tabContainer._setPositionalAttributes();
 
+            if (!gMultiProcessBrowser) {
+              let event = new CustomEvent("TabSwitchDone", {
+                bubbles: true,
+                cancelable: true
+              });
+              this.dispatchEvent(event);
+            }
+
             if (!aForceUpdate)
               TelemetryStopwatch.finish("FX_TAB_SWITCH_UPDATE_MS");
           ]]>
         </body>
       </method>
 
       <method name="_adjustFocusAfterTabSwitch">
         <parameter name="newTab"/>
@@ -2755,31 +2763,34 @@
           this._lastRelatedTab = null;
 
           this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
           this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
 
           let wasFocused = (document.activeElement == this.mCurrentTab);
 
           aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
-          this.mCurrentTab._selected = false;
+          this.mCurrentTab._logicallySelected = false;
+          this.mCurrentTab._visuallySelected = false;
 
           // invalidate caches
           this._browsers = null;
           this._visibleTabs = null;
 
           // use .item() instead of [] because dragging to the end of the strip goes out of
           // bounds: .item() returns null (so it acts like appendChild), but [] throws
           this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
 
           for (let i = 0; i < this.tabs.length; i++) {
             this.tabs[i]._tPos = i;
-            this.tabs[i]._selected = false;
+            this.tabs[i]._logicallySelected = false;
+            this.tabs[i]._visuallySelected = false;
           }
-          this.mCurrentTab._selected = true;
+          this.mCurrentTab._logicallySelected = true;
+          this.mCurrentTab._visuallySelected = true;
 
           if (wasFocused)
             this.mCurrentTab.focus();
 
           this.tabContainer._handleTabSelect(false);
 
           if (aTab.pinned)
             this.tabContainer._positionPinnedTabs();
@@ -2861,16 +2872,528 @@
         <parameter name="aTab"/><!-- can be from a different window as well -->
         <body>
           <![CDATA[
             return SessionStore.duplicateTab(window, aTab);
           ]]>
         </body>
       </method>
 
+      <!--
+        The tab switcher is responsible for asynchronously switching
+        tabs in e10s. It waits until the new tab is ready (i.e., the
+        layer tree is available) before switching to it. Then it
+        unloads the layer tree for the old tab.
+
+        The tab switcher is a state machine. For each tab, it
+        maintains state about whether the layer tree for the tab is
+        available, being loaded, being unloaded, or unavailable. It
+        also keeps track of the tab currently being displayed, the tab
+        it's trying to load, and the tab the user has asked to switch
+        to. The switcher object is created upon tab switch. It is
+        released when there are no pending tabs to load or unload.
+
+        The following general principles have guided the design:
+
+        1. We only request one layer tree at a time. If the user
+        switches to a different tab while waiting, we don't request
+        the new layer tree until the old tab has loaded or timed out.
+
+        2. If loading the layers for a tab times out, we show the
+        spinner and possibly request the layer tree for another tab if
+        the user has requested one.
+
+        3. We discard layer trees on a delay. This way, if the user is
+        switching among the same tabs frequently, we don't continually
+        load the same tabs.
+
+        It's important that we always show either the spinner or a tab
+        whose layers are available. Otherwise the compositor will draw
+        an entirely black frame, which is very jarring. To ensure this
+        never happens, we do the following:
+
+        1. When switching away from a tab, we assume the old tab might
+        still be drawn until a MozAfterPaint event occurs. Because
+        layout and compositing happen asynchronously, we don't have
+        any other way of knowing when the switch actually takes
+        place. Therefore, we don't unload the old tab until the next
+        MozAfterPaint event.
+
+        2. Suppose that the user switches from tab A to B and then
+        back to A. Suppose that we ask for tab A's layers to be
+        unloaded via message M1 after the first switch and then
+        request them again via message M2 once the second switch
+        happens. Both loading and unloading of layers happens
+        asynchronously, and this can cause problems. It's possible
+        that the content process publishes one last layer tree before
+        M1 is received. The parent process doesn't know that this
+        layer tree was published before M1 and not after M2, so it
+        will display the tab. However, once M1 arrives, the content
+        process will destroy the layer tree for A and now we will
+        display black for it.
+
+        To counter this problem, we keep tab A in a separate
+        "unloading" state until the layer tree is actually dropped in
+        the compositor thread. While the tab is in the "unloading"
+        state, we're not allowed to request layers for it. Once the
+        layers are dropped in the compositor, an event will fire and
+        we will transition the tab to the "unloaded" state. Then we
+        are free to request the tab's layers again.
+      -->
+      <field name="_switcher">null</field>
+      <method name="_getSwitcher">
+        <body><![CDATA[
+          if (this._switcher) {
+            return this._switcher;
+          }
+
+          let switcher = {
+            // How long to wait for a tab's layers to load. After this
+            // time elapses, we're free to put up the spinner and start
+            // trying to load a different tab.
+            TAB_SWITCH_TIMEOUT: 300 /* ms */,
+
+            // When the user hasn't switched tabs for this long, we unload
+            // layers for all tabs that aren't in use.
+            UNLOAD_DELAY: 300 /* ms */,
+
+            // The next three tabs form the principal state variables.
+            // See the assertions in postActions for their invariants.
+
+            // Tab the user requested most recently.
+            requestedTab: this.selectedTab,
+
+            // Tab we're currently trying to load.
+            loadingTab: null,
+
+            // We show this tab in case the requestedTab hasn't loaded yet.
+            lastVisibleTab: this.selectedTab,
+
+            // Auxilliary state variables:
+
+            visibleTab: this.selectedTab,   // Tab that's on screen.
+            spinnerTab: null,               // Tab showing a spinner.
+            originalTab: this.selectedTab,  // Tab that we started on.
+
+            tabbrowser: this,  // Reference to gBrowser.
+            loadTimer: null,   // TAB_SWITCH_TIMEOUT timer.
+            unloadTimer: null, // UNLOAD_DELAY timer.
+
+            // Map from tabs to STATE_* (below).
+            tabState: new Map(),
+
+            // Set of tabs that might be visible right now. We maintain
+            // this set because we can't be sure when a tab is actually
+            // drawn. A tab is added to this set when we ask to make it
+            // visible. All tabs but the most recently shown tab are
+            // removed from the set upon MozAfterPaint.
+            maybeVisibleTabs: new Set([this.selectedTab]),
+
+            STATE_UNLOADED: 0,
+            STATE_LOADING: 1,
+            STATE_LOADED: 2,
+            STATE_UNLOADING: 3,
+
+            logging: false,
+
+            getTabState: function(tab) {
+              let state = this.tabState.get(tab);
+              if (state === undefined) {
+                return this.STATE_UNLOADED;
+              }
+              return state;
+            },
+
+            setTabState: function(tab, state) {
+              if (state == this.STATE_UNLOADED) {
+                this.tabState.delete(tab);
+              } else {
+                this.tabState.set(tab, state);
+              }
+
+              let browser = tab.linkedBrowser;
+              let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+              if (state == this.STATE_LOADING) {
+                // Ask for a MozLayerTreeReady event.
+                fl.requestNotifyLayerTreeReady();
+                browser.docShellIsActive = true;
+              } else if (state == this.STATE_UNLOADING) {
+                // Ask for MozLayerTreeCleared event.
+                fl.requestNotifyLayerTreeCleared();
+                browser.docShellIsActive = false;
+              }
+            },
+
+            init: function() {
+              this.log("START");
+
+              window.addEventListener("MozAfterPaint", this);
+              window.addEventListener("MozLayerTreeReady", this);
+              window.addEventListener("MozLayerTreeCleared", this);
+              window.addEventListener("TabRemotenessChange", this);
+              this.setTabState(this.requestedTab, this.STATE_LOADED);
+            },
+
+            destroy: function() {
+              clearTimeout(this.unloadTimer);
+              clearTimeout(this.loadTimer);
+
+              window.removeEventListener("MozAfterPaint", this);
+              window.removeEventListener("MozLayerTreeReady", this);
+              window.removeEventListener("MozLayerTreeCleared", this);
+              window.removeEventListener("TabRemotenessChange", this);
+
+              this.tabbrowser._switcher = null;
+            },
+
+            finish: function() {
+              this.log("FINISH");
+
+              this.assert(this.tabbrowser._switcher);
+              this.assert(this.tabbrowser._switcher === this);
+              this.assert(!this.spinnerTab);
+              this.assert(!this.loadTimer);
+              this.assert(!this.loadingTab);
+              this.assert(this.lastVisibleTab === this.requestedTab);
+              this.assert(this.getTabState(this.requestedTab) == this.STATE_LOADED);
+
+              this.destroy();
+
+              let toBrowser = this.requestedTab.linkedBrowser;
+              toBrowser.setAttribute("type", "content-primary");
+
+              this.tabbrowser._adjustFocusAfterTabSwitch(this.requestedTab);
+
+              let fromBrowser = this.originalTab.linkedBrowser;
+              // It's possible that the tab we're switching from closed
+              // before we were able to finalize, in which case, fromBrowser
+              // doesn't exist.
+              if (fromBrowser) {
+                fromBrowser.setAttribute("type", "content-targetable");
+              }
+
+              let event = new CustomEvent("TabSwitchDone", {
+                bubbles: true,
+                cancelable: true
+              });
+              this.tabbrowser.dispatchEvent(event);
+            },
+
+            // This function is called after all the main state changes to
+            // make sure we display the right tab.
+            updateDisplay: function() {
+              // Figure out which tab we actually want visible right now.
+              let showTab = null;
+              if (this.getTabState(this.requestedTab) != this.STATE_LOADED &&
+                  this.lastVisibleTab && this.loadTimer) {
+                // If we can't show the requestedTab, and lastVisibleTab is
+                // available, show it.
+                showTab = this.lastVisibleTab;
+              } else {
+                // Show the requested tab. If it's not available, we'll show the spinner.
+                showTab = this.requestedTab;
+              }
+
+              // Show or hide the spinner as needed.
+              let needSpinner = this.getTabState(showTab) != this.STATE_LOADED;
+              if (!needSpinner && this.spinnerTab) {
+                this.tabbrowser.removeAttribute("pendingpaint");
+                this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
+                this.spinnerTab = null;
+              } else if (needSpinner && this.spinnerTab !== showTab) {
+                if (this.spinnerTab) {
+                  this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
+                }
+                this.spinnerTab = showTab;
+                this.tabbrowser.setAttribute("pendingpaint", "true");
+                this.spinnerTab.linkedBrowser.setAttribute("pendingpaint", "true");
+              }
+
+              // Switch to the tab we've decided to make visible.
+              if (this.visibleTab !== showTab) {
+                this.visibleTab = showTab;
+
+                this.maybeVisibleTabs.add(showTab);
+
+                let tabs = this.tabbrowser.mTabBox.tabs;
+                let tabPanel = this.tabbrowser.mPanelContainer;
+                let showPanel = tabs.getRelatedElement(showTab);
+                let index = Array.indexOf(tabPanel.childNodes, showPanel);
+                if (index != -1) {
+                  this.log(`Switch to tab ${index} - ${this.tinfo(showTab)}`);
+                  tabPanel.setAttribute("selectedIndex", index);
+                  if (showTab === this.requestedTab) {
+                    this.tabbrowser._adjustFocusAfterTabSwitch(showTab);
+                  }
+                }
+
+                // This doesn't necessarily exist if we're a new window and haven't switched tabs yet
+                if (this.lastVisibleTab)
+                  this.lastVisibleTab._visuallySelected = false;
+
+                this.visibleTab._visuallySelected = true;
+              }
+
+              this.lastVisibleTab = this.visibleTab;
+
+            },
+
+            assert: function(cond) {
+              if (!cond) {
+                dump("Assertion failure\n" + Error().stack);
+                throw new Error("Assertion failure");
+              }
+            },
+
+            // We've decided to try to load requestedTab.
+            loadRequestedTab: function() {
+              this.assert(!this.loadTimer);
+
+              // loadingTab can be non-null here if we timed out loading the current tab.
+              // In that case we just overwrite it with a different tab; it's had its chance.
+              this.loadingTab = this.requestedTab;
+              this.log("Loading tab " + this.tinfo(this.loadingTab));
+
+              this.setTabState(this.requestedTab, this.STATE_LOADING);
+              this.loadTimer = setTimeout(() => this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT);
+            },
+
+            // This function runs before every event. It fixes up the state
+            // to account for closed tabs.
+            preActions: function() {
+              this.assert(this.tabbrowser._switcher);
+              this.assert(this.tabbrowser._switcher === this);
+
+              for (let [tab, state] of this.tabState) {
+                if (!tab.linkedBrowser) {
+                  this.tabState.delete(tab);
+                }
+              }
+
+              if (this.lastVisibleTab && !this.lastVisibleTab.linkedBrowser) {
+                this.lastVisibleTab = null;
+              }
+              if (this.spinnerTab && !this.spinnerTab.linkedBrowser) {
+                this.spinnerTab = null;
+              }
+              if (this.loadingTab && !this.loadingTab.linkedBrowser) {
+                this.loadingTab = null;
+                clearTimeout(this.loadTimer);
+                this.loadTimer = null;
+              }
+            },
+
+            // This code runs after we've responded to an event or requested a new
+            // tab. It's expected that we've already updated all the principal
+            // state variables. This function takes care of updating any auxilliary
+            // state.
+            postActions: function() {
+              // Once we finish loading loadingTab, we null it out. So the state should
+              // always be LOADING.
+              this.assert(!this.loadingTab ||
+                          this.getTabState(this.loadingTab) == this.STATE_LOADING);
+
+              // We guarantee that loadingTab is non-null iff loadTimer is non-null. So
+              // the timer is set only when we're loading something.
+              this.assert(!this.loadTimer || this.loadingTab);
+              this.assert(!this.loadingTab || this.loadTimer);
+
+              // If we're not loading anything, try loading the requested tab.
+              if (!this.loadTimer && this.getTabState(this.requestedTab) == this.STATE_UNLOADED) {
+                this.loadRequestedTab();
+              }
+
+              // See how many tabs still have work to do.
+              let numPending = 0;
+              for (let [tab, state] of this.tabState) {
+                if (state == this.STATE_LOADED && tab !== this.requestedTab) {
+                  numPending++;
+                }
+                if (state == this.STATE_LOADING || state == this.STATE_UNLOADING) {
+                  numPending++;
+                }
+              }
+
+              this.updateDisplay();
+
+              // It's possible for updateDisplay to trigger one of our own event
+              // handlers, which might cause finish() to already have been called.
+              // Check for that before calling finish() again.
+              if (!this.tabbrowser._switcher) {
+                return;
+              }
+
+              if (numPending == 0) {
+                this.finish();
+              }
+
+              this.logState("done");
+            },
+
+            // Fires when we're ready to unload unused tabs.
+            onUnloadTimeout: function() {
+              this.logState("onUnloadTimeout");
+              this.preActions();
+
+              let numPending = 0;
+
+              // Unload any tabs that can be unloaded.
+              for (let [tab, state] of this.tabState) {
+                if (state == this.STATE_LOADED &&
+                    !this.maybeVisibleTabs.has(tab) &&
+                    tab !== this.lastVisibleTab &&
+                    tab !== this.loadingTab &&
+                    tab !== this.requestedTab)
+                {
+                  this.setTabState(tab, this.STATE_UNLOADING);
+                }
+
+                if (state != this.STATE_UNLOADED && tab !== this.requestedTab) {
+                  numPending++;
+                }
+              }
+
+              if (numPending) {
+                // Keep the timer going since there may be more tabs to unload.
+                this.unloadTimer = setTimeout(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
+              }
+
+              this.postActions();
+            },
+
+            // Fires when an ongoing load has taken too long.
+            onLoadTimeout: function() {
+              this.logState("onLoadTimeout");
+              this.preActions();
+              this.loadTimer = null;
+              this.loadingTab = null;
+              this.postActions();
+            },
+
+            // Fires when the layers become available for a tab.
+            onLayersReady: function(browser) {
+              this.logState("onLayersReady");
+
+              let tab = this.tabbrowser.getTabForBrowser(browser);
+              this.setTabState(tab, this.STATE_LOADED);
+
+              if (this.loadingTab === tab) {
+                clearTimeout(this.loadTimer);
+                this.loadTimer = null;
+                this.loadingTab = null;
+              }
+            },
+
+            // Fires when we paint the screen. Any tab switches we initiated
+            // previously are done, so there's no need to keep the old layers
+            // around.
+            onPaint: function() {
+              this.maybeVisibleTabs.clear();
+            },
+
+            // Called when we're done clearing the layers for a tab.
+            onLayersCleared: function(browser) {
+              this.logState("onLayersCleared");
+
+              let tab = this.tabbrowser.getTabForBrowser(browser);
+              if (tab) {
+                this.setTabState(tab, this.STATE_UNLOADED);
+              }
+            },
+
+            // Called when a tab switches from remote to non-remote. In this case
+            // a MozLayerTreeReady notification that we requested may never fire,
+            // so we need to simulate it.
+            onRemotenessChange: function(tab) {
+              this.logState("onRemotenessChange");
+              if (!tab.linkedBrowser.isRemoteBrowser) {
+                if (this.getTabState(tab) == this.STATE_LOADING) {
+                  this.onLayersReady(tab.linkedBrowser);
+                } else if (this.getTabState(tab) == this.STATE_UNLOADING) {
+                  this.onLayersCleared(tab.linkedBrowser);
+                }
+              }
+            },
+
+            // Called when the user asks to switch to a given tab.
+            requestTab: function(tab) {
+              if (tab === this.requestedTab) {
+                return;
+              }
+
+              this.logState("requestTab " + this.tinfo(tab));
+
+              this.requestedTab = tab;
+
+              this.preActions();
+
+              clearTimeout(this.unloadTimer);
+              this.unloadTimer = setTimeout(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
+
+              this.postActions();
+            },
+
+            handleEvent: function(event) {
+              this.preActions();
+
+              if (event.type == "MozLayerTreeReady") {
+                this.onLayersReady(event.originalTarget);
+              } if (event.type == "MozAfterPaint") {
+                this.onPaint();
+              } else if (event.type == "MozLayerTreeCleared") {
+                this.onLayersCleared(event.originalTarget);
+              } else if (event.type == "TabRemotenessChange") {
+                this.onRemotenessChange(event.target);
+              }
+
+              this.postActions();
+            },
+
+            tinfo: function(tab) {
+              if (tab) {
+                return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")";
+              } else {
+                return "null";
+              }
+            },
+
+            log: function(s) {
+              if (!this.logging)
+                return;
+              dump(s + "\n");
+            },
+
+            logState: function(prefix) {
+              if (!this.logging)
+                return;
+
+              dump(prefix + " ");
+              for (let i = 0; i < this.tabbrowser.tabs.length; i++) {
+                let tab = this.tabbrowser.tabs[i];
+                let state = this.getTabState(tab);
+
+                dump(i + ":");
+                if (tab === this.lastVisibleTab) dump("V");
+                if (tab === this.loadingTab) dump("L");
+                if (tab === this.requestedTab) dump("R");
+                if (state == this.STATE_LOADED) dump("(+)");
+                if (state == this.STATE_LOADING) dump("(+?)");
+                if (state == this.STATE_UNLOADED) dump("(-)");
+                if (state == this.STATE_UNLOADING) dump("(-?)");
+                dump(" ");
+              }
+              dump("\n");
+            },
+          };
+          this._switcher = switcher;
+          switcher.init();
+          return switcher;
+        ]]></body>
+      </method>
+
       <!-- BEGIN FORWARDED BROWSER PROPERTIES.  IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
            MAKE SURE TO ADD IT HERE AS WELL. -->
       <property name="canGoBack"
                 onget="return this.mCurrentBrowser.canGoBack;"
                 readonly="true"/>
 
       <property name="canGoForward"
                 onget="return this.mCurrentBrowser.canGoForward;"
@@ -3375,16 +3898,20 @@
                               .getService(nsIEventListenerService);
           els.removeSystemEventListener(document, "keydown", this, false);
           els.removeSystemEventListener(document, "keypress", this, false);
           window.removeEventListener("sizemodechange", this, false);
 
           if (gMultiProcessBrowser) {
             messageManager.removeMessageListener("DOMTitleChanged", this);
             messageManager.removeMessageListener("contextmenu", this);
+
+            if (this._switcher) {
+              this._switcher.destroy();
+            }
           }
         ]]>
       </destructor>
 
       <!-- Deprecated stuff, implemented for backwards compatibility. -->
       <method name="enterTabbedMode">
         <body>
           Application.console.log("enterTabbedMode is an obsolete method and " +
@@ -3399,164 +3926,16 @@
         </body>
       </method>
       <method name="getStripVisibility">
         <body>
           return this.tabContainer.visible;
         </body>
       </method>
 
-      <method name="_showBusySpinnerRemoteBrowser">
-        <parameter name="aBrowser"/>
-        <body><![CDATA[
-          aBrowser.setAttribute("pendingpaint", "true");
-          if (this._contentWaitingCount <= 0) {
-            // We are not currently spinning
-            this.setAttribute("pendingpaint", "true");
-            this._contentWaitingCount = 1;
-          } else {
-            this._contentWaitingCount++;
-          }
-        ]]></body>
-      </method>
-
-      <method name="_hideBusySpinnerRemoteBrowser">
-        <parameter name="aBrowser"/>
-        <body><![CDATA[
-          aBrowser.removeAttribute("pendingpaint");
-          this._contentWaitingCount--;
-          if (this._contentWaitingCount <= 0) {
-            this.removeAttribute("pendingpaint");
-          }
-        ]]></body>
-      </method>
-
-      <method name="_prepareForTabSwitch">
-        <parameter name="toTab"/>
-        <parameter name="fromTab"/>
-        <body><![CDATA[
-          const kTabSwitchTimeout = 300;
-          let toBrowser = this.getBrowserForTab(toTab);
-          let fromBrowser = fromTab ? this.getBrowserForTab(fromTab)
-                                    : null;
-
-          // We only want to wait for the MozAfterRemotePaint event if
-          // the tab we're switching to is a remote tab, and if the tab
-          // we're switching to isn't the one we've already got. The latter
-          // case can occur when closing tabs before the currently selected
-          // one.
-          let shouldWait = toBrowser.getAttribute("remote") == "true" &&
-                           toBrowser != fromBrowser;
-
-          let switchPromise;
-
-          if (shouldWait) {
-            let timeoutId;
-            let panels = this.mPanelContainer;
-
-            // Both the timeout and MozAfterPaint promises use this same
-            // logic to determine whether they should carry out the tab
-            // switch, or reject it outright.
-            let attemptTabSwitch = (aResolve, aReject) => {
-              if (this.selectedBrowser == toBrowser) {
-                aResolve();
-              } else {
-                // We switched away or closed the browser before we timed
-                // out. We reject, which will cancel the tab switch.
-                aReject();
-              }
-            };
-
-            let timeoutPromise = new Promise((aResolve, aReject) => {
-              timeoutId = setTimeout(() => {
-                if (toBrowser.isRemoteBrowser) {
-                  // The browser's remoteness could have changed since we
-                  // setTimeout so only show the spinner if it's still remote.
-                  this._showBusySpinnerRemoteBrowser(toBrowser);
-                }
-                attemptTabSwitch(aResolve, aReject);
-              }, kTabSwitchTimeout);
-            });
-
-            let paintPromise = new Promise((aResolve, aReject) => {
-              let onRemotePaint = () => {
-                toBrowser.removeEventListener("MozAfterRemotePaint", onRemotePaint);
-                this._hideBusySpinnerRemoteBrowser(toBrowser);
-                clearTimeout(timeoutId);
-                attemptTabSwitch(aResolve, aReject);
-              };
-              toBrowser.addEventListener("MozAfterRemotePaint", onRemotePaint);
-              toBrowser.QueryInterface(Ci.nsIFrameLoaderOwner)
-                       .frameLoader
-                       .requestNotifyAfterRemotePaint();
-               // We need to activate the docShell on the tab we're switching
-               // to - otherwise, we won't initiate a remote paint request and
-               // therefore we won't get the MozAfterRemotePaint event that we're
-               // waiting for.
-               // Note that this happens, as we require, even if the timeout in the
-               // timeoutPromise triggers before the paintPromise even runs.
-               toBrowser.docShellIsActive = true;
-            });
-
-            switchPromise = Promise.race([paintPromise, timeoutPromise]);
-          } else {
-            // Activate the docShell on the tab we're switching to.
-            toBrowser.docShellIsActive = true;
-
-            // No need to wait - just resolve immediately to do the switch ASAP.
-            switchPromise = Promise.resolve();
-          }
-
-          return switchPromise;
-        ]]></body>
-      </method>
-
-      <method name="_deactivateContent">
-        <parameter name="tab"/>
-        <body><![CDATA[
-          // It's unlikely, yet possible, that while we were waiting
-          // to deactivate this tab, that something closed it and wiped
-          // out the browser. For example, during a tab switch, while waiting
-          // for the MozAfterRemotePaint event to fire, something closes the
-          // original tab that the user had selected. If that's the case, then
-          // there's nothing to deactivate.
-          let browser = this.getBrowserForTab(tab);
-          if (browser && this.selectedBrowser != browser) {
-            browser.docShellIsActive = false;
-          }
-        ]]></body>
-      </method>
-
-      <method name="_finalizeTabSwitch">
-        <parameter name="toTab"/>
-        <parameter name="fromTab"/>
-        <body><![CDATA[
-          this._adjustFocusAfterTabSwitch(toTab);
-          this._deactivateContent(fromTab);
-
-          let toBrowser = this.getBrowserForTab(toTab);
-          toBrowser.setAttribute("type", "content-primary");
-
-          let fromBrowser = this.getBrowserForTab(fromTab);
-          // It's possible that the tab we're switching from closed
-          // before we were able to finalize, in which case, fromBrowser
-          // doesn't exist.
-          if (fromBrowser) {
-            fromBrowser.setAttribute("type", "content-targetable");
-          }
-        ]]></body>
-      </method>
-
-      <method name="_cancelTabSwitch">
-        <parameter name="toTab"/>
-        <body><![CDATA[
-          this._deactivateContent(toTab);
-        ]]></body>
-      </method>
-
       <property name="mContextTab" readonly="true"
                 onget="return TabContextMenu.contextTab;"/>
       <property name="mPrefs" readonly="true"
                 onget="return Services.prefs;"/>
       <property name="mTabContainer" readonly="true"
                 onget="return this.tabContainer;"/>
       <property name="mTabs" readonly="true"
                 onget="return this.tabs;"/>
@@ -5037,52 +5416,71 @@
   <binding id="tabbrowser-tab" display="xul:hbox"
            extends="chrome://global/content/bindings/tabbox.xml#tab">
     <resources>
       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
     </resources>
 
     <content context="tabContextMenu" closetabtext="&closeTab.label;">
       <xul:stack class="tab-stack" flex="1">
-        <xul:hbox xbl:inherits="pinned,selected,titlechanged,fadein"
+        <xul:hbox xbl:inherits="pinned,selected,visuallyselected,titlechanged,fadein"
                   class="tab-background">
-          <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+          <xul:hbox xbl:inherits="pinned,selected,visuallyselected,titlechanged"
                     class="tab-background-start"/>
-          <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+          <xul:hbox xbl:inherits="pinned,selected,visuallyselected,titlechanged"
                     class="tab-background-middle"/>
-          <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+          <xul:hbox xbl:inherits="pinned,selected,visuallyselected,titlechanged"
                     class="tab-background-end"/>
         </xul:hbox>
-        <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+        <xul:hbox xbl:inherits="pinned,selected,visuallyselected,titlechanged"
                   class="tab-content" align="center">
-          <xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
+          <xul:image xbl:inherits="fadein,pinned,busy,progress,selected,visuallyselected"
                      class="tab-throbber"
                      role="presentation"
                      layer="true" />
-          <xul:image xbl:inherits="src=image,fadein,pinned,selected,busy,crashed"
+          <xul:image xbl:inherits="src=image,fadein,pinned,selected,visuallyselected,busy,crashed"
                      anonid="tab-icon-image"
                      class="tab-icon-image"
                      validate="never"
                      role="presentation"/>
           <xul:image xbl:inherits="crashed,busy"
                      class="tab-icon-overlay"
                      role="presentation"/>
           <xul:label flex="1"
                      anonid="tab-label"
-                     xbl:inherits="value=visibleLabel,crop,accesskey,fadein,pinned,selected"
+                     xbl:inherits="value=visibleLabel,crop,accesskey,fadein,pinned,selected,visuallyselected"
                      class="tab-text tab-label"
                      role="presentation"/>
           <xul:toolbarbutton anonid="close-button"
-                             xbl:inherits="fadein,pinned,selected"
+                             xbl:inherits="fadein,pinned,selected,visuallyselected"
                              class="tab-close-button close-icon"/>
         </xul:hbox>
       </xul:stack>
     </content>
 
     <implementation>
+
+      <property name="_selected">
+        <setter>
+          <![CDATA[
+          // in e10s we want to only pseudo-select a tab before its rendering is done, so that
+          // the rest of the system knows that the tab is selected, but we don't want to update its
+          // visual status to selected until after we receive confirmation that its content has painted.
+          this._logicallySelected = val;
+
+          // If we're non-e10s we should update the visual selection as well at the same time
+          if (!gMultiProcessBrowser) {
+            this._visuallySelected = val;
+          }
+
+          return val;
+        ]]>
+        </setter>
+      </property>
+
       <property name="label">
         <getter>
           return this.getAttribute("label");
         </getter>
         <setter>
           this.setAttribute("label", val);
           let event = new CustomEvent("TabLabelModified", {
             bubbles: true,
@@ -5509,47 +5907,27 @@
         <![CDATA[
           if (val < 0 || val >= this.childNodes.length)
             return val;
 
           let toTab = this.getRelatedElement(this.childNodes[val]);
           let fromTab = this._selectedPanel ? this.getRelatedElement(this._selectedPanel)
                                             : null;
 
-          let switchPromise = gBrowser._prepareForTabSwitch(toTab, fromTab);
+          gBrowser._getSwitcher().requestTab(toTab);
 
           var panel = this._selectedPanel;
           var newPanel = this.childNodes[val];
           this._selectedPanel = newPanel;
           if (this._selectedPanel != panel) {
             var event = document.createEvent("Events");
             event.initEvent("select", true, true);
             this.dispatchEvent(event);
 
             this._selectedIndex = val;
-
-            switchPromise.then(() => {
-              // If we cannot find the tabpanel that we were trying to switch to, then
-              // it must have been removed before our Promise could be resolved. In
-              // that case, we just cancel the tab switch.
-              var updatedTabIndex = Array.indexOf(this.childNodes, newPanel);
-              if (updatedTabIndex == -1) {
-                gBrowser._cancelTabSwitch(toTab);
-              } else {
-                this.setAttribute("selectedIndex", updatedTabIndex);
-                gBrowser._finalizeTabSwitch(toTab, fromTab);
-              }
-            }, () => {
-              // If the promise rejected, that means we don't want to actually
-              // flip the deck, so we cancel the tab switch.
-              // We need to nullcheck the method we're about to call because
-              // the binding might be dead at this point.
-              if (gBrowser._cancelTabSwitch)
-                gBrowser._cancelTabSwitch(toTab);
-            });
           }
 
           return val;
         ]]>
         </setter>
       </property>
     </implementation>
   </binding>
--- a/browser/base/content/test/general/browser_selectTabAtIndex.js
+++ b/browser/base/content/test/general/browser_selectTabAtIndex.js
@@ -1,14 +1,17 @@
 function test() {
   for (let i = 0; i < 9; i++)
     gBrowser.addTab();
 
   var isLinux = navigator.platform.indexOf("Linux") == 0;
   for (let i = 9; i >= 1; i--) {
+    // Make sure the keystroke goes to chrome.
+    document.activeElement.blur();
+
     EventUtils.synthesizeKey(i.toString(), { altKey: isLinux, accelKey: !isLinux });
 
     is(gBrowser.tabContainer.selectedIndex, (i == 9 ? gBrowser.tabs.length : i) - 1,
        (isLinux ? "Alt" : "Accel") + "+" + i + " selects expected tab");
   }
 
   gBrowser.selectTabAtIndex(-3);
   is(gBrowser.tabContainer.selectedIndex, gBrowser.tabs.length - 3,
--- a/browser/base/content/test/general/browser_tab_dragdrop.js
+++ b/browser/base/content/test/general/browser_tab_dragdrop.js
@@ -27,16 +27,65 @@ let clickTest = Task.async(function*(tab
   is(newClicks, clicks + 1, "adding 1 more click on BODY");
 });
 
 function loadURI(tab, url) {
   tab.linkedBrowser.loadURI(url);
   return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
 }
 
+// Creates a framescript which caches the current object value from the plugin
+// in the page. checkObjectValue below verifies that the framescript is still
+// active for the browser and that the cached value matches that from the plugin
+// in the page which tells us the plugin hasn't been reinitialized.
+function cacheObjectValue(browser) {
+  let frame_script = function() {
+    let plugin = content.document.wrappedJSObject.body.firstChild;
+    let objectValue = plugin.getObjectValue();
+
+    addMessageListener("Test:CheckObjectValue", () => {
+      try {
+        let plugin = content.document.wrappedJSObject.body.firstChild;
+        sendAsyncMessage("Test:CheckObjectValue", {
+          result: plugin.checkObjectValue(objectValue)
+        });
+      }
+      catch (e) {
+        sendAsyncMessage("Test:CheckObjectValue", {
+          result: null,
+          exception: e.toString()
+        });
+      }
+    });
+  };
+
+  browser.messageManager.loadFrameScript("data:,(" + frame_script.toString() + ")();", false)
+}
+
+// See the notes for cacheObjectValue above.
+function checkObjectValue(browser) {
+  let mm = browser.messageManager;
+
+  return new Promise((resolve, reject) => {
+    let listener  = ({ data }) => {
+      mm.removeMessageListener("Test:CheckObjectValue", listener);
+      if (data.result === null) {
+        ok(false, "checkObjectValue threw an exception: " + data.exception);
+        reject(data.exception);
+      }
+      else {
+        resolve(data.result);
+      }
+    };
+
+    mm.addMessageListener("Test:CheckObjectValue", listener);
+    mm.sendAsyncMessage("Test:CheckObjectValue");
+  });
+}
+
 add_task(function*() {
   let embed = '<embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480"></embed>'
   setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
 
   // create a few tabs
   let tabs = [
     gBrowser.tabs[0],
     gBrowser.addTab("about:blank", {skipAnimation: true}),
@@ -52,39 +101,34 @@ add_task(function*() {
   yield loadURI(tabs[4], "data:text/html;charset=utf-8,<body onload='clicks=0' onclick='++clicks'>"+embed);
   gBrowser.selectedTab = tabs[3];
 
   swapTabsAndCloseOther(2, 3); // now: 0 1 2 4
   is(gBrowser.tabs[1], tabs[1], "tab1");
   is(gBrowser.tabs[2], tabs[3], "tab3");
   is(gBrowser.tabs[3], tabs[4], "tab4");
 
-  let plugin = tabs[4].linkedBrowser.contentDocument.wrappedJSObject.body.firstChild;
-  let tab4_plugin_object = plugin.getObjectValue();
+  cacheObjectValue(tabs[4].linkedBrowser);
 
   swapTabsAndCloseOther(3, 2); // now: 0 1 4
   gBrowser.selectedTab = gBrowser.tabs[2];
 
-  let doc = gBrowser.tabs[2].linkedBrowser.contentDocument.wrappedJSObject;
-  plugin = doc.body.firstChild;
-  ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
+  ok((yield checkObjectValue(gBrowser.tabs[2].linkedBrowser)), "same plugin instance");
 
   is(gBrowser.tabs[1], tabs[1], "tab1");
   is(gBrowser.tabs[2], tabs[3], "tab4");
 
   let clicks = yield getClicks(gBrowser.tabs[2]);
   is(clicks, 0, "no click on BODY so far");
   yield clickTest(gBrowser.tabs[2]);
 
   swapTabsAndCloseOther(2, 1); // now: 0 4
   is(gBrowser.tabs[1], tabs[1], "tab1");
 
-  doc = gBrowser.tabs[1].linkedBrowser.contentDocument.wrappedJSObject;
-  plugin = doc.body.firstChild;
-  ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
+  ok((yield checkObjectValue(gBrowser.tabs[1].linkedBrowser)), "same plugin instance");
 
   yield clickTest(gBrowser.tabs[1]);
 
   // Load a new document (about:blank) in tab4, then detach that tab into a new window.
   // In the new window, navigate back to the original document and click on its <body>,
   // verify that its onclick was called.
   gBrowser.selectedTab = tabs[1];
   yield loadURI(tabs[1], "about:blank");
--- a/browser/base/content/test/general/browser_tabfocus.js
+++ b/browser/base/content/test/general/browser_tabfocus.js
@@ -211,37 +211,36 @@ add_task(function*() {
   // When focus is in the tab bar, it should be retained there
   yield expectFocusShift(function () gBrowser.selectedTab.focus(),
                          "main-window", "tab2", true,
                          "focusing tab element");
   yield expectFocusShift(function () gBrowser.selectedTab = tab1,
                          "main-window", "tab1", true,
                          "tab change when selected tab element was focused");
 
-  let paintWaiter;
+  let switchWaiter;
   if (gMultiProcessBrowser) {
-    paintWaiter = new Promise((resolve, reject) => {
-      browser2.addEventListener("MozAfterRemotePaint", function paintListener() {
-        browser2.removeEventListener("MozAfterRemotePaint", paintListener, false);
+    switchWaiter = new Promise((resolve, reject) => {
+      gBrowser.addEventListener("TabSwitchDone", function listener() {
+        gBrowser.removeEventListener("TabSwitchDone", listener);
         executeSoon(resolve);
-      }, false);
-      browser2.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.requestNotifyAfterRemotePaint();
+      });
     });
   }
 
   yield expectFocusShift(function () gBrowser.selectedTab = tab2,
                          "main-window", "tab2", true,
                          "another tab change when selected tab element was focused");
 
   // When this a remote browser, wait for the paint on the second browser so that
   // any post tab-switching stuff has time to complete before blurring the tab.
   // Otherwise, the _adjustFocusAfterTabSwitch in tabbrowser gets confused and
   // isn't sure what tab is really focused.
   if (gMultiProcessBrowser) {
-    yield paintWaiter;
+    yield switchWaiter;
   }
 
   yield expectFocusShift(function () gBrowser.selectedTab.blur(),
                          "main-window", null, true,
                          "blurring tab element");
 
   // focusing the url field should switch active focus away from the browser but
   // not clear what would be the focus in the browser
--- a/browser/devtools/framework/test/browser_toolbox_hosts_size.js
+++ b/browser/devtools/framework/test/browser_toolbox_hosts_size.js
@@ -20,22 +20,22 @@ add_task(function*() {
   let nbox = gBrowser.getNotificationBox();
   let {clientHeight: nboxHeight, clientWidth: nboxWidth} = nbox;
   let toolbox = yield gDevTools.showToolbox(TargetFactory.forTab(tab));
 
   is (nbox.clientHeight, nboxHeight, "Opening the toolbox hasn't changed the height of the nbox");
   is (nbox.clientWidth, nboxWidth, "Opening the toolbox hasn't changed the width of the nbox");
 
   let iframe = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-bottom-iframe");
-  is (iframe.clientHeight, nboxHeight - 10, "The iframe fits within the available space ");
+  is (iframe.clientHeight, nboxHeight - 25, "The iframe fits within the available space");
 
   yield toolbox.switchHost(devtools.Toolbox.HostType.SIDE);
   iframe = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-side-iframe");
   iframe.style.minWidth = "1px"; // Disable the min width set in css
-  is (iframe.clientWidth, nboxWidth - 10, "The iframe fits within the available space");
+  is (iframe.clientWidth, nboxWidth - 25, "The iframe fits within the available space");
 
   yield cleanup(toolbox);
 });
 
 add_task(function*() {
   // Set size prefs to something reasonable, so we can check to make sure
   // they are being set properly.
   Services.prefs.setIntPref("devtools.toolbox.footer.height", 100);
--- a/browser/devtools/framework/toolbox-hosts.js
+++ b/browser/devtools/framework/toolbox-hosts.js
@@ -5,16 +5,22 @@
 "use strict";
 
 const {Cu} = require("chrome");
 const EventEmitter = require("devtools/toolkit/event-emitter");
 const {Promise: promise} = require("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/devtools/DOMHelpers.jsm");
 
+/* A host should always allow this much space for the page to be displayed.
+ * There is also a min-height on the browser, but we still don't want to set
+ * frame.height to be larger than that, since it can cause problems with
+ * resizing the toolbox and panel layout. */
+const MIN_PAGE_SIZE = 25;
+
 /**
  * A toolbox host represents an object that contains a toolbox (e.g. the
  * sidebar or a separate window). Any host object should implement the
  * following functions:
  *
  * create() - create the UI and emit a 'ready' event when the UI is ready to use
  * destroy() - destroy the host's UI
  */
@@ -52,17 +58,17 @@ BottomHost.prototype = {
 
     this._splitter = ownerDocument.createElement("splitter");
     this._splitter.setAttribute("class", "devtools-horizontal-splitter");
 
     this.frame = ownerDocument.createElement("iframe");
     this.frame.className = "devtools-toolbox-bottom-iframe";
     this.frame.height = Math.min(
       Services.prefs.getIntPref(this.heightPref),
-      this._nbox.clientHeight - 10 // Always show at least some page content
+      this._nbox.clientHeight - MIN_PAGE_SIZE
     );
 
     this._nbox.appendChild(this._splitter);
     this._nbox.appendChild(this.frame);
 
     let frameLoad = () => {
       this.emit("ready", this.frame);
       deferred.resolve(this.frame);
@@ -139,17 +145,17 @@ SidebarHost.prototype = {
     this._splitter = ownerDocument.createElement("splitter");
     this._splitter.setAttribute("class", "devtools-side-splitter");
 
     this.frame = ownerDocument.createElement("iframe");
     this.frame.className = "devtools-toolbox-side-iframe";
 
     this.frame.width = Math.min(
       Services.prefs.getIntPref(this.widthPref),
-      this._sidebar.clientWidth - 10 // Always show at least some page content
+      this._sidebar.clientWidth - MIN_PAGE_SIZE
     );
 
     this._sidebar.appendChild(this._splitter);
     this._sidebar.appendChild(this.frame);
 
     let frameLoad = () => {
       this.emit("ready", this.frame);
       deferred.resolve(this.frame);
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -2323,17 +2323,17 @@ TextEditor.prototype = {
  */
 function ElementEditor(aContainer, aNode) {
   this.container = aContainer;
   this.node = aNode;
   this.markup = this.container.markup;
   this.template = this.markup.template.bind(this.markup);
   this.doc = this.markup.doc;
 
-  this.attrs = {};
+  this.attrElements = new Map();
   this.animationTimers = {};
 
   // The templates will fill the following properties
   this.elt = null;
   this.tag = null;
   this.closeTag = null;
   this.attrList = null;
   this.newAttr = null;
@@ -2403,67 +2403,80 @@ ElementEditor.prototype = {
       flashElementOff(this.getAttributeElement(attrName));
     }, this.markup.CONTAINER_FLASHING_DURATION);
   },
 
   /**
    * Update the state of the editor from the node.
    */
   update: function() {
-    let attrs = this.node.attributes || [];
-    let attrsToRemove = new Set(this.attrList.querySelectorAll(".attreditor"));
-
-    // Only loop through the current attributes on the node, anything that's
-    // been removed will be removed from this DOM because it will be part of
-    // the attrsToRemove set.
-    for (let attr of attrs) {
-      let el = this.attrs[attr.name];
+    let nodeAttributes = this.node.attributes || [];
+
+    // Keep the data model in sync with attributes on the node.
+    let currentAttributes = new Set(nodeAttributes.map(a=>a.name));
+    for (let name of this.attrElements.keys()) {
+      if (!currentAttributes.has(name)) {
+        this.removeAttribute(name);
+      }
+    }
+
+    // Only loop through the current attributes on the node.  Missing
+    // attributes have already been removed at this point.
+    for (let attr of nodeAttributes) {
+      let el = this.attrElements.get(attr.name);
       let valueChanged = el && el.querySelector(".attr-value").innerHTML !== attr.value;
       let isEditing = el && el.querySelector(".editable").inplaceEditor;
       let canSimplyShowEditor = el && (!valueChanged || isEditing);
 
       if (canSimplyShowEditor) {
         // Element already exists and doesn't need to be recreated.
         // Just show it (it's hidden by default due to the template).
-        attrsToRemove.delete(el);
         el.style.removeProperty("display");
       } else {
         // Create a new editor, because the value of an existing attribute
         // has changed.
         let attribute = this._createAttribute(attr);
         attribute.style.removeProperty("display");
 
         // Temporarily flash the attribute to highlight the change.
         // But not if this is the first time the editor instance has
         // been created.
         if (this.initialized) {
           this.flashAttribute(attr.name);
         }
       }
     }
-
-    for (let el of attrsToRemove) {
-      el.remove();
-    }
   },
 
   _startModifyingAttributes: function() {
     return this.node.startModifyingAttributes();
   },
 
   /**
    * Get the element used for one of the attributes of this element
    * @param string attrName The name of the attribute to get the element for
    * @return DOMElement
    */
   getAttributeElement: function(attrName) {
     return this.attrList.querySelector(
       ".attreditor[data-attr=" + attrName + "] .attr-value");
   },
 
+  /**
+   * Remove an attribute from the attrElements object and the DOM
+   * @param string attrName The name of the attribute to remove
+   */
+  removeAttribute: function(attrName) {
+    let attr = this.attrElements.get(attrName);
+    if (attr) {
+      this.attrElements.delete(attrName);
+      attr.remove();
+    }
+  },
+
   _createAttribute: function(aAttr, aBefore = null) {
     // Create the template editor, which will save some variables here.
     let data = {
       attrName: aAttr.name,
     };
     this.template("attribute", data);
     var {attr, inner, name, val} = data;
 
@@ -2536,28 +2549,23 @@ ElementEditor.prototype = {
       }
     });
 
     // Figure out where we should place the attribute.
     let before = aBefore;
     if (aAttr.name == "id") {
       before = this.attrList.firstChild;
     } else if (aAttr.name == "class") {
-      let idNode = this.attrs["id"];
+      let idNode = this.attrElements.get("id");
       before = idNode ? idNode.nextSibling : this.attrList.firstChild;
     }
     this.attrList.insertBefore(attr, before);
 
-    // Remove the old version of this attribute from the DOM.
-    let oldAttr = this.attrs[aAttr.name];
-    if (oldAttr && oldAttr.parentNode) {
-      oldAttr.parentNode.removeChild(oldAttr);
-    }
-
-    this.attrs[aAttr.name] = attr;
+    this.removeAttribute(aAttr.name);
+    this.attrElements.set(aAttr.name, attr);
 
     let collapsedValue;
     if (aAttr.value.match(COLLAPSE_DATA_URL_REGEX)) {
       collapsedValue = truncateString(aAttr.value, COLLAPSE_DATA_URL_LENGTH);
     } else {
       collapsedValue = truncateString(aAttr.value, COLLAPSE_ATTRIBUTE_LENGTH);
     }
 
--- a/browser/devtools/markupview/test/browser_markupview_css_completion_style_attribute.js
+++ b/browser/devtools/markupview/test/browser_markupview_css_completion_style_attribute.js
@@ -135,12 +135,12 @@ function* checkData(index, editor, inspe
       ok(editor.popup.isOpen, "Popup is open");
     } else {
       ok(editor.popup._panel.state != "open" && editor.popup._panel.state != "showing",
         "Popup is closed");
     }
   } else {
     let nodeFront = yield getNodeFront("#node14", inspector);
     let editor = getContainerForNodeFront(nodeFront, inspector).editor;
-    let attr = editor.attrs["style"].querySelector(".editable");
+    let attr = editor.attrElements.get("style").querySelector(".editable");
     is(attr.textContent, completion, "Correct value is persisted after pressing Enter");
   }
 }
--- a/browser/devtools/markupview/test/browser_markupview_mutation_01.js
+++ b/browser/devtools/markupview/test/browser_markupview_mutation_01.js
@@ -35,16 +35,42 @@ const TEST_DATA = [
     check: function*(inspector) {
       let {editor} = yield getContainerForSelector("#node1", inspector);
       ok(![...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
         return attr.textContent.trim() === "newattr=\"newattrval\"";
       }), "newattr attribute removed");
     }
   },
   {
+    desc: "Re-adding an attribute",
+    test: () => {
+      let node1 = getNode("#node1");
+      node1.setAttribute("newattr", "newattrval");
+    },
+    check: function*(inspector) {
+      let {editor} = yield getContainerForSelector("#node1", inspector);
+      ok([...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
+        return attr.textContent.trim() === "newattr=\"newattrval\"";
+      }), "newattr attribute found");
+    }
+  },
+  {
+    desc: "Changing an attribute",
+    test: () => {
+      let node1 = getNode("#node1");
+      node1.setAttribute("newattr", "newattrchanged");
+    },
+    check: function*(inspector) {
+      let {editor} = yield getContainerForSelector("#node1", inspector);
+      ok([...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
+        return attr.textContent.trim() === "newattr=\"newattrchanged\"";
+      }), "newattr attribute found");
+    }
+  },
+  {
     desc: "Updating the text-content",
     test: () => {
       let node1 = getNode("#node1");
       node1.textContent = "newtext";
     },
     check: function*(inspector) {
       let {children} = yield getContainerForSelector("#node1", inspector);
       is(children.querySelector(".text").textContent.trim(), "newtext",
--- a/browser/devtools/markupview/test/browser_markupview_mutation_02.js
+++ b/browser/devtools/markupview/test/browser_markupview_mutation_02.js
@@ -118,17 +118,17 @@ function* assertNodeFlashing(nodeFront, 
   clearTimeout(container._flashMutationTimer);
   container._flashMutationTimer = null;
   container.tagState.classList.remove("theme-bg-contrast");
 }
 
 function* assertAttributeFlashing(nodeFront, attribute, inspector) {
   let container = getContainerForNodeFront(nodeFront, inspector);
   ok(container, "Markup container for node found");
-  ok(container.editor.attrs[attribute], "Attribute exists on editor");
+  ok(container.editor.attrElements.get(attribute), "Attribute exists on editor");
 
   let attributeElement = container.editor.getAttributeElement(attribute);
 
   ok(attributeElement.classList.contains("theme-bg-contrast"),
     "Element for " + attribute + " attribute is flashing");
 
   attributeElement.classList.remove("theme-bg-contrast");
 }
--- a/browser/devtools/markupview/test/browser_markupview_tag_edit_02.js
+++ b/browser/devtools/markupview/test/browser_markupview_tag_edit_02.js
@@ -17,17 +17,17 @@ add_task(function*() {
 
   info("Verify attributes, only ID should be there for now");
   yield assertAttributes("#test-div", {
     id: "test-div"
   });
 
   info("Focus the ID attribute and change its content");
   let {editor} = yield getContainerForSelector("#test-div", inspector);
-  let attr = editor.attrs["id"].querySelector(".editable");
+  let attr = editor.attrElements.get("id").querySelector(".editable");
   let mutated = inspector.once("markupmutation");
   setEditableFieldValue(attr,
     attr.textContent + ' class="newclass" style="color:green"', inspector);
   yield mutated;
 
   info("Verify attributes, should have ID, class and style");
   yield assertAttributes("#test-div", {
     id: "test-div",
--- a/browser/devtools/markupview/test/browser_markupview_tag_edit_07.js
+++ b/browser/devtools/markupview/test/browser_markupview_tag_edit_07.js
@@ -42,39 +42,39 @@ let TEST_DATA = [{
 }, {
   desc: "Try to add long data URL to make sure it is collapsed in attribute editor.",
   text: "style='"+DATA_URL_INLINE_STYLE+"'",
   expectedAttributes: {
     'style': DATA_URL_INLINE_STYLE
   },
   validate: (element, container, inspector) => {
     let editor = container.editor;
-    let visibleAttrText = editor.attrs["style"].querySelector(".attr-value").textContent;
+    let visibleAttrText = editor.attrElements.get("style").querySelector(".attr-value").textContent;
     is (visibleAttrText, DATA_URL_INLINE_STYLE_COLLAPSED);
   }
 }, {
   desc: "Try to add long attribute to make sure it is collapsed in attribute editor.",
   text: 'data-long="'+LONG_ATTRIBUTE+'"',
   expectedAttributes: {
     'data-long':LONG_ATTRIBUTE
   },
   validate: (element, container, inspector) => {
     let editor = container.editor;
-    let visibleAttrText = editor.attrs["data-long"].querySelector(".attr-value").textContent;
+    let visibleAttrText = editor.attrElements.get("data-long").querySelector(".attr-value").textContent;
     is (visibleAttrText, LONG_ATTRIBUTE_COLLAPSED)
   }
 }, {
   desc: "Try to add long data URL to make sure it is collapsed in attribute editor.",
   text: 'src="'+DATA_URL_ATTRIBUTE+'"',
   expectedAttributes: {
     "src": DATA_URL_ATTRIBUTE
   },
   validate: (element, container, inspector) => {
     let editor = container.editor;
-    let visibleAttrText = editor.attrs["src"].querySelector(".attr-value").textContent;
+    let visibleAttrText = editor.attrElements.get("src").querySelector(".attr-value").textContent;
     is (visibleAttrText, DATA_URL_ATTRIBUTE_COLLAPSED);
   }
 }];
 
 add_task(function*() {
   let {inspector} = yield addTab(TEST_URL).then(openInspector);
   yield runAddAttributesTests(TEST_DATA, "div", inspector)
 });
--- a/browser/devtools/markupview/test/browser_markupview_tag_edit_08.js
+++ b/browser/devtools/markupview/test/browser_markupview_tag_edit_08.js
@@ -31,29 +31,29 @@ function* testCollapsedLongAttribute(ins
 
   yield assertAttributes("#node24", {
     id: "node24",
     "class": "",
     "data-long": LONG_ATTRIBUTE
   });
 
   let {editor} = yield getContainerForSelector("#node24", inspector);
-  let attr = editor.attrs["data-long"].querySelector(".editable");
+  let attr = editor.attrElements.get("data-long").querySelector(".editable");
 
   // Check to make sure it has expanded after focus
   attr.focus();
   EventUtils.sendKey("return", inspector.panelWin);
   let input = inplaceEditor(attr).input;
   is (input.value, 'data-long="' + LONG_ATTRIBUTE + '"');
   EventUtils.sendKey("escape", inspector.panelWin);
 
   setEditableFieldValue(attr, input.value + ' data-short="ABC"', inspector);
   yield inspector.once("markupmutation");
 
-  let visibleAttrText = editor.attrs["data-long"].querySelector(".attr-value").textContent;
+  let visibleAttrText = editor.attrElements.get("data-long").querySelector(".attr-value").textContent;
   is (visibleAttrText, LONG_ATTRIBUTE_COLLAPSED)
 
   yield assertAttributes("#node24", {
     id: "node24",
     class: "",
     'data-long': LONG_ATTRIBUTE,
     "data-short": "ABC"
   });
@@ -64,17 +64,17 @@ function* testModifyInlineStyleWithQuote
 
   yield assertAttributes("#node26", {
     id: "node26",
     style: 'background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F");'
   });
 
   let onMutated = inspector.once("markupmutation");
   let {editor} = yield getContainerForSelector("#node26", inspector);
-  let attr = editor.attrs["style"].querySelector(".editable");
+  let attr = editor.attrElements.get("style").querySelector(".editable");
 
   attr.focus();
   EventUtils.sendKey("return", inspector.panelWin);
 
   let input = inplaceEditor(attr).input;
   let value = input.value;
 
   is (value,
@@ -100,17 +100,17 @@ function* testEditingAttributeWithMixedQ
 
   yield assertAttributes("#node27", {
     "id": "node27",
     "class": 'Double " and single \''
   });
 
   let onMutated = inspector.once("markupmutation");
   let {editor} = yield getContainerForSelector("#node27", inspector);
-  let attr = editor.attrs["class"].querySelector(".editable");
+  let attr = editor.attrElements.get("class").querySelector(".editable");
 
   attr.focus();
   EventUtils.sendKey("return", inspector.panelWin);
 
   let input = inplaceEditor(attr).input;
   let value = input.value;
 
   is (value, "class=\"Double &quot; and single '\"", "Value contains &quot;");
--- a/browser/devtools/markupview/test/browser_markupview_tag_edit_09.js
+++ b/browser/devtools/markupview/test/browser_markupview_tag_edit_09.js
@@ -22,17 +22,17 @@ function* testWellformedMixedCase(inspec
   info("Modifying a mixed-case attribute, " +
     "expecting the attribute's case to be preserved");
 
   info("Listening to markup mutations");
   let onMutated = inspector.once("markupmutation");
 
   info("Focusing the viewBox attribute editor");
   let {editor} = yield getContainerForSelector("svg", inspector);
-  let attr = editor.attrs["viewBox"].querySelector(".editable");
+  let attr = editor.attrElements.get("viewBox").querySelector(".editable");
   attr.focus();
   EventUtils.sendKey("return", inspector.panelWin);
 
   info("Editing the attribute value and waiting for the mutation event");
   let input = inplaceEditor(attr).input;
   input.value = "viewBox=\"0 0 1 1\"";
   EventUtils.sendKey("return", inspector.panelWin);
   yield onMutated;
@@ -48,17 +48,17 @@ function* testMalformedMixedCase(inspect
   info("Modifying a malformed, mixed-case attribute, " +
     "expecting the attribute's case to be preserved");
 
   info("Listening to markup mutations");
   let onMutated = inspector.once("markupmutation");
 
   info("Focusing the viewBox attribute editor");
   let {editor} = yield getContainerForSelector("svg", inspector);
-  let attr = editor.attrs["viewBox"].querySelector(".editable");
+  let attr = editor.attrElements.get("viewBox").querySelector(".editable");
   attr.focus();
   EventUtils.sendKey("return", inspector.panelWin);
 
   info("Editing the attribute value and waiting for the mutation event");
   let input = inplaceEditor(attr).input;
   input.value = "viewBox=\"<>\"";
   EventUtils.sendKey("return", inspector.panelWin);
   yield onMutated;
--- a/browser/devtools/markupview/test/helper_attributes_test_runner.js
+++ b/browser/devtools/markupview/test/helper_attributes_test_runner.js
@@ -128,17 +128,17 @@ function* runEditAttributesTest(test, in
   info("Editing attribute " + test.name + " with value " + test.value);
 
   let container = yield getContainerForSelector(test.node, inspector);
   ok(container && container.editor, "The markup-container for " + test.node +
     " was found");
 
   info("Listening for the markupmutation event");
   let nodeMutated = inspector.once("markupmutation");
-  let attr = container.editor.attrs[test.name].querySelector(".editable");
+  let attr = container.editor.attrElements.get(test.name).querySelector(".editable");
   setEditableFieldValue(attr, test.value, inspector);
   yield nodeMutated;
 
   info("Asserting the new attributes after edition");
   yield assertAttributes(test.node, test.expectedAttributes);
 
   info("Undo the change and assert that the attributes have been changed back");
   yield undoChange(inspector);
--- a/browser/devtools/netmonitor/test/browser.ini
+++ b/browser/devtools/netmonitor/test/browser.ini
@@ -52,17 +52,16 @@ skip-if= buildapp == 'mulet'
 [browser_net_copy_image_as_data_uri.js]
 [browser_net_copy_url.js]
 [browser_net_copy_as_curl.js]
 skip-if = e10s # Bug 1091596
 [browser_net_cyrillic-01.js]
 [browser_net_cyrillic-02.js]
 [browser_net_details-no-duplicated-content.js]
 [browser_net_filter-01.js]
-skip-if = e10s # Bug 1091603
 [browser_net_filter-02.js]
 [browser_net_filter-03.js]
 [browser_net_filter-04.js]
 [browser_net_footer-summary.js]
 [browser_net_html-preview.js]
 [browser_net_icon-preview.js]
 [browser_net_image-tooltip.js]
 [browser_net_json-long.js]
--- a/browser/devtools/performance/modules/front.js
+++ b/browser/devtools/performance/modules/front.js
@@ -292,18 +292,20 @@ PerformanceFront.prototype = {
     // for all targets and interacts with the whole platform, so we don't want
     // to affect other clients by stopping (or restarting) it.
     let profilerStatus = yield this._request("profiler", "isActive");
     if (profilerStatus.isActive) {
       this.emit("profiler-already-active");
       return profilerStatus.currentTime;
     }
 
-    // Extend the profiler options so that protocol.js doesn't modify the original.
-    let profilerOptions = extend({}, this._customProfilerOptions);
+    // If this._customProfilerOptions is defined, use those to pass in
+    // to the profiler actor. The profiler actor handles all the defaults
+    // now, so this should only be used for tests.
+    let profilerOptions = this._customProfilerOptions || {};
     yield this._request("profiler", "startProfiler", profilerOptions);
 
     this.emit("profiler-activated");
     return 0;
   }),
 
   /**
    * Starts the timeline actor.
@@ -396,29 +398,16 @@ PerformanceFront.prototype = {
 
     let delay = DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT;
     this._sitesPullTimeout = setTimeout(this._pullAllocationSites, delay);
 
     deferred.resolve();
   }),
 
   /**
-   * Overrides the options sent to the built-in profiler module when activating,
-   * such as the maximum entries count, the sampling interval etc.
-   *
-   * Used in tests and for older backend implementations.
-   */
-  _customProfilerOptions: {
-    entries: 1000000,
-    interval: 1,
-    features: ["js"],
-    threadFilters: ["GeckoMain"]
-  },
-
-  /**
    * Returns an object indicating if mock actors are being used or not.
    */
   getMocksInUse: function () {
     return {
       memory: this._usingMockMemory,
       timeline: this._usingMockTimeline
     };
   }
--- a/browser/themes/linux/browser-lightweightTheme.css
+++ b/browser/themes/linux/browser-lightweightTheme.css
@@ -6,26 +6,26 @@
 %filter substitution
 
 /*
  * LightweightThemeListener will append the current lightweight theme's header
  * image to the background-image for each of the following rulesets.
  */
 
 /* Lightweight theme on tabs */
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[selected=true]:-moz-lwtheme::before,
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[selected=true]:-moz-lwtheme::before {
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[visuallyselected=true]:-moz-lwtheme::before,
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[visuallyselected=true]:-moz-lwtheme::before {
   background-attachment: scroll, fixed;
   background-color: transparent;
   background-image: @fgTabTextureLWT@;/*, lwtHeader;*/
   background-position: 0 0, right top;
   background-repeat: repeat-x, no-repeat;
 }
 
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[visuallyselected=true]:-moz-lwtheme {
   background-attachment: scroll, scroll, fixed;
   background-color: transparent;
   background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
                     @fgTabTextureLWT@;/*,
                     lwtHeader;*/
   background-position: 0 0, 0 0, right top;
   background-repeat: repeat-x, repeat-x, no-repeat;
 }
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1836,25 +1836,25 @@ richlistitem[type~="action"][actiontype=
 /* Tab drag and drop */
 .tab-drop-indicator {
   list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
   margin-bottom: -9px;
   z-index: 3;
 }
 
 /* Tab close button */
-.tab-close-button:not([selected]):not(:hover) {
+.tab-close-button:not([visuallyselected]):not(:hover) {
   background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 64, 16, 48);
 }
 
-#TabsToolbar[brighttext] .tab-close-button:not([selected]):not(:hover) {
+#TabsToolbar[brighttext] .tab-close-button:not([visuallyselected]):not(:hover) {
   background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 80, 16, 64);
 }
 
-.tab-close-button:not([selected]):not(:hover):-moz-lwtheme-darktext {
+.tab-close-button:not([visuallyselected]):not(:hover):-moz-lwtheme-darktext {
   background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 96, 16, 80);
 }
 
 /* Tabstrip new tab button */
 .tabs-newtab-button,
 #TabsToolbar > #new-tab-button ,
 #TabsToolbar > #wrapper-new-tab-button > #new-tab-button {
   list-style-image: url("moz-icon://stock/gtk-add?size=menu");
@@ -1919,17 +1919,17 @@ richlistitem[type~="action"][actiontype=
   display: none;
 }
 
 /* All tabs menupopup */
 .alltabs-item > .menu-iconic-left > .menu-iconic-icon {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
 }
 
-.alltabs-item[selected="true"] {
+.alltabs-item[visuallyselected="true"] {
   font-weight: bold;
 }
 
 .alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon {
   list-style-image: url("chrome://global/skin/icons/loading_16.png");
 }
 
 .alltabs-item[tabIsVisible] {
--- a/browser/themes/osx/browser-lightweightTheme.css
+++ b/browser/themes/osx/browser-lightweightTheme.css
@@ -5,34 +5,34 @@
 %include shared.inc
 
 /*
  * LightweightThemeListener will append the current lightweight theme's header
  * image to the background-image for each of the following rulesets.
  */
 
 /* Lightweight theme on tabs */
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[selected=true]:-moz-lwtheme::before,
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[selected=true]:-moz-lwtheme::before {
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[visuallyselected=true]:-moz-lwtheme::before,
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[visuallyselected=true]:-moz-lwtheme::before {
   background-attachment: scroll, fixed;
   background-color: transparent;
   background-image: @fgTabTextureLWT@;/*, lwtHeader;*/
   background-position: 0 0, right top;
   background-repeat: repeat-x, no-repeat;
 }
 
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[visuallyselected=true]:-moz-lwtheme {
   background-attachment: scroll, scroll, fixed;
   background-color: transparent;
   background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
                     @fgTabTextureLWT@;/*,
                     lwtHeader;*/
   background-position: 0 0, 0 0, right top;
   background-repeat: repeat-x, repeat-x, no-repeat;
 }
 
 @media (min-resolution: 2dppx) {
-  #tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
+  #tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[visuallyselected=true]:-moz-lwtheme {
     background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png),
                       @fgTabTextureLWT@;/*,
                       lwtHeader;*/
   }
 }
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3040,36 +3040,36 @@ toolbarbutton.chevron > .toolbarbutton-m
   /* image preloading hack from shared/tabs.inc.css */
   #tabbrowser-tabs::before {
     background-image:
       url(chrome://browser/skin/tabbrowser/tab-background-end@2x.png),
       url(chrome://browser/skin/tabbrowser/tab-background-middle@2x.png),
       url(chrome://browser/skin/tabbrowser/tab-background-start@2x.png);
   }
 
-  .tabbrowser-tab:hover > .tab-stack > .tab-background:not([selected=true]),
+  .tabbrowser-tab:hover > .tab-stack > .tab-background:not([visuallyselected=true]),
   .tabs-newtab-button:hover {
     background-image: url(chrome://browser/skin/tabbrowser/tab-background-start@2x.png),
                       url(chrome://browser/skin/tabbrowser/tab-background-middle@2x.png),
                       url(chrome://browser/skin/tabbrowser/tab-background-end@2x.png);
   }
 
-  .tab-background-middle[selected=true] {
+  .tab-background-middle[visuallyselected=true] {
     background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png),
                       @fgTabTexture@,
                       none;
   }
 
-  .tab-background-start[selected=true]:-moz-locale-dir(ltr)::after,
-  .tab-background-end[selected=true]:-moz-locale-dir(rtl)::after {
+  .tab-background-start[visuallyselected=true]:-moz-locale-dir(ltr)::after,
+  .tab-background-end[visuallyselected=true]:-moz-locale-dir(rtl)::after {
     background-image: url(chrome://browser/skin/tabbrowser/tab-stroke-start@2x.png);
   }
 
-  .tab-background-end[selected=true]:-moz-locale-dir(ltr)::after,
-  .tab-background-start[selected=true]:-moz-locale-dir(rtl)::after {
+  .tab-background-end[visuallyselected=true]:-moz-locale-dir(ltr)::after,
+  .tab-background-start[visuallyselected=true]:-moz-locale-dir(rtl)::after {
     background-image: url(chrome://browser/skin/tabbrowser/tab-stroke-end@2x.png);
   }
 
   .tab-icon-image {
     list-style-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
   }
 
   .tab-throbber[busy] {
@@ -3077,50 +3077,50 @@ toolbarbutton.chevron > .toolbarbutton-m
   }
 
   .tab-throbber[progress] {
     list-style-image: url("chrome://browser/skin/tabbrowser/loading@2x.png");
   }
 
   /* Background tab separators */
   #tabbrowser-tabs[movingtab] > .tabbrowser-tab[beforeselected]:not([last-visible-tab])::after,
-  .tabbrowser-tab:not([selected]):not([afterselected-visible]):not([afterhovered]):not([first-visible-tab]):not(:hover)::before,
-  #tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([selected]):not([beforehovered]):not(:hover)::after {
+  .tabbrowser-tab:not([visuallyselected]):not([afterselected-visible]):not([afterhovered]):not([first-visible-tab]):not(:hover)::before,
+  #tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([visuallyselected]):not([beforehovered]):not(:hover)::after {
     background-image: url(chrome://browser/skin/tabbrowser/tab-separator@2x.png);
   }
 }
 
-.tabbrowser-tab:not(:hover) > .tab-stack > .tab-content > .tab-icon-image:not([selected="true"]) {
+.tabbrowser-tab:not(:hover) > .tab-stack > .tab-content > .tab-icon-image:not([visuallyselected="true"]) {
   opacity: .9;
 }
 
 /*
  * Force the overlay to create a new stacking context so it always appears on
  * top of the icon.
  */
 .tab-icon-overlay {
   opacity: 0.9999;
 }
 
-.tab-label:not([selected="true"]) {
+.tab-label:not([visuallyselected="true"]) {
   opacity: .7;
 }
 
 .tabbrowser-tab,
 .tabs-newtab-button {
   font: message-box;
   border: none;
 }
 
-.tabbrowser-tab[selected=true]:not(:-moz-lwtheme) {
+.tabbrowser-tab[visuallyselected=true]:not(:-moz-lwtheme) {
   /* overriding tabbox.css */
   color: inherit;
 }
 
-.tabbrowser-tab[selected=true] {
+.tabbrowser-tab[visuallyselected=true] {
   /* overriding tabbox.css */
   text-shadow: inherit;
 }
 
 .tabs-newtab-button > .toolbarbutton-icon {
   -moz-box-align: center;
   border: solid transparent;
   border-width: 0 11px;
@@ -3160,17 +3160,17 @@ toolbarbutton.chevron > .toolbarbutton-m
 }
 
 /* Background tabs:
  *
  * Decrease the height of the hoverable region of background tabs whenever the tabs are at the top
  * of the window (e.g. no menubar, tabs in titlebar, etc.) to make it easier to drag the window by
  * the titlebar. We don't need this in fullscreen since window dragging is not an issue there.
  */
-#main-window[tabsintitlebar]:not([inFullscreen]) .tab-background-middle:not([selected=true]) {
+#main-window[tabsintitlebar]:not([inFullscreen]) .tab-background-middle:not([visuallyselected=true]) {
   clip-path: url(chrome://browser/content/browser.xul#tab-hover-clip-path);
 }
 
 /**
  * Tab Drag and Drop
  */
 
 .tab-drop-indicator {
@@ -3196,22 +3196,22 @@ toolbarbutton.chevron > .toolbarbutton-m
 
 .tab-close-button {
   -moz-appearance: none;
   border: none !important;
   background: none;
   cursor: default;
 }
 
-#TabsToolbar[brighttext] .tab-close-button.close-icon:not([selected=true]):not(:hover) {
+#TabsToolbar[brighttext] .tab-close-button.close-icon:not([visuallyselected=true]):not(:hover) {
   -moz-image-region: rect(0, 64px, 16px, 48px);
 }
 
 @media (min-resolution: 2dppx) {
-  #TabsToolbar[brighttext] .tab-close-button.close-icon:not([selected=true]):not(:hover) {
+  #TabsToolbar[brighttext] .tab-close-button.close-icon:not([visuallyselected=true]):not(:hover) {
     -moz-image-region: rect(0, 128px, 32px, 96px);
   }
 }
 
 .tabbrowser-arrowscrollbox > .scrollbutton-up,
 .tabbrowser-arrowscrollbox > .scrollbutton-down {
   -moz-image-region: rect(0, 13px, 20px, 0);
   margin: 0 0 var(--tab-toolbar-navbar-overlap);
--- a/browser/themes/osx/devedition.css
+++ b/browser/themes/osx/devedition.css
@@ -80,16 +80,16 @@
 
 /* Don't use the default background for tabs toolbar */
 #TabsToolbar {
   -moz-appearance: none !important;
 }
 
 /* Tab styling - make sure to use an inverted icon for the selected tab
    (brighttext only covers the unselected tabs) */
-.tab-close-button[selected=true]:not(:hover) {
+.tab-close-button[visuallyselected=true]:not(:hover) {
   -moz-image-region: rect(0, 64px, 16px, 48px);
 }
 @media (min-resolution: 2dppx) {
-  .tab-close-button[selected=true]:not(:hover) {
+  .tab-close-button[visuallyselected=true]:not(:hover) {
     -moz-image-region: rect(0, 128px, 32px, 96px);
   }
 }
--- a/browser/themes/shared/devedition.inc.css
+++ b/browser/themes/shared/devedition.inc.css
@@ -136,27 +136,27 @@
   -moz-margin-start: 0;
 }
 
 .tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox {
   -moz-padding-end: 0;
   -moz-padding-start: 0;
 }
 
-.tab-background-start[selected=true]::after,
-.tab-background-start[selected=true]::before,
+.tab-background-start[visuallyselected=true]::after,
+.tab-background-start[visuallyselected=true]::before,
 .tab-background-start,
 .tab-background-end,
-.tab-background-end[selected=true]::after,
-.tab-background-end[selected=true]::before {
+.tab-background-end[visuallyselected=true]::after,
+.tab-background-end[visuallyselected=true]::before {
   width: 0;
 }
 
-.tab-background-start[selected=true]::after,
-.tab-background-end[selected=true]::after {
+.tab-background-start[visuallyselected=true]::after,
+.tab-background-end[visuallyselected=true]::after {
   -moz-margin-start: 0;
 }
 /* End override @tabCurveHalfWidth@ and @tabCurveWidth@ */
 
 #urlbar ::-moz-selection,
 #navigator-toolbox .searchbar-textbox ::-moz-selection,
 .browserContainer > findbar ::-moz-selection {
   background-color: var(--chrome-selection-background-color);
@@ -265,29 +265,29 @@ searchbar:not([oneoffui]) .search-go-but
 }
 
 .tab-background {
   visibility: hidden;
 }
 
 /* Make the tab splitter 1px wide with a solid background. */
 #tabbrowser-tabs[movingtab] > .tabbrowser-tab[beforeselected]:not([last-visible-tab])::after,
-.tabbrowser-tab:not([selected]):not([afterselected-visible]):not([afterhovered]):not([first-visible-tab]):not(:hover)::before,
-#tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([selected]):not([beforehovered]):not(:hover)::after {
+.tabbrowser-tab:not([visuallyselected]):not([afterselected-visible]):not([afterhovered]):not([first-visible-tab]):not(:hover)::before,
+#tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([visuallyselected]):not([beforehovered]):not(:hover)::after {
   background: var(--tab-separator-color);
   width: 1px;
   -moz-margin-start: 0;
   -moz-margin-end: -1px;
 }
 
 /* For the last tab separator, use margin-start of -1px to prevent jittering
    due to the ::after element causing the width of the tab to extend, which
    causes an overflow and makes it disappear, which removes the overflow and
    causes it to reappear, etc, etc. */
-#tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([selected]):not([beforehovered]):not(:hover)::after {
+#tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([visuallyselected]):not([beforehovered]):not(:hover)::after {
   -moz-margin-start: -1px;
   -moz-margin-end: 0;
 }
 
 .tabbrowser-arrowscrollbox > .scrollbutton-down,
 .tabbrowser-arrowscrollbox > .scrollbutton-up {
   background-color: var(--tab-background-color);
   border-color: transparent;
@@ -299,29 +299,29 @@ searchbar:not([oneoffui]) .search-go-but
 }
 
 .tabbrowser-tab {
   /* We normally rely on other tab elements for pointer events, but this
      theme hides those so we need it set here instead */
   pointer-events: auto;
 }
 
-.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]) > .tab-stack > .tab-content {
+.tabbrowser-tab[pinned][titlechanged]:not([visuallyselected="true"]) > .tab-stack > .tab-content {
   background-image: var(--pinned-tab-glow);
   background-position: center;
   background-size: 100%;
 }
 
 .tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):hover,
 .tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):hover,
 .tabbrowser-tab:hover {
   background-color: var(--tab-hover-background-color);
 }
 
-.tabbrowser-tab[selected] {
+.tabbrowser-tab[visuallyselected] {
   color: var(--tab-selection-color) !important; /* Override color: inherit */
   background-color: var(--tab-selection-background-color);
   box-shadow: var(--tab-selection-box-shadow);
 }
 
 /* Don't need space for the tab curves (66px - 30px) */
 .tabs-newtab-button {
   width: 36px;
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -42,17 +42,17 @@
   -moz-box-align: stretch;
 }
 
 .tabbrowser-tab[remote] {
   text-decoration: underline;
 }
 
 /* The selected tab should appear above adjacent tabs, .tabs-newtab-button and the highlight of #nav-bar */
-.tabbrowser-tab[selected=true] {
+.tabbrowser-tab[visuallyselected=true] {
   position: relative;
   z-index: 2;
 }
 
 .tab-background-middle {
   -moz-box-flex: 1;
   background-clip: padding-box;
   border-left: @tabCurveHalfWidth@ solid transparent;
@@ -162,132 +162,132 @@
   opacity: 0;
 }
 
 .tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator,
 .tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator {
   transition: opacity 150ms ease;
 }
 
-.tab-background-start[selected=true]::after,
-.tab-background-start[selected=true]::before,
+.tab-background-start[visuallyselected=true]::after,
+.tab-background-start[visuallyselected=true]::before,
 .tab-background-start,
 .tab-background-end,
-.tab-background-end[selected=true]::after,
-.tab-background-end[selected=true]::before {
+.tab-background-end[visuallyselected=true]::after,
+.tab-background-end[visuallyselected=true]::before {
   min-height: var(--tab-min-height);
   width: @tabCurveWidth@;
 }
 
-.tabbrowser-tab:not([selected=true]),
+.tabbrowser-tab:not([visuallyselected=true]),
 .tabbrowser-tab:-moz-lwtheme {
   color: inherit;
 }
 
 /* Selected tab */
 
 /*
  Tab background pseudo-elements which are positioned above .tab-background-start/end:
    - ::before - provides the fill of the tab curve and is clipped to the tab shape. This is where
                 pointer events go for the curve.
    - ::after  - provides the border/stroke of the tab curve and is overlayed above ::before.  Pointer
                 events go through to ::before to get the proper shape.
  */
 
 
-.tab-background-start[selected=true]::after,
-.tab-background-end[selected=true]::after {
+.tab-background-start[visuallyselected=true]::after,
+.tab-background-end[visuallyselected=true]::after {
   /* position ::after on top of its parent */
   -moz-margin-start: -@tabCurveWidth@;
   background-size: 100% 100%;
   content: "";
   display: -moz-box;
   position: relative;
 }
 
-.tab-background-start[selected=true]::before,
-.tab-background-end[selected=true]::before {
+.tab-background-start[visuallyselected=true]::before,
+.tab-background-end[visuallyselected=true]::before {
   /* all ::before pseudo elements */
   content: "";
   display: -moz-box;
 }
 
-.tab-background-start[selected=true]:-moz-locale-dir(ltr):not(:-moz-lwtheme)::before,
-.tab-background-end[selected=true]:-moz-locale-dir(rtl):not(:-moz-lwtheme)::before {
+.tab-background-start[visuallyselected=true]:-moz-locale-dir(ltr):not(:-moz-lwtheme)::before,
+.tab-background-end[visuallyselected=true]:-moz-locale-dir(rtl):not(:-moz-lwtheme)::before {
   background-image: url(chrome://browser/skin/tabbrowser/tab-selected-start.svg);
   background-size: 100% 100%;
 }
 
-.tab-background-end[selected=true]:-moz-locale-dir(ltr):not(:-moz-lwtheme)::before,
-.tab-background-start[selected=true]:-moz-locale-dir(rtl):not(:-moz-lwtheme)::before {
+.tab-background-end[visuallyselected=true]:-moz-locale-dir(ltr):not(:-moz-lwtheme)::before,
+.tab-background-start[visuallyselected=true]:-moz-locale-dir(rtl):not(:-moz-lwtheme)::before {
   background-image: url(chrome://browser/skin/tabbrowser/tab-selected-end.svg);
   background-size: 100% 100%;
 }
 
 /* For lightweight themes, clip the header image on start, middle, and end. */
-.tab-background-start[selected=true]:-moz-locale-dir(ltr):-moz-lwtheme::before,
-.tab-background-end[selected=true]:-moz-locale-dir(rtl):-moz-lwtheme::before {
+.tab-background-start[visuallyselected=true]:-moz-locale-dir(ltr):-moz-lwtheme::before,
+.tab-background-end[visuallyselected=true]:-moz-locale-dir(rtl):-moz-lwtheme::before {
   clip-path: url(chrome://browser/content/browser.xul#tab-curve-clip-path-start);
 }
 
-.tab-background-end[selected=true]:-moz-locale-dir(ltr):-moz-lwtheme::before,
-.tab-background-start[selected=true]:-moz-locale-dir(rtl):-moz-lwtheme::before {
+.tab-background-end[visuallyselected=true]:-moz-locale-dir(ltr):-moz-lwtheme::before,
+.tab-background-start[visuallyselected=true]:-moz-locale-dir(rtl):-moz-lwtheme::before {
   clip-path: url(chrome://browser/content/browser.xul#tab-curve-clip-path-end);
 }
 
-.tab-background-start[selected=true]:-moz-locale-dir(ltr)::after,
-.tab-background-end[selected=true]:-moz-locale-dir(rtl)::after {
+.tab-background-start[visuallyselected=true]:-moz-locale-dir(ltr)::after,
+.tab-background-end[visuallyselected=true]:-moz-locale-dir(rtl)::after {
   background-image: url(chrome://browser/skin/tabbrowser/tab-stroke-start.png);
 }
 
-.tab-background-end[selected=true]:-moz-locale-dir(ltr)::after,
-.tab-background-start[selected=true]:-moz-locale-dir(rtl)::after {
+.tab-background-end[visuallyselected=true]:-moz-locale-dir(ltr)::after,
+.tab-background-start[visuallyselected=true]:-moz-locale-dir(rtl)::after {
   background-image: url(chrome://browser/skin/tabbrowser/tab-stroke-end.png);
 }
 
-.tab-background-middle[selected=true] {
+.tab-background-middle[visuallyselected=true] {
   background-clip: padding-box, padding-box, content-box;
   background-color: @fgTabBackgroundColor@;
   background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
                     @fgTabTexture@,
                     none;
   background-repeat: repeat-x;
   background-size: auto 100%;
   /* The padding-top combined with background-clip: content-box (the bottom-most) ensure the
      background-color doesn't extend above the top border. */
   padding-top: 2px;
 }
 
 /* Selected tab lightweight theme styles.
    See browser-lightweightTheme.css for information about run-time changes to LWT styles. */
-.tab-background-middle[selected=true]:-moz-lwtheme {
+.tab-background-middle[visuallyselected=true]:-moz-lwtheme {
   background-color: transparent;
   background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
                     @fgTabTextureLWT@;/*,
                     lwtHeader;*/
   /* Don't stretch the LWT header images */
   background-size: auto 100%, auto 100%, auto auto;
 }
 
 /* These LWT styles are normally overridden by browser-lightweightTheme.css */
-.tab-background-start[selected=true]:-moz-lwtheme::before,
-.tab-background-end[selected=true]:-moz-lwtheme::before {
+.tab-background-start[visuallyselected=true]:-moz-lwtheme::before,
+.tab-background-end[visuallyselected=true]:-moz-lwtheme::before {
   background-image: @fgTabTextureLWT@;
 }
 
-.tab-background-start[selected=true]:-moz-lwtheme::before,
-.tab-background-end[selected=true]:-moz-lwtheme::before,
-.tab-background-middle[selected=true]:-moz-lwtheme {
+.tab-background-start[visuallyselected=true]:-moz-lwtheme::before,
+.tab-background-end[visuallyselected=true]:-moz-lwtheme::before,
+.tab-background-middle[visuallyselected=true]:-moz-lwtheme {
   background-color: transparent;
 }
 
 /* End selected tab */
 
 /* new tab button border and gradient on hover */
-.tabbrowser-tab:hover > .tab-stack > .tab-background:not([selected=true]),
+.tabbrowser-tab:hover > .tab-stack > .tab-background:not([visuallyselected=true]),
 .tabs-newtab-button:hover {
   background-image: url(chrome://browser/skin/tabbrowser/tab-background-start.png),
                     url(chrome://browser/skin/tabbrowser/tab-background-middle.png),
                     url(chrome://browser/skin/tabbrowser/tab-background-end.png);
   background-position: left bottom, @tabCurveWidth@ bottom, right bottom;
   background-repeat: no-repeat;
   background-size: @tabCurveWidth@ 100%, calc(100% - (2 * @tabCurveWidth@)) 100%, @tabCurveWidth@ 100%;
 }
@@ -306,28 +306,28 @@
 /* Pinned tabs */
 
 /* Pinned tab separators need position: absolute when positioned (during overflow). */
 #tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned]::before {
   height: 100%;
   position: absolute;
 }
 
-.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]) > .tab-stack > .tab-content {
+.tabbrowser-tab[pinned][titlechanged]:not([visuallyselected="true"]) > .tab-stack > .tab-content {
   background-image: radial-gradient(farthest-corner at center bottom, rgb(255,255,255) 3%, rgba(186,221,251,0.75) 20%, rgba(127,179,255,0.25) 40%, transparent 70%);
   background-position: center bottom var(--tab-toolbar-navbar-overlap);
   background-repeat: no-repeat;
   background-size: 85% 100%;
 }
 
 /* Background tab separators (3px wide).
    Also show separators beside the selected tab when dragging it. */
 #tabbrowser-tabs[movingtab] > .tabbrowser-tab[beforeselected]:not([last-visible-tab])::after,
-.tabbrowser-tab:not([selected]):not([afterselected-visible]):not([afterhovered]):not([first-visible-tab]):not(:hover)::before,
-#tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([selected]):not([beforehovered]):not(:hover)::after {
+.tabbrowser-tab:not([visuallyselected]):not([afterselected-visible]):not([afterhovered]):not([first-visible-tab]):not(:hover)::before,
+#tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([visuallyselected]):not([beforehovered]):not(:hover)::after {
   -moz-margin-start: -1.5px;
   -moz-margin-end: -1.5px;
   background-image: url(chrome://browser/skin/tabbrowser/tab-separator.png);
   background-position: left bottom var(--tab-toolbar-navbar-overlap);
   background-repeat: no-repeat;
   background-size: 3px 100%;
   content: "";
   display: -moz-box;
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -78,17 +78,17 @@
   @media (-moz-os-version: windows-vista),
          (-moz-os-version: windows-win7) {
     #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme),
     #browser-bottombox:not(:-moz-lwtheme),
     .browserContainer > findbar {
       background-color: @customToolbarColor@;
     }
 
-    .tab-background-middle[selected=true]:not(:-moz-lwtheme) {
+    .tab-background-middle[visuallyselected=true]:not(:-moz-lwtheme) {
       background-color: @customToolbarColor@;
     }
 
     #navigator-toolbox:not(:-moz-lwtheme)::after {
       background-color: #aabccf;
     }
 
     #urlbar:not(:-moz-lwtheme):not([focused]):hover,
--- a/browser/themes/windows/browser-lightweightTheme.css
+++ b/browser/themes/windows/browser-lightweightTheme.css
@@ -6,34 +6,34 @@
 %filter substitution
 
 /*
  * LightweightThemeListener will append the current lightweight theme's header
  * image to the background-image for each of the following rulesets.
  */
 
 /* Lightweight theme on tabs */
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[selected=true]:-moz-lwtheme::before,
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[selected=true]:-moz-lwtheme::before {
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[visuallyselected=true]:-moz-lwtheme::before,
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[visuallyselected=true]:-moz-lwtheme::before {
   background-attachment: scroll, fixed;
   background-color: transparent;
   background-image: @fgTabTextureLWT@;/*, lwtHeader;*/
   background-position: 0 0, right top;
   background-repeat: repeat-x, no-repeat;
 }
 
-#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[visuallyselected=true]:-moz-lwtheme {
   background-attachment: scroll, scroll, fixed;
   background-color: transparent;
   background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
                     @fgTabTextureLWT@;/*,
                     lwtHeader;*/
   background-position: 0 0, 0 0, right top;
   background-repeat: repeat-x, repeat-x, no-repeat;
 }
 
 @media (min-resolution: 1.25dppx) {
-  #tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
+  #tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[visuallyselected=true]:-moz-lwtheme {
     background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png),
                       @fgTabTextureLWT@;/*,
                       lwtHeader;*/
   }
 }
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1823,67 +1823,67 @@ toolbarbutton[type="socialmark"] > .tool
   /* image preloading hack from shared/tabs.inc.css */
   #tabbrowser-tabs::before {
     background-image:
       url(chrome://browser/skin/tabbrowser/tab-background-end@2x.png),
       url(chrome://browser/skin/tabbrowser/tab-background-middle@2x.png),
       url(chrome://browser/skin/tabbrowser/tab-background-start@2x.png);
   }
 
-  .tabbrowser-tab:hover > .tab-stack > .tab-background:not([selected=true]),
+  .tabbrowser-tab:hover > .tab-stack > .tab-background:not([visuallyselected=true]),
   .tabs-newtab-button:hover {
     background-image: url(chrome://browser/skin/tabbrowser/tab-background-start@2x.png),
                       url(chrome://browser/skin/tabbrowser/tab-background-middle@2x.png),
                       url(chrome://browser/skin/tabbrowser/tab-background-end@2x.png);
   }
 
-  .tab-background-middle[selected=true] {
+  .tab-background-middle[visuallyselected=true] {
     background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png),
                       @fgTabTexture@,
                       none;
   }
 
-  .tab-background-start[selected=true]:-moz-locale-dir(ltr)::after,
-  .tab-background-end[selected=true]:-moz-locale-dir(rtl)::after {
+  .tab-background-start[visuallyselected=true]:-moz-locale-dir(ltr)::after,
+  .tab-background-end[visuallyselected=true]:-moz-locale-dir(rtl)::after {
     background-image: url(chrome://browser/skin/tabbrowser/tab-stroke-start@2x.png);
   }
 
-  .tab-background-end[selected=true]:-moz-locale-dir(ltr)::after,
-  .tab-background-start[selected=true]:-moz-locale-dir(rtl)::after {
+  .tab-background-end[visuallyselected=true]:-moz-locale-dir(ltr)::after,
+  .tab-background-start[visuallyselected=true]:-moz-locale-dir(rtl)::after {
     background-image: url(chrome://browser/skin/tabbrowser/tab-stroke-end@2x.png);
   }
 }
 
 %ifndef WINDOWS_AERO
 /* Use lighter colors of buttons and text in the titlebar on luna-blue */
 @media (-moz-windows-theme: luna-blue) {
   #tabbrowser-tabs[movingtab] > .tabbrowser-tab[beforeselected]:not([last-visible-tab])::after,
-  .tabbrowser-tab:not([selected]):not([afterselected-visible]):not([afterhovered]):not([first-visible-tab]):not(:hover)::before,
-  #tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([selected]):not([beforehovered]):not(:hover)::after {
+  .tabbrowser-tab:not([visuallyselected]):not([afterselected-visible]):not([afterhovered]):not([first-visible-tab]):not(:hover)::before,
+  #tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([visuallyselected]):not([beforehovered]):not(:hover)::after {
     background-image: url("chrome://browser/skin/tabbrowser/tab-separator-luna-blue.png");
   }
 }
 %endif
 
-#TabsToolbar[brighttext] .tab-close-button:not(:hover):not([selected="true"]) {
+#TabsToolbar[brighttext] .tab-close-button:not(:hover):not([visuallyselected="true"]) {
   -moz-image-region: rect(0, 64px, 16px, 48px) !important;
 }
 
 /* tabbrowser-tab focus ring */
 .tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-label {
   outline: 1px dotted;
 }
 
 /* Background tabs:
  *
  * Decrease the height of the hoverable region of background tabs whenever the tabs are at the top
  * of the window (e.g. no menubar, tabs in titlebar, etc.) to make it easier to drag the window by
  * the titlebar. We don't need this in fullscreen since window dragging is not an issue there.
  */
-#main-window[tabsintitlebar][sizemode=normal] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar .tab-background-middle:not([selected=true]) {
+#main-window[tabsintitlebar][sizemode=normal] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar .tab-background-middle:not([visuallyselected=true]) {
   clip-path: url(chrome://browser/content/browser.xul#tab-hover-clip-path);
 }
 
 /* Tab DnD indicator */
 .tab-drop-indicator {
   list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
   margin-bottom: -9px;
   z-index: 3;
--- a/browser/themes/windows/devedition.css
+++ b/browser/themes/windows/devedition.css
@@ -153,11 +153,11 @@
   /* Reset image-region from the windows theme */
   -moz-image-region: auto !important;
   /* Add margin otherwise it looks weird */
   -moz-margin-start: 2px;
 }
 
 /* Tab styling - make sure to use an inverted icon for the selected tab
    (brighttext only covers the unselected tabs) */
-.tab-close-button[selected=true]:not(:hover) {
+.tab-close-button[visuallyselected=true]:not(:hover) {
   -moz-image-region: rect(0, 64px, 16px, 48px);
 }
--- a/build/autoconf/nspr.m4
+++ b/build/autoconf/nspr.m4
@@ -59,27 +59,35 @@ AC_ARG_WITH(nspr-exec-prefix,
 		NSPR_LIBS="${NO_NSPR_CONFIG_SYSTEM_LDFLAGS}"
 		NSPR_VERSION_STRING="$NO_NSPR_CONFIG_SYSTEM_VERSION"
 	else
 	    no_nspr="yes"
 	fi
 
 	if test -z "$no_nspr"; then
 		nspr_config_major_version=`echo $NSPR_VERSION_STRING | \
-			sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
+			sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\(\.\([[0-9]]*\)\)\{0,1\}/\1/'`
 		nspr_config_minor_version=`echo $NSPR_VERSION_STRING | \
-			sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
+			sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\(\.\([[0-9]]*\)\)\{0,1\}/\2/'`
 		nspr_config_micro_version=`echo $NSPR_VERSION_STRING | \
-			sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
+			sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\(\.\([[0-9]]*\)\)\{0,1\}/\4/'`
+		if test -z "$nspr_config_micro_version"; then
+			nspr_config_micro_version="0"
+		fi
+
 		min_nspr_major_version=`echo $min_nspr_version | \
-			sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
+			sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\(\.\([[0-9]]*\)\)\{0,1\}/\1/'`
 		min_nspr_minor_version=`echo $min_nspr_version | \
-			sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
+			sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\(\.\([[0-9]]*\)\)\{0,1\}/\2/'`
 		min_nspr_micro_version=`echo $min_nspr_version | \
-			sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
+			sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\(\.\([[0-9]]*\)\)\{0,1\}/\4/'`
+		if test -z "$min_nspr_micro_version"; then
+			min_nspr_micro_version="0"
+		fi
+
 		if test "$nspr_config_major_version" -ne "$min_nspr_major_version"; then
 			no_nspr="yes"
 		elif test "$nspr_config_major_version" -eq "$min_nspr_major_version" &&
 		     test "$nspr_config_minor_version" -lt "$min_nspr_minor_version"; then
 			no_nspr="yes"
 		elif test "$nspr_config_major_version" -eq "$min_nspr_major_version" &&
 		     test "$nspr_config_minor_version" -eq "$min_nspr_minor_version" &&
 		     test "$nspr_config_micro_version" -lt "$min_nspr_micro_version"; then
--- a/build/autoconf/nss.m4
+++ b/build/autoconf/nss.m4
@@ -40,27 +40,35 @@ AC_ARG_WITH(nss-exec-prefix,
 	no_nss=""
 	if test "$NSS_CONFIG" = "no"; then
 		no_nss="yes"
 	else
 		NSS_CFLAGS=`$NSS_CONFIG $nss_config_args --cflags`
 		NSS_LIBS=`$NSS_CONFIG $nss_config_args --libs`
 
 		nss_config_major_version=`$NSS_CONFIG $nss_config_args --version | \
-			sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
+			sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\(\.\([[0-9]]*\)\)\{0,1\}/\1/'`
 		nss_config_minor_version=`$NSS_CONFIG $nss_config_args --version | \
-			sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
+			sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\(\.\([[0-9]]*\)\)\{0,1\}/\2/'`
 		nss_config_micro_version=`$NSS_CONFIG $nss_config_args --version | \
-			sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
+			sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\(\.\([[0-9]]*\)\)\{0,1\}/\4/'`
+		if test -z "$nss_config_micro_version"; then
+			nss_config_micro_version="0"
+		fi
+
 		min_nss_major_version=`echo $min_nss_version | \
-			sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
+			sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\(\.\([[0-9]]*\)\)\{0,1\}/\1/'`
 		min_nss_minor_version=`echo $min_nss_version | \
-			sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
+			sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\(\.\([[0-9]]*\)\)\{0,1\}/\2/'`
 		min_nss_micro_version=`echo $min_nss_version | \
-			sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
+			sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\(\.\([[0-9]]*\)\)\{0,1\}/\4/'`
+		if test -z "$min_nss_micro_version"; then
+			min_nss_micro_version="0"
+		fi
+
 		if test "$nss_config_major_version" -lt "$min_nss_major_version"; then
 			no_nss="yes"
 		elif test "$nss_config_major_version" -eq "$min_nss_major_version" &&
 		     test "$nss_config_minor_version" -lt "$min_nss_minor_version"; then
 			no_nss="yes"
 		elif test "$nss_config_major_version" -eq "$min_nss_major_version" &&
 		     test "$nss_config_minor_version" -eq "$min_nss_minor_version" &&
 		     test "$nss_config_micro_version" -lt "$min_nss_micro_version"; then
--- a/docshell/base/TimelineMarker.h
+++ b/docshell/base/TimelineMarker.h
@@ -28,19 +28,19 @@ public:
                  TracingMetadata aMetaData,
                  const nsAString& aCause);
 
   virtual ~TimelineMarker();
 
   // Check whether two markers should be considered the same,
   // for the purpose of pairing start and end markers.  Normally
   // this definition suffices.
-  virtual bool Equals(const TimelineMarker* aOther)
+  virtual bool Equals(const TimelineMarker& aOther)
   {
-    return strcmp(mName, aOther->mName) == 0;
+    return strcmp(mName, aOther.mName) == 0;
   }
 
   // Add details specific to this marker type to aMarker.  The
   // standard elements have already been set.  This method is
   // called on both the starting and ending markers of a pair.
   // Ordinarily the ending marker doesn't need to do anything
   // here.
   virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) {}
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2972,20 +2972,20 @@ nsDocShell::PopProfileTimelineMarkers(
 
   nsTArray<mozilla::dom::ProfileTimelineMarker> profileTimelineMarkers;
   SequenceRooter<mozilla::dom::ProfileTimelineMarker> rooter(
     aCx, &profileTimelineMarkers);
 
   // If we see an unpaired START, we keep it around for the next call
   // to PopProfileTimelineMarkers.  We store the kept START objects in
   // this array.
-  nsTArray<TimelineMarker*> keptMarkers;
+  nsTArray<UniquePtr<TimelineMarker>> keptMarkers;
 
   for (uint32_t i = 0; i < mProfileTimelineMarkers.Length(); ++i) {
-    TimelineMarker* startPayload = mProfileTimelineMarkers[i];
+    UniquePtr<TimelineMarker>& startPayload = mProfileTimelineMarkers[i];
     const char* startMarkerName = startPayload->GetName();
 
     bool hasSeenPaintedLayer = false;
     bool isPaint = strcmp(startMarkerName, "Paint") == 0;
 
     // If we are processing a Paint marker, we append information from
     // all the embedded Layer markers to this array.
     dom::Sequence<dom::ProfileTimelineLayerRect> layerRectangles;
@@ -2997,26 +2997,26 @@ nsDocShell::PopProfileTimelineMarkers(
       // for the matching end.  It doesn't hurt to apply this logic to
       // all event types.
       uint32_t markerDepth = 0;
 
       // The assumption is that the devtools timeline flushes markers frequently
       // enough for the amount of markers to always be small enough that the
       // nested for loop isn't going to be a performance problem.
       for (uint32_t j = i + 1; j < mProfileTimelineMarkers.Length(); ++j) {
-        TimelineMarker* endPayload = mProfileTimelineMarkers[j];
+        UniquePtr<TimelineMarker>& endPayload = mProfileTimelineMarkers[j];
         const char* endMarkerName = endPayload->GetName();
 
         // Look for Layer markers to stream out paint markers.
         if (isPaint && strcmp(endMarkerName, "Layer") == 0) {
           hasSeenPaintedLayer = true;
           endPayload->AddLayerRectangles(layerRectangles);
         }
 
-        if (!startPayload->Equals(endPayload)) {
+        if (!startPayload->Equals(*endPayload)) {
           continue;
         }
 
         // Pair start and end markers.
         if (endPayload->GetMetaData() == TRACING_INTERVAL_START) {
           ++markerDepth;
         } else if (endPayload->GetMetaData() == TRACING_INTERVAL_END) {
           if (markerDepth > 0) {
@@ -3043,24 +3043,23 @@ nsDocShell::PopProfileTimelineMarkers(
 
             break;
           }
         }
       }
 
       // If we did not see the corresponding END, keep the START.
       if (!hasSeenEnd) {
-        keptMarkers.AppendElement(mProfileTimelineMarkers[i]);
+        keptMarkers.AppendElement(Move(mProfileTimelineMarkers[i]));
         mProfileTimelineMarkers.RemoveElementAt(i);
         --i;
       }
     }
   }
 
-  ClearProfileTimelineMarkers();
   mProfileTimelineMarkers.SwapElements(keptMarkers);
 
   if (!ToJSValue(aCx, profileTimelineMarkers, aProfileTimelineMarkers)) {
     JS_ClearPendingException(aCx);
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
@@ -3081,20 +3080,20 @@ nsDocShell::AddProfileTimelineMarker(con
 {
   if (mProfileTimelineRecording) {
     TimelineMarker* marker = new TimelineMarker(this, aName, aMetaData);
     mProfileTimelineMarkers.AppendElement(marker);
   }
 }
 
 void
-nsDocShell::AddProfileTimelineMarker(UniquePtr<TimelineMarker>& aMarker)
+nsDocShell::AddProfileTimelineMarker(UniquePtr<TimelineMarker>&& aMarker)
 {
   if (mProfileTimelineRecording) {
-    mProfileTimelineMarkers.AppendElement(aMarker.release());
+    mProfileTimelineMarkers.AppendElement(Move(aMarker));
   }
 }
 
 NS_IMETHODIMP
 nsDocShell::SetWindowDraggingAllowed(bool aValue)
 {
   nsRefPtr<nsDocShell> parent = GetParentDocshell();
   if (!aValue && mItemType == typeChrome && !parent) {
@@ -3120,19 +3119,16 @@ nsDocShell::GetWindowDraggingAllowed(boo
     *aValue = mWindowDraggingAllowed;
   }
   return NS_OK;
 }
 
 void
 nsDocShell::ClearProfileTimelineMarkers()
 {
-  for (uint32_t i = 0; i < mProfileTimelineMarkers.Length(); ++i) {
-    delete mProfileTimelineMarkers[i];
-  }
   mProfileTimelineMarkers.Clear();
 }
 
 nsIDOMStorageManager*
 nsDocShell::TopSessionStorageManager()
 {
   nsresult rv;
 
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -267,17 +267,17 @@ public:
   // is no longer applied
   void NotifyAsyncPanZoomStopped();
 
   // Add new profile timeline markers to this docShell. This will only add
   // markers if the docShell is currently recording profile timeline markers.
   // See nsIDocShell::recordProfileTimelineMarkers
   void AddProfileTimelineMarker(const char* aName,
                                 TracingMetadata aMetaData);
-  void AddProfileTimelineMarker(mozilla::UniquePtr<TimelineMarker>& aMarker);
+  void AddProfileTimelineMarker(mozilla::UniquePtr<TimelineMarker>&& aMarker);
 
   // Global counter for how many docShells are currently recording profile
   // timeline markers
   static unsigned long gProfileTimelineRecordingsCount;
 
 protected:
   // Object Management
   virtual ~nsDocShell();
@@ -979,17 +979,17 @@ private:
 
   // A depth count of how many times NotifyRunToCompletionStart
   // has been called without a matching NotifyRunToCompletionStop.
   uint32_t mJSRunToCompletionDepth;
 
   // True if recording profiles.
   bool mProfileTimelineRecording;
 
-  nsTArray<TimelineMarker*> mProfileTimelineMarkers;
+  nsTArray<mozilla::UniquePtr<TimelineMarker>> mProfileTimelineMarkers;
 
   // Get rid of all the timeline markers accumulated so far
   void ClearProfileTimelineMarkers();
 
   // Separate function to do the actual name (i.e. not _top, _self etc.)
   // searching for FindItemWithName.
   nsresult DoFindItemWithName(const char16_t* aName,
                               nsISupports* aRequestor,
--- a/docshell/test/browser/frame-head.js
+++ b/docshell/test/browser/frame-head.js
@@ -3,16 +3,18 @@
 
 // Functions that are automatically loaded as frame scripts for
 // timeline tests.
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 let { Promise } = Cu.import('resource://gre/modules/Promise.jsm', {});
 
+Cu.import("resource://gre/modules/Timer.jsm");
+
 // Functions that look like mochitest functions but forward to the
 // browser process.
 
 this.ok = function(value, message) {
   sendAsyncMessage("browser:test:ok", {
     value: !!value,
     message: message});
 }
@@ -76,30 +78,32 @@ this.timelineContentTest = function(test
     info("Stop recording");
     docShell.recordProfileTimelineMarkers = false;
     finish();
   });
 }
 
 function timelineWaitForMarkers(docshell, searchFor) {
   if (typeof(searchFor) == "string") {
+    let searchForString = searchFor;
     let f = function (markers) {
-      return markers.some(m => m.name == searchFor);
+      return markers.some(m => m.name == searchForString);
     };
     searchFor = f;
   }
 
   return new Promise(function(resolve, reject) {
     let waitIterationCount = 0;
     let maxWaitIterationCount = 10; // Wait for 2sec maximum
     let markers = [];
 
-    let interval = content.setInterval(() => {
+    setTimeout(function timeoutHandler() {
       let newMarkers = docshell.popProfileTimelineMarkers();
       markers = [...markers, ...newMarkers];
       if (searchFor(markers) || waitIterationCount > maxWaitIterationCount) {
-        content.clearInterval(interval);
         resolve(markers);
+      } else {
+        setTimeout(timeoutHandler, 200);
+        waitIterationCount++;
       }
-      waitIterationCount++;
     }, 200);
   });
 }
--- a/docshell/test/browser/head.js
+++ b/docshell/test/browser/head.js
@@ -34,20 +34,29 @@ function makeTimelineTest(frameScriptNam
       finish();
       gBrowser.removeCurrentTab();
     });
   });
 }
 
 /* Open a URL for a timeline test.  */
 function timelineTestOpenUrl(url) {
-  return new Promise(function(resolve, reject) {
-    window.focus();
+  window.focus();
 
+  let tabSwitchPromise = new Promise((resolve, reject) => {
+    window.gBrowser.addEventListener("TabSwitchDone", function listener() {
+      window.gBrowser.removeEventListener("TabSwitchDone", listener);
+      resolve();
+    });
+  });
+
+  let loadPromise = new Promise(function(resolve, reject) {
     let tab = window.gBrowser.selectedTab = window.gBrowser.addTab(url);
     let linkedBrowser = tab.linkedBrowser;
 
     linkedBrowser.addEventListener("load", function onload() {
       linkedBrowser.removeEventListener("load", onload, true);
       resolve(tab);
     }, true);
   });
+
+  return Promise.all([tabSwitchPromise, loadPromise]).then(([_, tab]) => tab);
 }
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -929,23 +929,23 @@ public:
                         const nsAString& aCause)
     : TimelineMarker(aDocShell, "ConsoleTime", aMetaData, aCause)
   {
     if (aMetaData == TRACING_INTERVAL_END) {
       CaptureStack();
     }
   }
 
-  virtual bool Equals(const TimelineMarker* aOther) override
+  virtual bool Equals(const TimelineMarker& aOther) override
   {
     if (!TimelineMarker::Equals(aOther)) {
       return false;
     }
     // Console markers must have matching causes as well.
-    return GetCause() == aOther->GetCause();
+    return GetCause() == aOther.GetCause();
   }
 
   virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override
   {
     if (GetMetaData() == TRACING_INTERVAL_START) {
       aMarker.mCauseName.Construct(GetCause());
     } else {
       aMarker.mEndStack = GetStack();
@@ -1052,17 +1052,17 @@ Console::Method(JSContext* aCx, MethodNa
         JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
         if (jsString) {
           nsAutoJSString key;
           if (key.init(aCx, jsString)) {
             mozilla::UniquePtr<TimelineMarker> marker =
               MakeUnique<ConsoleTimelineMarker>(docShell,
                                                 aMethodName == MethodTime ? TRACING_INTERVAL_START : TRACING_INTERVAL_END,
                                                 key);
-            docShell->AddProfileTimelineMarker(marker);
+            docShell->AddProfileTimelineMarker(Move(marker));
           }
         }
       }
 
     } else {
       WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
       MOZ_ASSERT(workerPrivate);
 
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -307,17 +307,16 @@ AutoJSAPI::AutoJSAPI()
   , mOldAutoJSAPIOwnsErrorReporting(false)
 {
 }
 
 AutoJSAPI::~AutoJSAPI()
 {
   if (mOwnErrorReporting) {
     MOZ_ASSERT(NS_IsMainThread(), "See corresponding assertion in TakeOwnershipOfErrorReporting()");
-    JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(mOldAutoJSAPIOwnsErrorReporting);
 
     if (HasException()) {
 
       // AutoJSAPI uses a JSAutoNullableCompartment, and may be in a null
       // compartment when the destructor is called. However, the JS engine
       // requires us to be in a compartment when we fetch the pending exception.
       // In this case, we enter the privileged junk scope and don't dispatch any
       // error events.
@@ -337,16 +336,23 @@ AutoJSAPI::~AutoJSAPI()
           DispatchScriptErrorEvent(win, JS_GetRuntime(cx()), xpcReport, exn);
         } else {
           xpcReport->LogToConsole();
         }
       } else {
         NS_WARNING("OOMed while acquiring uncaught exception from JSAPI");
       }
     }
+
+    // We need to do this _after_ processing the existing exception, because the
+    // JS engine can throw while doing that, and uses this bit to determine what
+    // to do in that case: squelch the exception if the bit is set, otherwise
+    // call the error reporter. Calling WarningOnlyErrorReporter with a
+    // non-warning will assert, so we need to make sure we do the former.
+    JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(mOldAutoJSAPIOwnsErrorReporting);
   }
 
   if (mOldErrorReporter.isSome()) {
     JS_SetErrorReporter(JS_GetRuntime(cx()), mOldErrorReporter.value());
   }
 }
 
 void
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -2774,32 +2774,40 @@ nsFrameLoader::RequestNotifyAfterRemoteP
 
 NS_IMETHODIMP
 nsFrameLoader::RequestNotifyLayerTreeReady()
 {
   if (mRemoteBrowser) {
     return mRemoteBrowser->RequestNotifyLayerTreeReady() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
   }
 
+  if (!mOwnerContent) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   nsRefPtr<AsyncEventDispatcher> event =
     new AsyncEventDispatcher(mOwnerContent,
                              NS_LITERAL_STRING("MozLayerTreeReady"),
                              true, false);
   event->PostDOMEvent();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::RequestNotifyLayerTreeCleared()
 {
   if (mRemoteBrowser) {
     return mRemoteBrowser->RequestNotifyLayerTreeCleared() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
   }
 
+  if (!mOwnerContent) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   nsRefPtr<AsyncEventDispatcher> event =
     new AsyncEventDispatcher(mOwnerContent,
                              NS_LITERAL_STRING("MozLayerTreeCleared"),
                              true, false);
   event->PostDOMEvent();
 
   return NS_OK;
 }
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1234,16 +1234,17 @@ GK_ATOM(videocontrols, "videocontrols")
 GK_ATOM(viewport, "viewport")
 GK_ATOM(viewport_height, "viewport-height")
 GK_ATOM(viewport_initial_scale, "viewport-initial-scale")
 GK_ATOM(viewport_maximum_scale, "viewport-maximum-scale")
 GK_ATOM(viewport_minimum_scale, "viewport-minimum-scale")
 GK_ATOM(viewport_user_scalable, "viewport-user-scalable")
 GK_ATOM(viewport_width, "viewport-width")
 GK_ATOM(visibility, "visibility")
+GK_ATOM(visuallyselected, "visuallyselected")
 GK_ATOM(vlink, "vlink")
 GK_ATOM(vspace, "vspace")
 GK_ATOM(wbr, "wbr")
 GK_ATOM(when, "when")
 GK_ATOM(where, "where")
 GK_ATOM(widget, "widget")
 GK_ATOM(width, "width")
 GK_ATOM(window, "window")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -638,19 +638,18 @@ public:
                             const override;
   virtual bool has(JSContext *cx, JS::Handle<JSObject*> proxy,
                    JS::Handle<jsid> id, bool *bp) const override;
   virtual bool get(JSContext *cx, JS::Handle<JSObject*> proxy,
                    JS::Handle<JSObject*> receiver,
                    JS::Handle<jsid> id,
                    JS::MutableHandle<JS::Value> vp) const override;
   virtual bool set(JSContext *cx, JS::Handle<JSObject*> proxy,
-                   JS::Handle<JSObject*> receiver,
-                   JS::Handle<jsid> id,
-                   JS::MutableHandle<JS::Value> vp,
+                   JS::Handle<jsid> id, JS::Handle<JS::Value> v,
+                   JS::Handle<JS::Value> receiver,
                    JS::ObjectOpResult &result) const override;
 
   // SpiderMonkey extensions
   virtual bool getPropertyDescriptor(JSContext* cx,
                                      JS::Handle<JSObject*> proxy,
                                      JS::Handle<jsid> id,
                                      JS::MutableHandle<JSPropertyDescriptor> desc)
                                      const override;
@@ -904,29 +903,29 @@ nsOuterWindowProxy::get(JSContext *cx, J
   }
   // Else fall through to js::Wrapper
 
   return js::Wrapper::get(cx, proxy, receiver, id, vp);
 }
 
 bool
 nsOuterWindowProxy::set(JSContext *cx, JS::Handle<JSObject*> proxy,
-                        JS::Handle<JSObject*> receiver,
                         JS::Handle<jsid> id,
-                        JS::MutableHandle<JS::Value> vp,
+                        JS::Handle<JS::Value> v,
+                        JS::Handle<JS::Value> receiver,
                         JS::ObjectOpResult &result) const
 {
   int32_t index = GetArrayIndexFromId(cx, id);
   if (IsArrayIndex(index)) {
     // Reject the set.  It's up to the caller to decide whether to throw a
     // TypeError.  If the caller is strict mode JS code, it'll throw.
     return result.failReadOnly();
   }
 
-  return js::Wrapper::set(cx, proxy, receiver, id, vp, result);
+  return js::Wrapper::set(cx, proxy, id, v, receiver, result);
 }
 
 bool
 nsOuterWindowProxy::getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle<JSObject*> proxy,
                                                  JS::AutoIdVector &props) const
 {
   // BaseProxyHandler::keys seems to do what we want here: call
   // ownPropertyKeys and then filter out the non-enumerable properties.
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -9802,17 +9802,17 @@ class CGProxySpecialOperation(CGPerSigna
     foundVar: For getters and deleters, the generated code can also set a bool
     variable, declared by the caller, if the given indexed or named property
     already existed. If the caller wants this, it should pass the name of the
     bool variable as the foundVar keyword argument to the constructor. The
     caller is responsible for declaring the variable and initializing it to
     false.
     """
     def __init__(self, descriptor, operation, checkFound=True,
-                 argumentMutableValue=None, resultVar=None, foundVar=None):
+                 argumentHandleValue=None, resultVar=None, foundVar=None):
         self.checkFound = checkFound
         self.foundVar = foundVar or "found"
 
         nativeName = MakeNativeName(descriptor.binaryNameFor(operation))
         operation = descriptor.operations[operation]
         assert len(operation.signatures()) == 1
         signature = operation.signatures()[0]
 
@@ -9827,22 +9827,22 @@ class CGProxySpecialOperation(CGPerSigna
         if operation.isSetter() or operation.isCreator():
             # arguments[0] is the index or name of the item that we're setting.
             argument = arguments[1]
             info = getJSToNativeConversionInfo(
                 argument.type, descriptor,
                 treatNullAs=argument.treatNullAs,
                 sourceDescription=("value being assigned to %s setter" %
                                    descriptor.interface.identifier.name))
-            if argumentMutableValue is None:
-                argumentMutableValue = "desc.value()"
+            if argumentHandleValue is None:
+                argumentHandleValue = "desc.value()"
             templateValues = {
                 "declName": argument.identifier.name,
                 "holderName": argument.identifier.name + "_holder",
-                "val": argumentMutableValue,
+                "val": argumentHandleValue,
                 "obj": "obj",
                 "passedToJSImpl": "false"
             }
             self.cgRoot.prepend(instantiateJSToNativeConversion(info, templateValues))
         elif operation.isGetter() or operation.isDeleter():
             if foundVar is None:
                 self.cgRoot.prepend(CGGeneric("bool found = false;\n"))
 
@@ -9877,20 +9877,20 @@ class CGProxyIndexedOperation(CGProxySpe
     If checkFound is False, will just assert that the prop is found instead of
     checking that it is before wrapping the value.
 
     resultVar: See the docstring for CGCallGenerator.
 
     foundVar: See the docstring for CGProxySpecialOperation.
     """
     def __init__(self, descriptor, name, doUnwrap=True, checkFound=True,
-                 argumentMutableValue=None, resultVar=None, foundVar=None):
+                 argumentHandleValue=None, resultVar=None, foundVar=None):
         self.doUnwrap = doUnwrap
         CGProxySpecialOperation.__init__(self, descriptor, name, checkFound,
-                                         argumentMutableValue=argumentMutableValue,
+                                         argumentHandleValue=argumentHandleValue,
                                          resultVar=resultVar,
                                          foundVar=foundVar)
 
     def define(self):
         # Our first argument is the id we're getting.
         argName = self.arguments[0].identifier.name
         if argName == "index":
             # We already have our index in a variable with that name
@@ -9938,19 +9938,19 @@ class CGProxyIndexedPresenceChecker(CGPr
         CGProxyIndexedGetter.__init__(self, descriptor, foundVar=foundVar)
         self.cgRoot.append(CGGeneric("(void)result;\n"))
 
 
 class CGProxyIndexedSetter(CGProxyIndexedOperation):
     """
     Class to generate a call to an indexed setter.
     """
-    def __init__(self, descriptor, argumentMutableValue=None):
+    def __init__(self, descriptor, argumentHandleValue=None):
         CGProxyIndexedOperation.__init__(self, descriptor, 'IndexedSetter',
-                                         argumentMutableValue=argumentMutableValue)
+                                         argumentHandleValue=argumentHandleValue)
 
 
 class CGProxyIndexedDeleter(CGProxyIndexedOperation):
     """
     Class to generate a call to an indexed deleter.
 
     resultVar: See the docstring for CGCallGenerator.
 
@@ -9968,20 +9968,20 @@ class CGProxyNamedOperation(CGProxySpeci
 
     'value' is the jsval to use for the name; None indicates that it should be
     gotten from the property id.
 
     resultVar: See the docstring for CGCallGenerator.
 
     foundVar: See the docstring for CGProxySpecialOperation.
     """
-    def __init__(self, descriptor, name, value=None, argumentMutableValue=None,
+    def __init__(self, descriptor, name, value=None, argumentHandleValue=None,
                  resultVar=None, foundVar=None):
         CGProxySpecialOperation.__init__(self, descriptor, name,
-                                         argumentMutableValue=argumentMutableValue,
+                                         argumentHandleValue=argumentHandleValue,
                                          resultVar=resultVar,
                                          foundVar=foundVar)
         self.value = value
 
     def define(self):
         # Our first argument is the id we're getting.
         argName = self.arguments[0].identifier.name
         if argName == "id":
@@ -10069,19 +10069,19 @@ class CGProxyNamedPresenceChecker(CGProx
         CGProxyNamedGetter.__init__(self, descriptor, foundVar=foundVar)
         self.cgRoot.append(CGGeneric("(void)result;\n"))
 
 
 class CGProxyNamedSetter(CGProxyNamedOperation):
     """
     Class to generate a call to a named setter.
     """
-    def __init__(self, descriptor, argumentMutableValue=None):
+    def __init__(self, descriptor, argumentHandleValue=None):
         CGProxyNamedOperation.__init__(self, descriptor, 'NamedSetter',
-                                       argumentMutableValue=argumentMutableValue)
+                                       argumentHandleValue=argumentHandleValue)
 
 
 class CGProxyNamedDeleter(CGProxyNamedOperation):
     """
     Class to generate a call to a named deleter.
 
     resultVar: See the docstring for CGCallGenerator.
 
@@ -10688,17 +10688,17 @@ class CGDOMJSProxyHandler_get(ClassMetho
             named=getNamed)
 
 
 class CGDOMJSProxyHandler_setCustom(ClassMethod):
     def __init__(self, descriptor):
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'proxy'),
                 Argument('JS::Handle<jsid>', 'id'),
-                Argument('JS::MutableHandle<JS::Value>', 'vp'),
+                Argument('JS::Handle<JS::Value>', 'v'),
                 Argument('bool*', 'done')]
         ClassMethod.__init__(self, "setCustom", "bool", args, virtual=True, override=True, const=True)
         self.descriptor = descriptor
 
     def getBody(self):
         assertion = ("MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),\n"
                      '           "Should not have a XrayWrapper here");\n')
 
@@ -10713,17 +10713,17 @@ class CGDOMJSProxyHandler_setCustom(Clas
                                  "Can't cope with [OverrideBuiltins] and an indexed getter")
             if self.descriptor.operations['NamedCreator'] is not namedSetter:
                 raise ValueError("In interface " + self.descriptor.name + ": " +
                                  "Can't cope with named setter that is not also a named creator")
             if self.descriptor.hasUnforgeableMembers:
                 raise ValueError("In interface " + self.descriptor.name + ": " +
                                  "Can't cope with [OverrideBuiltins] and unforgeable members")
 
-            callSetter = CGProxyNamedSetter(self.descriptor, argumentMutableValue="vp")
+            callSetter = CGProxyNamedSetter(self.descriptor, argumentHandleValue="v")
             return (assertion +
                     callSetter.define() +
                     "*done = true;\n"
                     "return true;\n")
 
         # As an optimization, if we are going to call an IndexedSetter, go
         # ahead and call it and have done.
         indexedSetter = self.descriptor.operations['IndexedSetter']
@@ -10738,17 +10738,17 @@ class CGDOMJSProxyHandler_setCustom(Clas
                 if (IsArrayIndex(index)) {
                   $*{callSetter}
                   *done = true;
                   return true;
                 }
 
                 """,
                 callSetter=CGProxyIndexedSetter(self.descriptor,
-                                                argumentMutableValue="vp").define())
+                                                argumentHandleValue="v").define())
         else:
             setIndexed = ""
 
         return (assertion +
                 setIndexed +
                 "*done = false;\n"
                 "return true;\n")
 
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -214,23 +214,24 @@ DOMProxyHandler::defineProperty(JSContex
   if (!js::DefineOwnProperty(cx, expando, id, desc, result)) {
     return false;
   }
   *defined = true;
   return true;
 }
 
 bool
-DOMProxyHandler::set(JSContext *cx, Handle<JSObject*> proxy, Handle<JSObject*> receiver,
-                     Handle<jsid> id, MutableHandle<JS::Value> vp, ObjectOpResult &result) const
+DOMProxyHandler::set(JSContext *cx, Handle<JSObject*> proxy, Handle<jsid> id,
+                     Handle<JS::Value> v, Handle<JS::Value> receiver,
+                     ObjectOpResult &result) const
 {
   MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
              "Should not have a XrayWrapper here");
   bool done;
-  if (!setCustom(cx, proxy, id, vp, &done)) {
+  if (!setCustom(cx, proxy, id, v, &done)) {
     return false;
   }
   if (done) {
     return result.succeed();
   }
 
   // Make sure to ignore our named properties when checking for own
   // property descriptors for a set.
@@ -247,17 +248,17 @@ DOMProxyHandler::set(JSContext *cx, Hand
     if (!js::GetObjectProto(cx, proxy, &proto)) {
       return false;
     }
     if (proto && !JS_GetPropertyDescriptorById(cx, proto, id, &desc)) {
       return false;
     }
   }
 
-  return js::SetPropertyIgnoringNamedGetter(cx, proxy, id, vp, receiver, desc, result);
+  return js::SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, desc, result);
 }
 
 bool
 DOMProxyHandler::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                          JS::Handle<jsid> id, JS::ObjectOpResult &result) const
 {
   JS::Rooted<JSObject*> expando(cx);
   if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) {
@@ -345,17 +346,17 @@ IdToInt32(JSContext* cx, JS::Handle<jsid
     return -1;
   }
 
   return i;
 }
 
 bool
 DOMProxyHandler::setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
-                           JS::MutableHandle<JS::Value> vp, bool *done) const
+                           JS::Handle<JS::Value> v, bool *done) const
 {
   *done = false;
   return true;
 }
 
 //static
 JSObject *
 DOMProxyHandler::GetExpandoObject(JSObject *obj)
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -117,27 +117,27 @@ public:
   bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                JS::ObjectOpResult &result) const override;
   bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy,
                          JS::ObjectOpResult& result) const override;
   bool isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible)
                     const override;
   bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
            bool* bp) const override;
-  bool set(JSContext *cx, JS::Handle<JSObject*> proxy, JS::Handle<JSObject*> receiver,
-           JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp, JS::ObjectOpResult &result)
+  bool set(JSContext *cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+           JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver, JS::ObjectOpResult &result)
            const override;
 
   /*
    * If assigning to proxy[id] hits a named setter with OverrideBuiltins or
    * an indexed setter, call it and set *done to true on success. Otherwise, set
    * *done to false.
    */
   virtual bool setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
-                         JS::MutableHandle<JS::Value> vp, bool *done) const;
+                         JS::Handle<JS::Value> v, bool *done) const;
 
   static JSObject* GetExpandoObject(JSObject* obj);
 
   /* GetAndClearExpandoObject does not DROP or clear the preserving wrapper flag. */
   static JSObject* GetAndClearExpandoObject(JSObject* obj);
   static JSObject* EnsureExpandoObject(JSContext* cx,
                                        JS::Handle<JSObject*> obj);
 
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1089,17 +1089,17 @@ EventListenerManager::HandleEventInterna
               nsDocShell* ds = static_cast<nsDocShell*>(docShell.get());
               nsAutoString typeStr;
               (*aDOMEvent)->GetType(typeStr);
               uint16_t phase;
               (*aDOMEvent)->GetEventPhase(&phase);
               mozilla::UniquePtr<TimelineMarker> marker =
                 MakeUnique<EventTimelineMarker>(ds, TRACING_INTERVAL_START,
                                                 phase, typeStr);
-              ds->AddProfileTimelineMarker(marker);
+              ds->AddProfileTimelineMarker(Move(marker));
             }
           }
 
           if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent,
                                            aCurrentTarget))) {
             aEvent->mFlags.mExceptionHasBeenRisen = true;
           }
 
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -23,16 +23,17 @@
 #include "mozilla/dom/FetchDriver.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/Request.h"
 #include "mozilla/dom/Response.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/Telemetry.h"
 
 #include "InternalRequest.h"
 #include "InternalResponse.h"
 
 #include "nsFormData.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
@@ -226,29 +227,33 @@ FetchRequest(nsIGlobalObject* aGlobal, c
     }
 
     nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
     if (!doc) {
       aRv.Throw(NS_ERROR_FAILURE);
       return nullptr;
     }
 
+    Telemetry::Accumulate(Telemetry::FETCH_IS_MAINTHREAD, 1);
+
     nsRefPtr<MainThreadFetchResolver> resolver = new MainThreadFetchResolver(p);
     nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
     nsRefPtr<FetchDriver> fetch =
       new FetchDriver(r, doc->NodePrincipal(), loadGroup);
     fetch->SetDocument(doc);
     aRv = fetch->Fetch(resolver);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
   } else {
     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(worker);
 
+    Telemetry::Accumulate(Telemetry::FETCH_IS_MAINTHREAD, 0);
+
     if (worker->IsServiceWorker()) {
       r->SetSkipServiceWorker();
     }
 
     nsRefPtr<MainThreadFetchRunnable> run = new MainThreadFetchRunnable(worker, p, r);
     if (NS_FAILED(NS_DispatchToMainThread(run))) {
       NS_WARNING("MainThreadFetchRunnable dispatch failed!");
     }
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -55,16 +55,19 @@ FetchDriver::~FetchDriver()
 }
 
 nsresult
 FetchDriver::Fetch(FetchDriverObserver* aObserver)
 {
   workers::AssertIsOnMainThread();
   mObserver = aObserver;
 
+  Telemetry::Accumulate(Telemetry::SERVICE_WORKER_REQUEST_PASSTHROUGH,
+                        mRequest->WasCreatedByFetchEvent());
+
   return Fetch(false /* CORS flag */);
 }
 
 nsresult
 FetchDriver::Fetch(bool aCORSFlag)
 {
   // We do not currently implement parts of the spec that lead to recursion.
   MOZ_ASSERT(mFetchRecursionCount == 0);
--- a/dom/fetch/InternalRequest.cpp
+++ b/dom/fetch/InternalRequest.cpp
@@ -36,16 +36,17 @@ InternalRequest::GetRequestConstructorCo
   copy->mSameOriginDataURL = true;
   copy->mPreserveContentCodings = true;
   // The default referrer is already about:client.
 
   copy->mContentPolicyType = nsIContentPolicy::TYPE_FETCH;
   copy->mMode = mMode;
   copy->mCredentialsMode = mCredentialsMode;
   copy->mCacheMode = mCacheMode;
+  copy->mCreatedByFetchEvent = mCreatedByFetchEvent;
   return copy.forget();
 }
 
 already_AddRefed<InternalRequest>
 InternalRequest::Clone()
 {
   nsRefPtr<InternalRequest> clone = new InternalRequest(*this);
 
@@ -82,16 +83,17 @@ InternalRequest::InternalRequest(const I
   , mForceOriginHeader(aOther.mForceOriginHeader)
   , mPreserveContentCodings(aOther.mPreserveContentCodings)
   , mSameOriginDataURL(aOther.mSameOriginDataURL)
   , mSandboxedStorageAreaURLs(aOther.mSandboxedStorageAreaURLs)
   , mSkipServiceWorker(aOther.mSkipServiceWorker)
   , mSynchronous(aOther.mSynchronous)
   , mUnsafeRequest(aOther.mUnsafeRequest)
   , mUseURLCredentials(aOther.mUseURLCredentials)
+  , mCreatedByFetchEvent(aOther.mCreatedByFetchEvent)
 {
   // NOTE: does not copy body stream... use the fallible Clone() for that
 }
 
 InternalRequest::~InternalRequest()
 {
 }
 
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -278,16 +278,34 @@ public:
     nsCOMPtr<nsIInputStream> s = mBodyStream;
     s.forget(aStream);
   }
 
   // The global is used as the client for the new object.
   already_AddRefed<InternalRequest>
   GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const;
 
+  bool
+  WasCreatedByFetchEvent() const
+  {
+    return mCreatedByFetchEvent;
+  }
+
+  void
+  SetCreatedByFetchEvent()
+  {
+    mCreatedByFetchEvent = true;
+  }
+
+  void
+  ClearCreatedByFetchEvent()
+  {
+    mCreatedByFetchEvent = false;
+  }
+
 private:
   // Does not copy mBodyStream.  Use fallible Clone() for complete copy.
   explicit InternalRequest(const InternalRequest& aOther);
 
   ~InternalRequest();
 
   nsCString mMethod;
   nsCString mURL;
@@ -312,14 +330,18 @@ private:
   bool mForceOriginHeader;
   bool mPreserveContentCodings;
   bool mSameOriginDataURL;
   bool mSandboxedStorageAreaURLs;
   bool mSkipServiceWorker;
   bool mSynchronous;
   bool mUnsafeRequest;
   bool mUseURLCredentials;
+  // This is only set when a Request object is created by a fetch event.  We
+  // use it to check if Service Workers are simply fetching intercepted Request
+  // objects without modifying them.
+  bool mCreatedByFetchEvent = false;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_InternalRequest_h
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -142,26 +142,29 @@ Request::Constructor(const GlobalObject&
     return nullptr;
   }
   RequestMode mode = aInit.mMode.WasPassed() ? aInit.mMode.Value() : fallbackMode;
   RequestCredentials credentials =
     aInit.mCredentials.WasPassed() ? aInit.mCredentials.Value()
                                    : fallbackCredentials;
 
   if (mode != RequestMode::EndGuard_) {
+    request->ClearCreatedByFetchEvent();
     request->SetMode(mode);
   }
 
   if (credentials != RequestCredentials::EndGuard_) {
+    request->ClearCreatedByFetchEvent();
     request->SetCredentialsMode(credentials);
   }
 
   RequestCache cache = aInit.mCache.WasPassed() ?
                        aInit.mCache.Value() : fallbackCache;
   if (cache != RequestCache::EndGuard_) {
+    request->ClearCreatedByFetchEvent();
     request->SetCacheMode(cache);
   }
 
   // Request constructor step 14.
   if (aInit.mMethod.WasPassed()) {
     nsAutoCString method(aInit.mMethod.Value());
     nsAutoCString upperCaseMethod = method;
     ToUpperCase(upperCaseMethod);
@@ -180,30 +183,33 @@ Request::Constructor(const GlobalObject&
 
     // Step 14.2
     if (upperCaseMethod.EqualsLiteral("DELETE") ||
         upperCaseMethod.EqualsLiteral("GET") ||
         upperCaseMethod.EqualsLiteral("HEAD") ||
         upperCaseMethod.EqualsLiteral("POST") ||
         upperCaseMethod.EqualsLiteral("PUT") ||
         upperCaseMethod.EqualsLiteral("OPTIONS")) {
+      request->ClearCreatedByFetchEvent();
       request->SetMethod(upperCaseMethod);
     } else {
+      request->ClearCreatedByFetchEvent();
       request->SetMethod(method);
     }
   }
 
   nsRefPtr<InternalHeaders> requestHeaders = request->Headers();
 
   nsRefPtr<InternalHeaders> headers;
   if (aInit.mHeaders.WasPassed()) {
     nsRefPtr<Headers> h = Headers::Constructor(aGlobal, aInit.mHeaders.Value(), aRv);
     if (aRv.Failed()) {
       return nullptr;
     }
+    request->ClearCreatedByFetchEvent();
     headers = h->GetInternalHeaders();
   } else {
     headers = new InternalHeaders(*requestHeaders);
   }
 
   requestHeaders->Clear();
 
   if (request->Mode() == RequestMode::No_cors) {
@@ -239,16 +245,17 @@ Request::Constructor(const GlobalObject&
     const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& bodyInit = aInit.mBody.Value();
     nsCOMPtr<nsIInputStream> stream;
     nsCString contentType;
     aRv = ExtractByteStreamFromBody(bodyInit,
                                     getter_AddRefs(stream), contentType);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
+    request->ClearCreatedByFetchEvent();
     request->SetBody(stream);
 
     if (!contentType.IsVoid() &&
         !requestHeaders->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
       requestHeaders->Append(NS_LITERAL_CSTRING("Content-Type"),
                              contentType, aRv);
     }
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -607,18 +607,17 @@ parent:
                             TabId openerTabId)
         returns (ContentParentId cpId, bool isForApp, bool isForBrowser, TabId tabId);
     sync BridgeToChildProcess(ContentParentId cpId);
 
     /**
      * This call connects the content process to a plugin process. While this
      * call runs, a new PluginModuleParent will be created in the ContentChild
      * via bridging. The corresponding PluginModuleChild will live in the plugin
-     * process. We use intr semantics here to ensure that the PluginModuleParent
-     * allocation message is dispatched before LoadPlugin returns.
+     * process.
      */
     sync LoadPlugin(uint32_t pluginId) returns (nsresult rv);
 
     /**
      * This call is used by asynchronous plugin instantiation to notify the
      * content parent that it is now safe to initiate the plugin bridge for
      * the specified plugin id. When this call returns, the requested bridge
      * connection has been made.
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -272,16 +272,17 @@ TabParent::TabParent(nsIContentParent* a
   , mMarkedDestroying(false)
   , mIsDestroyed(false)
   , mAppPackageFileDescriptorSent(false)
   , mSendOfflineStatus(true)
   , mChromeFlags(aChromeFlags)
   , mInitedByParent(false)
   , mTabId(aTabId)
   , mCreatingWindow(false)
+  , mNeedLayerTreeReadyNotification(false)
 {
   MOZ_ASSERT(aManager);
 }
 
 TabParent::~TabParent()
 {
 }
 
@@ -2384,16 +2385,22 @@ TabParent::DeallocPRenderFrameParent(PRe
 bool
 TabParent::RecvGetRenderFrameInfo(PRenderFrameParent* aRenderFrame,
                                   TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                   uint64_t* aLayersId)
 {
   RenderFrameParent* renderFrame = static_cast<RenderFrameParent*>(aRenderFrame);
   renderFrame->GetTextureFactoryIdentifier(aTextureFactoryIdentifier);
   *aLayersId = renderFrame->GetLayersId();
+
+  if (mNeedLayerTreeReadyNotification) {
+    RequestNotifyLayerTreeReady();
+    mNeedLayerTreeReadyNotification = false;
+  }
+
   return true;
 }
 
 bool
 TabParent::AllowContentIME()
 {
   nsFocusManager* fm = nsFocusManager::GetFocusManager();
   NS_ENSURE_TRUE(fm, false);
@@ -2686,21 +2693,21 @@ class LayerTreeUpdateObserver : public C
   }
 };
 
 bool
 TabParent::RequestNotifyLayerTreeReady()
 {
   RenderFrameParent* frame = GetRenderFrame();
   if (!frame) {
-    return false;
+    mNeedLayerTreeReadyNotification = true;
+  } else {
+    CompositorParent::RequestNotifyLayerTreeReady(frame->GetLayersId(),
+                                                  new LayerTreeUpdateObserver());
   }
-
-  CompositorParent::RequestNotifyLayerTreeReady(frame->GetLayersId(),
-                                                new LayerTreeUpdateObserver());
   return true;
 }
 
 bool
 TabParent::RequestNotifyLayerTreeCleared()
 {
   RenderFrameParent* frame = GetRenderFrame();
   if (!frame) {
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -515,16 +515,21 @@ private:
     // frame scripts for that tab are loaded before any scripts start to run in
     // the window. We can't load the frame scripts the normal way, using
     // separate IPC messages, since they won't be processed by the child until
     // returning to the event loop, which is too late. Instead, we queue up
     // frame scripts that we intend to load and send them as part of the
     // CreateWindow response. Then TabChild loads them immediately.
     nsTArray<FrameScriptInfo> mDelayedFrameScripts;
 
+    // If the user called RequestNotifyLayerTreeReady and the RenderFrameParent
+    // wasn't ready yet, we set this flag and call RequestNotifyLayerTreeReady
+    // again once the RenderFrameParent arrives.
+    bool mNeedLayerTreeReadyNotification;
+
 private:
     // This is used when APZ needs to find the TabParent associated with a layer
     // to dispatch events.
     typedef nsDataHashtable<nsUint64HashKey, TabParent*> LayerToTabParentTable;
     static LayerToTabParentTable* sLayerToTabParentTable;
 
     static void AddTabParentToTable(uint64_t aLayersId, TabParent* aTabParent);
     static void RemoveTabParentFromTable(uint64_t aLayersId);
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -1815,17 +1815,17 @@ NPError
 
     NPBool success = _convertpoint(instance, 
                                   pluginX,  pluginY, NPCoordinateSpacePlugin, 
                                  &screenX, &screenY, NPCoordinateSpaceScreen);
 
     if (success) {
         return mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(menu,
                                     screenX, screenY,
-                                    PluginModuleChild::GetChrome(),
+                                    InstCast(instance)->Manager(),
                                     ProcessBrowserEvents);
     } else {
         NS_WARNING("Convertpoint failed, could not created contextmenu.");
         return NPERR_GENERIC_ERROR;
     }
 
 #else
     NS_WARNING("Not supported on this platform!");
--- a/dom/webidl/FetchEvent.webidl
+++ b/dom/webidl/FetchEvent.webidl
@@ -12,17 +12,17 @@
  Exposed=(ServiceWorker)]
 interface FetchEvent : Event {
   readonly attribute Request request;
 
   // https://github.com/slightlyoff/ServiceWorker/issues/631
   readonly attribute Client? client; // The window issuing the request.
   readonly attribute boolean isReload;
 
-  [Throws] void respondWith(Promise<Response> r);
-  [Throws] void respondWith(Response r);
+  [Throws]
+  void respondWith((Response or Promise<Response>) r);
 };
 
 dictionary FetchEventInit : EventInit {
   Request request;
   Client client;
   boolean isReload;
 };
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -277,47 +277,42 @@ RespondWithHandler::CancelRequest()
 {
   nsCOMPtr<nsIRunnable> runnable = new CancelChannelRunnable(mInterceptedChannel);
   NS_DispatchToMainThread(runnable);
 }
 
 } // anonymous namespace
 
 void
-FetchEvent::RespondWith(Promise& aPromise, ErrorResult& aRv)
+FetchEvent::RespondWith(const ResponseOrPromise& aArg, ErrorResult& aRv)
 {
   if (mWaitToRespond) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
+  nsRefPtr<Promise> promise;
+
+  if (aArg.IsResponse()) {
+    nsRefPtr<Response> res = &aArg.GetAsResponse();
+    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(worker);
+    worker->AssertIsOnWorkerThread();
+    promise = Promise::Create(worker->GlobalScope(), aRv);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+    promise->MaybeResolve(res);
+  } else if (aArg.IsPromise()) {
+    promise = &aArg.GetAsPromise();
+  }
   mWaitToRespond = true;
   nsRefPtr<RespondWithHandler> handler =
     new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode());
-  aPromise.AppendNativeHandler(handler);
-}
-
-void
-FetchEvent::RespondWith(Response& aResponse, ErrorResult& aRv)
-{
-  if (mWaitToRespond) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-
-  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
-  MOZ_ASSERT(worker);
-  worker->AssertIsOnWorkerThread();
-  nsRefPtr<Promise> promise = Promise::Create(worker->GlobalScope(), aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return;
-  }
-  promise->MaybeResolve(&aResponse);
-
-  RespondWith(*promise, aRv);
+  promise->AppendNativeHandler(handler);
 }
 
 already_AddRefed<ServiceWorkerClient>
 FetchEvent::GetClient()
 {
   if (!mClient) {
     if (!mClientInfo) {
       return nullptr;
--- a/dom/workers/ServiceWorkerEvents.h
+++ b/dom/workers/ServiceWorkerEvents.h
@@ -14,16 +14,17 @@
 #include "mozilla/dom/Response.h"
 #include "nsProxyRelease.h"
 
 class nsIInterceptedChannel;
 
 namespace mozilla {
 namespace dom {
   class Request;
+  class ResponseOrPromise;
 } // namespace dom
 } // namespace mozilla
 
 BEGIN_WORKERS_NAMESPACE
 
 class ServiceWorker;
 class ServiceWorkerClient;
 
@@ -77,20 +78,17 @@ public:
 
   bool
   IsReload() const
   {
     return mIsReload;
   }
 
   void
-  RespondWith(Promise& aPromise, ErrorResult& aRv);
-
-  void
-  RespondWith(Response& aResponse, ErrorResult& aRv);
+  RespondWith(const ResponseOrPromise& aArg, ErrorResult& aRv);
 
   already_AddRefed<Promise>
   ForwardTo(const nsAString& aUrl);
 
   already_AddRefed<Promise>
   Default();
 };
 
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -2297,16 +2297,20 @@ private:
     reqInit.mMode.Construct(mRequestMode);
     reqInit.mCredentials.Construct(mRequestCredentials);
 
     ErrorResult rv;
     nsRefPtr<Request> request = Request::Constructor(globalObj, requestInfo, reqInit, rv);
     if (NS_WARN_IF(rv.Failed())) {
       return false;
     }
+    // For Telemetry, note that this Request object was created by a Fetch event.
+    nsRefPtr<InternalRequest> internalReq = request->GetInternalRequest();
+    MOZ_ASSERT(internalReq);
+    internalReq->SetCreatedByFetchEvent();
 
     RootedDictionary<FetchEventInit> init(aCx);
     init.mRequest.Construct();
     init.mRequest.Value() = request;
     init.mBubbles = false;
     init.mCancelable = true;
     init.mIsReload.Construct(mIsReload);
     nsRefPtr<FetchEvent> event =
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -844,19 +844,18 @@ LayerTransactionParent::Attach(ShadowLay
 bool
 LayerTransactionParent::RecvClearCachedResources()
 {
   if (mRoot) {
     // NB: |mRoot| here is the *child* context's root.  In this parent
     // context, it's just a subtree root.  We need to scope the clear
     // of resources to exactly that subtree, so we specify it here.
     mLayerManager->ClearCachedResources(mRoot);
-
-    mShadowLayersManager->NotifyClearCachedResources(this);
   }
+  mShadowLayersManager->NotifyClearCachedResources(this);
   return true;
 }
 
 bool
 LayerTransactionParent::RecvForceComposite()
 {
   mShadowLayersManager->ForceComposite(this);
   return true;
--- a/gfx/layers/ipc/PCompositor.ipdl
+++ b/gfx/layers/ipc/PCompositor.ipdl
@@ -24,19 +24,17 @@ namespace mozilla {
 namespace layers {
 
 
 /**
  * The PCompositor protocol is used to manage communication between
  * the main thread and the compositor thread context. It's primary
  * purpose is to manage the PLayerTransaction sub protocol.
  */
-// This should really be 'sync', but we're using 'rpc' as a workaround
-// for Bug 716631.
-intr protocol PCompositor
+sync protocol PCompositor
 {
   // A Compositor manages a single Layer Manager (PLayerTransaction)
   manages PLayerTransaction;
 
 child:
   // The child should invalidate everything so that the whole window is redrawn.
   async InvalidateAll();
 
--- a/gfx/layers/ipc/PImageBridge.ipdl
+++ b/gfx/layers/ipc/PImageBridge.ipdl
@@ -18,17 +18,17 @@ using mozilla::layers::TextureFlags from
 namespace mozilla {
 namespace layers {
 
 /**
  * The PImageBridge protocol is used to allow isolated threads or processes to push
  * frames directly to the compositor thread/process without relying on the main thread
  * which might be too busy dealing with content script.
  */
-intr protocol PImageBridge
+sync protocol PImageBridge
 {
   manages PCompositable;
   manages PTexture;
 
 child:
   async ParentAsyncMessages(AsyncParentMessageData[] aMessages);
 
 parent:
--- a/gfx/tests/crashtests/358732-3.html
+++ b/gfx/tests/crashtests/358732-3.html
@@ -1,18 +1,17 @@
 <html>
 <head>
 <title>Testcase 3 - Bug 358732  Crash in _moz_cairo_win32_scaled_font_select_font, part 2</title>
 <script id="script">
 var i=0;
 function doe() {
-netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-var navigator1 = top.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebNavigation);
-var docShell = navigator1.QueryInterface(Components.interfaces.nsIDocShell);
-var docviewer = docShell.contentViewer;
+var navigator1 = SpecialPowers.wrap(top).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).getInterface(SpecialPowers.Ci.nsIWebNavigation);
+var docShell = navigator1.QueryInterface(SpecialPowers.Ci.nsIDocShell);
+var docviewer = docShell.contentViewer.QueryInterface(SpecialPowers.Ci.nsIMarkupDocumentViewer);
 docviewer.textZoom=i;
 i=i+0.2;
 if (i>10)
  i = 0;
 }
 setInterval(doe, 50);
 
 </script>
--- a/gfx/tests/crashtests/358732-iframe.html
+++ b/gfx/tests/crashtests/358732-iframe.html
@@ -7,20 +7,19 @@
 <script>
 var doc = document;
 if (document.getElementById('content')) {
   doc = document.getElementById('content').contentDocument;
 }
 
 var docviewer;
 function do_onload() {
-netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-var navigator = parent.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebNavigation);
-var docShell = navigator.QueryInterface(Components.interfaces.nsIDocShell);
-docviewer = docShell.contentViewer;
+var navigator1 = SpecialPowers.wrap(parent).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).getInterface(SpecialPowers.Ci.nsIWebNavigation);
+var docShell = navigator1.QueryInterface(SpecialPowers.Ci.nsIDocShell);
+docviewer = docShell.contentViewer.QueryInterface(SpecialPowers.Ci.nsIMarkupDocumentViewer);
 
 setTimeout(doe,500, 0.2);
 }
 do_onload();
 
 
 
 function doe(i) {
--- a/js/ipc/JavaScriptBase.h
+++ b/js/ipc/JavaScriptBase.h
@@ -66,20 +66,19 @@ class JavaScriptBase : public WrapperOwn
                       ReturnStatus *rs, bool *bp) {
         return Answer::RecvHasOwn(ObjectId::deserialize(objId), id, rs, bp);
     }
     bool RecvGet(const uint64_t &objId, const ObjectVariant &receiverVar,
                    const JSIDVariant &id,
                    ReturnStatus *rs, JSVariant *result) {
         return Answer::RecvGet(ObjectId::deserialize(objId), receiverVar, id, rs, result);
     }
-    bool RecvSet(const uint64_t &objId, const ObjectVariant &receiverVar,
-                 const JSIDVariant &id, const JSVariant &value, ReturnStatus *rs,
-                 JSVariant *result) {
-        return Answer::RecvSet(ObjectId::deserialize(objId), receiverVar, id, value, rs, result);
+    bool RecvSet(const uint64_t &objId, const JSIDVariant &id, const JSVariant &value,
+                 const JSVariant &receiverVar, ReturnStatus *rs) {
+        return Answer::RecvSet(ObjectId::deserialize(objId), id, value, receiverVar, rs);
     }
 
     bool RecvIsExtensible(const uint64_t &objId, ReturnStatus *rs,
                             bool *result) {
         return Answer::RecvIsExtensible(ObjectId::deserialize(objId), rs, result);
     }
     bool RecvCallOrConstruct(const uint64_t &objId, InfallibleTArray<JSParam> &&argv,
                              const bool &construct, ReturnStatus *rs, JSVariant *result,
@@ -156,20 +155,19 @@ class JavaScriptBase : public WrapperOwn
                     ReturnStatus *rs, bool *bp) {
         return Base::SendHasOwn(objId.serialize(), id, rs, bp);
     }
     bool SendGet(const ObjectId &objId, const ObjectVariant &receiverVar,
                  const JSIDVariant &id,
                  ReturnStatus *rs, JSVariant *result) {
         return Base::SendGet(objId.serialize(), receiverVar, id, rs, result);
     }
-    bool SendSet(const ObjectId &objId, const ObjectVariant &receiverVar,
-                 const JSIDVariant &id, const JSVariant &value, ReturnStatus *rs,
-                 JSVariant *result) {
-        return Base::SendSet(objId.serialize(), receiverVar, id, value, rs, result);
+    bool SendSet(const ObjectId &objId, const JSIDVariant &id, const JSVariant &value,
+                 const JSVariant &receiverVar, ReturnStatus *rs) {
+        return Base::SendSet(objId.serialize(), id, value, receiverVar, rs);
     }
 
     bool SendIsExtensible(const ObjectId &objId, ReturnStatus *rs,
                           bool *result) {
         return Base::SendIsExtensible(objId.serialize(), rs, result);
     }
     bool SendCallOrConstruct(const ObjectId &objId, const nsTArray<JSParam> &argv,
                              const bool &construct, ReturnStatus *rs, JSVariant *result,
--- a/js/ipc/PJavaScript.ipdl
+++ b/js/ipc/PJavaScript.ipdl
@@ -28,17 +28,17 @@ both:
     prio(high) sync GetPropertyDescriptor(uint64_t objId, JSIDVariant id) returns (ReturnStatus rs, PPropertyDescriptor result);
     prio(high) sync GetOwnPropertyDescriptor(uint64_t objId, JSIDVariant id) returns (ReturnStatus rs, PPropertyDescriptor result);
     prio(high) sync DefineProperty(uint64_t objId, JSIDVariant id, PPropertyDescriptor descriptor) returns (ReturnStatus rs);
     prio(high) sync Delete(uint64_t objId, JSIDVariant id) returns (ReturnStatus rs);
 
     prio(high) sync Has(uint64_t objId, JSIDVariant id) returns (ReturnStatus rs, bool has);
     prio(high) sync HasOwn(uint64_t objId, JSIDVariant id) returns (ReturnStatus rs, bool has);
     prio(high) sync Get(uint64_t objId, ObjectVariant receiver, JSIDVariant id) returns (ReturnStatus rs, JSVariant result);
-    prio(high) sync Set(uint64_t objId, ObjectVariant receiver, JSIDVariant id, JSVariant value) returns (ReturnStatus rs, JSVariant result);
+    prio(high) sync Set(uint64_t objId, JSIDVariant id, JSVariant value, JSVariant receiver) returns (ReturnStatus rs);
 
     prio(high) sync IsExtensible(uint64_t objId) returns (ReturnStatus rs, bool result);
     prio(high) sync CallOrConstruct(uint64_t objId, JSParam[] argv, bool construct) returns (ReturnStatus rs, JSVariant result, JSParam[] outparams);
     prio(high) sync HasInstance(uint64_t objId, JSVariant v) returns (ReturnStatus rs, bool has);
     prio(high) sync ObjectClassIs(uint64_t objId, uint32_t classValue) returns (bool result);
     prio(high) sync ClassName(uint64_t objId) returns (nsString name);
     prio(high) sync GetPrototype(uint64_t objId) returns (ReturnStatus rs, ObjectOrNullVariant result);
     prio(high) sync RegExpToShared(uint64_t objId) returns (ReturnStatus rs, nsString source, uint32_t flags);
--- a/js/ipc/WrapperAnswer.cpp
+++ b/js/ipc/WrapperAnswer.cpp
@@ -303,52 +303,43 @@ WrapperAnswer::RecvGet(const ObjectId &o
         return fail(cx, rs);
 
     LOG("get %s.%s = %s", ReceiverObj(objId), Identifier(idVar), OutVariant(*result));
 
     return ok(rs);
 }
 
 bool
-WrapperAnswer::RecvSet(const ObjectId &objId, const ObjectVariant &receiverVar,
-                       const JSIDVariant &idVar, const JSVariant &value, ReturnStatus *rs,
-                       JSVariant *resultValue)
+WrapperAnswer::RecvSet(const ObjectId &objId, const JSIDVariant &idVar, const JSVariant &value,
+                       const JSVariant &receiverVar, ReturnStatus *rs)
 {
     // We may run scripted setters.
     AutoEntryScript aes(xpc::NativeGlobal(scopeForTargetObjects()));
     JSContext *cx = aes.cx();
 
-    // The outparam will be written to the buffer, so it must be set even if
-    // the parent won't read it.
-    *resultValue = UndefinedVariant();
-
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
         return fail(cx, rs);
 
-    RootedObject receiver(cx, fromObjectVariant(cx, receiverVar));
-    if (!receiver)
-        return fail(cx, rs);
-
     LOG("set %s[%s] = %s", ReceiverObj(objId), Identifier(idVar), InVariant(value));
 
     RootedId id(cx);
     if (!fromJSIDVariant(cx, idVar, &id))
         return fail(cx, rs);
 
     RootedValue val(cx);
     if (!fromVariant(cx, value, &val))
         return fail(cx, rs);
 
-    ObjectOpResult result;
-    RootedValue receiverVal(cx, ObjectValue(*receiver));
-    if (!JS_ForwardSetPropertyTo(cx, obj, id, val, receiverVal, result))
+    RootedValue receiver(cx);
+    if (!fromVariant(cx, receiverVar, &receiver))
         return fail(cx, rs);
 
-    if (!toVariant(cx, val, resultValue))
+    ObjectOpResult result;
+    if (!JS_ForwardSetPropertyTo(cx, obj, id, val, receiver, result))
         return fail(cx, rs);
 
     return ok(rs, result);
 }
 
 bool
 WrapperAnswer::RecvIsExtensible(const ObjectId &objId, ReturnStatus *rs, bool *result)
 {
--- a/js/ipc/WrapperAnswer.h
+++ b/js/ipc/WrapperAnswer.h
@@ -32,19 +32,18 @@ class WrapperAnswer : public virtual Jav
 
     bool RecvHas(const ObjectId &objId, const JSIDVariant &id,
                  ReturnStatus *rs, bool *bp);
     bool RecvHasOwn(const ObjectId &objId, const JSIDVariant &id,
                     ReturnStatus *rs, bool *bp);
     bool RecvGet(const ObjectId &objId, const ObjectVariant &receiverVar,
                  const JSIDVariant &id,
                  ReturnStatus *rs, JSVariant *result);
-    bool RecvSet(const ObjectId &objId, const ObjectVariant &receiverVar,
-                 const JSIDVariant &id, const JSVariant &value, ReturnStatus *rs,
-                 JSVariant *result);
+    bool RecvSet(const ObjectId &objId, const JSIDVariant &id, const JSVariant &value,
+                 const JSVariant &receiverVar, ReturnStatus *rs);
 
     bool RecvIsExtensible(const ObjectId &objId, ReturnStatus *rs,
                           bool *result);
     bool RecvCallOrConstruct(const ObjectId &objId, InfallibleTArray<JSParam> &&argv,
                              const bool &construct, ReturnStatus *rs, JSVariant *result,
                              nsTArray<JSParam> *outparams);
     bool RecvHasInstance(const ObjectId &objId, const JSVariant &v, ReturnStatus *rs, bool *bp);
     bool RecvObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -105,19 +105,18 @@ class CPOWProxyHandler : public BaseProx
                          ObjectOpResult &result) const override;
     virtual bool enumerate(JSContext *cx, HandleObject proxy, MutableHandleObject objp) const override;
     virtual bool preventExtensions(JSContext *cx, HandleObject proxy,
                                    ObjectOpResult &result) const override;
     virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) const override;
     virtual bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const override;
     virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver,
                      HandleId id, MutableHandleValue vp) const override;
-    virtual bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
-                     JS::HandleId id, JS::MutableHandleValue vp,
-                     JS::ObjectOpResult &result) const override;
+    virtual bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
+                     JS::HandleValue receiver, JS::ObjectOpResult &result) const override;
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) const override;
     virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const override;
 
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc) const override;
     virtual bool hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const override;
     virtual bool getOwnEnumerablePropertyKeys(JSContext *cx, HandleObject proxy,
                                               AutoIdVector &props) const override;
@@ -512,51 +511,47 @@ WrapperOwner::get(JSContext *cx, HandleO
 
         vp.set(ObjectValue(*toStringObj));
     }
 
     return true;
 }
 
 bool
-CPOWProxyHandler::set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
-                      JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult &result) const
+CPOWProxyHandler::set(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
+                      JS::HandleValue receiver, JS::ObjectOpResult &result) const
 {
-    FORWARD(set, (cx, proxy, receiver, id, vp, result));
+    FORWARD(set, (cx, proxy, id, v, receiver, result));
 }
 
 bool
-WrapperOwner::set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
-                  JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult &result)
+WrapperOwner::set(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
+                  JS::HandleValue receiver, JS::ObjectOpResult &result)
 {
     ObjectId objId = idOf(proxy);
 
-    ObjectVariant receiverVar;
-    if (!toObjectVariant(cx, receiver, &receiverVar))
-        return false;
-
     JSIDVariant idVar;
     if (!toJSIDVariant(cx, id, &idVar))
         return false;
 
     JSVariant val;
-    if (!toVariant(cx, vp, &val))
+    if (!toVariant(cx, v, &val))
+        return false;
+
+    JSVariant receiverVar;
+    if (!toVariant(cx, receiver, &receiverVar))
         return false;
 
     ReturnStatus status;
-    JSVariant resultValue;
-    if (!SendSet(objId, receiverVar, idVar, val, &status, &resultValue))
+    if (!SendSet(objId, idVar, val, receiverVar, &status))
         return ipcfail(cx);
 
     LOG_STACK();
 
-    if (!ok(cx, status, result))
-        return false;
-
-    return fromVariant(cx, resultValue, vp);
+    return ok(cx, status, result);
 }
 
 bool
 CPOWProxyHandler::getOwnEnumerablePropertyKeys(JSContext *cx, HandleObject proxy,
                                                AutoIdVector &props) const
 {
     FORWARD(getOwnEnumerablePropertyKeys, (cx, proxy, props));
 }
--- a/js/ipc/WrapperOwner.h
+++ b/js/ipc/WrapperOwner.h
@@ -37,18 +37,18 @@ class WrapperOwner : public virtual Java
     bool ownPropertyKeys(JSContext *cx, JS::HandleObject proxy, JS::AutoIdVector &props);
     bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
                  JS::ObjectOpResult &result);
     bool preventExtensions(JSContext *cx, JS::HandleObject proxy, JS::ObjectOpResult &result);
     bool isExtensible(JSContext *cx, JS::HandleObject proxy, bool *extensible);
     bool has(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, bool *bp);
     bool get(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
              JS::HandleId id, JS::MutableHandleValue vp);
-    bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
-             JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult &result);
+    bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
+             JS::HandleValue receiver, JS::ObjectOpResult &result);
     bool callOrConstruct(JSContext *cx, JS::HandleObject proxy, const JS::CallArgs &args,
                          bool construct);
 
     // SpiderMonkey extensions.
     bool getPropertyDescriptor(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
                                JS::MutableHandle<JSPropertyDescriptor> desc);
     bool hasOwn(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, bool *bp);
     bool getOwnEnumerablePropertyKeys(JSContext *cx, JS::HandleObject proxy,
@@ -123,19 +123,18 @@ class WrapperOwner : public virtual Java
 
     virtual bool SendHas(const ObjectId &objId, const JSIDVariant &id,
                          ReturnStatus *rs, bool *bp) = 0;
     virtual bool SendHasOwn(const ObjectId &objId, const JSIDVariant &id,
                             ReturnStatus *rs, bool *bp) = 0;
     virtual bool SendGet(const ObjectId &objId, const ObjectVariant &receiverVar,
                          const JSIDVariant &id,
                          ReturnStatus *rs, JSVariant *result) = 0;
-    virtual bool SendSet(const ObjectId &objId, const ObjectVariant &receiverVar,
-                         const JSIDVariant &id, const JSVariant &value,
-                         ReturnStatus *rs, JSVariant *result) = 0;
+    virtual bool SendSet(const ObjectId &objId, const JSIDVariant &id, const JSVariant &value,
+                         const JSVariant &receiverVar, ReturnStatus *rs) = 0;
 
     virtual bool SendIsExtensible(const ObjectId &objId, ReturnStatus *rs,
                                   bool *result) = 0;
     virtual bool SendCallOrConstruct(const ObjectId &objId, const nsTArray<JSParam> &argv,
                                      const bool &construct, ReturnStatus *rs, JSVariant *result,
                                      nsTArray<JSParam> *outparams) = 0;
     virtual bool SendHasInstance(const ObjectId &objId, const JSVariant &v,
                                  ReturnStatus *rs, bool *bp) = 0;
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -337,18 +337,18 @@ typedef bool
                      JS::Handle<JSPropertyDescriptor> desc,
                      JS::ObjectOpResult &result);
 typedef bool
 (* HasPropertyOp)(JSContext *cx, JS::HandleObject obj, JS::HandleId id, bool *foundp);
 typedef bool
 (* GetPropertyOp)(JSContext *cx, JS::HandleObject obj, JS::HandleObject receiver, JS::HandleId id,
                   JS::MutableHandleValue vp);
 typedef bool
-(* SetPropertyOp)(JSContext *cx, JS::HandleObject obj, JS::HandleObject receiver, JS::HandleId id,
-                  JS::MutableHandleValue vp, JS::ObjectOpResult &result);
+(* SetPropertyOp)(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue v,
+                  JS::HandleValue receiver, JS::ObjectOpResult &result);
 typedef bool
 (* GetOwnPropertyOp)(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
                      JS::MutableHandle<JSPropertyDescriptor> desc);
 typedef bool
 (* DeletePropertyOp)(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
                      JS::ObjectOpResult &result);
 
 typedef bool
--- a/js/public/Proxy.h
+++ b/js/public/Proxy.h
@@ -290,18 +290,18 @@ class JS_FRIEND_API(BaseProxyHandler)
      * that ProxyHandler subclasses don't have to provide every single method.
      *
      * The base-class implementations work by calling getPropertyDescriptor().
      * They do not follow any standard. When in doubt, override them.
      */
     virtual bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const;
     virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver,
                      HandleId id, MutableHandleValue vp) const;
-    virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver,
-                     HandleId id, MutableHandleValue vp, ObjectOpResult &result) const;
+    virtual bool set(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v,
+                     HandleValue receiver, ObjectOpResult &result) const;
 
     /*
      * [[Call]] and [[Construct]] are standard internal methods but according
      * to the spec, they are not present on every object.
      *
      * SpiderMonkey never calls a proxy's call()/construct() internal method
      * unless isCallable()/isConstructor() returns true for that proxy.
      *
@@ -390,19 +390,18 @@ class JS_FRIEND_API(DirectProxyHandler) 
                                        bool *succeeded) const override;
     virtual bool preventExtensions(JSContext *cx, HandleObject proxy,
                                    ObjectOpResult &result) const override;
     virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) const override;
     virtual bool has(JSContext *cx, HandleObject proxy, HandleId id,
                      bool *bp) const override;
     virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver,
                      HandleId id, MutableHandleValue vp) const override;
-    virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver,
-                     HandleId id, MutableHandleValue vp,
-                     ObjectOpResult &result) const override;
+    virtual bool set(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v,
+                     HandleValue receiver, ObjectOpResult &result) const override;
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) const override;
     virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const override;
 
     /* SpiderMonkey extensions. */
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc) const override;
     virtual bool hasOwn(JSContext *cx, HandleObject proxy, HandleId id,
                         bool *bp) const override;
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -4438,30 +4438,23 @@ FoldMaskedArrayIndex(FunctionCompiler &f
 {
     MOZ_ASSERT((*indexExpr)->isKind(PNK_BITAND));
 
     ParseNode *indexNode = BitwiseLeft(*indexExpr);
     ParseNode *maskNode = BitwiseRight(*indexExpr);
 
     uint32_t mask2;
     if (IsLiteralOrConstInt(f, maskNode, &mask2)) {
-        // Flag the access to skip the bounds check if the mask ensures that an 'out of
-        // bounds' access can not occur based on the current heap length constraint.
-        if (mask2 == 0) {
+        // Flag the access to skip the bounds check if the mask ensures that an
+        // 'out of bounds' access can not occur based on the current heap length
+        // constraint. The unsigned maximum of a masked index is the mask
+        // itself, so check that the mask is not negative and compare the mask
+        // to the known minimum heap length.
+        if (int32_t(mask2) >= 0 && mask2 < f.m().minHeapLength())
             *needsBoundsCheck = NO_BOUNDS_CHECK;
-        } else {
-            uint32_t minHeap = f.m().minHeapLength();
-            uint32_t minHeapZeroes = CountLeadingZeroes32(minHeap - 1);
-            uint32_t maskZeroes = CountLeadingZeroes32(mask2);
-            if ((minHeapZeroes < maskZeroes) ||
-                (IsPowerOfTwo(minHeap) && minHeapZeroes == maskZeroes))
-            {
-                *needsBoundsCheck = NO_BOUNDS_CHECK;
-            }
-        }
         *mask &= mask2;
         *indexExpr = indexNode;
         return true;
     }
 
     return false;
 }
 
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -1899,80 +1899,80 @@ TypedObject::obj_getArrayElement(JSConte
     }
 
     Rooted<TypeDescr*> elementType(cx, &typeDescr->as<ArrayTypeDescr>().elementType());
     size_t offset = elementType->size() * index;
     return Reify(cx, elementType, typedObj, offset, vp);
 }
 
 bool
-TypedObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
-                             MutableHandleValue vp, ObjectOpResult &result)
+TypedObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                             HandleValue receiver, ObjectOpResult &result)
 {
     Rooted<TypedObject *> typedObj(cx, &obj->as<TypedObject>());
 
     switch (typedObj->typeDescr().kind()) {
       case type::Scalar:
       case type::Reference:
         break;
 
       case type::Simd:
         break;
 
       case type::Array: {
         if (JSID_IS_ATOM(id, cx->names().length)) {
-            if (obj == receiver) {
+            if (receiver.isObject() && obj == &receiver.toObject()) {
                 JS_ReportErrorNumber(cx, GetErrorMessage,
                                      nullptr, JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
                 return false;
             }
             return result.failReadOnly();
         }
 
         uint32_t index;
         if (IdIsIndex(id, &index)) {
-            if (obj != receiver)
-                return SetPropertyByDefining(cx, obj, receiver, id, vp, false, result);
+            if (!receiver.isObject() || obj != &receiver.toObject())
+                return SetPropertyByDefining(cx, obj, id, v, receiver, false, result);
 
             if (index >= uint32_t(typedObj->length())) {
                 JS_ReportErrorNumber(cx, GetErrorMessage,
                                      nullptr, JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX);
                 return false;
             }
 
             Rooted<TypeDescr*> elementType(cx);
             elementType = &typedObj->typeDescr().as<ArrayTypeDescr>().elementType();
             size_t offset = elementType->size() * index;
-            if (!ConvertAndCopyTo(cx, elementType, typedObj, offset, NullPtr(), vp))
+            if (!ConvertAndCopyTo(cx, elementType, typedObj, offset, NullPtr(), v))
                 return false;
             return result.succeed();
         }
         break;
       }
 
       case type::Struct: {
         Rooted<StructTypeDescr*> descr(cx, &typedObj->typeDescr().as<StructTypeDescr>());
 
         size_t fieldIndex;
         if (!descr->fieldIndex(id, &fieldIndex))
             break;
 
-        if (obj != receiver)
-            return SetPropertyByDefining(cx, obj, receiver, id, vp, false, result);
+        if (!receiver.isObject() || obj != &receiver.toObject())
+            return SetPropertyByDefining(cx, obj, id, v, receiver, false, result);
 
         size_t offset = descr->fieldOffset(fieldIndex);
         Rooted<TypeDescr*> fieldType(cx, &descr->fieldDescr(fieldIndex));
         RootedAtom fieldName(cx, &descr->fieldName(fieldIndex));
-        if (!ConvertAndCopyTo(cx, fieldType, typedObj, offset, fieldName, vp))
+        if (!ConvertAndCopyTo(cx, fieldType, typedObj, offset, fieldName, v))
             return false;
         return result.succeed();
       }
     }
 
-    return SetPropertyOnProto(cx, obj, receiver, id, vp, result);
+    return SetPropertyOnProto(cx, obj, id, v, receiver, result);
 }
 
 bool
 TypedObject::obj_getOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id,
                                           MutableHandle<JSPropertyDescriptor> desc)
 {
     Rooted<TypedObject *> typedObj(cx, &obj->as<TypedObject>());
     if (!typedObj->isAttached()) {
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -535,18 +535,18 @@ class TypedObject : public JSObject
     static bool obj_hasProperty(JSContext *cx, HandleObject obj, HandleId id, bool *foundp);
 
     static bool obj_getProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
                                 HandleId id, MutableHandleValue vp);
 
     static bool obj_getElement(JSContext *cx, HandleObject obj, HandleObject receiver,
                                uint32_t index, MutableHandleValue vp);
 
-    static bool obj_setProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
-                                HandleId id, MutableHandleValue vp, ObjectOpResult &result);
+    static bool obj_setProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                                HandleValue receiver, ObjectOpResult &result);
 
     static bool obj_getOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id,
                                              MutableHandle<JSPropertyDescriptor> desc);
 
     static bool obj_deleteProperty(JSContext *cx, HandleObject obj, HandleId id,
                                    ObjectOpResult &result);
 
     static bool obj_enumerate(JSContext *cx, HandleObject obj, AutoIdVector &properties);
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -149,18 +149,40 @@ function GetIterator(obj, method) {
     // Step 5.
     if (!IsObject(iterator))
         ThrowError(JSMSG_NOT_ITERABLE, ToString(iterator));
 
     // Step 6.
     return iterator;
 }
 
+// ES6 draft 20150317 7.3.20.
 function SpeciesConstructor(obj, defaultConstructor) {
-    var C = obj.constructor;
-    if (C === undefined) {
+    // Step 1.
+    assert(IsObject(obj), "not passed an object");
+
+    // Steps 2-3.
+    var ctor = obj.constructor;
+
+    // Step 4.
+    if (ctor === undefined)
         return defaultConstructor;
-    }
-    if (!IsConstructor(C)) {
-        ThrowError(JSMSG_NOT_CONSTRUCTOR, DecompileArg(1, C));
-    }
-    return C;
+
+    // Step 5.
+    if (!IsObject(ctor))
+        ThrowError(JSMSG_NOT_NONNULL_OBJECT, "object's 'constructor' property");
+
+    // Steps 6-7.  We don't yet implement @@species and Symbol.species, so we
+    // don't implement this correctly right now.  Somebody fix this!
+    var s = /* ctor[Symbol.species] */ undefined;
+
+    // Step 8.
+    if (s === undefined || s === null)
+        return defaultConstructor;
+
+    // Step 9.
+    if (IsConstructor(s))
+        return s;
+
+    // Step 10.
+    ThrowError(JSMSG_NOT_CONSTRUCTOR,
+               "@@species property of object's constructor");
 }
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -108,31 +108,49 @@ enum class AllocKind {
     JITCODE,
     LIMIT,
     LAST = LIMIT - 1
 };
 
 static_assert(uint8_t(AllocKind::OBJECT0) == 0, "Please check AllocKind iterations and comparisons"
     " of the form |kind <= AllocKind::OBJECT_LAST| to ensure their range is still valid!");
 
+// Returns a sequence for use in a range-based for loop,
+// to iterate over all alloc kinds.
 inline decltype(mozilla::MakeEnumeratedRange<int>(AllocKind::FIRST, AllocKind::LIMIT))
 AllAllocKinds()
 {
     return mozilla::MakeEnumeratedRange<int>(AllocKind::FIRST, AllocKind::LIMIT);
 }
 
+// Returns a sequence for use in a range-based for loop,
+// to iterate over all object alloc kinds.
 inline decltype(mozilla::MakeEnumeratedRange<int>(AllocKind::OBJECT0, AllocKind::OBJECT_LIMIT))
 ObjectAllocKinds()
 {
     return mozilla::MakeEnumeratedRange<int>(AllocKind::OBJECT0, AllocKind::OBJECT_LIMIT);
 }
 
+// Returns a sequence for use in a range-based for loop,
+// to iterate over alloc kinds from |first| to |limit|, exclusive.
+inline decltype(mozilla::MakeEnumeratedRange<int>(AllocKind::FIRST, AllocKind::LIMIT))
+SomeAllocKinds(AllocKind first = AllocKind::FIRST, AllocKind limit = AllocKind::LIMIT)
+{
+    MOZ_ASSERT(limit <= AllocKind::LIMIT);
+    MOZ_ASSERT(first <= limit);
+    return mozilla::MakeEnumeratedRange<int>(first, limit);
+}
+
+// AllAllocKindArray<ValueType> gives an enumerated array of ValueTypes,
+// with each index corresponding to a particular alloc kind.
 template<typename ValueType> using AllAllocKindArray =
     mozilla::EnumeratedArray<AllocKind, AllocKind::LIMIT, ValueType>;
 
+// ObjectAllocKindArray<ValueType> gives an enumerated array of ValueTypes,
+// with each index corresponding to a particular object alloc kind.
 template<typename ValueType> using ObjectAllocKindArray =
     mozilla::EnumeratedArray<AllocKind, AllocKind::OBJECT_LIMIT, ValueType>;
 
 static inline JSGCTraceKind
 MapAllocToTraceKind(AllocKind kind)
 {
     static const JSGCTraceKind map[] = {
         JSTRACE_OBJECT,       /* AllocKind::OBJECT0 */
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1134074.js
@@ -0,0 +1,10 @@
+
+setJitCompilerOption("ion.warmup.trigger", 30);
+function bar(i) {
+  if (i >= 40)
+    return;
+  if ("aaa,bbb,ccc".split(",")[0].length != 3)
+    throw "???";
+  bar(i + 1);
+}
+bar(0);
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -8361,17 +8361,17 @@ DoSetPropFallback(JSContext *cx, Baselin
         if (!SetNameOperation(cx, script, pc, obj, rhs))
             return false;
     } else if (op == JSOP_SETALIASEDVAR || op == JSOP_INITALIASEDLEXICAL) {
         obj->as<ScopeObject>().setAliasedVar(cx, ScopeCoordinate(pc), name, rhs);
     } else {
         MOZ_ASSERT(op == JSOP_SETPROP || op == JSOP_STRICTSETPROP);
 
         RootedValue v(cx, rhs);
-        if (!PutProperty(cx, obj, id, &v, op == JSOP_STRICTSETPROP))
+        if (!PutProperty(cx, obj, id, v, op == JSOP_STRICTSETPROP))
             return false;
     }
 
     // Leave the RHS on the stack.
     res.set(rhs);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -1562,19 +1562,16 @@ EmitCallProxyGet(JSContext *cx, MacroAss
     masm.movePtr(StackPointer, argIdReg);
 
     // Pushing object and receiver.  Both are the same, so Handle to one is equivalent to
     // handle to other.
     masm.Push(object);
     masm.Push(object);
     masm.movePtr(StackPointer, argProxyReg);
 
-    // Unused space, to keep the same stack layout as Proxy::set frames.
-    PushObjectOpResult(masm);
-
     masm.loadJSContext(argJSContextReg);
 
     if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
         return false;
     masm.enterFakeExitFrame(IonOOLProxyExitFrameLayout::Token());
 
     // Make the call.
     masm.setupUnalignedABICall(5, scratch);
@@ -2216,87 +2213,88 @@ EmitObjectOpResultCheck(MacroAssembler &
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ReportStrictErrorOrWarning));
     masm.branchIfFalseBool(ReturnReg, failure);
 
     // }
     masm.bind(&noStrictError);
 }
 
 static bool
+ProxySetProperty(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v, bool strict)
+{
+    RootedValue receiver(cx, ObjectValue(*proxy));
+    ObjectOpResult result;
+    return Proxy::set(cx, proxy, id, v, receiver, result)
+           && result.checkStrictErrorOrWarning(cx, proxy, id, strict);
+}
+
+static bool
 EmitCallProxySet(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher,
                  HandleId propId, RegisterSet liveRegs, Register object,
                  ConstantOrRegister value, void *returnAddr, bool strict)
 {
     MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);
 
     // Remaining registers should be free, but we still need to use |object| so
     // leave it alone.
     //
     // WARNING: We do not take() the register used by |value|, if any, so
     // regSet is going to re-allocate it. Hence the emitted code must not touch
     // any of the registers allocated from regSet until after the last use of
     // |value|. (We can't afford to take it, either, because x86.)
     RegisterSet regSet(RegisterSet::All());
     regSet.take(AnyRegister(object));
 
-    // Proxy::set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
-    //            MutableHandleValue vp, ObjectOpResult &result)
+    // ProxySetProperty(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v,
+    //                  bool strict);
     Register argJSContextReg = regSet.takeGeneral();
     Register argProxyReg     = regSet.takeGeneral();
     Register argIdReg        = regSet.takeGeneral();
-    Register argVpReg        = regSet.takeGeneral();
-    Register argResultReg    = regSet.takeGeneral();
+    Register argValueReg     = regSet.takeGeneral();
+    Register argStrictReg    = regSet.takeGeneral();
 
     Register scratch         = regSet.takeGeneral();
 
     // Push stubCode for marking.
     attacher.pushStubCodePointer(masm);
 
-    // Push args on stack first so we can take pointers to make handles.
+    // Push args on stack so we can take pointers to make handles.
+    // Push value before touching any other registers (see WARNING above).
     masm.Push(value);
-    masm.movePtr(StackPointer, argVpReg);
+    masm.movePtr(StackPointer, argValueReg);
+
+    masm.move32(Imm32(strict), argStrictReg);
 
     masm.Push(propId, scratch);
     masm.movePtr(StackPointer, argIdReg);
 
     // Pushing object and receiver.  Both are the same, so Handle to one is equivalent to
     // handle to other.
     masm.Push(object);
     masm.Push(object);
     masm.movePtr(StackPointer, argProxyReg);
 
-    // Allocate result out-param.
-    PushObjectOpResult(masm);
-    masm.movePtr(StackPointer, argResultReg);
-
     masm.loadJSContext(argJSContextReg);
 
     if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
         return false;
     masm.enterFakeExitFrame(IonOOLProxyExitFrameLayout::Token());
 
     // Make the call.
-    masm.setupUnalignedABICall(6, scratch);
+    masm.setupUnalignedABICall(5, scratch);
     masm.passABIArg(argJSContextReg);
     masm.passABIArg(argProxyReg);
-    masm.passABIArg(argProxyReg);
     masm.passABIArg(argIdReg);
-    masm.passABIArg(argVpReg);
-    masm.passABIArg(argResultReg);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, Proxy::set));
+    masm.passABIArg(argValueReg);
+    masm.passABIArg(argStrictReg);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ProxySetProperty));
 
     // Test for error.
     masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
 
-    // Test for strict failure. We emit the check even in non-strict mode in
-    // order to pick up the warning if extraWarnings is enabled.
-    EmitObjectOpResultCheck<IonOOLProxyExitFrameLayout>(masm, masm.exceptionLabel(), strict,
-                                                        scratch, argJSContextReg, argProxyReg,
-                                                        argIdReg, argVpReg, argResultReg);
-
     // masm.leaveExitFrame & pop locals
     masm.adjustStack(IonOOLProxyExitFrameLayout::Size());
 
     masm.icRestoreLive(liveRegs, aic);
     return true;
 }
 
 bool
@@ -2503,18 +2501,19 @@ GenerateCallSetter(JSContext *cx, IonScr
         // We can't take all our registers up front, because on x86 we need 2
         // for the value, one for scratch, 5 for the arguments, which makes 8,
         // but we only have 7 to work with.  So only grab the ones we need
         // before we push value and release its reg back into the set.
         Register argResultReg = regSet.takeGeneral();
 
         SetterOp target = shape->setterOp();
         MOZ_ASSERT(target);
+
         // JSSetterOp: bool fn(JSContext *cx, HandleObject obj,
-        //                     HandleId id, MutableHandleValue vp, ObjectOpResult &result);
+        //                     HandleId id, HandleValue value, ObjectOpResult &result);
 
         // First, allocate an ObjectOpResult on the stack. We push this before
         // the stubCode pointer in order to match the layout of
         // IonOOLSetterOpExitFrameLayout.
         PushObjectOpResult(masm);
         masm.movePtr(StackPointer, argResultReg);
 
         attacher.pushStubCodePointer(masm);
@@ -2525,21 +2524,21 @@ GenerateCallSetter(JSContext *cx, IonScr
         } else {
             masm.Push(value.reg());
             regSet.add(value.reg());
         }
 
         // OK, now we can grab our remaining registers and grab the pointer to
         // what we just pushed into one of them.
         Register argJSContextReg = regSet.takeGeneral();
-        Register argVpReg        = regSet.takeGeneral();
+        Register argValueReg     = regSet.takeGeneral();
         // We can just reuse the "object" register for argObjReg
         Register argObjReg       = object;
         Register argIdReg        = regSet.takeGeneral();
-        masm.movePtr(StackPointer, argVpReg);
+        masm.movePtr(StackPointer, argValueReg);
 
         // push canonical jsid from shape instead of propertyname.
         masm.Push(shape->propid(), argIdReg);
         masm.movePtr(StackPointer, argIdReg);
 
         masm.Push(object);
         masm.movePtr(StackPointer, argObjReg);
 
@@ -2549,28 +2548,30 @@ GenerateCallSetter(JSContext *cx, IonScr
             return false;
         masm.enterFakeExitFrame(IonOOLSetterOpExitFrameLayout::Token());
 
         // Make the call.
         masm.setupUnalignedABICall(5, scratchReg);
         masm.passABIArg(argJSContextReg);
         masm.passABIArg(argObjReg);
         masm.passABIArg(argIdReg);
-        masm.passABIArg(argVpReg);
+        masm.passABIArg(argValueReg);
         masm.passABIArg(argResultReg);
         masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target));
 
         // Test for error.
         masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
 
-        // Test for failure.
+        // Test for strict failure. We emit the check even in non-strict mode
+        // in order to pick up the warning if extraWarnings is enabled.
         EmitObjectOpResultCheck<IonOOLSetterOpExitFrameLayout>(masm, masm.exceptionLabel(),
                                                                strict, scratchReg,
                                                                argJSContextReg, argObjReg,
-                                                               argIdReg, argVpReg, argResultReg);
+                                                               argIdReg, argValueReg,
+                                                               argResultReg);
 
         // masm.leaveExitFrame & pop locals.
         masm.adjustStack(IonOOLSetterOpExitFrameLayout::Size());
     } else {
         MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, shape));
 
         JSFunction *target = &shape->setterValue().toObject().as<JSFunction>();
         uint32_t framePushedBefore = masm.framePushed();
--- a/js/src/jit/JitFrames.h
+++ b/js/src/jit/JitFrames.h
@@ -694,27 +694,24 @@ class IonOOLSetterOpExitFrameLayout : pu
 
     static size_t Size() {
         return sizeof(IonOOLSetterOpExitFrameLayout);
     }
 };
 
 // Proxy::get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
 //            MutableHandleValue vp)
-// Proxy::set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
-//            MutableHandleValue vp, ObjectOpResult &result)
+// ProxySetProperty(JSContext *cx, HandleObject proxy, HandleId id, MutableHandleValue vp,
+//                  bool strict)
 class IonOOLProxyExitFrameLayout
 {
   protected: // only to silence a clang warning about unused private fields
     ExitFooterFrame footer_;
     ExitFrameLayout exit_;
 
-    // result out-parameter (unused for Proxy::get)
-    JS::ObjectOpResult result_;
-
     // The proxy object.
     JSObject *proxy_;
 
     // Object for HandleObject
     JSObject *receiver_;
 
     // id for HandleId
     jsid id_;
@@ -729,32 +726,20 @@ class IonOOLProxyExitFrameLayout
 
   public:
     static JitCode *Token() { return (JitCode *)IonOOLProxyExitFrameLayoutToken; }
 
     static inline size_t Size() {
         return sizeof(IonOOLProxyExitFrameLayout);
     }
 
-    static size_t offsetOfObject() {
-        return offsetof(IonOOLProxyExitFrameLayout, proxy_);
-    }
-
     static size_t offsetOfResult() {
         return offsetof(IonOOLProxyExitFrameLayout, vp0_);
     }
 
-    static size_t offsetOfId() {
-        return offsetof(IonOOLProxyExitFrameLayout, id_);
-    }
-
-    static size_t offsetOfObjectOpResult() {
-        return offsetof(IonOOLProxyExitFrameLayout, result_);
-    }
-
     inline JitCode **stubCode() {
         return &stubCode_;
     }
     inline Value *vp() {
         return reinterpret_cast<Value*>(&vp0_);
     }
     inline jsid *id() {
         return &id_;
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -1537,19 +1537,24 @@ IonBuilder::inlineConstantStringSplit(Ca
 
     if (!key.maybeTypes()->hasType(TypeSet::StringType()))
         return InliningStatus_NotInlined;
 
     uint32_t initLength = templateObject->as<ArrayObject>().length();
     if (templateObject->getDenseInitializedLength() != initLength)
         return InliningStatus_NotInlined;
 
+    JSContext *cx = GetJitContext()->cx;
     Vector<MConstant *, 0, SystemAllocPolicy> arrayValues;
     for (uint32_t i = 0; i < initLength; i++) {
-        MConstant *value = MConstant::New(alloc(), templateObject->getDenseElement(i), constraints());
+        JSAtom *str = js::AtomizeString(cx, templateObject->getDenseElement(i).toString());
+        if (!str)
+            return InliningStatus_Error;
+
+        MConstant *value = MConstant::New(alloc(), StringValue(str), constraints());
         if (!TypeSetIncludes(key.maybeTypes(), value->type(), value->resultTypeSet()))
             return InliningStatus_NotInlined;
 
         if (!arrayValues.append(value))
             return InliningStatus_Error;
     }
     callInfo.setImplicitlyUsedUnchecked();
 
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -705,16 +705,18 @@ MConstant::MConstant(const js::Value &vp
         // Ion compilation. Give it an unknown typeset to poison any type sets
         // it merges with.
         //
         // TODO We could track uninitialized lexicals more precisely by tracking
         // them in type sets.
         setResultTypeSet(MakeUnknownTypeSet());
     }
 
+    MOZ_ASSERT_IF(vp.isString(), vp.toString()->isAtom());
+
     setMovable();
 }
 
 MConstant::MConstant(JSObject *obj)
   : value_(ObjectValue(*obj))
 {
     MOZ_ASSERT(!IsInsideNursery(obj));
     setResultType(MIRType_Object);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -1148,17 +1148,18 @@ class MQuaternaryInstruction : public MA
         MDefinition *third = getOperand(2);
         MDefinition *fourth = getOperand(3);
 
         return op() + first->id() + second->id() +
                       third->id() + fourth->id();
     }
 };
 
-class MVariadicInstruction : public MInstruction
+template <class T>
+class MVariadicT : public T
 {
     FixedList<MUse> operands_;
 
   protected:
     bool init(TempAllocator &alloc, size_t length) {
         return operands_.init(alloc, length);
     }
     void initOperand(size_t index, MDefinition *operand) {
@@ -1185,16 +1186,18 @@ class MVariadicInstruction : public MIns
         MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
         return u - &operands_[0];
     }
     void replaceOperand(size_t index, MDefinition *operand) final override {
         operands_[index].replaceProducer(operand);
     }
 };
 
+typedef MVariadicT<MInstruction> MVariadicInstruction;
+
 // Generates an LSnapshot without further effect.
 class MStart : public MNullaryInstruction
 {
   public:
     enum StartType {
         StartType_Default,
         StartType_Osr
     };
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -429,45 +429,44 @@ StringFromCharCode(JSContext *cx, int32_
 
     return NewStringCopyN<CanGC>(cx, &c, 1);
 }
 
 bool
 SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, HandleValue value,
             bool strict, jsbytecode *pc)
 {
-    RootedValue v(cx, value);
     RootedId id(cx, NameToId(name));
 
     JSOp op = JSOp(*pc);
 
     if (op == JSOP_SETALIASEDVAR) {
         // Aliased var assigns ignore readonly attributes on the property, as
         // required for initializing 'const' closure variables.
         Shape *shape = obj->as<NativeObject>().lookup(cx, name);
         MOZ_ASSERT(shape && shape->hasSlot());
         obj->as<NativeObject>().setSlotWithType(cx, shape, value);
         return true;
     }
 
+    RootedValue receiver(cx, ObjectValue(*obj));
     ObjectOpResult result;
     if (MOZ_LIKELY(!obj->getOps()->setProperty)) {
         if (!NativeSetProperty(
-                cx, obj.as<NativeObject>(), obj.as<NativeObject>(), id,
+                cx, obj.as<NativeObject>(), id, value, receiver,
                 (op == JSOP_SETNAME || op == JSOP_STRICTSETNAME ||
                  op == JSOP_SETGNAME || op == JSOP_STRICTSETGNAME)
                 ? Unqualified
                 : Qualified,
-                &v,
                 result))
         {
             return false;
         }
     } else {
-        if (!SetProperty(cx, obj, obj, id, &v, result))
+        if (!SetProperty(cx, obj, id, value, receiver, result))
             return false;
     }
     return result.checkStrictErrorOrWarning(cx, obj, id, strict);
 }
 
 bool
 InterruptCheck(JSContext *cx)
 {
--- a/js/src/jit/mips/CodeGenerator-mips.cpp
+++ b/js/src/jit/mips/CodeGenerator-mips.cpp
@@ -2015,70 +2015,48 @@ CodeGeneratorMIPS::visitAsmJSPassStackAr
         } else {
             masm.storeDouble(ToFloatRegister(ins->arg()).doubleOverlay(0),
                              Address(StackPointer, mir->spOffset()));
         }
     }
 }
 
 void
-CodeGeneratorMIPS::visitUDiv(LUDiv *ins)
-{
-    Register lhs = ToRegister(ins->lhs());
-    Register rhs = ToRegister(ins->rhs());
-    Register output = ToRegister(ins->output());
-
-    Label done;
-    if (ins->mir()->canBeDivideByZero()) {
-        if (ins->mir()->isTruncated()) {
-            Label notzero;
-            masm.ma_b(rhs, rhs, &notzero, Assembler::NonZero, ShortJump);
-            masm.move32(Imm32(0), output);
-            masm.ma_b(&done, ShortJump);
-            masm.bind(&notzero);
-        } else {
-            MOZ_ASSERT(ins->mir()->fallible());
-            bailoutCmp32(Assembler::Equal, rhs, Imm32(0), ins->snapshot());
-        }
-    }
-
-    masm.as_divu(lhs, rhs);
-    masm.as_mflo(output);
-
-    if (!ins->mir()->isTruncated())
-        bailoutCmp32(Assembler::LessThan, output, Imm32(0), ins->snapshot());
-
-    masm.bind(&done);
-}
-
-void
-CodeGeneratorMIPS::visitUMod(LUMod *ins)
+CodeGeneratorMIPS::visitUDivOrMod(LUDivOrMod *ins)
 {
     Register lhs = ToRegister(ins->lhs());
     Register rhs = ToRegister(ins->rhs());
     Register output = ToRegister(ins->output());
     Label done;
 
-    if (ins->mir()->canBeDivideByZero()) {
+    // Prevent divide by zero.
+    if (ins->canBeDivideByZero()) {
         if (ins->mir()->isTruncated()) {
             // Infinity|0 == 0
             Label notzero;
             masm.ma_b(rhs, rhs, &notzero, Assembler::NonZero, ShortJump);
             masm.move32(Imm32(0), output);
             masm.ma_b(&done, ShortJump);
             masm.bind(&notzero);
         } else {
-            MOZ_ASSERT(ins->mir()->fallible());
             bailoutCmp32(Assembler::Equal, rhs, Imm32(0), ins->snapshot());
         }
     }
 
     masm.as_divu(lhs, rhs);
     masm.as_mfhi(output);
 
+    // If the remainder is > 0, bailout since this must be a double.
+    if (ins->mir()->isDiv()) {
+        if (!ins->mir()->toDiv()->canTruncateRemainder())
+          bailoutCmp32(Assembler::NonZero, output, output, ins->snapshot());
+        // Get quotient
+        masm.as_mflo(output);
+    }
+
     if (!ins->mir()->isTruncated())
         bailoutCmp32(Assembler::LessThan, output, Imm32(0), ins->snapshot());
 
     masm.bind(&done);
 }
 
 void
 CodeGeneratorMIPS::visitEffectiveAddress(LEffectiveAddress *ins)
--- a/js/src/jit/mips/CodeGenerator-mips.h
+++ b/js/src/jit/mips/CodeGenerator-mips.h
@@ -268,18 +268,17 @@ class CodeGeneratorMIPS : public CodeGen
     void visitAsmJSLoadFFIFunc(LAsmJSLoadFFIFunc *ins);
 
     void visitAsmJSPassStackArg(LAsmJSPassStackArg *ins);
 
     void generateInvalidateEpilogue();
 
   protected:
     void visitEffectiveAddress(LEffectiveAddress *ins);
-    void visitUDiv(LUDiv *ins);
-    void visitUMod(LUMod *ins);
+    void visitUDivOrMod(LUDivOrMod *ins);
 
   public:
     // Unimplemented SIMD instructions
     void visitSimdSplatX4(LSimdSplatX4 *lir) { MOZ_CRASH("NYI"); }
     void visitInt32x4(LInt32x4 *ins) { MOZ_CRASH("NYI"); }
     void visitFloat32x4(LFloat32x4 *ins) { MOZ_CRASH("NYI"); }
     void visitSimdReinterpretCast(LSimdReinterpretCast *ins) { MOZ_CRASH("NYI"); }
     void visitSimdExtractElementI(LSimdExtractElementI *ins) { MOZ_CRASH("NYI"); }
--- a/js/src/jit/mips/LIR-mips.h
+++ b/js/src/jit/mips/LIR-mips.h
@@ -350,33 +350,30 @@ class LMulI : public LBinaryMath<0>
   public:
     LIR_HEADER(MulI);
 
     MMul *mir() {
         return mir_->toMul();
     }
 };
 
-class LUDiv : public LBinaryMath<0>
+class LUDivOrMod : public LBinaryMath<0>
 {
   public:
-    LIR_HEADER(UDiv);
-
-    MDiv *mir() {
-        return mir_->toDiv();
-    }
-};
+    LIR_HEADER(UDivOrMod);
 
-class LUMod : public LBinaryMath<0>
-{
-  public:
-    LIR_HEADER(UMod);
+    MBinaryArithInstruction *mir() const {
+        MOZ_ASSERT(mir_->isDiv() || mir_->isMod());
+        return static_cast<MBinaryArithInstruction *>(mir_);
+    }
 
-    MMod *mir() {
-        return mir_->toMod();
+    bool canBeDivideByZero() const {
+        if (mir_->isMod())
+            return mir_->toMod()->canBeDivideByZero();
+        return mir_->toDiv()->canBeDivideByZero();
     }
 };
 
 class LAsmJSLoadFuncPtr : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(AsmJSLoadFuncPtr);
     LAsmJSLoadFuncPtr(const LAllocation &index) {
--- a/js/src/jit/mips/LOpcodes-mips.h
+++ b/js/src/jit/mips/LOpcodes-mips.h
@@ -15,13 +15,12 @@
     _(DivI)                     \
     _(DivPowTwoI)               \
     _(ModI)                     \
     _(ModPowTwoI)               \
     _(ModMaskI)                 \
     _(PowHalfD)                 \
     _(AsmJSUInt32ToDouble)      \
     _(AsmJSUInt32ToFloat32)     \
-    _(UDiv)                     \
-    _(UMod)                     \
+    _(UDivOrMod)                \
     _(AsmJSLoadFuncPtr)
 
 #endif // jit_mips_LOpcodes_mips_h__
--- a/js/src/jit/mips/Lowering-mips.cpp
+++ b/js/src/jit/mips/Lowering-mips.cpp
@@ -414,32 +414,32 @@ LIRGeneratorMIPS::visitAsmJSNeg(MAsmJSNe
 }
 
 void
 LIRGeneratorMIPS::lowerUDiv(MDiv *div)
 {
     MDefinition *lhs = div->getOperand(0);
     MDefinition *rhs = div->getOperand(1);
 
-    LUDiv *lir = new(alloc()) LUDiv;
+    LUDivOrMod *lir = new(alloc()) LUDivOrMod;
     lir->setOperand(0, useRegister(lhs));
     lir->setOperand(1, useRegister(rhs));
     if (div->fallible())
         assignSnapshot(lir, Bailout_DoubleOutput);
 
     define(lir, div);
 }
 
 void
 LIRGeneratorMIPS::lowerUMod(MMod *mod)
 {
     MDefinition *lhs = mod->getOperand(0);
     MDefinition *rhs = mod->getOperand(1);
 
-    LUMod *lir = new(alloc()) LUMod;
+    LUDivOrMod *lir = new(alloc()) LUDivOrMod;
     lir->setOperand(0, useRegister(lhs));
     lir->setOperand(1, useRegister(rhs));
     if (mod->fallible())
         assignSnapshot(lir, Bailout_DoubleOutput);
 
     define(lir, mod);
 }
 
--- a/js/src/jit/mips/MacroAssembler-mips.h
+++ b/js/src/jit/mips/MacroAssembler-mips.h
@@ -229,16 +229,25 @@ class MacroAssemblerMIPS : public Assemb
     void ma_b(Register lhs, ImmPtr imm, Label *l, Condition c, JumpKind jumpKind = LongJump) {
         ma_b(lhs, Imm32(uint32_t(imm.value)), l, c, jumpKind);
     }
     void ma_b(Register lhs, ImmGCPtr imm, Label *l, Condition c, JumpKind jumpKind = LongJump) {
         MOZ_ASSERT(lhs != ScratchRegister);
         ma_li(ScratchRegister, imm);
         ma_b(lhs, ScratchRegister, l, c, jumpKind);
     }
+    void ma_b(Register lhs, ImmWord imm, Label *l, Condition c, JumpKind jumpKind = LongJump)
+    {
+        ma_b(lhs, Imm32(uint32_t(imm.value)), l, c, jumpKind);
+    }
+    void ma_b(Address addr, ImmWord imm, Label *l, Condition c, JumpKind jumpKind = LongJump)
+    {
+        ma_b(addr, Imm32(uint32_t(imm.value)), l, c, jumpKind);
+    }
+
     void ma_b(Register lhs, Address addr, Label *l, Condition c, JumpKind jumpKind = LongJump);
     void ma_b(Address addr, Imm32 imm, Label *l, Condition c, JumpKind jumpKind = LongJump);
     void ma_b(Address addr, ImmGCPtr imm, Label *l, Condition c, JumpKind jumpKind = LongJump);
     void ma_b(Address addr, Register rhs, Label *l, Condition c, JumpKind jumpKind = LongJump) {
         MOZ_ASSERT(rhs != ScratchRegister);
         ma_lw(ScratchRegister, addr);
         ma_b(ScratchRegister, rhs, l, c, jumpKind);
     }
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -77,16 +77,17 @@ MSG_DEF(JSMSG_MALFORMED_UTF8_CHAR,     1
 MSG_DEF(JSMSG_WRONG_CONSTRUCTOR,       1, JSEXN_TYPEERR, "wrong constructor called for {0}")
 MSG_DEF(JSMSG_BUILTIN_CTOR_NO_NEW,     1, JSEXN_NONE, "calling a builtin {0} constructor without new is deprecated and will be forbidden in ES6")
 MSG_DEF(JSMSG_PROTO_SETTING_SLOW,      0, JSEXN_NONE, "mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create")
 MSG_DEF(JSMSG_BAD_GENERATOR_YIELD,     1, JSEXN_TYPEERR, "yield from closing generator {0}")
 MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE,      0, JSEXN_TYPEERR, "reduce of empty array with no initial value")
 MSG_DEF(JSMSG_UNEXPECTED_TYPE,         2, JSEXN_TYPEERR, "{0} is {1}")
 MSG_DEF(JSMSG_MISSING_FUN_ARG,         2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")
 MSG_DEF(JSMSG_NOT_NONNULL_OBJECT,      1, JSEXN_TYPEERR, "{0} is not a non-null object")
+MSG_DEF(JSMSG_SET_NON_OBJECT_RECEIVER, 1, JSEXN_TYPEERR, "can't assign to properties of {0}: not an object")
 MSG_DEF(JSMSG_INVALID_DESCRIPTOR,      0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified")
 MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE,   1, JSEXN_TYPEERR, "{0} is not extensible")
 MSG_DEF(JSMSG_CANT_REDEFINE_PROP,      1, JSEXN_TYPEERR, "can't redefine non-configurable property {0}")
 MSG_DEF(JSMSG_CANT_APPEND_TO_ARRAY,    0, JSEXN_TYPEERR, "can't add elements past the end of an array if its length property is unwritable")
 MSG_DEF(JSMSG_CANT_REDEFINE_ARRAY_LENGTH, 0, JSEXN_TYPEERR, "can't redefine array length")
 MSG_DEF(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH, 0, JSEXN_TYPEERR, "can't define array index property past the end of an array with non-writable length")
 MSG_DEF(JSMSG_BAD_GET_SET_FIELD,       1, JSEXN_TYPEERR, "property descriptor's {0} field is neither undefined nor a function")
 MSG_DEF(JSMSG_THROW_TYPE_ERROR,        0, JSEXN_TYPEERR, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them")
--- a/js/src/jsapi-tests/testSetPropertyIgnoringNamedGetter.cpp
+++ b/js/src/jsapi-tests/testSetPropertyIgnoringNamedGetter.cpp
@@ -22,23 +22,23 @@ class CustomProxyHandler : public Direct
     }
 
     bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                   MutableHandle<JSPropertyDescriptor> desc) const override
     {
         return impl(cx, proxy, id, desc, true);
     }
 
-    bool set(JSContext *cx, HandleObject proxy, HandleObject receiver,
-             HandleId id, MutableHandleValue vp, ObjectOpResult &result) const override
+    bool set(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver,
+             ObjectOpResult &result) const override
     {
         Rooted<JSPropertyDescriptor> desc(cx);
         if (!DirectProxyHandler::getPropertyDescriptor(cx, proxy, id, &desc))
             return false;
-        return SetPropertyIgnoringNamedGetter(cx, proxy, id, vp, receiver, desc, result);
+        return SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, desc, result);
     }
 
   private:
     bool impl(JSContext *cx, HandleObject proxy, HandleId id,
               MutableHandle<JSPropertyDescriptor> desc, bool ownOnly) const
     {
         if (JSID_IS_STRING(id)) {
             bool match;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -142,17 +142,17 @@ JS::ObjectOpResult::reportStrictErrorOrW
                                                bool strict)
 {
     static_assert(unsigned(OkCode) == unsigned(JSMSG_NOT_AN_ERROR),
                   "unsigned value of OkCode must not be an error code");
     MOZ_ASSERT(code_ != Uninitialized);
     MOZ_ASSERT(!ok());
 
     unsigned flags = strict ? JSREPORT_ERROR : (JSREPORT_WARNING | JSREPORT_STRICT);
-    if (code_ == JSMSG_OBJECT_NOT_EXTENSIBLE) {
+    if (code_ == JSMSG_OBJECT_NOT_EXTENSIBLE || code_ == JSMSG_SET_NON_OBJECT_RECEIVER) {
         RootedValue val(cx, ObjectValue(*obj));
         return ReportValueErrorFlags(cx, flags, code_, JSDVG_IGNORE_STACK, val,
                                      NullPtr(), nullptr, nullptr);
     }
     if (ErrorTakesIdArgument(code_)) {
         RootedValue idv(cx, IdToValue(id));
         RootedString str(cx, ValueToSource(cx, idv));
         if (!str)
@@ -2813,93 +2813,87 @@ JS_GetUCProperty(JSContext *cx, HandleOb
         return false;
     RootedId id(cx, AtomToId(atom));
     return JS_GetPropertyById(cx, obj, id, vp);
 }
 
 JS_PUBLIC_API(bool)
 JS_SetPropertyById(JSContext *cx, HandleObject obj, HandleId id, HandleValue v)
 {
-    RootedValue value(cx, v);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj, id);
 
+    RootedValue receiver(cx, ObjectValue(*obj));
     ObjectOpResult ignored;
-    return SetProperty(cx, obj, obj, id, &value, ignored);
+    return SetProperty(cx, obj, id, v, receiver, ignored);
 }
 
 JS_PUBLIC_API(bool)
 JS_ForwardSetPropertyTo(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
                         HandleValue receiver, ObjectOpResult &result)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj, id, receiver);
 
-    // XXX Bug 603201 will eliminate this ToObject.
-    RootedObject receiverObj(cx, ToObject(cx, receiver));
-    if (!receiverObj)
-        return false;
-
-    RootedValue value(cx, v);
-    return SetProperty(cx, obj, receiverObj, id, &value, result);
+    return SetProperty(cx, obj, id, v, receiver, result);
 }
 
 static bool
-SetElement(JSContext *cx, HandleObject obj, uint32_t index, MutableHandleValue vp)
+SetElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue v)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    assertSameCompartment(cx, obj, vp);
-
+    assertSameCompartment(cx, obj, v);
+
+    RootedValue receiver(cx, ObjectValue(*obj));
     ObjectOpResult ignored;
-    return SetElement(cx, obj, obj, index, vp, ignored);
+    return SetElement(cx, obj, index, v, receiver, ignored);
 }
 
 JS_PUBLIC_API(bool)
 JS_SetElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue v)
 {
-    RootedValue value(cx, v);
-    return SetElement(cx, obj, index, &value);
+    return SetElement(cx, obj, index, v);
 }
 
 JS_PUBLIC_API(bool)
 JS_SetElement(JSContext *cx, HandleObject obj, uint32_t index, HandleObject v)
 {
     RootedValue value(cx, ObjectOrNullValue(v));
-    return SetElement(cx, obj, index, &value);
+    return SetElement(cx, obj, index, value);
 }
 
 JS_PUBLIC_API(bool)
 JS_SetElement(JSContext *cx, HandleObject obj, uint32_t index, HandleString v)
 {
     RootedValue value(cx, StringValue(v));
-    return SetElement(cx, obj, index, &value);
+    return SetElement(cx, obj, index, value);
 }
 
 JS_PUBLIC_API(bool)
 JS_SetElement(JSContext *cx, HandleObject obj, uint32_t index, int32_t v)
 {
     RootedValue value(cx, NumberValue(v));
-    return SetElement(cx, obj, index, &value);
+    return SetElement(cx, obj, index, value);
 }
 
 JS_PUBLIC_API(bool)
 JS_SetElement(JSContext *cx, HandleObject obj, uint32_t index, uint32_t v)
 {
     RootedValue value(cx, NumberValue(v));
-    return SetElement(cx, obj, index, &value);
+    return SetElement(cx, obj, index, value);
 }
 
 JS_PUBLIC_API(bool)
 JS_SetElement(JSContext *cx, HandleObject obj, uint32_t index, double v)
 {
     RootedValue value(cx, NumberValue(v));
-    return SetElement(cx, obj, index, &value);
+    return SetElement(cx, obj, index, value);
 }
 
 JS_PUBLIC_API(bool)
 JS_SetProperty(JSContext *cx, HandleObject obj, const char *name, HandleValue v)
 {
     JSAtom *atom = Atomize(cx, name, strlen(name));
     if (!atom)
         return false;
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -359,18 +359,17 @@ SetArrayElement(JSContext *cx, HandleObj
             return false;
         MOZ_ASSERT(result == NativeObject::ED_SPARSE);
     }
 
     RootedId id(cx);
     if (!ToId(cx, index, &id))
         return false;
 
-    RootedValue tmp(cx, v);
-    return SetProperty(cx, obj, obj, id, &tmp);
+    return SetProperty(cx, obj, id, v);
 }
 
 /*
  * Attempt to delete the element |index| from |obj| as if by
  * |obj.[[Delete]](index)|.
  *
  * If an error occurs while attempting to delete the element (that is, the call
  * to [[Delete]] threw), return false.
@@ -429,17 +428,17 @@ DeletePropertyOrThrow(JSContext *cx, Han
     }
     return true;
 }
 
 bool
 js::SetLengthProperty(JSContext *cx, HandleObject obj, double length)
 {
     RootedValue v(cx, NumberValue(length));
-    return SetProperty(cx, obj, obj, cx->names().length, &v);
+    return SetProperty(cx, obj, cx->names().length, v);
 }
 
 /*
  * Since SpiderMonkey supports cross-class prototype-based delegation, we have
  * to be careful about the length getter and setter being called on an object
  * not of Array class. For the getter, we search obj's prototype chain for the
  * array that caused this getter to be invoked. In the setter case to overcome
  * the JSPROP_SHARED attribute, we must define a shadowing length property.
@@ -1265,21 +1264,20 @@ InitArrayElements(JSContext *cx, HandleO
     MOZ_ASSERT(start == MAX_ARRAY_INDEX + 1);
     RootedValue value(cx);
     RootedId id(cx);
     RootedValue indexv(cx);
     double index = MAX_ARRAY_INDEX + 1;
     do {
         value = *vector++;
         indexv = DoubleValue(index);
-        if (!ValueToId<CanGC>(cx, indexv, &id) ||
-            !SetProperty(cx, obj, obj, id, &value))
-        {
+        if (!ValueToId<CanGC>(cx, indexv, &id))
             return false;
-        }
+        if (!SetProperty(cx, obj, id, value))
+            return false;
         index += 1;
     } while (vector != end);
 
     return true;
 }
 
 static bool
 array_reverse(JSContext *cx, unsigned argc, Value *vp)
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -224,16 +224,23 @@ ReportError(JSContext *cx, const char *m
         reportp->errorNumber == JSMSG_UNCAUGHT_EXCEPTION)
     {
         reportp->flags |= JSREPORT_EXCEPTION;
     }
 
     if (cx->options().autoJSAPIOwnsErrorReporting() || JS_IsRunning(cx)) {
         if (ErrorToException(cx, message, reportp, callback, userRef))
             return;
+
+        /*
+         * The AutoJSAPI error reporter only allows warnings to be reported so
+         * just ignore this error rather than try to report it.
+         */
+        if (cx->options().autoJSAPIOwnsErrorReporting())
+            return;
     }
 
     /*
      * Call the error reporter only if an exception wasn't raised.
      */
     if (message)
         CallErrorReporter(cx, message, reportp);
 }
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -603,16 +603,22 @@ js::ErrorToException(JSContext *cx, cons
     // Flag the error report passed in to indicate an exception was raised.
     reportp->flags |= JSREPORT_EXCEPTION;
     return true;
 }
 
 static bool
 IsDuckTypedErrorObject(JSContext *cx, HandleObject exnObject, const char **filename_strp)
 {
+    /*
+     * This function is called from ErrorReport::init and so should not generate
+     * any new exceptions.
+     */
+    AutoClearPendingException acpe(cx);
+
     bool found;
     if (!JS_HasProperty(cx, exnObject, js_message_str, &found) || !found)
         return false;
 
     const char *filename_str = *filename_strp;
     if (!JS_HasProperty(cx, exnObject, filename_str, &found) || !found) {
         /* Now try "fileName", in case this quacks like an Error */
         filename_str = js_fileName_str;
--- a/js/src/jsexn.h
+++ b/js/src/jsexn.h
@@ -110,11 +110,25 @@ static inline JSExnType
 ExnTypeFromProtoKey(JSProtoKey key)
 {
     JSExnType type = static_cast<JSExnType>(key - JSProto_Error);
     MOZ_ASSERT(type >= JSEXN_ERR);
     MOZ_ASSERT(type < JSEXN_LIMIT);
     return type;
 }
 
+class AutoClearPendingException
+{
+    JSContext *cx;
+
+  public:
+    explicit AutoClearPendingException(JSContext *cxArg)
+      : cx(cxArg)
+    { }
+
+    ~AutoClearPendingException() {
+        cx->clearPendingException();
+    }
+};
+
 } // namespace js
 
 #endif /* jsexn_h */
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -363,18 +363,18 @@ proxy_DefineProperty(JSContext *cx, JS::
                      JS::Handle<JSPropertyDescriptor> desc,
                      JS::ObjectOpResult &result);
 extern JS_FRIEND_API(bool)
 proxy_HasProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id, bool *foundp);
 extern JS_FRIEND_API(bool)
 proxy_GetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleObject receiver, JS::HandleId id,
                   JS::MutableHandleValue vp);
 extern JS_FRIEND_API(bool)
-proxy_SetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleObject receiver, JS::HandleId id,
-                  JS::MutableHandleValue bp, JS::ObjectOpResult &result);
+proxy_SetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue bp,
+                  JS::HandleValue receiver, JS::ObjectOpResult &result);
 extern JS_FRIEND_API(bool)
 proxy_GetOwnPropertyDescriptor(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
                                JS::MutableHandle<JSPropertyDescriptor> desc);
 extern JS_FRIEND_API(bool)
 proxy_DeleteProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
                      JS::ObjectOpResult &result);
 
 extern JS_FRIEND_API(void)
@@ -2622,17 +2622,17 @@ ForwardToNative(JSContext *cx, JSNative 
  * set() in this way.  It carries out all the steps of BaseProxyHandler::set()
  * except the initial getOwnPropertyDescriptor() call.  The caller must supply
  * that descriptor as the 'ownDesc' parameter.
  *
  * Implemented in proxy/BaseProxyHandler.cpp.
  */
 JS_FRIEND_API(bool)
 SetPropertyIgnoringNamedGetter(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
-                               JS::MutableHandleValue vp, JS::HandleObject receiver,
+                               JS::HandleValue v, JS::HandleValue receiver,
                                JS::Handle<JSPropertyDescriptor> ownDesc,
                                JS::ObjectOpResult &result);
 
 JS_FRIEND_API(void)
 ReportErrorWithId(JSContext *cx, const char *msg, JS::HandleId id);
 
 // This function is for one specific use case, please don't use this for anything else!
 extern JS_FRIEND_API(bool)
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2298,17 +2298,17 @@ struct ArenasToUpdate
     bool done() { return initialized && arena == nullptr; }
     ArenaHeader* next(AutoLockHelperThreadState& lock);
     ArenaHeader *getArenasToUpdate(AutoLockHelperThreadState& lock, unsigned max);
 
   private:
     bool initialized;
     KindsToUpdate kinds;
     Zone *zone;          // Zone to process
-    unsigned kind;       // Current alloc kind to process
+    AllocKind kind;      // Current alloc kind to process
     ArenaHeader *arena;  // Next arena to process
 
     bool shouldProcessKind(AllocKind kind);
 };
 
 bool ArenasToUpdate::shouldProcessKind(AllocKind kind)
 {
     MOZ_ASSERT(kind < AllocKind::LIMIT);
@@ -2344,42 +2344,46 @@ ArenasToUpdate::ArenasToUpdate(Zone *zon
     MOZ_ASSERT(kinds && !(kinds & ~ALL));
 }
 
 ArenaHeader *
 ArenasToUpdate::next(AutoLockHelperThreadState& lock)
 {
     // Find the next arena to update.
     //
-    // Note that this uses a generator-like arrangement. The first time this is
-    // called, |initialized| is false and the for-loops below are entered in the
-    // normal way, returning the first arena found. In subsequent invocations we
-    // jump directly into the body of the for loops just after the previous
-    // return. All state is stored in class members and so preserved between
-    // invocations.
+    // The first time this is called, we initialize the kind and arena and loop
+    // over all alloc kinds, returning the first arena found. In subsequent
+    // invocations we go through the remaining arenas first, then increment the
+    // kind manually before looping over the remaining kinds.
 
     if (initialized) {
         MOZ_ASSERT(arena);
-        MOZ_ASSERT(shouldProcessKind(AllocKind(kind)));
+        MOZ_ASSERT(shouldProcessKind(kind));
         MOZ_ASSERT(zone);
-        goto resumePoint;
-    }
-
-    initialized = true;
-    for (kind = 0; kind < size_t(AllocKind::LIMIT); ++kind) {
-        if (shouldProcessKind(AllocKind(kind))) {
-            for (arena = zone->arenas.getFirstArena(AllocKind(kind));
-                 arena;
-                 arena = arena->next)
-            {
+        arena = arena->next;
+        if (arena)
+            return arena;
+        kind = AllocKind(uint8_t(kind) + 1);
+    } else {
+        initialized = true;
+        arena = nullptr;
+        kind = AllocKind::FIRST;
+    }
+
+    for (auto i : SomeAllocKinds(kind)) {
+        if (shouldProcessKind(i)) {
+            arena = zone->arenas.getFirstArena(i);
+            if (arena) {
+                kind = i;
                 return arena;
-              resumePoint:;
             }
         }
     }
+
+    kind = AllocKind::LIMIT;
     zone = nullptr;
     return nullptr;
 }
 
 ArenaHeader *
 ArenasToUpdate::getArenasToUpdate(AutoLockHelperThreadState& lock, unsigned count)
 {
     if (!zone)
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1570,35 +1570,36 @@ js::CreateThisForFunction(JSContext *cx,
 
         return nobj;
     }
 
     return obj;
 }
 
 /* static */ bool
-JSObject::nonNativeSetProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
-                               HandleId id, MutableHandleValue vp, ObjectOpResult &result)
+JSObject::nonNativeSetProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                               HandleValue receiver, ObjectOpResult &result)
 {
+    RootedValue value(cx, v);
     if (MOZ_UNLIKELY(obj->watched())) {
         WatchpointMap *wpmap = cx->compartment()->watchpointMap;
-        if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp))
+        if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, &value))
             return false;
     }
-    return obj->getOps()->setProperty(cx, obj, receiver, id, vp, result);
+    return obj->getOps()->setProperty(cx, obj, id, value, receiver, result);
 }
 
 /* static */ bool
-JSObject::nonNativeSetElement(JSContext *cx, HandleObject obj, HandleObject receiver,
-                              uint32_t index, MutableHandleValue vp, ObjectOpResult &result)
+JSObject::nonNativeSetElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue v,
+                              HandleValue receiver, ObjectOpResult &result)
 {
     RootedId id(cx);
     if (!IndexToId(cx, index, &id))
         return false;
-    return nonNativeSetProperty(cx, obj, receiver, id, vp, result);
+    return nonNativeSetProperty(cx, obj, id, v, receiver, result);
 }
 
 JS_FRIEND_API(bool)
 JS_CopyPropertyFrom(JSContext *cx, HandleId id, HandleObject target,
                     HandleObject obj, PropertyCopyBehavior copyBehavior)
 {
     // |obj| and |cx| are generally not same-compartment with |target| here.
     assertSameCompartment(cx, obj, id);
@@ -3254,32 +3255,16 @@ js::DefineElement(ExclusiveContext *cx, 
     MOZ_ASSERT(setter != JS_StrictPropertyStub);
 
     RootedId id(cx);
     if (!IndexToId(cx, index, &id))
         return false;
     return DefineProperty(cx, obj, id, value, getter, setter, attrs);
 }
 
-bool
-js::SetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandlePropertyName name,
-                MutableHandleValue vp)
-{
-    RootedId id(cx, NameToId(name));
-    return SetProperty(cx, obj, receiver, id, vp);
-}
-
-bool
-js::PutProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, MutableHandleValue value,
-                bool strict)
-{
-    RootedId id(cx, NameToId(name));
-    return PutProperty(cx, obj, id, value, strict);
-}
-
 
 /*** SpiderMonkey nonstandard internal methods ***************************************************/
 
 bool
 js::SetImmutablePrototype(ExclusiveContext *cx, HandleObject obj, bool *succeeded)
 {
     if (obj->hasLazyPrototype()) {
         if (!cx->shouldBeJSContext())
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -469,22 +469,22 @@ class JSObject : public js::gc::Cell
      * Get the property with the given id, then call it as a function with the
      * given arguments, providing this object as |this|. If the property isn't
      * callable a TypeError will be thrown. On success the value returned by
      * the call is stored in *vp.
      */
     bool callMethod(JSContext *cx, js::HandleId id, unsigned argc, js::Value *argv,
                     js::MutableHandleValue vp);
 
-    static bool nonNativeSetProperty(JSContext *cx, js::HandleObject obj,
-                                     js::HandleObject receiver, js::HandleId id,
-                                     js::MutableHandleValue vp, JS::ObjectOpResult &result);
-    static bool nonNativeSetElement(JSContext *cx, js::HandleObject obj,
-                                    js::HandleObject receiver, uint32_t index,
-                                    js::MutableHandleValue vp, JS::ObjectOpResult &result);
+    static bool nonNativeSetProperty(JSContext *cx, js::HandleObject obj, js::HandleId id,
+                                     js::HandleValue v, js::HandleValue receiver,
+                                     JS::ObjectOpResult &result);
+    static bool nonNativeSetElement(JSContext *cx, js::HandleObject obj, uint32_t index,
+                                    js::HandleValue v, js::HandleValue receiver,
+                                    JS::ObjectOpResult &result);
 
     static bool swap(JSContext *cx, JS::HandleObject a, JS::HandleObject b);
 
   private:
     void fixDictionaryShapeAfterSwap();
 
   public:
     inline void initArrayClass();
@@ -844,77 +844,77 @@ GetPropertyNoGC(JSContext *cx, JSObject 
 {
     return GetPropertyNoGC(cx, obj, receiver, NameToId(name), vp);
 }
 
 inline bool
 GetElementNoGC(JSContext *cx, JSObject *obj, JSObject *receiver, uint32_t index, Value *vp);
 
 /*
- * ES6 [[Set]]. Carry out the assignment `obj[id] = vp`.
+ * ES6 [[Set]]. Carry out the assignment `obj[id] = v`.
  *
  * The `receiver` argument has to do with how [[Set]] interacts with the
  * prototype chain and proxies. It's hard to explain and ES6 doesn't really
- * try. Long story short, if you just want bog-standard assignment, pass the
- * same object as both obj and receiver.
+ * try. Long story short, if you just want bog-standard assignment, pass
+ * `ObjectValue(*obj)` as receiver. Or better, use one of the signatures that
+ * doesn't have a receiver parameter.
  *
- * When obj != receiver, it's a reasonable guess that a proxy is involved, obj
- * is the proxy's target, and the proxy is using SetProperty to finish an
- * assignment that started out as `receiver[id] = vp`, by delegating it to obj.
- *
- * Strict errors: ES6 specifies that this method returns a boolean value
- * indicating whether assignment "succeeded". We currently take a `strict`
- * argument instead, but this has to change. See bug 1113369.
+ * Callers pass obj != receiver e.g. when a proxy is involved, obj is the
+ * proxy's target, and the proxy is using SetProperty to finish an assignment
+ * that started out as `receiver[id] = v`, by delegating it to obj.
  */
 inline bool
-SetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
-            MutableHandleValue vp, ObjectOpResult &result);
+SetProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+            HandleValue receiver, ObjectOpResult &result);
 
 inline bool
-SetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, PropertyName *name,
-            MutableHandleValue vp, ObjectOpResult &result)
+SetProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v)
 {
-    RootedId id(cx, NameToId(name));
-    return SetProperty(cx, obj, receiver, id, vp, result);
+    RootedValue receiver(cx, ObjectValue(*obj));
+    ObjectOpResult result;
+    return SetProperty(cx, obj, id, v, receiver, result) &&
+           result.checkStrict(cx, obj, id);
 }
 
 inline bool
-SetElement(JSContext *cx, HandleObject obj, HandleObject receiver, uint32_t index,
-           MutableHandleValue vp, ObjectOpResult &result);
+SetProperty(JSContext *cx, HandleObject obj, PropertyName *name, HandleValue v,
+            HandleValue receiver, ObjectOpResult &result)
+{
+    RootedId id(cx, NameToId(name));
+    return SetProperty(cx, obj, id, v, receiver, result);
+}
 
 inline bool
-SetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
-            MutableHandleValue vp)
+SetProperty(JSContext *cx, HandleObject obj, PropertyName *name, HandleValue v)
 {
+    RootedId id(cx, NameToId(name));
+    RootedValue receiver(cx, ObjectValue(*obj));
     ObjectOpResult result;
-    return SetProperty(cx, obj, receiver, id, vp, result) &&
-           result.checkStrict(cx, receiver, id);
+    return SetProperty(cx, obj, id, v, receiver, result) &&
+           result.checkStrict(cx, obj, id);
 }
 
-extern bool
-SetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandlePropertyName name,
-            MutableHandleValue vp);
+inline bool
+SetElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue v,
+           HandleValue receiver, ObjectOpResult &result);
 
 /*
  * ES6 draft rev 31 (15 Jan 2015) 7.3.3 Put (O, P, V, Throw), except that on
  * success, the spec says this is supposed to return a boolean value, which we
  * don't bother doing.
  */
 inline bool
-PutProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue value, bool strict)
+PutProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v, bool strict)
 {
+    RootedValue receiver(cx, ObjectValue(*obj));
     ObjectOpResult result;
-    return SetProperty(cx, obj, obj, id, value, result) &&
+    return SetProperty(cx, obj, id, v, receiver, result) &&
            result.checkStrictErrorOrWarning(cx, obj, id, strict);
 }
 
-extern bool
-PutProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, MutableHandleValue value,
-            bool strict);
-
 /*
  * ES6 [[Delete]]. Equivalent to the JS code `delete obj[id]`.
  */
 inline bool
 DeleteProperty(JSContext *cx, HandleObject obj, HandleId id, ObjectOpResult &result);
 
 inline bool
 DeleteElement(JSContext *cx, HandleObject obj, uint32_t index, ObjectOpResult &result);
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2175,17 +2175,17 @@ class MOZ_STACK_CLASS StringRegExpGuard
             if (nobj->lookup(cx, cx->names().lastIndex)->writable()) {
                 nobj->zeroLastIndex();
                 return true;
             }
         }
 
         // Handle everything else generically (including throwing if .lastIndex is non-writable).
         RootedValue zero(cx, Int32Value(0));
-        return SetProperty(cx, obj_, obj_, cx->names().lastIndex, &zero);
+        return SetProperty(cx, obj_, cx->names().lastIndex, zero);
     }
 
     RegExpShared &regExp() { return *re_; }
 
     bool regExpIsObject() { return obj_ != nullptr; }
     HandleObject regExpObject() {
         MOZ_ASSERT(regExpIsObject());
         return obj_;
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -132,18 +132,18 @@ class JS_FRIEND_API(CrossCompartmentWrap
     virtual bool setImmutablePrototype(JSContext *cx, HandleObject proxy,
                                        bool *succeeded) const override;
     virtual bool preventExtensions(JSContext *cx, HandleObject wrapper,
                                    ObjectOpResult &result) const override;
     virtual bool isExtensible(JSContext *cx, HandleObject wrapper, bool *extensible) const override;
     virtual bool has(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) const override;
     virtual bool get(JSContext *cx, HandleObject wrapper, HandleObject receiver,
                      HandleId id, MutableHandleValue vp) const override;
-    virtual bool set(JSContext *cx, HandleObject wrapper, HandleObject receiver, HandleId id,
-                     MutableHandleValue vp, ObjectOpResult &result) const override;
+    virtual bool set(JSContext *cx, HandleObject wrapper, HandleId id, HandleValue v,
+                     HandleValue receiver, ObjectOpResult &result) const override;
     virtual bool call(JSContext *cx, HandleObject wrapper, const CallArgs &args) const override;
     virtual bool construct(JSContext *cx, HandleObject wrapper, const CallArgs &args) const override;
 
     /* SpiderMonkey extensions. */
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc) const override;
     virtual bool hasOwn(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) const override;
     virtual bool getOwnEnumerablePropertyKeys(JSContext *cx, HandleObject wrapper,
--- a/js/src/proxy/BaseProxyHandler.cpp
+++ b/js/src/proxy/BaseProxyHandler.cpp
@@ -56,110 +56,113 @@ BaseProxyHandler::get(JSContext *cx, Han
         return true;
     }
     MOZ_ASSERT(desc.getter() != JS_PropertyStub);
     if (!desc.getter()) {
         vp.set(desc.value());
         return true;
     }
     if (desc.hasGetterObject())
-        return InvokeGetterOrSetter(cx, receiver, ObjectValue(*desc.getterObject()),
-                                    0, nullptr, vp);
+        return InvokeGetter(cx, receiver, ObjectValue(*desc.getterObject()), vp);
     if (!desc.isShared())
         vp.set(desc.value());
     else
         vp.setUndefined();
 
     return CallJSGetterOp(cx, desc.getter(), receiver, id, vp);
 }
 
 bool
-BaseProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver,
-                      HandleId id, MutableHandleValue vp, ObjectOpResult &result) const
+BaseProxyHandler::set(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v,
+                      HandleValue receiver, ObjectOpResult &result) const
 {
     assertEnteredPolicy(cx, proxy, id, SET);
 
     // This method is not covered by any spec, but we follow ES6 draft rev 28
     // (2014 Oct 14) 9.1.9 fairly closely, adapting it slightly for
     // SpiderMonkey's particular foibles.
 
     // Steps 2-3.  (Step 1 is a superfluous assertion.)
     Rooted<PropertyDescriptor> ownDesc(cx);
     if (!getOwnPropertyDescriptor(cx, proxy, id, &ownDesc))
         return false;
 
     // The rest is factored out into a separate function with a weird name.
     // This algorithm continues just below.
-    return SetPropertyIgnoringNamedGetter(cx, proxy, id, vp, receiver, ownDesc, result);
+    return SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc, result);
 }
 
 bool
-js::SetPropertyIgnoringNamedGetter(JSContext *cx, HandleObject obj, HandleId id,
-                                   MutableHandleValue vp, HandleObject receiver,
-                                   Handle<PropertyDescriptor> ownDesc_,
+js::SetPropertyIgnoringNamedGetter(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                                   HandleValue receiver, Handle<PropertyDescriptor> ownDesc_,
                                    ObjectOpResult &result)
 {
     Rooted<PropertyDescriptor> ownDesc(cx, ownDesc_);
 
     // Step 4.
     if (!ownDesc.object()) {
         // The spec calls this variable "parent", but that word has weird
         // connotations in SpiderMonkey, so let's go with "proto".
         RootedObject proto(cx);
         if (!GetPrototype(cx, obj, &proto))
             return false;
         if (proto)
-            return SetProperty(cx, proto, receiver, id, vp, result);
+            return SetProperty(cx, proto, id, v, receiver, result);
 
         // Step 4.d.
         ownDesc.setDataDescriptor(UndefinedHandleValue, JSPROP_ENUMERATE);
     }
 
     // Step 5.
     if (ownDesc.isDataDescriptor()) {
         // Steps 5.a-b.
         if (!ownDesc.writable())
             return result.fail(JSMSG_READ_ONLY);
+        if (!receiver.isObject())
+            return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
+        RootedObject receiverObj(cx, &receiver.toObject());
 
         // Nonstandard SpiderMonkey special case: setter ops.
         SetterOp setter = ownDesc.setter();
         MOZ_ASSERT(setter != JS_StrictPropertyStub);
-        if (setter && setter != JS_StrictPropertyStub)
-            return CallJSSetterOp(cx, setter, receiver, id, vp, result);
+        if (setter && setter != JS_StrictPropertyStub) {
+            RootedValue valCopy(cx, v);
+            return CallJSSetterOp(cx, setter, receiverObj, id, &valCopy, result);
+        }
 
         // Steps 5.c-d. Adapt for SpiderMonkey by using HasOwnProperty instead
         // of the standard [[GetOwnProperty]].
         bool existingDescriptor;
-        if (!HasOwnProperty(cx, receiver, id, &existingDescriptor))
+        if (!HasOwnProperty(cx, receiverObj, id, &existingDescriptor))
             return false;
 
         // Steps 5.e-f.
         unsigned attrs =
             existingDescriptor
             ? JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_PERMANENT
             : JSPROP_ENUMERATE;
 
         // A very old nonstandard SpiderMonkey extension: default to the Class
         // getter and setter ops.
-        const Class *clasp = receiver->getClass();
+        const Class *clasp = receiverObj->getClass();
         MOZ_ASSERT(clasp->getProperty != JS_PropertyStub);
         MOZ_ASSERT(clasp->setProperty != JS_StrictPropertyStub);
-        return DefineProperty(cx, receiver, id, vp, clasp->getProperty, clasp->setProperty,
+        return DefineProperty(cx, receiverObj, id, v, clasp->getProperty, clasp->setProperty,
                               attrs, result);
     }
 
     // Step 6.
     MOZ_ASSERT(ownDesc.isAccessorDescriptor());
     RootedObject setter(cx);
     if (ownDesc.hasSetterObject())
         setter = ownDesc.setterObject();
     if (!setter)
         return result.fail(JSMSG_GETTER_ONLY);
     RootedValue setterValue(cx, ObjectValue(*setter));
-    if (!InvokeGetterOrSetter(cx, receiver, setterValue, 1, vp.address(), vp))
+    if (!InvokeSetter(cx, receiver, setterValue, v))
         return false;
     return result.succeed();
 }
 
 bool
 BaseProxyHandler::getOwnEnumerablePropertyKeys(JSContext *cx, HandleObject proxy,
                                                AutoIdVector &props) const
 {
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -164,24 +164,25 @@ CrossCompartmentWrapper::get(JSContext *
 
         if (!Wrapper::get(cx, wrapper, receiverCopy, id, vp))
             return false;
     }
     return cx->compartment()->wrap(cx, vp);
 }
 
 bool
-CrossCompartmentWrapper::set(JSContext *cx, HandleObject wrapper, HandleObject receiver,
-                             HandleId id, MutableHandleValue vp, ObjectOpResult &result) const
+CrossCompartmentWrapper::set(JSContext *cx, HandleObject wrapper, HandleId id, HandleValue v,
+                             HandleValue receiver, ObjectOpResult &result) const
 {
-    RootedObject receiverCopy(cx, receiver);
+    RootedValue valCopy(cx, v);
+    RootedValue receiverCopy(cx, receiver);
     PIERCE(cx, wrapper,
-           cx->compartment()->wrap(cx, &receiverCopy) &&
-           cx->compartment()->wrap(cx, vp),
-           Wrapper::set(cx, wrapper, receiverCopy, id, vp, result),
+           cx->compartment()->wrap(cx, &valCopy) &&
+           cx->compartment()->wrap(cx, &receiverCopy),
+           Wrapper::set(cx, wrapper, id, valCopy, receiverCopy, result),
            NOTHING);
 }
 
 bool
 CrossCompartmentWrapper::getOwnEnumerablePropertyKeys(JSContext *cx, HandleObject wrapper,
                                                       AutoIdVector &props) const
 {
     PIERCE(cx, wrapper,
--- a/js/src/proxy/DirectProxyHandler.cpp
+++ b/js/src/proxy/DirectProxyHandler.cpp
@@ -211,22 +211,22 @@ DirectProxyHandler::get(JSContext *cx, H
                         HandleId id, MutableHandleValue vp) const
 {
     assertEnteredPolicy(cx, proxy, id, GET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetProperty(cx, target, receiver, id, vp);
 }
 
 bool
-DirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver,
-                        HandleId id, MutableHandleValue vp, ObjectOpResult &result) const
+DirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v,
+                        HandleValue receiver, ObjectOpResult &result) const
 {
     assertEnteredPolicy(cx, proxy, id, SET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
-    return SetProperty(cx, target, receiver, id, vp, result);
+    return SetProperty(cx, target, id, v, receiver, result);
 }
 
 bool
 DirectProxyHandler::getOwnEnumerablePropertyKeys(JSContext *cx, HandleObject proxy,
                                                  AutoIdVector &props) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -302,33 +302,33 @@ Proxy::callProp(JSContext *cx, HandleObj
             return false;
     }
 #endif
 
     return true;
 }
 
 bool
-Proxy::set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
-           MutableHandleValue vp, ObjectOpResult &result)
+Proxy::set(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver,
+           ObjectOpResult &result)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
     if (!policy.allowed()) {
         if (!policy.returnValue())
             return false;
         return result.succeed();
     }
 
     // Special case. See the comment on BaseProxyHandler::mHasPrototype.
     if (handler->hasPrototype())
-        return handler->BaseProxyHandler::set(cx, proxy, receiver, id, vp, result);
+        return handler->BaseProxyHandler::set(cx, proxy, id, v, receiver, result);
 
-    return handler->set(cx, proxy, receiver, id, vp, result);
+    return handler->set(cx, proxy, id, v, receiver, result);
 }
 
 bool
 Proxy::getOwnEnumerablePropertyKeys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
 {
     JS_CHECK_RECURSION(cx, return false);
     const BaseProxyHandler *handler = proxy->as<ProxyObject>().handler();
     AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true);
@@ -575,20 +575,20 @@ js::proxy_HasProperty(JSContext *cx, JS:
 bool
 js::proxy_GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
                       MutableHandleValue vp)
 {
     return Proxy::get(cx, obj, receiver, id, vp);
 }
 
 bool
-js::proxy_SetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
-                      MutableHandleValue vp, ObjectOpResult &result)
+js::proxy_SetProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                      HandleValue receiver, ObjectOpResult &result)
 {
-    return Proxy::set(cx, obj, receiver, id, vp, result);
+    return Proxy::set(cx, obj, id, v, receiver, result);
 }
 
 bool
 js::proxy_GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id,
                                    MutableHandle<JSPropertyDescriptor> desc)
 {
     return Proxy::getOwnPropertyDescriptor(cx, obj, id, desc);
 }
--- a/js/src/proxy/Proxy.h
+++ b/js/src/proxy/Proxy.h
@@ -37,18 +37,18 @@ class Proxy
     static bool preventExtensions(JSContext *cx, HandleObject proxy, ObjectOpResult &result);
     static bool getPrototype(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
     static bool setPrototype(JSContext *cx, HandleObject proxy, HandleObject proto,
                              ObjectOpResult &result);
     static bool setImmutablePrototype(JSContext *cx, HandleObject proxy, bool *succeeded);
     static bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp);
     static bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
                     MutableHandleValue vp);
-    static bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
-                    MutableHandleValue vp, ObjectOpResult &result);
+    static bool set(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v,
+                    HandleValue receiver, ObjectOpResult &result);
     static bool call(JSContext *cx, HandleObject proxy, const CallArgs &args);
     static bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args);
 
     /* SpiderMonkey extensions. */
     static bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                       MutableHandle<JSPropertyDescriptor> desc);
     static bool hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp);
     static bool getOwnEnumerablePropertyKeys(JSContext *cx, HandleObject proxy,
--- a/js/src/proxy/ScriptedDirectProxyHandler.cpp
+++ b/js/src/proxy/ScriptedDirectProxyHandler.cpp
@@ -917,45 +917,45 @@ ScriptedDirectProxyHandler::get(JSContex
 
     // step 13
     vp.set(trapResult);
     return true;
 }
 
 // ES6 draft rev 32 (2015 Feb 2) 9.5.9 Proxy.[[Set]](P, V, Receiver)
 bool
-ScriptedDirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver,
-                                HandleId id, MutableHandleValue vp, ObjectOpResult &result) const
+ScriptedDirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v,
+                                HandleValue receiver, ObjectOpResult &result) const
 {
     // step 2-3 (Steps 1 and 4 are irrelevant assertions.)
     RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
     if (!handler) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
         return false;
     }
 
     // step 5-7
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     RootedValue trap(cx);
     if (!GetProperty(cx, handler, handler, cx->names().set, &trap))
         return false;
 
     // step 8
     if (trap.isUndefined())
-        return SetProperty(cx, target, receiver, id, vp, result);
+        return SetProperty(cx, target, id, v, receiver, result);
 
     // step 9-10
     RootedValue value(cx);
     if (!IdToStringOrSymbol(cx, id, &value))
         return false;
     Value argv[] = {
         ObjectOrNullValue(target),
         value,
-        vp.get(),
-        ObjectValue(*receiver)
+        v.get(),
+        receiver.get()
     };
     RootedValue trapResult(cx);
     if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
         return false;
 
     // step 11
     if (!ToBoolean(trapResult))
         return result.fail(JSMSG_PROXY_SET_RETURNED_FALSE);
@@ -964,17 +964,17 @@ ScriptedDirectProxyHandler::set(JSContex
     Rooted<PropertyDescriptor> desc(cx);
     if (!GetOwnPropertyDescriptor(cx, target, id, &desc))
         return false;
 
     // step 14
     if (desc.object()) {
         if (desc.isDataDescriptor() && !desc.configurable() && !desc.writable()) {
             bool same;
-            if (!SameValue(cx, vp, desc.value(), &same))
+            if (!SameValue(cx, v, desc.value(), &same))
                 return false;
             if (!same) {
                 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_SET_NW_NC);
                 return false;
             }
         }
 
         if (desc.isAccessorDescriptor() && !desc.configurable() && desc.setterObject() == nullptr) {
--- a/js/src/proxy/ScriptedDirectProxyHandler.h
+++ b/js/src/proxy/ScriptedDirectProxyHandler.h
@@ -41,18 +41,18 @@ class ScriptedDirectProxyHandler : publi
 
     virtual bool preventExtensions(JSContext *cx, HandleObject proxy,
                                    ObjectOpResult &result) const override;
     virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) const override;
 
     virtual bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const override;
     virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
                      MutableHandleValue vp) const override;
-    virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
-                     MutableHandleValue vp, ObjectOpResult &result) const override;
+    virtual bool set(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v,
+                     HandleValue receiver, ObjectOpResult &result) const override;
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) const override;
     virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const override;
 
     /* SpiderMonkey extensions. */
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc) const override;
     virtual bool hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const override {
         return BaseProxyHandler::hasOwn(cx, proxy, id, bp);
--- a/js/src/proxy/ScriptedIndirectProxyHandler.cpp
+++ b/js/src/proxy/ScriptedIndirectProxyHandler.cpp
@@ -299,60 +299,65 @@ ScriptedIndirectProxyHandler::get(JSCont
     if (!GetDerivedTrap(cx, handler, cx->names().get, &fval))
         return false;
     if (!IsCallable(fval))
         return BaseProxyHandler::get(cx, proxy, receiver, id, vp);
     return Trap(cx, handler, fval, 2, argv.begin(), vp);
 }
 
 bool
-ScriptedIndirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver,
-                                  HandleId id, MutableHandleValue vp, ObjectOpResult &result) const
+ScriptedIndirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v,
+                                  HandleValue receiver, ObjectOpResult &result) const
 {
     RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
     RootedValue idv(cx);
     if (!IdToStringOrSymbol(cx, id, &idv))
         return false;
     JS::AutoValueArray<3> argv(cx);
-    argv[0].setObjectOrNull(receiver);
+    argv[0].set(receiver);
     argv[1].set(idv);
-    argv[2].set(vp);
+    argv[2].set(v);
     RootedValue fval(cx);
     if (!GetDerivedTrap(cx, handler, cx->names().set, &fval))
         return false;
     if (!IsCallable(fval))
-        return derivedSet(cx, proxy, receiver, id, vp, result);
+        return derivedSet(cx, proxy, id, v, receiver, result);
     if (!Trap(cx, handler, fval, 3, argv.begin(), &idv))
         return false;
     return result.succeed();
 }
 
 static bool
-CallSetter(JSContext *cx, HandleObject obj, HandleId id, SetterOp op, unsigned attrs,
-           MutableHandleValue vp, ObjectOpResult &result)
+CallSetter(JSContext *cx, HandleValue receiver, HandleId id, SetterOp op, unsigned attrs,
+           HandleValue v, ObjectOpResult &result)
 {
     if (attrs & JSPROP_SETTER) {
-        RootedValue opv(cx, CastAsObjectJsval(op));
-        if (!InvokeGetterOrSetter(cx, obj, opv, 1, vp.address(), vp))
+        RootedValue fval(cx, CastAsObjectJsval(op));
+        if (!InvokeSetter(cx, receiver, fval, v))
             return false;
         return result.succeed();
     }
 
     if (attrs & JSPROP_GETTER)
         return result.fail(JSMSG_GETTER_ONLY);
 
+    if (!receiver.isObject())
+        return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
+    RootedObject receiverObj(cx, &receiver.toObject());
+
     if (!op)
         return result.succeed();
 
-    return CallJSSetterOp(cx, op, obj, id, vp, result);
+    RootedValue valCopy(cx, v);
+    return CallJSSetterOp(cx, op, receiverObj, id, &valCopy, result);
 }
 
 bool
-ScriptedIndirectProxyHandler::derivedSet(JSContext *cx, HandleObject proxy, HandleObject receiver,
-                                         HandleId id, MutableHandleValue vp,
+ScriptedIndirectProxyHandler::derivedSet(JSContext *cx, HandleObject proxy, HandleId id,
+                                         HandleValue v, HandleValue receiver,
                                          ObjectOpResult &result) const
 {
     // Find an own or inherited property. The code here is strange for maximum
     // backward compatibility with earlier code written before ES6 and before
     // SetPropertyIgnoringNamedGetter.
     //
     // As of March 2015, testing/specialpowers/content/specialpowersAPI.js
     // depends on the call to getPropertyDescriptor below, because it does
@@ -373,43 +378,42 @@ ScriptedIndirectProxyHandler::derivedSet
         MOZ_ASSERT(desc.getter() != JS_PropertyStub);
         MOZ_ASSERT(desc.setter() != JS_StrictPropertyStub);
 
         // Check for read-only properties.
         if (desc.isDataDescriptor() && !desc.writable())
             return result.fail(descIsOwn ? JSMSG_READ_ONLY : JSMSG_CANT_REDEFINE_PROP);
 
         if (desc.hasSetterObject() || desc.setter()) {
-            if (!CallSetter(cx, receiver, id, desc.setter(), desc.attributes(), vp, result))
+            if (!CallSetter(cx, receiver, id, desc.setter(), desc.attributes(), v, result))
                 return false;
             if (!result)
                 return true;
             if (!proxy->is<ProxyObject>() ||
                 proxy->as<ProxyObject>().handler() != this ||
                 desc.isShared())
             {
                 return result.succeed();
             }
         }
-        desc.value().set(vp.get());
+        desc.value().set(v);
 
         if (descIsOwn) {
             MOZ_ASSERT(desc.object() == proxy);
             return this->defineProperty(cx, proxy, id, desc, result);
         }
-        return DefineProperty(cx, receiver, id, desc.value(), desc.getter(), desc.setter(),
-                              desc.attributes(), result);
+    } else {
+        desc.setDataDescriptor(v, JSPROP_ENUMERATE);
     }
-    desc.object().set(receiver);
-    desc.value().set(vp.get());
-    desc.setAttributes(JSPROP_ENUMERATE);
-    desc.setGetter(nullptr);
-    desc.setSetter(nullptr); // Pick up the class getter/setter.
-    return DefineProperty(cx, receiver, id, desc.value(), nullptr, nullptr, JSPROP_ENUMERATE,
-                          result);
+
+    if (!receiver.isObject())
+        return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
+    RootedObject receiverObj(cx, &receiver.toObject());
+    desc.object().set(receiverObj);
+    return DefineProperty(cx, receiverObj, id, desc, result);
 }
 
 bool
 ScriptedIndirectProxyHandler::getOwnEnumerablePropertyKeys(JSContext *cx, HandleObject proxy,
                                                            AutoIdVector &props) const
 {
     RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
     RootedValue value(cx);
--- a/js/src/proxy/ScriptedIndirectProxyHandler.h
+++ b/js/src/proxy/ScriptedIndirectProxyHandler.h
@@ -32,36 +32,36 @@ class ScriptedIndirectProxyHandler : pub
     virtual bool enumerate(JSContext *cx, HandleObject proxy,
                            MutableHandleObject objp) const override;
     virtual bool preventExtensions(JSContext *cx, HandleObject proxy,
                                    ObjectOpResult &result) const override;
     virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) const override;
     virtual bool has(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const override;
     virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
                      MutableHandleValue vp) const override;
-    virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
-                     MutableHandleValue vp, ObjectOpResult &result) const override;
+    virtual bool set(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v,
+                     HandleValue receiver, ObjectOpResult &result) const override;
 
     /* SpiderMonkey extensions. */
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc) const override;
     virtual bool hasOwn(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const override;
     virtual bool getOwnEnumerablePropertyKeys(JSContext *cx, HandleObject proxy,
                                               AutoIdVector &props) const override;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) const override;
     virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) const override;
     virtual bool isScripted() const override { return true; }
 
     static const char family;
     static const ScriptedIndirectProxyHandler singleton;
 
 private:
-    bool derivedSet(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
-                    MutableHandleValue vp, ObjectOpResult &result) const;
+    bool derivedSet(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v,
+                    HandleValue receiver, ObjectOpResult &result) const;
 };
 
 /* Derived class to handle Proxy.createFunction() */
 class CallableScriptedIndirectProxyHandler : public ScriptedIndirectProxyHandler
 {
   public:
     CallableScriptedIndirectProxyHandler() : ScriptedIndirectProxyHandler() { }
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) const override;
--- a/js/src/tests/ecma_6/TypedArray/slice.js
+++ b/js/src/tests/ecma_6/TypedArray/slice.js
@@ -56,19 +56,22 @@ for (var constructor of constructors) {
     undefConstructor.constructor = undefined;
     assertDeepEq(undefConstructor.slice(1), new constructor(1));
 
     assertThrowsInstanceOf(() => {
         var strConstructor = new constructor;
         strConstructor.constructor = "not a constructor";
         strConstructor.slice(123);
     }, TypeError, "Assert that we have an invalid constructor");
-    assertThrowsInstanceOf(() => {
-        var mathConstructor = new constructor;
-        mathConstructor.constructor = Math.sin;
-        mathConstructor.slice(123);
-    }, TypeError, "Assert that we have an invalid constructor");
 
+    // If obj.constructor[@@species] is undefined or null -- which it has to be
+    // if we don't implement @@species -- then the default constructor is used.
+    var mathConstructor = new constructor(8);
+    mathConstructor.constructor = Math.sin;
+    assertDeepEq(mathConstructor.slice(4), new constructor(4));
+
+    assertEq("species" in Symbol, false,
+             "you've implemented @@species -- add real tests here!");
 }
 
 if (typeof reportCompare === "function")
     reportCompare(true, true);
 
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -7632,17 +7632,17 @@ DebuggerEnv_setVariable(JSContext *cx, u
         if (!HasProperty(cx, env, id, &has))
             return false;
         if (!has) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_VARIABLE_NOT_FOUND);
             return false;
         }
 
         /* Just set the property. */
-        if (!SetProperty(cx, env, env, id, &v))
+        if (!SetProperty(cx, env, id, v))
             return false;
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 static const JSPropertySpec DebuggerEnv_properties[] = {
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -607,21 +607,17 @@ class GlobalObject : public NativeObject
     bool addIntrinsicValue(JSContext *cx, HandleId id, HandleValue value);
 
     bool setIntrinsicValue(JSContext *cx, PropertyName *name, HandleValue value) {
 #ifdef DEBUG
         RootedObject self(cx, this);
         MOZ_ASSERT(cx->runtime()->isSelfHostingGlobal(self));
 #endif
         RootedObject holder(cx, intrinsicsHolder());
-        RootedValue valCopy(cx, value);
-        ObjectOpResult result;
-        bool ok = SetProperty(cx, holder, holder, name, &valCopy, result);
-        MOZ_ASSERT_IF(ok, result);
-        return ok;
+        return SetProperty(cx, holder, name, value);
     }
 
     bool getSelfHostedFunction(JSContext *cx, HandleAtom selfHostedName, HandleAtom name,
                                unsigned nargs, MutableHandleValue funVal);
 
     bool hasRegExpStatics() const;
     RegExpStatics *getRegExpStatics(ExclusiveContext *cx) const;
     RegExpStatics *getAlreadyCreatedRegExpStatics() const;
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -305,46 +305,45 @@ SetNameOperation(JSContext *cx, JSScript
                *pc == JSOP_STRICTSETGNAME);
     MOZ_ASSERT_IF(*pc == JSOP_SETGNAME && !script->hasPollutedGlobalScope(),
                   scope == cx->global());
     MOZ_ASSERT_IF(*pc == JSOP_STRICTSETGNAME && !script->hasPollutedGlobalScope(),
                   scope == cx->global());
 
     bool strict = *pc == JSOP_STRICTSETNAME || *pc == JSOP_STRICTSETGNAME;
     RootedPropertyName name(cx, script->getName(pc));
-    RootedValue valCopy(cx, val);
 
     // In strict mode, assigning to an undeclared global variable is an
     // error. To detect this, we call NativeSetProperty directly and pass
     // Unqualified. It stores the error, if any, in |result|.
     bool ok;
     ObjectOpResult result;
     RootedId id(cx, NameToId(name));
+    RootedValue receiver(cx, ObjectValue(*scope));
     if (scope->isUnqualifiedVarObj()) {
         MOZ_ASSERT(!scope->getOps()->setProperty);
-        ok = NativeSetProperty(cx, scope.as<NativeObject>(), scope.as<NativeObject>(), id,
-                               Unqualified, &valCopy, result);
+        ok = NativeSetProperty(cx, scope.as<NativeObject>(), id, val, receiver, Unqualified,
+                               result);
     } else {
-        ok = SetProperty(cx, scope, scope, id, &valCopy, result);
+        ok = SetProperty(cx, scope, id, val, receiver, result);
     }
     return ok && result.checkStrictErrorOrWarning(cx, scope, id, strict);
 }
 
 inline bool
 InitPropertyOperation(JSContext *cx, JSOp op, HandleObject obj, HandleId id, HandleValue rhs)
 {
     if (obj->is<PlainObject>() || obj->is<JSFunction>()) {
         unsigned propAttrs = GetInitDataPropAttrs(op);
         return NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs, nullptr, nullptr,
                                     propAttrs);
     }
 
     MOZ_ASSERT(obj->as<UnboxedPlainObject>().layout().lookup(id));
-    RootedValue v(cx, rhs);
-    return PutProperty(cx, obj, id, &v, false);
+    return PutProperty(cx, obj, id, rhs, false);
 }
 
 inline bool
 DefVarOrConstOperation(JSContext *cx, HandleObject varobj, HandlePropertyName dn, unsigned attrs)
 {
     MOZ_ASSERT(varobj->isQualifiedVarObj());
 
     RootedShape prop(cx);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -308,62 +308,30 @@ GetNameOperation(JSContext *cx, Interpre
     /* Kludge to allow (typeof foo == "undefined") tests. */
     JSOp op2 = JSOp(pc[JSOP_GETNAME_LENGTH]);
     if (op2 == JSOP_TYPEOF)
         return FetchName<true>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp);
     return FetchName<false>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp);
 }
 
 static bool
-SetObjectProperty(JSContext *cx, JSOp op, HandleValue lval, HandleId id, MutableHandleValue rref)
+SetPropertyOperation(JSContext *cx, JSOp op, HandleValue lval, HandleId id, HandleValue rval)
 {
-    MOZ_ASSERT(lval.isObject());
-
-    RootedObject obj(cx, &lval.toObject());
-
-    ObjectOpResult result;
-    if (MOZ_LIKELY(!obj->getOps()->setProperty)) {
-        if (!NativeSetProperty(cx, obj.as<NativeObject>(), obj.as<NativeObject>(), id,
-                               Qualified, rref, result))
-        {
-            return false;
-        }
-    } else {
-        if (!SetProperty(cx, obj, obj, id, rref, result))
-            return false;
-    }
-
-    return result.checkStrictErrorOrWarning(cx, obj, id, op == JSOP_STRICTSETPROP);
-}
-
-static bool
-SetPrimitiveProperty(JSContext *cx, JSOp op, HandleValue lval, HandleId id,
-                     MutableHandleValue rref)
-{
-    MOZ_ASSERT(lval.isPrimitive());
+    MOZ_ASSERT(op == JSOP_SETPROP || op == JSOP_STRICTSETPROP);
 
     RootedObject obj(cx, ToObjectFromStack(cx, lval));
     if (!obj)
         return false;
 
+    // Note: ES6 specifies that the value lval, not obj, is passed as receiver
+    // to obj's [[Set]] internal method. See bug 603201.
     RootedValue receiver(cx, ObjectValue(*obj));
-    return SetObjectProperty(cx, op, receiver, id, rref);
-}
-
-static bool
-SetPropertyOperation(JSContext *cx, JSOp op, HandleValue lval, HandleId id, HandleValue rval)
-{
-    MOZ_ASSERT(op == JSOP_SETPROP || op == JSOP_STRICTSETPROP);
-
-    RootedValue rref(cx, rval);
-
-    if (lval.isPrimitive())
-        return SetPrimitiveProperty(cx, op, lval, id, &rref);
-
-    return SetObjectProperty(cx, op, lval, id, &rref);
+    ObjectOpResult result;
+    return SetProperty(cx, obj, id, rval, receiver, result) &&
+           result.checkStrictErrorOrWarning(cx, obj, id, op == JSOP_STRICTSETPROP);
 }
 
 bool
 js::ReportIsNotFunction(JSContext *cx, HandleValue v, int numToSkip, MaybeConstruct construct)
 {
     unsigned error = construct ? JSMSG_NOT_CONSTRUCTOR : JSMSG_NOT_FUNCTION;
     int spIndex = numToSkip >= 0 ? -(numToSkip + 1) : JSDVG_SEARCH_STACK;
 
@@ -611,26 +579,34 @@ js::InvokeConstructor(JSContext *cx, Val
     if (!InvokeConstructor(cx, args))
         return false;
 
     rval.set(args.rval());
     return true;
 }
 
 bool
-js::InvokeGetterOrSetter(JSContext *cx, JSObject *obj, Value fval, unsigned argc,
-                         Value *argv, MutableHandleValue rval)
+js::InvokeGetter(JSContext *cx, JSObject *obj, Value fval, MutableHandleValue rval)
 {
     /*
      * Invoke could result in another try to get or set the same id again, see
      * bug 355497.
      */
     JS_CHECK_RECURSION(cx, return false);
 
-    return Invoke(cx, ObjectValue(*obj), fval, argc, argv, rval);
+    return Invoke(cx, ObjectValue(*obj), fval, 0, nullptr, rval);
+}
+
+bool
+js::InvokeSetter(JSContext *cx, const Value &thisv, Value fval, HandleValue v)
+{
+    JS_CHECK_RECURSION(cx, return false);
+
+    RootedValue ignored(cx);
+    return Invoke(cx, thisv, fval, 1, v.address(), &ignored);
 }
 
 bool
 js::ExecuteKernel(JSContext *cx, HandleScript script, JSObject &scopeChainArg, const Value &thisv,
                   ExecuteType type, AbstractFramePtr evalInFrame, Value *result)
 {
     MOZ_ASSERT_IF(evalInFrame, type == EXECUTE_DEBUG);
     MOZ_ASSERT_IF(type == EXECUTE_GLOBAL, !IsSyntacticScope(&scopeChainArg));
@@ -1398,17 +1374,17 @@ SetObjectElementOperation(JSContext *cx,
                 script->baselineScript()->noteArrayWriteHole(script->pcToOffset(pc));
         }
     }
 
     if (obj->isNative() && !JSID_IS_INT(id) && !obj->setHadElementsAccess(cx))
         return false;
 
     RootedValue tmp(cx, value);
-    return PutProperty(cx, obj, id, &tmp, strict);
+    return PutProperty(cx, obj, id, tmp, strict);
 }
 
 static MOZ_NEVER_INLINE bool
 Interpret(JSContext *cx, RunState &state)
 {
 /*
  * Define macros for an interpreter loop. Opcode dispatch may be either by a
  * switch statement or by indirect goto (aka a threaded interpreter), depending
@@ -3892,17 +3868,18 @@ js::DefFunOperation(JSContext *cx, Handl
     /*
      * Non-global properties, and global properties which we aren't simply
      * redefining, must be set.  First, this preserves their attributes.
      * Second, this will produce warnings and/or errors as necessary if the
      * specified Call object property is not writable (const).
      */
 
     /* Step 5f. */
-    return PutProperty(cx, parent, name, &rval, script->strict());
+    RootedId id(cx, NameToId(name));
+    return PutProperty(cx, parent, id, rval, script->strict());
 }
 
 bool
 js::SetCallOperation(JSContext *cx)
 {
     JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_LEFTSIDE_OF_ASS);
     return false;
 }
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -71,22 +71,24 @@ Invoke(JSContext *cx, CallArgs args, May
  * called at any time and it takes care of copying the given callee, this, and
  * arguments onto the stack.
  */
 extern bool
 Invoke(JSContext *cx, const Value &thisv, const Value &fval, unsigned argc, const Value *argv,
        MutableHandleValue rval);
 
 /*
- * This helper takes care of the infinite-recursion check necessary for
+ * These helpers take care of the infinite-recursion check necessary for
  * getter/setter calls.
  */
 extern bool
-InvokeGetterOrSetter(JSContext *cx, JSObject *obj, Value fval, unsigned argc, Value *argv,
-                     MutableHandleValue rval);
+InvokeGetter(JSContext *cx, JSObject *obj, Value fval, MutableHandleValue rval);
+
+extern bool
+InvokeSetter(JSContext *cx, const Value &thisv, Value fval, HandleValue v);
 
 /*
  * InvokeConstructor implement a function call from a constructor context
  * (e.g. 'new') handling the the creation of the new 'this' object.
  */
 extern bool
 InvokeConstructor(JSContext *cx, CallArgs args);
 
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -67,30 +67,28 @@ NativeObject::setShouldConvertDoubleElem
 inline void
 NativeObject::clearShouldConvertDoubleElements()
 {
     MOZ_ASSERT(is<ArrayObject>() && !hasEmptyElements());
     getElementsHeader()->clearShouldConvertDoubleElements();
 }
 
 inline void
-NativeObject::setDenseElementWithType(ExclusiveContext *cx, uint32_t index,
-                                      const Value &val)
+NativeObject::setDenseElementWithType(ExclusiveContext *cx, uint32_t index, const Value &val)
 {
     // Avoid a slow AddTypePropertyId call if the type is the same as the type
     // of the previous element.
     TypeSet::Type thisType = TypeSet::GetValueType(val);
     if (index == 0 || TypeSet::GetValueType(elements_[index - 1]) != thisType)
         AddTypePropertyId(cx, this, JSID_VOID, thisType);
     setDenseElementMaybeConvertDouble(index, val);
 }
 
 inline void
-NativeObject::initDenseElementWithType(ExclusiveContext *cx, uint32_t index,
-                                       const Value &val)
+NativeObject::initDenseElementWithType(ExclusiveContext *cx, uint32_t index, const Value &val)
 {
     MOZ_ASSERT(!shouldConvertDoubleElements());
     AddTypePropertyId(cx, this, JSID_VOID, val);
     initDenseElement(index, val);
 }
 
 inline void
 NativeObject::setDenseElementHole(ExclusiveContext *cx, uint32_t index)
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -1125,18 +1125,18 @@ UpdateShapeTypeAndValue(ExclusiveContext
     if (!shape->hasSlot() || !shape->hasDefaultGetter() || !shape->hasDefaultSetter())
         MarkTypePropertyNonData(cx, obj, id);
     if (!shape->writable())
         MarkTypePropertyNonWritable(cx, obj, id);
     return true;
 }
 
 static bool
-NativeSetExistingDataProperty(JSContext *cx, HandleNativeObject obj, HandleObject receiver,
-                              HandleShape shape, MutableHandleValue vp, ObjectOpResult &result);
+NativeSetExistingDataProperty(JSContext *cx, HandleNativeObject obj, HandleShape shape,
+                              HandleValue v, HandleValue receiver, ObjectOpResult &result);
 
 static inline bool
 DefinePropertyOrElement(ExclusiveContext *cx, HandleNativeObject obj, HandleId id,
                         GetterOp getter, SetterOp setter, unsigned attrs, HandleValue value,
                         bool callSetterAfterwards, ObjectOpResult &result)
 {
     MOZ_ASSERT(getter != JS_PropertyStub);
     MOZ_ASSERT(setter != JS_StrictPropertyStub);
@@ -1219,18 +1219,19 @@ DefinePropertyOrElement(ExclusiveContext
     if (!CallAddPropertyHook(cx, obj, shape, value))
         return false;
 
     if (callSetterAfterwards && setter) {
         MOZ_ASSERT(!(attrs & JSPROP_GETTER));
         MOZ_ASSERT(!(attrs & JSPROP_SETTER));
         if (!cx->shouldBeJSContext())
             return false;
-        RootedValue nvalue(cx, value);
-        return NativeSetExistingDataProperty(cx->asJSContext(), obj, obj, shape, &nvalue, result);
+        RootedValue receiver(cx, ObjectValue(*obj));
+        return NativeSetExistingDataProperty(cx->asJSContext(), obj, shape, value, receiver,
+                                             result);
     }
 
     return result.succeed();
 }
 
 static unsigned
 ApplyOrDefaultAttributes(unsigned attrs, const Shape *shape = nullptr)
 {
@@ -1626,17 +1627,17 @@ js::NativeHasProperty(JSContext *cx, Han
 
 static inline bool
 CallGetter(JSContext* cx, HandleObject receiver, HandleShape shape, MutableHandleValue vp)
 {
     MOZ_ASSERT(!shape->hasDefaultGetter());
 
     if (shape->hasGetterValue()) {
         Value fval = shape->getterValue();
-        return InvokeGetterOrSetter(cx, receiver, fval, 0, 0, vp);
+        return InvokeGetter(cx, receiver, fval, vp);
     }
 
     RootedId id(cx, shape->propid());
     return CallJSGetterOp(cx, shape->getterOp(), receiver, id, vp);
 }
 
 template <AllowGC allowGC>
 static MOZ_ALWAYS_INLINE bool
@@ -1974,23 +1975,28 @@ MaybeReportUndeclaredVarAssignment(JSCon
                                         JSMSG_UNDECLARED_VAR, bytes.ptr());
 }
 
 /*
  * When a [[Set]] operation finds no existing property with the given id
  * or finds a writable data property on the prototype chain, we end up here.
  * Finish the [[Set]] by defining a new property on receiver.
  *
- * This implements ES6 draft rev 28, 9.1.9 [[Set]] steps 5.c-f, but it
+ * This implements ES6 draft rev 28, 9.1.9 [[Set]] steps 5.b-f, but it
  * is really old code and there are a few barnacles.
  */
 bool
-js::SetPropertyByDefining(JSContext *cx, HandleObject obj, HandleObject receiver,
-                          HandleId id, HandleValue v, bool objHasOwn, ObjectOpResult &result)
+js::SetPropertyByDefining(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                          HandleValue receiverValue, bool objHasOwn, ObjectOpResult &result)
 {
+    // Step 5.b.
+    if (!receiverValue.isObject())
+        return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
+    RootedObject receiver(cx, &receiverValue.toObject());
+
     // Step 5.c-d: Test whether receiver has an existing own property
     // receiver[id]. The spec calls [[GetOwnProperty]]; js::HasOwnProperty is
     // the same thing except faster in the non-proxy case. Sometimes we can
     // even optimize away the HasOwnProperty call.
     bool existing;
     if (receiver == obj) {
         // The common case. The caller has necessarily done a property lookup
         // on obj and passed us the answer as objHasOwn.
@@ -2042,62 +2048,60 @@ js::SetPropertyByDefining(JSContext *cx,
     // JSSetterOp is called after defining the new property.
     Rooted<NativeObject*> nativeReceiver(cx, &receiver->as<NativeObject>());
     return DefinePropertyOrElement(cx, nativeReceiver, id, getter, setter, attrs, v, true, result);
 }
 
 // When setting |id| for |receiver| and |obj| has no property for id, continue
 // the search up the prototype chain.
 bool
-js::SetPropertyOnProto(JSContext *cx, HandleObject obj, HandleObject receiver,
-                       HandleId id, MutableHandleValue vp, ObjectOpResult &result)
+js::SetPropertyOnProto(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                       HandleValue receiver, ObjectOpResult &result)
 {
     MOZ_ASSERT(!obj->is<ProxyObject>());
 
     RootedObject proto(cx, obj->getProto());
     if (proto)
-        return SetProperty(cx, proto, receiver, id, vp, result);
-    return SetPropertyByDefining(cx, obj, receiver, id, vp, false, result);
+        return SetProperty(cx, proto, id, v, receiver, result);
+    return SetPropertyByDefining(cx, obj, id, v, receiver, false, result);
 }
 
 /*
  * Implement "the rest of" assignment to a property when no property receiver[id]
  * was found anywhere on the prototype chain.
  *
  * FIXME: This should be updated to follow ES6 draft rev 28, section 9.1.9,
  * steps 4.d.i and 5.
- *
- * Note that receiver is not necessarily native.
  */
 static bool
-SetNonexistentProperty(JSContext *cx, HandleNativeObject obj, HandleObject receiver, HandleId id,
-                       QualifiedBool qualified, HandleValue v, ObjectOpResult &result)
+SetNonexistentProperty(JSContext *cx, HandleNativeObject obj, HandleId id, HandleValue v,
+                       HandleValue receiver, QualifiedBool qualified, ObjectOpResult &result)
 {
     // We should never add properties to lexical blocks.
-    MOZ_ASSERT(!receiver->is<BlockObject>());
+    MOZ_ASSERT_IF(receiver.isObject(), !receiver.toObject().is<BlockObject>());
 
-    if (receiver->isUnqualifiedVarObj() && !qualified) {
+    if (!qualified && receiver.isObject() && receiver.toObject().isUnqualifiedVarObj()) {
         if (!MaybeReportUndeclaredVarAssignment(cx, JSID_TO_STRING(id)))
             return false;
     }
 
-    return SetPropertyByDefining(cx, obj, receiver, id, v, false, result);
+    return SetPropertyByDefining(cx, obj, id, v, receiver, false, result);
 }
 
 /*
  * Set an existing own property obj[index] that's a dense element or typed
  * array element.
  */
 static bool
-SetDenseOrTypedArrayElement(JSContext *cx, HandleNativeObject obj, uint32_t index,
-                            MutableHandleValue vp, ObjectOpResult &result)
+SetDenseOrTypedArrayElement(JSContext *cx, HandleNativeObject obj, uint32_t index, HandleValue v,
+                            ObjectOpResult &result)
 {
     if (IsAnyTypedArray(obj)) {
         double d;
-        if (!ToNumber(cx, vp, &d))
+        if (!ToNumber(cx, v, &d))
             return false;
 
         // Silently do nothing for out-of-bounds sets, for consistency with
         // current behavior.  (ES6 currently says to throw for this in
         // strict mode code, so we may eventually need to change.)
         uint32_t len = AnyTypedArrayLength(obj);
         if (index < len) {
             if (obj->is<TypedArrayObject>())
@@ -2109,222 +2113,224 @@ SetDenseOrTypedArrayElement(JSContext *c
     }
 
     if (WouldDefinePastNonwritableLength(obj, index))
         return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
 
     if (!obj->maybeCopyElementsForWrite(cx))
         return false;
 
-    obj->setDenseElementWithType(cx, index, vp);
+    obj->setDenseElementWithType(cx, index, v);
     return result.succeed();
 }
 
 /*
  * Finish assignment to a shapeful data property of a native object obj. This
  * conforms to no standard and there is a lot of legacy baggage here.
  */
 static bool
-NativeSetExistingDataProperty(JSContext *cx, HandleNativeObject obj, HandleObject receiver,
-                              HandleShape shape, MutableHandleValue vp, ObjectOpResult &result)
+NativeSetExistingDataProperty(JSContext *cx, HandleNativeObject obj, HandleShape shape,
+                              HandleValue v, HandleValue receiver, ObjectOpResult &result)
 {
     MOZ_ASSERT(obj->isNative());
     MOZ_ASSERT(shape->isDataDescriptor());
 
     if (shape->hasDefaultSetter()) {
         if (shape->hasSlot()) {
             // The common path. Standard data property.
 
             // Global properties declared with 'var' will be initially
             // defined with an undefined value, so don't treat the initial
             // assignments to such properties as overwrites.
             bool overwriting = !obj->is<GlobalObject>() || !obj->getSlot(shape->slot()).isUndefined();
-            obj->setSlotWithType(cx, shape, vp, overwriting);
+            obj->setSlotWithType(cx, shape, v, overwriting);
             return result.succeed();
         }
 
         // Bizarre: shared (slotless) property that's writable but has no
         // JSSetterOp. JS code can't define such a property, but it can be done
         // through the JSAPI. Treat it as non-writable.
         return result.fail(JSMSG_GETTER_ONLY);
     }
 
     MOZ_ASSERT(!obj->is<DynamicWithObject>());  // See bug 1128681.
 
     uint32_t sample = cx->runtime()->propertyRemovals;
     RootedId id(cx, shape->propid());
-    if (!CallJSSetterOp(cx, shape->setterOp(), obj, id, vp, result))
+    RootedValue value(cx, v);
+    if (!CallJSSetterOp(cx, shape->setterOp(), obj, id, &value, result))
         return false;
 
     // Update any slot for the shape with the value produced by the setter,
     // unless the setter deleted the shape.
     if (shape->hasSlot() &&
         (MOZ_LIKELY(cx->runtime()->propertyRemovals == sample) ||
          obj->contains(cx, shape)))
     {
-        obj->setSlot(shape->slot(), vp);
+        obj->setSlot(shape->slot(), value);
     }
 
     return true;  // result is populated by CallJSSetterOp above.
 }
 
 /*
- * Finish the assignment `receiver[id] = vp` when an existing property (shape)
+ * Finish the assignment `receiver[id] = v` when an existing property (shape)
  * has been found on a native object (pobj). This implements ES6 draft rev 32
  * (2015 Feb 2) 9.1.9 steps 5 and 6.
  *
  * It is necessary to pass both id and shape because shape could be an implicit
  * dense or typed array element (i.e. not actually a pointer to a Shape).
  */
 static bool
-SetExistingProperty(JSContext *cx, HandleNativeObject obj, HandleObject receiver, HandleId id,
-                    HandleNativeObject pobj, HandleShape shape, MutableHandleValue vp,
+SetExistingProperty(JSContext *cx, HandleNativeObject obj, HandleId id, HandleValue v,
+                    HandleValue receiver, HandleNativeObject pobj, HandleShape shape,
                     ObjectOpResult &result)
 {
     // Step 5 for dense elements.
     if (IsImplicitDenseOrTypedArrayElement(shape)) {
         // Step 5.a is a no-op: all dense elements are writable.
-        // Step 5.b has to do with non-object receivers, which we don't support yet.
 
         // Pure optimization for the common case:
-        if (pobj == receiver)
-            return SetDenseOrTypedArrayElement(cx, pobj, JSID_TO_INT(id), vp, result);
+        if (receiver.isObject() && pobj == &receiver.toObject())
+            return SetDenseOrTypedArrayElement(cx, pobj, JSID_TO_INT(id), v, result);
 
-        // Steps 5.c-f.
-        return SetPropertyByDefining(cx, obj, receiver, id, vp, obj == pobj, result);
+        // Steps 5.b-f.
+        return SetPropertyByDefining(cx, obj, id, v, receiver, obj == pobj, result);
     }
 
     // Step 5 for all other properties.
     if (shape->isDataDescriptor()) {
         // Step 5.a.
         if (!shape->writable())
             return result.fail(JSMSG_READ_ONLY);
 
         // steps 5.c-f.
-        if (pobj == receiver) {
+        if (receiver.isObject() && pobj == &receiver.toObject()) {
             // Pure optimization for the common case. There's no point performing
             // the lookup in step 5.c again, as our caller just did it for us. The
             // result is |shape|.
 
             // Steps 5.e.i-ii.
             if (pobj->is<ArrayObject>() && id == NameToId(cx->names().length)) {
                 Rooted<ArrayObject*> arr(cx, &pobj->as<ArrayObject>());
-                return ArraySetLength(cx, arr, id, shape->attributes(), vp, result);
+                return ArraySetLength(cx, arr, id, shape->attributes(), v, result);
             }
-            return NativeSetExistingDataProperty(cx, obj, receiver, shape, vp, result);
+            return NativeSetExistingDataProperty(cx, obj, shape, v, receiver, result);
         }
 
         // SpiderMonkey special case: assigning to an inherited slotless
         // property causes the setter to be called, instead of shadowing,
         // unless the existing property is JSPROP_SHADOWABLE (see bug 552432)
         // or it's the array length property.
         if (!shape->hasSlot() &&
             !shape->hasShadowable() &&
             !(pobj->is<ArrayObject>() && id == NameToId(cx->names().length)))
         {
             // Even weirder sub-special-case: inherited slotless data property
             // with default setter. Wut.
             if (shape->hasDefaultSetter())
                 return result.succeed();
 
-            return CallJSSetterOp(cx, shape->setterOp(), obj, id, vp, result);
+            RootedValue valCopy(cx, v);
+            return CallJSSetterOp(cx, shape->setterOp(), obj, id, &valCopy, result);
         }
 
         // Shadow pobj[id] by defining a new data property receiver[id].
         // Delegate everything to SetPropertyByDefining.
-        return SetPropertyByDefining(cx, obj, receiver, id, vp, obj == pobj, result);
+        return SetPropertyByDefining(cx, obj, id, v, receiver, obj == pobj, result);
     }
 
     // Steps 6-11.
     MOZ_ASSERT(shape->isAccessorDescriptor());
     MOZ_ASSERT_IF(!shape->hasSetterObject(), shape->hasDefaultSetter());
     if (shape->hasDefaultSetter())
         return result.fail(JSMSG_GETTER_ONLY);
     Value setter = ObjectValue(*shape->setterObject());
-    if (!InvokeGetterOrSetter(cx, receiver, setter, 1, vp.address(), vp))
+    if (!InvokeSetter(cx, receiver, setter, v))
         return false;
     return result.succeed();
 }
 
 bool
-js::NativeSetProperty(JSContext *cx, HandleNativeObject obj, HandleObject receiver, HandleId id,
-                      QualifiedBool qualified, MutableHandleValue vp, ObjectOpResult &result)
+js::NativeSetProperty(JSContext *cx, HandleNativeObject obj, HandleId id, HandleValue value,
+                      HandleValue receiver, QualifiedBool qualified, ObjectOpResult &result)
 {
     // Fire watchpoints, if any.
+    RootedValue v(cx, value);
     if (MOZ_UNLIKELY(obj->watched())) {
         WatchpointMap *wpmap = cx->compartment()->watchpointMap;
-        if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp))
+        if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, &v))
             return false;
     }
 
     // Step numbers below reference ES6 rev 27 9.1.9, the [[Set]] internal
     // method for ordinary objects. We substitute our own names for these names
-    // used in the spec: O -> pobj, P -> id, V -> *vp, ownDesc -> shape.
+    // used in the spec: O -> pobj, P -> id, ownDesc -> shape.
     RootedShape shape(cx);
     RootedNativeObject pobj(cx, obj);
 
     // This loop isn't explicit in the spec algorithm. See the comment on step
     // 4.c.i below. (There's a very similar loop in the NativeGetProperty
     // implementation, but unfortunately not similar enough to common up.)
     for (;;) {
         // Steps 2-3. ('done' is a SpiderMonkey-specific thing, used below.)
         bool done;
         if (!LookupOwnPropertyInline<CanGC>(cx, pobj, id, &shape, &done))
             return false;
 
         if (shape) {
             // Steps 5-6.
-            return SetExistingProperty(cx, obj, receiver, id, pobj, shape, vp, result);
+            return SetExistingProperty(cx, obj, id, v, receiver, pobj, shape, result);
         }
 
         // Steps 4.a-b. The check for 'done' on this next line is tricky.
         // done can be true in exactly these unlikely-sounding cases:
         // - We're looking up an element, and pobj is a TypedArray that
         //   doesn't have that many elements.
         // - We're being called from a resolve hook to assign to the property
         //   being resolved.
         // What they all have in common is we do not want to keep walking
         // the prototype chain.
         RootedObject proto(cx, done ? nullptr : pobj->getProto());
         if (!proto) {
             // Step 4.d.i (and step 5).
-            return SetNonexistentProperty(cx, obj, receiver, id, qualified, vp, result);
+            return SetNonexistentProperty(cx, obj, id, v, receiver, qualified, result);
         }
 
         // Step 4.c.i. If the prototype is also native, this step is a
         // recursive tail call, and we don't need to go through all the
         // plumbing of SetProperty; the top of the loop is where we're going to
         // end up anyway. But if pobj is non-native, that optimization would be
         // incorrect.
         if (!proto->isNative()) {
             // Unqualified assignments are not specified to go through [[Set]]
             // at all, but they do go through this function. So check for
             // unqualified assignment to a nonexistent global (a strict error).
             if (!qualified) {
                 bool found;
                 if (!HasProperty(cx, proto, id, &found))
                     return false;
                 if (!found)
-                    return SetNonexistentProperty(cx, obj, receiver, id, qualified, vp, result);
+                    return SetNonexistentProperty(cx, obj, id, v, receiver, qualified, result);
             }
 
-            return SetProperty(cx, proto, receiver, id, vp, result);
+            return SetProperty(cx, proto, id, v, receiver, result);
         }
         pobj = &proto->as<NativeObject>();
     }
 }
 
 bool
-js::NativeSetElement(JSContext *cx, HandleNativeObject obj, HandleObject receiver, uint32_t index,
-                     MutableHandleValue vp, ObjectOpResult &result)
+js::NativeSetElement(JSContext *cx, HandleNativeObject obj, uint32_t index, HandleValue v,
+                     HandleValue receiver, ObjectOpResult &result)
 {
     RootedId id(cx);
     if (!IndexToId(cx, index, &id))
         return false;
-    return NativeSetProperty(cx, obj, receiver, id, Qualified, vp, result);
+    return NativeSetProperty(cx, obj, id, v, receiver, Qualified, result);
 }
 
 /*** [[Delete]] **********************************************************************************/
 
 // ES6 draft rev31 9.1.10 [[Delete]]
 bool
 js::NativeDeleteProperty(JSContext *cx, HandleNativeObject obj, HandleId id,
                          ObjectOpResult &result)
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -1307,42 +1307,42 @@ NativeGetProperty(JSContext *cx, HandleN
 
 inline bool
 NativeGetElement(JSContext *cx, HandleNativeObject obj, uint32_t index, MutableHandleValue vp)
 {
     return NativeGetElement(cx, obj, obj, index, vp);
 }
 
 bool
-SetPropertyByDefining(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
-                      HandleValue v, bool objHasOwn, ObjectOpResult &result);
+SetPropertyByDefining(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                      HandleValue receiver, bool objHasOwn, ObjectOpResult &result);
 
 bool
-SetPropertyOnProto(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
-                   MutableHandleValue vp, ObjectOpResult &result);
+SetPropertyOnProto(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                   HandleValue receiver, ObjectOpResult &result);
 
 /*
  * Indicates whether an assignment operation is qualified (`x.y = 0`) or
  * unqualified (`y = 0`). In strict mode, the latter is an error if no such
  * variable already exists.
  *
  * Used as an argument to NativeSetProperty.
  */
 enum QualifiedBool {
     Unqualified = 0,
     Qualified = 1
 };
 
 extern bool
-NativeSetProperty(JSContext *cx, HandleNativeObject obj, HandleObject receiver, HandleId id,
-                  QualifiedBool qualified, MutableHandleValue vp, ObjectOpResult &result);
+NativeSetProperty(JSContext *cx, HandleNativeObject obj, HandleId id, HandleValue v,
+                  HandleValue receiver, QualifiedBool qualified, ObjectOpResult &result);
 
 extern bool
-NativeSetElement(JSContext *cx, HandleNativeObject obj, HandleObject receiver, uint32_t index,
-                 MutableHandleValue vp, ObjectOpResult &result);
+NativeSetElement(JSContext *cx, HandleNativeObject obj, uint32_t index, HandleValue v,
+                 HandleValue receiver, ObjectOpResult &result);
 
 extern bool
 NativeDeleteProperty(JSContext *cx, HandleNativeObject obj, HandleId id, ObjectOpResult &result);
 
 
 /*** SpiderMonkey nonstandard internal methods ***************************************************/
 
 template <AllowGC allowGC>
@@ -1443,26 +1443,26 @@ inline bool
 js::GetPropertyNoGC(JSContext *cx, JSObject *obj, JSObject *receiver, jsid id, Value *vp)
 {
     if (obj->getOps()->getProperty)
         return false;
     return NativeGetPropertyNoGC(cx, &obj->as<NativeObject>(), receiver, id, vp);
 }
 
 inline bool
-js::SetProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
-                HandleId id, MutableHandleValue vp, ObjectOpResult &result)
+js::SetProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                HandleValue receiver, ObjectOpResult &result)
 {
     if (obj->getOps()->setProperty)
-        return JSObject::nonNativeSetProperty(cx, obj, receiver, id, vp, result);
-    return NativeSetProperty(cx, obj.as<NativeObject>(), receiver, id, Qualified, vp, result);
+        return JSObject::nonNativeSetProperty(cx, obj, id, v, receiver, result);
+    return NativeSetProperty(cx, obj.as<NativeObject>(), id, v, receiver, Qualified, result);
 }
 
 inline bool
-js::SetElement(JSContext *cx, HandleObject obj, HandleObject receiver, uint32_t index,
-               MutableHandleValue vp, ObjectOpResult &result)
+js::SetElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue v,
+               HandleValue receiver, ObjectOpResult &result)
 {
     if (obj->getOps()->setProperty)
-        return JSObject::nonNativeSetElement(cx, obj, receiver, index, vp, result);
-    return NativeSetElement(cx, obj.as<NativeObject>(), receiver, index, vp, result);
+        return JSObject::nonNativeSetElement(cx, obj, index, v, receiver, result);
+    return NativeSetElement(cx, obj.as<NativeObject>(), index, v, receiver, result);
 }
 
 #endif /* vm_NativeObject_h */
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -294,17 +294,17 @@ CallObject::createHollowForDebug(JSConte
     if (!callobj)
         return nullptr;
 
     RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
     RootedId id(cx);
     RootedScript script(cx, callee->nonLazyScript());
     for (BindingIter bi(script); !bi.done(); bi++) {
         id = NameToId(bi->name());
-        if (!SetProperty(cx, callobj, callobj, id, &optimizedOut))
+        if (!SetProperty(cx, callobj, id, optimizedOut))
             return nullptr;
     }
 
     return callobj;
 }
 
 const Class CallObject::class_ = {
     "Call",
@@ -487,24 +487,24 @@ static bool
 with_GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
                  MutableHandleValue vp)
 {
     RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
     return GetProperty(cx, actual, actual, id, vp);
 }
 
 static bool
-with_SetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
-                 MutableHandleValue vp, ObjectOpResult &result)
+with_SetProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                 HandleValue receiver, ObjectOpResult &result)
 {
     RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
-    RootedObject actualReceiver(cx, receiver);
-    if (receiver == obj)
-        actualReceiver = actual;
-    return SetProperty(cx, actual, actualReceiver, id, vp, result);
+    RootedValue actualReceiver(cx, receiver);
+    if (receiver.isObject() && &receiver.toObject() == obj)
+        actualReceiver.setObject(*actual);
+    return SetProperty(cx, actual, id, v, actualReceiver, result);
 }
 
 static bool
 with_GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id,
                               MutableHandle<JSPropertyDescriptor> desc)
 {
     RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
     return GetOwnPropertyDescriptor(cx, actual, id, desc);
@@ -924,18 +924,18 @@ static bool
 uninitialized_GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
                           MutableHandleValue vp)
 {
     ReportUninitializedLexicalId(cx, id);
     return false;
 }
 
 static bool
-uninitialized_SetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
-                          MutableHandleValue vp, ObjectOpResult &result)
+uninitialized_SetProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                          HandleValue receiver, ObjectOpResult &result)
 {
     ReportUninitializedLexicalId(cx, id);
     return false;
 }
 
 static bool
 uninitialized_GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc)
@@ -1587,34 +1587,38 @@ class DebugScopeProxy : public BaseProxy
           case ACCESS_LOST:
             vp.setMagic(JS_OPTIMIZED_OUT);
             return true;
           default:
             MOZ_CRASH("bad AccessResult");
         }
     }
 
-    bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
-             MutableHandleValue vp, ObjectOpResult &result) const override
+    bool set(JSContext *cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver,
+             ObjectOpResult &result) const override
     {
         Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>());
         Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope());
 
         if (debugScope->isOptimizedOut())
             return Throw(cx, id, JSMSG_DEBUG_CANT_SET_OPT_ENV);
 
         AccessResult access;
-        if (!handleUnaliasedAccess(cx, debugScope, scope, id, SET, vp, &access))
+        RootedValue valCopy(cx, v);
+        if (!handleUnaliasedAccess(cx, debugScope, scope, id, SET, &valCopy, &access))
             return false;
 
         switch (access) {
           case ACCESS_UNALIASED:
             return result.succeed();
           case ACCESS_GENERIC:
-            return SetProperty(cx, scope, scope, id, vp, result);
+            {
+                RootedValue scopeVal(cx, ObjectValue(*scope));
+                return SetProperty(cx, scope, id, v, scopeVal, result);
+            }
           default:
             MOZ_CRASH("bad AccessResult");
         }
     }
 
     bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
                         Handle<PropertyDescriptor> desc,
                         ObjectOpResult &result) const override
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -331,20 +331,20 @@ js::intrinsic_UnsafePutElements(JSContex
         MOZ_ASSERT(args[idxi].isInt32());
 
         RootedObject arrobj(cx, &args[arri].toObject());
         uint32_t idx = args[idxi].toInt32();
 
         if (IsAnyTypedArray(arrobj.get()) || arrobj->is<TypedObject>()) {
             MOZ_ASSERT_IF(IsAnyTypedArray(arrobj.get()), idx < AnyTypedArrayLength(arrobj.get()));
             MOZ_ASSERT_IF(arrobj->is<TypedObject>(), idx < uint32_t(arrobj->as<TypedObject>().length()));
-            RootedValue tmp(cx, args[elemi]);
             // XXX: Always non-strict.
             ObjectOpResult ignored;
-            if (!SetElement(cx, arrobj, arrobj, idx, &tmp, ignored))
+            RootedValue receiver(cx, ObjectValue(*arrobj));
+            if (!SetElement(cx, arrobj, idx, args[elemi], receiver, ignored))
                 return false;
         } else {
             MOZ_ASSERT(idx < arrobj->as<ArrayObject>().getDenseInitializedLength());
             arrobj->as<ArrayObject>().setDenseElementWithType(cx, idx, args[elemi]);
         }
     }
 
     args.rval().setUndefined();
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -754,46 +754,48 @@ UnboxedPlainObject::obj_getProperty(JSCo
         vp.setUndefined();
         return true;
     }
 
     return GetProperty(cx, proto, receiver, id, vp);
 }
 
 /* static */ bool
-UnboxedPlainObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
-                                    HandleId id, MutableHandleValue vp, ObjectOpResult &result)
+UnboxedPlainObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                                    HandleValue receiver, ObjectOpResult &result)
 {
     const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
 
     if (const UnboxedLayout::Property *property = layout.lookup(id)) {
-        if (obj == receiver) {
-            if (obj->as<UnboxedPlainObject>().setValue(cx, *property, vp))
+        if (receiver.isObject() && obj == &receiver.toObject()) {
+            if (obj->as<UnboxedPlainObject>().setValue(cx, *property, v))
                 return result.succeed();
 
             if (!convertToNative(cx, obj))
                 return false;
-            return SetProperty(cx, obj, receiver, id, vp, result);
+            return SetProperty(cx, obj, id, v, receiver, result);
         }
 
-        return SetPropertyByDefining(cx, obj, receiver, id, vp, false, result);
+        return SetPropertyByDefining(cx, obj, id, v, receiver, false, result);
     }
 
     if (UnboxedExpandoObject *expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
         if (expando->containsShapeOrElement(cx, id)) {
             // Update property types on the unboxed object as well.
-            AddTypePropertyId(cx, obj, id, vp);
+            AddTypePropertyId(cx, obj, id, v);
 
             RootedObject nexpando(cx, expando);
-            RootedObject nreceiver(cx, (obj == receiver) ? expando : receiver.get());
-            return SetProperty(cx, nexpando, nreceiver, id, vp, result);
+            RootedValue nreceiver(cx, (receiver.isObject() && obj == &receiver.toObject())
+                                      ? ObjectValue(*expando)
+                                      : receiver);
+            return SetProperty(cx, nexpando, id, v, nreceiver, result);
         }
     }
 
-    return SetPropertyOnProto(cx, obj, receiver, id, vp, result);
+    return SetPropertyOnProto(cx, obj, id, v, receiver, result);
 }
 
 /* static */ bool
 UnboxedPlainObject::obj_getOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id,
                                                  MutableHandle<JSPropertyDescriptor> desc)
 {
     const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
 
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -196,18 +196,18 @@ class UnboxedPlainObject : public JSObje
                                    Handle<JSPropertyDescriptor> desc,
                                    ObjectOpResult &result);
 
     static bool obj_hasProperty(JSContext *cx, HandleObject obj, HandleId id, bool *foundp);
 
     static bool obj_getProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
                                 HandleId id, MutableHandleValue vp);
 
-    static bool obj_setProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
-                                HandleId id, MutableHandleValue vp, ObjectOpResult &result);
+    static bool obj_setProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                                HandleValue receiver, ObjectOpResult &result);
 
     static bool obj_getOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id,
                                              MutableHandle<JSPropertyDescriptor> desc);
 
     static bool obj_deleteProperty(JSContext *cx, HandleObject obj, HandleId id,
                                    ObjectOpResult &result);
 
     static bool obj_enumerate(JSContext *cx, HandleObject obj, AutoIdVector &properties);
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -24,21 +24,21 @@ namespace js {
  * versions.  If deserialization fails, the data should be invalidated if
  * possible.
  *
  * When you change this, run make_opcode_doc.py and copy the new output into
  * this wiki page:
  *
  *  https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
  */
-static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 268;
+static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 269;
 static const uint32_t XDR_BYTECODE_VERSION =
     uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
 
-static_assert(JSErr_Limit == 389,
+static_assert(JSErr_Limit == 390,
               "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
               "removed MSG_DEFs from js.msg, you should increment "
               "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "
               "expected JSErr_Limit value.");
 
 class XDRBuffer {
   public:
     explicit XDRBuffer(JSContext *cx)
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -728,22 +728,22 @@ xpc::SandboxProxyHandler::get(JSContext 
                               JS::Handle<jsid> id,
                               JS::MutableHandle<Value> vp) const
 {
     return BaseProxyHandler::get(cx, proxy, receiver, id, vp);
 }
 
 bool
 xpc::SandboxProxyHandler::set(JSContext *cx, JS::Handle<JSObject*> proxy,
-                              JS::Handle<JSObject*> receiver,
                               JS::Handle<jsid> id,
-                              JS::MutableHandle<Value> vp,
+                              JS::Handle<Value> v,
+                              JS::Handle<Value> receiver,
                               JS::ObjectOpResult &result) const
 {
-    return BaseProxyHandler::set(cx, proxy, receiver, id, vp, result);
+    return BaseProxyHandler::set(cx, proxy, id, v, receiver, result);
 }
 
 bool
 xpc::SandboxProxyHandler::getOwnEnumerablePropertyKeys(JSContext *cx,
                                                        JS::Handle<JSObject*> proxy,
                                                        AutoIdVector &props) const
 {
     return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props);
--- a/js/xpconnect/wrappers/AddonWrapper.cpp
+++ b/js/xpconnect/wrappers/AddonWrapper.cpp
@@ -114,34 +114,34 @@ AddonWrapper<Base>::get(JSContext *cx, J
     } else {
         vp.set(desc.value());
         return true;
     }
 }
 
 template<typename Base>
 bool
-AddonWrapper<Base>::set(JSContext *cx, JS::HandleObject wrapper, JS::HandleObject receiver,
-                        JS::HandleId id, JS::MutableHandleValue vp,
-                        JS::ObjectOpResult &result) const
+AddonWrapper<Base>::set(JSContext *cx, JS::HandleObject wrapper, JS::HandleId id, JS::HandleValue v,
+                        JS::HandleValue receiver, JS::ObjectOpResult &result) const
 {
     Rooted<JSPropertyDescriptor> desc(cx);
     if (!Interpose(cx, wrapper, nullptr, id, &desc))
         return false;
 
     if (!desc.object())
-        return Base::set(cx, wrapper, receiver, id, vp, result);
+        return Base::set(cx, wrapper, id, v, receiver, result);
 
     if (desc.setter()) {
         MOZ_ASSERT(desc.hasSetterObject());
         JS::AutoValueVector args(cx);
-        if (!args.append(vp))
+        if (!args.append(v))
             return false;
         RootedValue fval(cx, ObjectValue(*desc.setterObject()));
-        if (!JS_CallFunctionValue(cx, receiver, fval, args, vp))
+        RootedValue ignored(cx);
+        if (!JS::Call(cx, receiver, fval, args, &ignored))
             return false;
         return result.succeed();
     }
 
     return result.failCantSetInterposed();
 }
 
 template<typename Base>
--- a/js/xpconnect/wrappers/AddonWrapper.h
+++ b/js/xpconnect/wrappers/AddonWrapper.h
@@ -29,19 +29,18 @@ class AddonWrapper : public Base {
                                           JS::MutableHandle<JSPropertyDescriptor> desc) const override;
     virtual bool defineProperty(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
                                 JS::Handle<JSPropertyDescriptor> desc,
                                 JS::ObjectOpResult &result) const override;
     virtual bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
                          JS::ObjectOpResult &result) const override;
     virtual bool get(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> receiver,
                      JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const override;
-    virtual bool set(JSContext *cx, JS::HandleObject wrapper, JS::HandleObject receiver,
-                     JS::HandleId id, JS::MutableHandleValue vp,
-                     JS::ObjectOpResult &result) const override;
+    virtual bool set(JSContext *cx, JS::HandleObject wrapper, JS::HandleId id, JS::HandleValue v,
+                     JS::HandleValue receiver, JS::ObjectOpResult &result) const override;
 
     virtual bool getPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper,
                                        JS::Handle<jsid> id,
                                        JS::MutableHandle<JSPropertyDescriptor> desc) const override;
 
     static const AddonWrapper singleton;
 };
 
--- a/js/xpconnect/wrappers/ChromeObjectWrapper.cpp
+++ b/js/xpconnect/wrappers/ChromeObjectWrapper.cpp
@@ -25,18 +25,17 @@ ChromeObjectWrapper::defineProperty(JSCo
                                     JS::ObjectOpResult &result) const
 {
     if (!AccessCheck::checkPassToPrivilegedCode(cx, wrapper, desc.value()))
         return false;
     return ChromeObjectWrapperBase::defineProperty(cx, wrapper, id, desc, result);
 }
 
 bool
-ChromeObjectWrapper::set(JSContext *cx, HandleObject wrapper,
-                         HandleObject receiver, HandleId id,
-                         MutableHandleValue vp, ObjectOpResult &result) const
+ChromeObjectWrapper::set(JSContext *cx, HandleObject wrapper, HandleId id, HandleValue v,
+                         HandleValue receiver, ObjectOpResult &result) const
 {
-    if (!AccessCheck::checkPassToPrivilegedCode(cx, wrapper, vp))
+    if (!AccessCheck::checkPassToPrivilegedCode(cx, wrapper, v))
         return false;
-    return ChromeObjectWrapperBase::set(cx, wrapper, receiver, id, vp, result);
+    return ChromeObjectWrapperBase::set(cx, wrapper, id, v, receiver, result);
 }
 
 }
--- a/js/xpconnect/wrappers/ChromeObjectWrapper.h
+++ b/js/xpconnect/wrappers/ChromeObjectWrapper.h
@@ -26,19 +26,18 @@ class ChromeObjectWrapper : public Chrom
 {
   public:
     MOZ_CONSTEXPR ChromeObjectWrapper() : ChromeObjectWrapperBase(0) {}
 
     virtual bool defineProperty(JSContext *cx, JS::Handle<JSObject*> wrapper,
                                 JS::Handle<jsid> id,
                                 JS::Handle<JSPropertyDescriptor> desc,
                                 JS::ObjectOpResult &result) const override;
-    virtual bool set(JSContext *cx, JS::Handle<JSObject*> wrapper,
-                     JS::Handle<JSObject*> receiver, JS::Handle<jsid> id,
-                     JS::MutableHandle<JS::Value> vp,
+    virtual bool set(JSContext *cx, JS::HandleObject wrapper, JS::HandleId id,
+                     JS::HandleValue v, JS::HandleValue receiver,
                      JS::ObjectOpResult &result) const override;
 
     static const ChromeObjectWrapper singleton;
 };
 
 } /* namespace xpc */
 
 #endif /* __ChromeObjectWrapper_h__ */
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -2076,25 +2076,25 @@ XrayWrapper<Base, Traits>::get(JSContext
     // Skip our Base if it isn't already ProxyHandler.
     // NB: None of the functions we call are prepared for the receiver not
     // being the wrapper, so ignore the receiver here.
     return js::BaseProxyHandler::get(cx, wrapper, Traits::HasPrototype ? receiver : wrapper, id, vp);
 }
 
 template <typename Base, typename Traits>
 bool
-XrayWrapper<Base, Traits>::set(JSContext *cx, HandleObject wrapper,
-                               HandleObject receiver, HandleId id,
-                               MutableHandleValue vp, ObjectOpResult &result) const
+XrayWrapper<Base, Traits>::set(JSContext *cx, HandleObject wrapper, HandleId id, HandleValue v,
+                               HandleValue receiver, ObjectOpResult &result) const
 {
     MOZ_ASSERT(!Traits::HasPrototype);
     // Skip our Base if it isn't already BaseProxyHandler.
     // NB: None of the functions we call are prepared for the receiver not
     // being the wrapper, so ignore the receiver here.
-    return js::BaseProxyHandler::set(cx, wrapper, wrapper, id, vp, result);
+    RootedValue wrapperValue(cx, ObjectValue(*wrapper));
+    return js::BaseProxyHandler::set(cx, wrapper, id, v, wrapperValue, result);
 }
 
 template <typename Base, typename Traits>
 bool
 XrayWrapper<Base, Traits>::has(JSContext *cx, HandleObject wrapper,
                                HandleId id, bool *bp) const
 {
     // Skip our Base if it isn't already ProxyHandler.
--- a/js/xpconnect/wrappers/XrayWrapper.h
+++ b/js/xpconnect/wrappers/XrayWrapper.h
@@ -436,18 +436,18 @@ class XrayWrapper : public Base {
                                        bool *succeeded) const override;
     virtual bool preventExtensions(JSContext *cx, JS::Handle<JSObject*> wrapper,
                                    JS::ObjectOpResult &result) const override;
     virtual bool isExtensible(JSContext *cx, JS::Handle<JSObject*> wrapper, bool *extensible) const override;
     virtual bool has(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
                      bool *bp) const override;
     virtual bool get(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> receiver,
                      JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const override;
-    virtual bool set(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> receiver,
-                     JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp,
+    virtual bool set(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
+                     JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
                      JS::ObjectOpResult &result) const override;
     virtual bool call(JSContext *cx, JS::Handle<JSObject*> wrapper,
                       const JS::CallArgs &args) const override;
     virtual bool construct(JSContext *cx, JS::Handle<JSObject*> wrapper,
                            const JS::CallArgs &args) const override;
 
     /* SpiderMonkey extensions. */
     virtual bool getPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
@@ -510,18 +510,18 @@ public:
                                           JS::MutableHandle<JSPropertyDescriptor> desc) const override;
 
     // We just forward the high-level methods to the BaseProxyHandler versions
     // which implement them in terms of lower-level methods.
     virtual bool has(JSContext *cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                      bool *bp) const override;
     virtual bool get(JSContext *cx, JS::Handle<JSObject*> proxy, JS::Handle<JSObject*> receiver,
                      JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const override;
-    virtual bool set(JSContext *cx, JS::Handle<JSObject*> proxy, JS::Handle<JSObject*> receiver,
-                     JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp,
+    virtual bool set(JSContext *cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+                     JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
                      JS::ObjectOpResult &result) const override;
 
     virtual bool getPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> proxy,
                                        JS::Handle<jsid> id,
                                        JS::MutableHandle<JSPropertyDescriptor> desc) const override;
     virtual bool hasOwn(JSContext *cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                         bool *bp) const override;
     virtual bool getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle<JSObject*> proxy,
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -4931,17 +4931,17 @@ FrameLayerBuilder::DrawPaintedLayer(Pain
 
   if (presContext && presContext->GetDocShell() && isActiveLayerManager) {
     nsDocShell* docShell = static_cast<nsDocShell*>(presContext->GetDocShell());
     bool isRecording;
     docShell->GetRecordProfileTimelineMarkers(&isRecording);
     if (isRecording) {
       mozilla::UniquePtr<TimelineMarker> marker =
         MakeUnique<LayerTimelineMarker>(docShell, aRegionToDraw);
-      docShell->AddProfileTimelineMarker(marker);
+      docShell->AddProfileTimelineMarker(Move(marker));
     }
   }
 
   if (!aRegionToInvalidate.IsEmpty()) {
     aLayer->AddInvalidRect(aRegionToInvalidate.GetBounds());
   }
 }
 
--- a/layout/generic/crashtests/370174-3.html
+++ b/layout/generic/crashtests/370174-3.html
@@ -11,18 +11,16 @@ function doe2() {
 document.body.setAttribute('style', '');
 document.getElementsByTagName('head')[0].setAttribute('style', '');
 document.body.offsetHeight;
 document.getElementsByTagName('span')[0].setAttribute('style', 'display: table-row;');
 }
 setTimeout(doe2,100);
 
 function tripleclick(){
-netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-var wu = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                .getInterface(Components.interfaces.nsIDOMWindowUtils);
+var wu = SpecialPowers.DOMWindowUtils;
 wu.sendMouseEvent('mousedown',  500, 500, 0, 3, 0);
 setTimeout(tripleclick,20);
 setTimeout(function(){window.location.reload()}, 200);
 }
 setTimeout(tripleclick,200);
 </SCRIPT>
 </BODY></HTML>
\ No newline at end of file
--- a/layout/generic/crashtests/370174-4.html
+++ b/layout/generic/crashtests/370174-4.html
@@ -7,17 +7,17 @@
 <style>
 
 
 </style>
 </head>
 <body>
 
 
-<iframe id="content" src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%3Cbody%3E%0A%3Cscript%3Enetscape.security.PrivilegeManager.enablePrivilege%28%22UniversalXPConnect%22%29%3B%0Avar%20wu%20%3D%20%20window.QueryInterface%28Components.interfaces.nsIInterfaceRequestor%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.getInterface%28Components.interfaces.nsIDOMWindowUtils%29%3B%0A%0Avar%20i%20%3D%20Math.floor%28Math.random%28%29*20%29%3B%0Awu.sendMouseEvent%28%27mousedown%27%2C%20%2020*i%2C%2020*i%2C%200%2C%203%2C%200%29%3B%0Awu.sendMouseEvent%28%27mouseup%27%2C%20%2020*i%2C%2020*i%2C%200%2C%203%2C%200%29%3B%0A%3C/script%3E%0A%0A%3Cembed%20type%3D%22bbb%22%20style%3D%22display%3A%20-moz-inline-stack%3B%22%3E%0A%0A%3C/body%3E%3C/html%3E" style="width:1000px;height: 700px;"></iframe>
+<iframe id="content" src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%3Cbody%3E%0A%3Cscript%3E%0Avar%20wu%20%3D%20SpecialPowers.DOMWindowUtils%3B%0Avar%20i%20%3D%20Math.floor%28Math.random%28%29*20%29%3B%0Awu.sendMouseEvent%28%27mousedown%27%2C%20%2020*i%2C%2020*i%2C%200%2C%203%2C%200%29%3B%0Awu.sendMouseEvent%28%27mouseup%27%2C%20%2020*i%2C%2020*i%2C%200%2C%203%2C%200%29%3B%0A%3C/script%3E%0A%0A%3Cembed%20type%3D%22bbb%22%20style%3D%22display%3A%20-moz-inline-stack%3B%22%3E%0A%0A%3C/body%3E%3C/html%3E%22%20style%3D%22width%3A1000px%3Bheight%3A%20700px%3B"></iframe>
 <script>
 function doe2() {
 document.getElementById('content').src = document.getElementById('content').src + '?1';
 
 }
 setInterval(doe2, 1000);
 </script>
 </body>
--- a/mobile/android/base/reading/LocalReadingListStorage.java
+++ b/mobile/android/base/reading/LocalReadingListStorage.java
@@ -43,64 +43,70 @@ public class LocalReadingListStorage imp
     private final Queue<ClientReadingListRecord> changes;
 
     /**
      * These are deletions that result from uploading new or changed records to the server.
      * They should always correspond to local records.
      * These are not common: they should only occur if a conflict occurs.
      */
     private final Queue<ClientReadingListRecord> deletions;
+    private final Queue<String> deletedGUIDs;
 
     /**
      * These are additions or changes fetched from the server.
      * At the point of collection we don't know if they're records
      * that exist locally.
      *
      * Batching these here, rather than in the client or the synchronizer,
      * puts the storage implementation in control of when batches are flushed,
      * or if batches are used at all.
      */
     private final Queue<ServerReadingListRecord> additionsOrChanges;
 
     LocalReadingListChangeAccumulator() {
       this.changes = new ConcurrentLinkedQueue<>();
       this.deletions = new ConcurrentLinkedQueue<>();
+      this.deletedGUIDs = new ConcurrentLinkedQueue<>();
       this.additionsOrChanges = new ConcurrentLinkedQueue<>();
     }
 
     public boolean flushDeletions() throws RemoteException {
-      if (deletions.isEmpty()) {
+      if (deletions.isEmpty() && deletedGUIDs.isEmpty()) {
         return true;
       }
 
       long[] ids = new long[deletions.size()];
-      String[] guids = new String[deletions.size()];
+      String[] guids = new String[deletions.size() + deletedGUIDs.size()];
       int iID = 0;
       int iGUID = 0;
       for (ClientReadingListRecord record : deletions) {
         if (record.clientMetadata.id > -1L) {
           ids[iID++] = record.clientMetadata.id;
         } else {
           final String guid = record.getGUID();
           if (guid == null) {
             continue;
           }
           guids[iGUID++] = guid;
         }
       }
+      for (String guid : deletedGUIDs) {
+        guids[iGUID++] = guid;
+      }
 
       if (iID > 0) {
         client.delete(URI_WITH_DELETED, RepoUtils.computeSQLLongInClause(ids, ReadingListItems._ID), null);
       }
 
       if (iGUID > 0) {
         client.delete(URI_WITH_DELETED, RepoUtils.computeSQLInClause(iGUID, ReadingListItems.GUID), guids);
       }
 
       deletions.clear();
+      deletedGUIDs.clear();
       return true;
     }
 
     public boolean flushRecordChanges() throws RemoteException {
       if (changes.isEmpty() && additionsOrChanges.isEmpty()) {
         return true;
       }
 
@@ -207,16 +213,21 @@ public class LocalReadingListStorage imp
     }
 
     @Override
     public void addDeletion(ClientReadingListRecord record) {
       deletions.add(record);
     }
 
     @Override
+    public void addDeletion(String guid) {
+      deletedGUIDs.add(guid);
+    }
+
+    @Override
     public void addChangedRecord(ClientReadingListRecord record) {
       changes.add(record);
     }
 
     @Override
     public void addUploadedRecord(ClientReadingListRecord up,
                                   ServerReadingListRecord down) {
       // TODO
@@ -314,16 +325,30 @@ public class LocalReadingListStorage imp
     try {
       return client.query(URI_WITHOUT_DELETED, projection, selection, null, null);
     } catch (RemoteException e) {
       throw new IllegalStateException(e);
     }
   }
 
   @Override
+  public Cursor getDeletedItems() {
+    final String[] projection = new String[] {
+      ReadingListItems.GUID,
+    };
+
+    final String selection = "(" + ReadingListItems.IS_DELETED + " = 1) AND (" + ReadingListItems.GUID + " IS NOT NULL)";
+    try {
+      return client.query(URI_WITH_DELETED, projection, selection, null, null);
+    } catch (RemoteException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  @Override
   public Cursor getNew() {
     // N.B., query for items that have no GUID, regardless of status.
     // They should all be marked as NEW, but belt and braces.
     final String selection = WHERE_STATUS_NEW + " OR (" + ReadingListItems.GUID + " IS NULL)";
 
     try {
       return client.query(URI_WITHOUT_DELETED, null, selection, null, null);
     } catch (RemoteException e) {
--- a/mobile/android/base/reading/ReadingListChangeAccumulator.java
+++ b/mobile/android/base/reading/ReadingListChangeAccumulator.java
@@ -6,14 +6,15 @@ package org.mozilla.gecko.reading;
 
 
 /**
  * Grab one of these, then you can add records to it by parsing
  * server responses. Finishing it will flush those changes (e.g.,
  * via UPDATE) to the DB.
  */
 public interface ReadingListChangeAccumulator {
+  void addDeletion(String guid);
   void addDeletion(ClientReadingListRecord record);
   void addChangedRecord(ClientReadingListRecord record);
   void addUploadedRecord(ClientReadingListRecord up, ServerReadingListRecord down);
   void addDownloadedRecord(ServerReadingListRecord down);
   void finish() throws Exception;
 }
--- a/mobile/android/base/reading/ReadingListClient.java
+++ b/mobile/android/base/reading/ReadingListClient.java
@@ -426,16 +426,86 @@ public class ReadingListClient {
     }
 
     @Override
     void again(ClientReadingListRecord record) {
       patch(record, PatchBatchingUploadDelegate.this);
     }
   }
 
+  private class DeleteBatchingDelegate implements ReadingListDeleteDelegate {
+    private final Queue<String> queue;
+    private final ReadingListDeleteDelegate batchDeleteDelegate;
+    private final Executor executor;
+
+    DeleteBatchingDelegate(Queue<String> guids,
+                           ReadingListDeleteDelegate batchDeleteDelegate,
+                           Executor executor) {
+      this.queue = guids;
+      this.batchDeleteDelegate = batchDeleteDelegate;
+      this.executor = executor;
+    }
+
+    void next() {
+      final String guid = queue.poll();
+      executor.execute(new Runnable() {
+        @Override
+        public void run() {
+          if (guid == null) {
+            batchDeleteDelegate.onBatchDone();
+            return;
+          }
+
+          again(guid);
+        }
+      });
+    }
+
+    void again(String guid) {
+      delete(guid, DeleteBatchingDelegate.this, -1L);
+    }
+
+    @Override
+    public void onSuccess(ReadingListRecordResponse response,
+                          ReadingListRecord record) {
+      batchDeleteDelegate.onSuccess(response, record);
+      next();
+    }
+
+    @Override
+    public void onPreconditionFailed(String guid, MozResponse response) {
+      batchDeleteDelegate.onPreconditionFailed(guid, response);
+      next();
+    }
+
+    @Override
+    public void onRecordMissingOrDeleted(String guid, MozResponse response) {
+      batchDeleteDelegate.onRecordMissingOrDeleted(guid, response);
+      next();
+    }
+
+    @Override
+    public void onFailure(Exception e) {
+      batchDeleteDelegate.onFailure(e);
+      next();
+    }
+
+    @Override
+    public void onFailure(MozResponse response) {
+      batchDeleteDelegate.onFailure(response);
+      next();
+    }
+
+    @Override
+    public void onBatchDone() {
+      // This should never occur, but if it does, pass through.
+      batchDeleteDelegate.onBatchDone();
+    }
+  }
+
   // Deliberately declare `delegate` non-final so we can't capture it below. We prefer
   // to use `recordDelegate` explicitly.
   public void getOne(final String guid, ReadingListRecordDelegate delegate, final long ifModifiedSince) {
     final BaseResource r = getRelativeArticleResource(guid);
     r.delegate = new SingleRecordResourceDelegate(r, auth, delegate, ReadingListRecordResponse.FACTORY, ifModifiedSince, guid);
     if (ReadingListConstants.DEBUG) {
       Logger.info(LOG_TAG, "Getting record " + guid);
     }
@@ -506,16 +576,27 @@ public class ReadingListClient {
 
     final ExtendedJSONObject body = up.toJSON();
     if (ReadingListConstants.DEBUG) {
       Logger.info(LOG_TAG, "Uploading new record: " + body.toJSONString());
     }
     r.post(body);
   }
 
+  public void delete(final Queue<String> guids, final Executor executor, final ReadingListDeleteDelegate batchDeleteDelegate) {
+    if (guids.isEmpty()) {
+      batchDeleteDelegate.onBatchDone();
+      return;
+    }
+
+    final ReadingListDeleteDelegate deleteDelegate = new DeleteBatchingDelegate(guids, batchDeleteDelegate, executor);
+
+    delete(guids.poll(), deleteDelegate, -1L);
+  }
+
   public void delete(final String guid, final ReadingListDeleteDelegate delegate, final long ifUnmodifiedSince) {
     final BaseResource r = getRelativeArticleResource(guid);
 
     // If If-Unmodified-Since is provided, and the record has been modified,
     // we'll receive a 412 Precondition Failed.
     // If the record is missing or already deleted, a 404 will be returned.
     // Otherwise, the response will be the deleted record.
     r.delegate = new ReadingListResourceDelegate<ReadingListRecordResponse>(r, auth, ReadingListRecordResponse.FACTORY) {
--- a/mobile/android/base/reading/ReadingListClientRecordFactory.java
+++ b/mobile/android/base/reading/ReadingListClientRecordFactory.java
@@ -5,19 +5,19 @@
 package org.mozilla.gecko.reading;
 
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
 import org.mozilla.gecko.reading.ReadingListRecord.ServerMetadata;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 
 import android.annotation.TargetApi;
+import android.database.AbstractWindowedCursor;
 import android.database.Cursor;
 import android.database.CursorWindow;
-import android.database.sqlite.SQLiteCursor;
 import android.os.Build;
 
 /**
  * This class converts database rows into {@link ClientReadingListRecord}s.
  *
  * In doing so it has to:
  *
  *  * Translate column names.
@@ -127,21 +127,21 @@ public class ReadingListClientRecordFact
     default:
       // Do nothing.
       return;
     }
   }
 
   @SuppressWarnings("deprecation")
   private final void fillGingerbread(ExtendedJSONObject o, Cursor c, String f, int i) {
-    if (!(c instanceof SQLiteCursor)) {
+    if (!(c instanceof AbstractWindowedCursor)) {
       throw new IllegalStateException("Unable to handle cursors that don't have a CursorWindow!");
     }
 
-    final SQLiteCursor sqc = (SQLiteCursor) c;
+    final AbstractWindowedCursor sqc = (AbstractWindowedCursor) c;
     final CursorWindow w = sqc.getWindow();
     final int pos = c.getPosition();
     if (w.isNull(pos, i)) {
       putNull(o, f);
     } else if (w.isString(pos, i)) {
       put(o, f, c.getString(i));
     } else if (w.isLong(pos, i)) {
       put(o, f, c.getLong(i));
--- a/mobile/android/base/reading/ReadingListDeleteDelegate.java
+++ b/mobile/android/base/reading/ReadingListDeleteDelegate.java
@@ -3,17 +3,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.reading;
 
 import org.mozilla.gecko.sync.net.MozResponse;
 
 /**
  * Response delegate for a server DELETE.
- * Only one of these methods will be called, and it will be called precisely once.
+ * Only one of these methods will be called, and it will be called precisely once,
+ * unless batching is used.
  */
 public interface ReadingListDeleteDelegate {
   void onSuccess(ReadingListRecordResponse response, ReadingListRecord record);
   void onPreconditionFailed(String guid, MozResponse response);
   void onRecordMissingOrDeleted(String guid, MozResponse response);
   void onFailure(Exception e);
   void onFailure(MozResponse response);
+  void onBatchDone();
 }
--- a/mobile/android/base/reading/ReadingListStorage.java
+++ b/mobile/android/base/reading/ReadingListStorage.java
@@ -3,13 +3,14 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.reading;
 
 import android.database.Cursor;
 
 public interface ReadingListStorage {
   Cursor getModified();
+  Cursor getDeletedItems();
   Cursor getStatusChanges();
   Cursor getNew();
   Cursor getAll();
   ReadingListChangeAccumulator getChangeAccumulator();
 }
--- a/mobile/android/base/reading/ReadingListSyncAdapter.java
+++ b/mobile/android/base/reading/ReadingListSyncAdapter.java
@@ -77,16 +77,22 @@ public class ReadingListSyncAdapter exte
     @Override
     public void onUnableToSync(Exception e) {
       Logger.warn(LOG_TAG, "Unable to sync.", e);
       cpc.release();
       syncDelegate.handleError(e);
     }
 
     @Override
+    public void onDeletionsUploadComplete() {
+      Logger.debug(LOG_TAG, "Step: onDeletionsUploadComplete");
+      this.result.stats.numEntries += 1;   // TODO: Bug 1140809.
+    }
+
+    @Override
     public void onStatusUploadComplete(Collection<String> uploaded,
                                        Collection<String> failed) {
       Logger.debug(LOG_TAG, "Step: onStatusUploadComplete");
       this.result.stats.numEntries += 1;   // TODO: Bug 1140809.
     }
 
     @Override
     public void onNewItemUploadComplete(Collection<String> uploaded,
--- a/mobile/android/base/reading/ReadingListSynchronizer.java
+++ b/mobile/android/base/reading/ReadingListSynchronizer.java
@@ -171,16 +171,90 @@ public class ReadingListSynchronizer {
           next.fail(e);
         }
         return;
       }
       next.fail();
     }
   }
 
+  private static class DeletionUploadDelegate implements ReadingListDeleteDelegate {
+    private final ReadingListChangeAccumulator acc;
+    private final StageDelegate next;
+
+    DeletionUploadDelegate(ReadingListChangeAccumulator acc, StageDelegate next) {
+      this.acc = acc;
+      this.next = next;
+    }
+
+    @Override
+    public void onBatchDone() {
+      try {
+        acc.finish();
+      } catch (Exception e) {
+        next.fail(e);
+        return;
+      }
+
+      next.next();
+    }
+
+    @Override
+    public void onSuccess(ReadingListRecordResponse response,
+                          ReadingListRecord record) {
+      Logger.debug(LOG_TAG, "Tracking uploaded deletion " + record.getGUID());
+      acc.addDeletion(record.getGUID());
+    }
+
+    @Override
+    public void onPreconditionFailed(String guid, MozResponse response) {
+      // Should never happen.
+    }
+
+    @Override
+    public void onRecordMissingOrDeleted(String guid, MozResponse response) {
+      // Great!
+      Logger.debug(LOG_TAG, "Tracking redundant deletion " + guid);
+      acc.addDeletion(guid);
+    }
+
+    @Override
+    public void onFailure(Exception e) {
+      // Ignore.
+    }
+
+    @Override
+    public void onFailure(MozResponse response) {
+      // Ignore.
+    }
+  }
+
+
+  private Queue<String> collectDeletedIDsFromCursor(Cursor cursor) {
+    try {
+      final Queue<String> toDelete = new LinkedList<>();
+
+      final int columnGUID = cursor.getColumnIndexOrThrow(ReadingListItems.GUID);
+
+      while (cursor.moveToNext()) {
+        final String guid = cursor.getString(columnGUID);
+        if (guid == null) {
+          // Nothing we can do here.
+          continue;
+        }
+
+        toDelete.add(guid);
+      }
+
+      return toDelete;
+    } finally {
+      cursor.close();
+    }
+  }
+
   private static class StatusUploadDelegate implements ReadingListRecordUploadDelegate {
     private final ReadingListChangeAccumulator acc;
 
     public volatile int failures = 0;
     private final StageDelegate next;
 
     StatusUploadDelegate(ReadingListChangeAccumulator acc, StageDelegate next) {
       this.acc = acc;
@@ -457,16 +531,46 @@ public class ReadingListSynchronizer {
         toUpload.add(record);
       }
       return toUpload;
     } finally {
       cursor.close();
     }
   }
 
+  protected void uploadDeletions(final StageDelegate delegate) {
+    try {
+      final Cursor cursor = local.getDeletedItems();
+
+      if (cursor == null) {
+        delegate.fail(new RuntimeException("Unable to get unread item cursor."));
+        return;
+      }
+
+      final Queue<String> toDelete = collectDeletedIDsFromCursor(cursor);
+
+      // Nothing to do.
+      if (toDelete.isEmpty()) {
+        Logger.debug(LOG_TAG, "No new deletions to upload. Skipping.");
+        delegate.next();
+        return;
+      } else {
+        Logger.debug(LOG_TAG, "Deleting " + toDelete.size() + " records from the server.");
+      }
+
+      final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator();
+      final DeletionUploadDelegate deleteDelegate = new DeletionUploadDelegate(acc, delegate);
+
+      // Don't send I-U-S; we're happy for the client to win, because this is a one-way state change.
+      this.remote.delete(toDelete, executor, deleteDelegate);
+    } catch (Exception e) {
+      delegate.fail(e);
+    }
+  }
+
   // N.B., status changes for items that haven't been uploaded yet are dealt with in
   // uploadNewItems.
   protected void uploadUnreadChanges(final StageDelegate delegate) {
     try {
       final Cursor cursor = local.getStatusChanges();
 
       if (cursor == null) {
         delegate.fail(new RuntimeException("Unable to get unread item cursor."));
@@ -692,53 +796,68 @@ public class ReadingListSynchronizer {
     try {
       remote.getAll(spec, recordDelegate, since);
     } catch (URISyntaxException e) {
       delegate.fail(e);
     }
   }
 
   /**
-   * Upload unread changes, then upload new items, then call `done`.
+   * Upload deletions and unread changes, then upload new items, then call `done`.
    * Substantially modified records are uploaded last.
    *
    * @param syncDelegate only used for status callbacks.
    */
   private void syncUp(final ReadingListSynchronizerDelegate syncDelegate, final StageDelegate done) {
-    // Second.
+    // Third.
     final StageDelegate onNewItemsUploaded = new NextDelegate(executor) {
       @Override
       public void doNext() {
         syncDelegate.onNewItemUploadComplete(null, null);
         done.next();
       }
 
       @Override
       public void doFail(Exception e) {
         done.fail(e);
       }
     };
 
-    // First.
+    // Second.
     final StageDelegate onUnreadChangesUploaded = new NextDelegate(executor) {
       @Override
       public void doNext() {
         syncDelegate.onStatusUploadComplete(null, null);
         uploadNewItems(onNewItemsUploaded);
       }
 
       @Override
       public void doFail(Exception e) {
         Logger.warn(LOG_TAG, "Uploading unread changes failed.", e);
         done.fail(e);
       }
     };
 
+    // First.
+    final StageDelegate onDeletionsUploaded = new NextDelegate(executor) {
+      @Override
+      public void doNext() {
+        syncDelegate.onDeletionsUploadComplete();
+        uploadUnreadChanges(onUnreadChangesUploaded);
+      }
+
+      @Override
+      public void doFail(Exception e) {
+        Logger.warn(LOG_TAG, "Uploading deletions failed.", e);
+        done.fail(e);
+      }
+    };
+
     try {
-      uploadUnreadChanges(onUnreadChangesUploaded);
+      uploadDeletions(onDeletionsUploaded);
     } catch (Exception ee) {
       done.fail(ee);
     }
   }
 
 
   /**
    * Do an upload-only sync.
--- a/mobile/android/base/reading/ReadingListSynchronizerDelegate.java
+++ b/mobile/android/base/reading/ReadingListSynchronizerDelegate.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.reading;
 import java.util.Collection;
 
 public interface ReadingListSynchronizerDelegate {
   // Called on failure.
   void onUnableToSync(Exception e);
 
   // These are called sequentially, or not at all
   // if a failure occurs.
+  void onDeletionsUploadComplete();
   void onStatusUploadComplete(Collection<String> uploaded, Collection<String> failed);
   void onNewItemUploadComplete(Collection<String> uploaded, Collection<String> failed);
   void onDownloadComplete();
   void onModifiedUploadComplete();
 
   // If no failure occurred, called at the end.
   void onComplete();
 }
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -308,30 +308,29 @@ Tester.prototype = {
       }
     }
 
     // Make sure the window is raised before each test.
     this.SimpleTest.waitForFocus(aCallback);
   },
 
   finish: function Tester_finish(aSkipSummary) {
-    TabDestroyObserver.destroy();
-
     this.Promise.Debugging.flushUncaughtErrors();
 
     var passCount = this.tests.reduce(function(a, f) a + f.passCount, 0);
     var failCount = this.tests.reduce(function(a, f) a + f.failCount, 0);
     var todoCount = this.tests.reduce(function(a, f) a + f.todoCount, 0);
 
     if (this.repeat > 0) {
       --this.repeat;
       this.currentTestIndex = -1;
       this.nextTest();
     }
     else{
+      TabDestroyObserver.destroy();
       Services.console.unregisterListener(this);
       Services.obs.removeObserver(this, "chrome-document-global-created");
       Services.obs.removeObserver(this, "content-document-global-created");
       this.Promise.Debugging.clearUncaughtErrorObservers();
       this._treatUncaughtRejectionsAsFailures = false;
 
       // In the main process, we print the ShutdownLeaksCollector message here.
       let pid = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processID;
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -211,16 +211,21 @@
     "description": "Time spent on one asynchronous SnowWhite freeing (ms)"
   },
   "DEVICE_RESET_REASON": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 10,
     "description": "GPU Device Reset Reason (ok, hung, removed, reset, internal error, invalid call, out of memory)"
   },
+  "FETCH_IS_MAINTHREAD": {
+    "expires_in_version": "50",
+    "kind": "boolean",
+    "description": "Was Fetch request initiated from the main thread?"
+  },
   "FORGET_SKIPPABLE_MAX": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "10000",
     "n_buckets": 50,
     "description": "Max time spent on one forget skippable (ms)"
   },
   "GC_REASON_2": {
@@ -7271,16 +7276,21 @@
   },
   "SERVICE_WORKER_REGISTRATION_LOADING": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "5000",
     "n_buckets": 20,
     "description": "Tracking how ServiceWorkerRegistrar loads data before the first content is shown"
   },
+  "SERVICE_WORKER_REQUEST_PASSTHROUGH": {
+    "expires_in_version": "50",
+    "kind": "boolean",
+    "description": "Intercepted fetch sending back same Request object."
+  },
   "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Stores 1 if generating a call URL succeeded, and 0 if it failed."
   },
   "LOOP_CLIENT_CALL_URL_SHARED": {
     "expires_in_version": "never",
     "kind": "boolean",
--- a/toolkit/content/widgets/tabbox.xml
+++ b/toolkit/content/widgets/tabbox.xml
@@ -696,17 +696,17 @@
 
   <binding id="tab" display="xul:button" role="xul:tab"
            extends="chrome://global/content/bindings/general.xml#control-item">
     <resources>
       <stylesheet src="chrome://global/skin/tabbox.css"/>
     </resources>
 
     <content>
-      <xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected" flex="1">
+      <xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected,visuallyselected" flex="1">
         <xul:image class="tab-icon"
                    xbl:inherits="validate,src=image"
                    role="presentation"/>
         <xul:label class="tab-text"
                    xbl:inherits="value=label,accesskey,crop,disabled"
                    flex="1"
                    role="presentation"/>
       </xul:hbox>
@@ -722,23 +722,23 @@
             return null;
           ]]>
         </getter>
       </property>
 
       <property name="selected" readonly="true"
                 onget="return this.getAttribute('selected') == 'true';"/>
 
-      <property name="_selected">
+      <property name="_visuallySelected">
         <setter>
           <![CDATA[
           if (val)
-            this.setAttribute("selected", "true");
+            this.setAttribute("visuallyselected", "true");
           else
-            this.removeAttribute("selected");
+            this.removeAttribute("visuallyselected");
 
           if (this.previousSibling && this.previousSibling.localName == "tab") {
             if (val)
               this.previousSibling.setAttribute("beforeselected", "true");
             else
               this.previousSibling.removeAttribute("beforeselected");
             this.removeAttribute("first-tab");
           }
@@ -754,16 +754,40 @@
           }
           else
             this.setAttribute("last-tab", "true");
           return val;
         ]]>
         </setter>
       </property>
 
+      <property name="_logicallySelected">
+        <setter>
+          <![CDATA[
+          if (val)
+            this.setAttribute("selected", "true");
+          else
+            this.removeAttribute("selected");
+
+          return val;
+          ]]>
+        </setter>
+      </property>
+
+      <property name="_selected">
+        <setter>
+          <![CDATA[
+          // If our tab switching is synchronous, then logical selection = visual selection
+          this._logicallySelected = val;
+          this._visuallySelected = val;
+          return val;
+          ]]>
+        </setter>
+      </property>
+
       <property name="linkedPanel" onget="return this.getAttribute('linkedpanel')"
                                    onset="this.setAttribute('linkedpanel', val); return val;"/>
 
       <field name="arrowKeysShouldWrap" readonly="true">
 #ifdef XP_MACOSX
         true
 #else
         false
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -2401,17 +2401,39 @@ var WalkerActor = protocol.ActorClass({
       for (let node of this._orphaned) {
         // Release the orphaned node.  Nodes or children that have been
         // retained will be moved to this._retainedOrphans.
         this.releaseNode(node);
       }
       this._orphaned = new Set();
     }
 
-    return pending;
+
+    // Clear out any duplicate attribute mutations before sending them over
+    // the protocol.  Keep only the most recent change for each attribute.
+    let targetMap = {};
+    let filtered = pending.reverse().filter(mutation => {
+      if (mutation.type === "attributes") {
+        if (!targetMap[mutation.target]) {
+          targetMap[mutation.target] = {};
+        }
+        let attributesForTarget = targetMap[mutation.target];
+
+        if (attributesForTarget[mutation.attributeName]) {
+          // Since the array was reversed, if we've seen this attribute already
+          // then this one is a duplicate and can be skipped.
+          return false;
+        }
+
+        attributesForTarget[mutation.attributeName] = true;
+      }
+      return true;
+    }).reverse();
+
+    return filtered;
   }, {
     request: {
       cleanup: Option(0)
     },
     response: {
       mutations: RetVal("array:dommutation")
     }
   }),
--- a/toolkit/devtools/server/actors/profiler.js
+++ b/toolkit/devtools/server/actors/profiler.js
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {Cc, Ci, Cu, Cr} = require("chrome");
 const Services = require("Services");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils.js");
 
-let DEFAULT_PROFILER_ENTRIES = 1000000;
+let DEFAULT_PROFILER_ENTRIES = 10000000;
 let DEFAULT_PROFILER_INTERVAL = 1;
 let DEFAULT_PROFILER_FEATURES = ["js"];
 let DEFAULT_PROFILER_THREADFILTERS = ["GeckoMain"];
 
 /**
  * The nsIProfiler is target agnostic and interacts with the whole platform.
  * Therefore, special care needs to be given to make sure different actor
  * consumers (i.e. "toolboxes") don't interfere with each other.
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -66,16 +66,17 @@ function WebConsoleActor(aConnection, aP
 
   this._prefs = {};
 
   this.dbg = this.parentActor.makeDebugger();
 
   this._netEvents = new Map();
   this._gripDepth = 0;
   this._listeners = new Set();
+  this._lastConsoleInputEvaluation = undefined;
 
   this._onWillNavigate = this._onWillNavigate.bind(this);
   this._onChangedToplevelDocument = this._onChangedToplevelDocument.bind(this);
   events.on(this.parentActor, "changed-toplevel-document", this._onChangedToplevelDocument);
   this._onObserverNotification = this._onObserverNotification.bind(this);
   if (this.parentActor.isRootActor) {
     Services.obs.addObserver(this._onObserverNotification,
                              "last-pb-context-exited", false);
@@ -353,16 +354,17 @@ WebConsoleActor.prototype =
     this.conn.removeActorPool(this._actorPool);
     if (this.parentActor.isRootActor) {
       Services.obs.removeObserver(this._onObserverNotification,
                                   "last-pb-context-exited");
     }
     this._actorPool = null;
 
     this._jstermHelpersCache = null;
+    this._lastConsoleInputEvaluation = null;
     this._evalWindow = null;
     this._netEvents.clear();
     this.dbg.enabled = false;
     this.dbg = null;
     this.conn = null;
   },
 
   /**
@@ -498,16 +500,27 @@ WebConsoleActor.prototype =
    * @param object aActor
    *        The actor instance you want to release.
    */
   releaseActor: function WCA_releaseActor(aActor)
   {
     this._actorPool.removeActor(aActor.actorID);
   },
 
+  /**
+   * Returns the latest web console input evaluation.
+   * This is undefined if no evaluations have been completed.
+   *
+   * @return object
+   */
+  getLastConsoleInputEvaluation: function WCU_getLastConsoleInputEvaluation()
+  {
+    return this._lastConsoleInputEvaluation;
+  },
+
   //////////////////
   // Request handlers for known packet types.
   //////////////////
 
   /**
    * Handler for the "startListeners" request.
    *
    * @param object aRequest
@@ -811,16 +824,18 @@ WebConsoleActor.prototype =
     // the console should remain functional.
     let resultGrip;
     try {
       resultGrip = this.createValueGrip(result);
     } catch (e) {
       errorMessage = e;
     }
 
+    this._lastConsoleInputEvaluation = result;
+
     return {
       from: this.actorID,
       input: input,
       result: resultGrip,
       timestamp: timestamp,
       exception: errorGrip,
       exceptionMessage: this._createStringGrip(errorMessage),
       helperResult: helperResult,
--- a/toolkit/devtools/server/tests/mochitest/test_inspector-mutations-attr.html
+++ b/toolkit/devtools/server/tests/mochitest/test_inspector-mutations-attr.html
@@ -40,20 +40,22 @@ addTest(function setup() {
     }).then(runNextTest));
   });
 });
 
 addTest(setupAttrTest);
 addTest(testAddAttribute);
 addTest(testChangeAttribute);
 addTest(testRemoveAttribute);
+addTest(testQueuedMutations);
 addTest(setupFrameAttrTest);
 addTest(testAddAttribute);
 addTest(testChangeAttribute);
 addTest(testRemoveAttribute);
+addTest(testQueuedMutations);
 
 function setupAttrTest() {
   attrNode = gInspectee.querySelector("#a")
   promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(node => {
     attrFront = node;
   }).then(runNextTest));
 }
 
@@ -80,35 +82,70 @@ function testAddAttribute() {
     is(attrFront.attributes.length, 3, "Should have id and two new attributes.");
     is(attrFront.getAttribute("data-newattr"), "newvalue", "Node front should have the first new attribute");
     is(attrFront.getAttribute("data-newattr2"), "newvalue", "Node front should have the second new attribute.");
     runNextTest();
   });
 }
 
 function testChangeAttribute() {
-  attrNode.setAttribute("data-newattr", "changedvalue");
-  gWalker.once("mutations", () => {
+  attrNode.setAttribute("data-newattr", "changedvalue1");
+  attrNode.setAttribute("data-newattr", "changedvalue2");
+  attrNode.setAttribute("data-newattr", "changedvalue3");
+  gWalker.once("mutations", mutations => {
+    is(mutations.length, 1, "Only one mutation is sent for multiple queued attribute changes");
     is(attrFront.attributes.length, 3, "Should have id and two new attributes.");
-    is(attrFront.getAttribute("data-newattr"), "changedvalue", "Node front should have the changed first value");
+    is(attrFront.getAttribute("data-newattr"), "changedvalue3", "Node front should have the changed first value");
     is(attrFront.getAttribute("data-newattr2"), "newvalue", "Second value should remain unchanged.");
     runNextTest();
   });
 }
 
 function testRemoveAttribute() {
   attrNode.removeAttribute("data-newattr2");
   gWalker.once("mutations", () => {
     is(attrFront.attributes.length, 2, "Should have id and one remaining attribute.");
-    is(attrFront.getAttribute("data-newattr"), "changedvalue", "Node front should still have the first value");
+    is(attrFront.getAttribute("data-newattr"), "changedvalue3", "Node front should still have the first value");
     ok(!attrFront.hasAttribute("data-newattr2"), "Second value should be removed.");
     runNextTest();
   })
 }
 
+function testQueuedMutations() {
+  // All modifications to each attribute should be queued in one mutation event.
+
+  attrNode.removeAttribute("data-newattr");
+  attrNode.setAttribute("data-newattr", "1");
+  attrNode.removeAttribute("data-newattr");
+  attrNode.setAttribute("data-newattr", "2");
+  attrNode.removeAttribute("data-newattr");
+
+  for (var i = 0; i <= 1000; i++) {
+    attrNode.setAttribute("data-newattr2", i);
+  }
+
+  attrNode.removeAttribute("data-newattr3");
+  attrNode.setAttribute("data-newattr3", "1");
+  attrNode.removeAttribute("data-newattr3");
+  attrNode.setAttribute("data-newattr3", "2");
+  attrNode.removeAttribute("data-newattr3");
+  attrNode.setAttribute("data-newattr3", "3");
+
+  gWalker.once("mutations", mutations => {
+    is(mutations.length, 3, "Only one mutation each is sent for multiple queued attribute changes");
+    is(attrFront.attributes.length, 3, "Should have id, data-newattr2, and data-newattr3.");
+
+    is(attrFront.getAttribute("data-newattr2"), "1000", "Node front should still have the correct value");
+    is(attrFront.getAttribute("data-newattr3"), "3", "Node front should still have the correct value");
+    ok(!attrFront.hasAttribute("data-newattr"), "Attribute value should be removed.");
+
+    runNextTest();
+  })
+}
+
 addTest(function cleanup() {
   delete gInspectee;
   delete gWalker;
   delete gClient;
   runNextTest();
 });
 
 
--- a/toolkit/devtools/webconsole/network-monitor.js
+++ b/toolkit/devtools/webconsole/network-monitor.js
@@ -748,23 +748,18 @@ NetworkMonitor.prototype = {
     let event = {};
     event.startedDateTime = new Date(Math.round(aTimestamp / 1000)).toISOString();
     event.headersSize = aExtraStringData.length;
     event.method = aChannel.requestMethod;
     event.url = aChannel.URI.spec;
     event.private = httpActivity.private;
 
     // Determine if this is an XHR request.
-    try {
-      let callbacks = aChannel.notificationCallbacks;
-      let xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null;
-      httpActivity.isXHR = event.isXHR = !!xhrRequest;
-    } catch (e) {
-      httpActivity.isXHR = event.isXHR = false;
-    }
+    httpActivity.isXHR = event.isXHR =
+        (aChannel.loadInfo.contentPolicyType === Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST);
 
     // Determine the HTTP version.
     aChannel.QueryInterface(Ci.nsIHttpChannelInternal);
     aChannel.getRequestVersion(httpVersionMaj, httpVersionMin);
 
     event.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
                                   httpVersionMin.value;
 
--- a/toolkit/devtools/webconsole/test/chrome.ini
+++ b/toolkit/devtools/webconsole/test/chrome.ini
@@ -10,20 +10,21 @@ support-files =
 [test_basics.html]
 [test_bug819670_getter_throws.html]
 [test_cached_messages.html]
 [test_consoleapi.html]
 [test_consoleapi_innerID.html]
 [test_file_uri.html]
 [test_reflow.html]
 [test_jsterm.html]
+[test_jsterm_cd_iframe.html]
+[test_jsterm_last_result.html]
 [test_network_get.html]
 [test_network_longstring.html]
 [test_network_post.html]
 [test_network_security-hpkp.html]
 [test_network_security-hsts.html]
 [test_nsiconsolemessage.html]
 [test_object_actor.html]
 [test_object_actor_native_getters.html]
 [test_object_actor_native_getters_lenient_this.html]
 [test_page_errors.html]
 [test_throw.html]
-[test_jsterm_cd_iframe.html]
--- a/toolkit/devtools/webconsole/test/common.js
+++ b/toolkit/devtools/webconsole/test/common.js
@@ -3,16 +3,20 @@
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 Cu.import("resource://gre/modules/Services.jsm");
+const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+
+// This gives logging to stdout for tests
+var {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
 
 let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
 let WebConsoleUtils = devtools.require("devtools/toolkit/webconsole/utils").Utils;
 
 let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
                           .getService(Ci.nsIConsoleAPIStorage);
 
 let {ConsoleServiceListener, ConsoleAPIListener} =
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_jsterm_last_result.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for the $_ getter</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript;version=1.8" src="common.js"></script>
+  <!-- Any copyright is dedicated to the Public Domain.
+     - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the $_ getter</p>
+
+<iframe id="content-iframe" src="http://example.com/chrome/toolkit/devtools/webconsole/test/sandboxed_iframe.html"></iframe>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+let gState;
+
+function evaluateJS(input, callback) {
+  return new Promise((resolve, reject) => {
+    gState.client.evaluateJSAsync(input, response => {
+      if (callback) {
+        callback(response);
+      }
+      resolve(response);
+    });
+  });
+}
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+  attachConsole([], state => {
+    gState = state;
+    let tests = [checkUndefinedResult,checkAdditionResult,checkObjectResult];
+    runTests(tests, testEnd);
+  }, true);
+}
+
+let checkUndefinedResult = Task.async(function*() {
+  info ("$_ returns undefined if nothing has evaluated yet");
+  let response = yield evaluateJS("$_");
+  basicResultCheck(response, "$_", undefined);
+  nextTest();
+});
+
+let checkAdditionResult = Task.async(function*() {
+  info ("$_ returns last value and performs basic arithmetic");
+  let response = yield evaluateJS("2+2");
+  basicResultCheck(response, "2+2", 4);
+
+  response = yield evaluateJS("$_");
+  basicResultCheck(response, "$_", 4);
+
+  response = yield evaluateJS("$_ + 2");
+  basicResultCheck(response, "$_ + 2", 6);
+
+  response = yield evaluateJS("$_ + 4");
+  basicResultCheck(response, "$_ + 4", 10);
+
+  nextTest();
+});
+
+let checkObjectResult = Task.async(function*() {
+  info ("$_ has correct references to objects");
+
+  let response = yield evaluateJS("var foo = {bar:1}; foo;");
+  basicResultCheck(response, "var foo = {bar:1}; foo;", {
+    type: "object",
+    class: "Object",
+    actor: /[a-z]/,
+  });
+  checkObject(response.result.preview.ownProperties, {
+    bar: {
+      value: 1
+    }
+  });
+
+  response = yield evaluateJS("$_");
+  basicResultCheck(response, "$_", {
+    type: "object",
+    class: "Object",
+    actor: /[a-z]/,
+  });
+  checkObject(response.result.preview.ownProperties, {
+    bar: {
+      value: 1
+    }
+  });
+
+  top.foo.bar = 2;
+
+  response = yield evaluateJS("$_");
+  basicResultCheck(response, "$_", {
+    type: "object",
+    class: "Object",
+    actor: /[a-z]/,
+  });
+  checkObject(response.result.preview.ownProperties, {
+    bar: {
+      value: 2
+    }
+  });
+
+  nextTest();
+});
+
+function basicResultCheck(response, input, output) {
+  checkObject(response, {
+    from: gState.actor,
+    input: input,
+    result: output,
+  });
+  ok(!response.exception, "no eval exception");
+  ok(!response.helperResult, "no helper result");
+}
+
+function testEnd()
+{
+  closeDebugger(gState, function() {
+    gState = null;
+    SimpleTest.finish();
+  });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
--- a/toolkit/devtools/webconsole/utils.js
+++ b/toolkit/devtools/webconsole/utils.js
@@ -1545,16 +1545,30 @@ function JSTermHelpers(aOwner)
    *         Returns the result of document.querySelectorAll(aSelector).
    */
   aOwner.sandbox.$$ = function JSTH_$$(aSelector)
   {
     return aOwner.window.document.querySelectorAll(aSelector);
   };
 
   /**
+   * Returns the result of the last console input evaluation
+   *
+   * @return object|undefined
+   * Returns last console evaluation or undefined
+   */
+  Object.defineProperty(aOwner.sandbox, "$_", {
+    get: function() {
+      return aOwner.consoleActor.getLastConsoleInputEvaluation();
+    },
+    enumerable: true,
+    configurable: true
+  });
+
+  /**
    * Runs an xPath query and returns all matched nodes.
    *
    * @param string aXPath
    *        xPath search query to execute.
    * @param [optional] nsIDOMNode aContext
    *        Context to run the xPath query on. Uses window.document if not set.
    * @return array of nsIDOMNode
    */
--- a/toolkit/themes/faststripe/global/tabbox.css
+++ b/toolkit/themes/faststripe/global/tabbox.css
@@ -46,40 +46,40 @@ tab:-moz-locale-dir(rtl) {
   border-bottom-left-radius: 1px;
   border-bottom-right-radius: 0px;
 }
 
 .tab-text {
   margin: 0 !important;
 }
 
-tab[selected="true"] {
+tab[visuallyselected="true"] {
   margin-top: 0;
   border-bottom-color: transparent;
   padding: 1px 6px 4px 6px;
 }
 
 tab:focus > .tab-middle {
   /* Don't specify the outline-color, we should always use initial value. */
   outline: 1px dotted;
 }
 
 tab[beforeselected="true"]:not(:-moz-locale-dir(rtl)),
-tab[selected="true"]:-moz-locale-dir(rtl) + tab {
+tab[visuallyselected="true"]:-moz-locale-dir(rtl) + tab {
   border-right: none;
   border-top-right-radius: 0;
 }
 
-tab[selected="true"]:not(:-moz-locale-dir(rtl)) + tab,
+tab[visuallyselected="true"]:not(:-moz-locale-dir(rtl)) + tab,
 tab[beforeselected="true"]:-moz-locale-dir(rtl) {
   border-left: none;
   border-top-left-radius: 0;
 }
 
-tab:first-of-type[selected="true"] {
+tab:first-of-type[visuallyselected="true"] {
   padding-right: 5px;
   padding-left: 5px;
 }
 
 /* ::::: tab-bottom ::::::::::
    :: Tabs that are attached to the bottom of a panel, but not necessarily
    :: a tabpanels.
    ::::: */
@@ -91,28 +91,28 @@ tab:first-of-type[selected="true"] {
   border-bottom: 1px solid #555555;
   border-top-left-radius: 0;
   border-top-right-radius: 0;
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 2px;
   padding: 2px 4px 1px 4px;
 }
 
-.tab-bottom[selected="true"] {
+.tab-bottom[visuallyselected="true"] {
   margin-bottom: 0;
   -moz-border-top-colors: -moz-Dialog;
   padding: 4px 6px 1px 6px;
 }
 
 .tab-bottom[beforeselected="true"]:not(:-moz-locale-dir(rtl)),
-.tab-bottom[selected="true"]:-moz-locale-dir(rtl) + .tab-bottom {
+.tab-bottom[visuallyselected="true"]:-moz-locale-dir(rtl) + .tab-bottom {
   border-bottom-right-radius: 0;
 }
 
-.tab-bottom[selected="true"]:not(:-moz-locale-dir(rtl)) + .tab-bottom,
+.tab-bottom[visuallyselected="true"]:not(:-moz-locale-dir(rtl)) + .tab-bottom,
 .tab-bottom[beforeselected="true"]:-moz-locale-dir(rtl) {
   border-bottom-left-radius: 0;
 }
 
 /* ::::: tabs-bottom ::::: */
 
 .tabs-bottom > .tabs-left,
 .tabs-bottom > .tabs-right {
--- a/toolkit/themes/linux/global/in-content/common.css
+++ b/toolkit/themes/linux/global/in-content/common.css
@@ -1,15 +1,15 @@
 /* - 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 ../../../shared/in-content/common.inc.css
 
-xul|tab[selected] {
+xul|tab[visuallyselected] {
   /* Override styles for tab[selected] from
      toolkit/themes/linux/global/tabbox.css */
   margin-bottom: 0;
   border-bottom-left-radius: 0;
   border-bottom-right-radius: 0;
 }
 
 xul|button,
--- a/toolkit/themes/linux/global/tabbox.css
+++ b/toolkit/themes/linux/global/tabbox.css
@@ -43,17 +43,17 @@ tab {
   -moz-border-left-colors: ThreeDHighlight ThreeDLightShadow;
   border-top-left-radius: 2px;
   border-top-right-radius: 2px;
   padding: 3px 4px;
   background-color: -moz-Dialog;
   color: -moz-DialogText;
 }
 
-tab[selected="true"] {
+tab[visuallyselected="true"] {
   z-index: 1;
   margin-top: 0;
   margin-bottom: -2px;
   border-bottom-left-radius: 3px;
   border-bottom-right-radius: 3px;
   padding-top: 4px;
   padding-bottom: 6px;
 }
@@ -78,14 +78,14 @@ tab + tab {
   border-bottom: 2px solid;
   -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow;
   border-top-left-radius: 0;
   border-top-right-radius: 0;
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 2px;
 }
 
-.tab-bottom[selected="true"] {
+.tab-bottom[visuallyselected="true"] {
   margin-bottom: 0;
   margin-top: -2px;
   padding-top: 6px;
   padding-bottom: 4px;
 }
--- a/toolkit/themes/osx/global/in-content/common.css
+++ b/toolkit/themes/osx/global/in-content/common.css
@@ -5,17 +5,17 @@
 %include ../shared.inc
 %include ../../../shared/in-content/common.inc.css
 
 xul|tabs {
   padding-right: 0;
   padding-left: 0;
 }
 
-xul|tab[selected] {
+xul|tab[visuallyselected] {
   text-shadow: none;
 }
 
 xul|button,
 html|button,
 xul|colorpicker[type="button"],
 xul|menulist {
   margin-top: 3px;
@@ -90,9 +90,9 @@ xul|radio[focused="true"] > .radio-check
 xul|tab:-moz-focusring > .tab-middle > .tab-text {
 	outline: 2px solid rgba(0,149,221,0.5);
 	outline-offset: 1px;
 	-moz-outline-radius: 2px;
 }
 
 xul|radio[focused="true"] > .radio-check {
 	-moz-outline-radius: 100%;
-}
\ No newline at end of file
+}
--- a/toolkit/themes/osx/global/tabbox.css
+++ b/toolkit/themes/osx/global/tabbox.css
@@ -46,17 +46,17 @@ tab:first-of-type {
   -moz-padding-start: 2px;
 }
 
 tab:last-of-type {
   -moz-padding-end: 2px;
 }
 
 @media (-moz-mac-lion-theme) {
-  tab[selected="true"] {
+  tab[visuallyselected="true"] {
     color: #FFF;
     text-shadow: rgba(0, 0, 0, 0.4) 0 1px;
   }
 }
 
 .tab-middle {
   padding: 1px 6px 2px;
 }
@@ -99,24 +99,24 @@ tabs.tabs-bottom > tab {
   -moz-border-end: 1px solid rgba(0, 0, 0, 0.19);
 }
 
 tabbox.tabs-bottom > tabs > tab > .tab-middle,
 tabs.tabs-bottom > tab > .tab-middle {
   padding: 1px 2px 0 2px;
 }
 
-tabbox.tabs-bottom > tabs > tab:not([selected=true]):hover,
-tabs.tabs-bottom > tab:not([selected=true]):hover {
+tabbox.tabs-bottom > tabs > tab:not([visuallyselected=true]):hover,
+tabs.tabs-bottom > tab:not([visuallyselected=true]):hover {
   background-color: rgba(0, 0, 0, 0.1);
   -moz-border-end-color: rgba(0, 0, 0, 0.1);
 }
 
-tabbox.tabs-bottom > tabs > tab[selected=true],
-tabs.tabs-bottom > tab[selected=true] {
+tabbox.tabs-bottom > tabs > tab[visuallyselected=true],
+tabs.tabs-bottom > tab[visuallyselected=true] {
   color: #000;
   text-shadow: none;
   border: solid #888;
   border-width: 0 2px 2px;
   border-radius: 2px;
   -moz-border-left-colors: rgba(0, 0, 0, 0.08) #888;
   -moz-border-right-colors: rgba(0, 0, 0, 0.08) #888;
   -moz-border-bottom-colors: rgba(0, 0, 0, 0.08) #888;
@@ -127,24 +127,24 @@ tabs.tabs-bottom > tab[selected=true] {
 }
 
 tabbox.tabs-bottom > tabs > tab[beforeselected=true],
 tabs.tabs-bottom > tab[beforeselected=true] {
   -moz-border-end-color: transparent;
   -moz-margin-end: -2px;
 }
 
-tabbox.tabs-bottom > tabs > tab:first-of-type:not([selected=true]),
-tabs.tabs-bottom > tab:first-of-type:not([selected=true]) {
+tabbox.tabs-bottom > tabs > tab:first-of-type:not([visuallyselected=true]),
+tabs.tabs-bottom > tab:first-of-type:not([visuallyselected=true]) {
   -moz-border-start: 4px solid transparent;
 }
 
-tabbox.tabs-bottom > tabs > tab:first-of-type[selected=true],
-tabs.tabs-bottom > tab:first-of-type[selected=true] {
+tabbox.tabs-bottom > tabs > tab:first-of-type[visuallyselected=true],
+tabs.tabs-bottom > tab:first-of-type[visuallyselected=true] {
   -moz-margin-start: 2px;
 }
 
 tabbox.tabs-bottom,
 tabbox.tabs-bottom > tabpanels,
-tabbox.tabs-bottom > tabs > tab[selected=true] > .tab-middle,
-tabs.tabs-bottom > tab[selected=true] > .tab-middle {
+tabbox.tabs-bottom > tabs > tab[visuallyselected=true] > .tab-middle,
+tabs.tabs-bottom > tab[visuallyselected=true] > .tab-middle {
   -moz-appearance: dialog;
 }
--- a/toolkit/themes/windows/global/tabbox.css
+++ b/toolkit/themes/windows/global/tabbox.css
@@ -55,40 +55,40 @@ tab:-moz-locale-dir(rtl) {
   border-bottom-left-radius: 1px;
   border-bottom-right-radius: 0px;
 }
 
 .tab-text {
   margin: 0 !important;
 }
 
-tab[selected="true"] {
+tab[visuallyselected="true"] {
   margin-top: 0;
   border-bottom-color: transparent;
   padding: 1px 6px 4px 6px;
 }
 
 tab:-moz-focusring > .tab-middle {
   /* Don't specify the outline-color, we should always use initial value. */
   outline: 1px dotted;
 }
 
 tab[beforeselected="true"]:-moz-locale-dir(ltr),
-tab[selected="true"]:-moz-locale-dir(rtl) + tab {
+tab[visuallyselected="true"]:-moz-locale-dir(rtl) + tab {
   border-right: none;
   border-top-right-radius: 0;
 }
 
-tab[selected="true"]:-moz-locale-dir(ltr) + tab,
+tab[visuallyselected="true"]:-moz-locale-dir(ltr) + tab,
 tab[beforeselected="true"]:-moz-locale-dir(rtl) {
   border-left: none;
   border-top-left-radius: 0;
 }
 
-tab:first-of-type[selected="true"] {
+tab:first-of-type[visuallyselected="true"] {
   padding-right: 5px;
   padding-left: 5px;
 }
 
 /* ::::: tab-bottom ::::::::::
    :: Tabs that are attached to the bottom of a panel, but not necessarily
    :: a tabpanels.
    ::::: */
@@ -102,28 +102,28 @@ tab:first-of-type[selected="true"] {
   -moz-border-bottom-colors: ThreeDShadow ThreeDLightShadow;
   border-top-left-radius: 0;
   border-top-right-radius: 0;
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 2px;
   padding: 2px 4px 1px 4px;
 }
 
-.tab-bottom[selected="true"] {
+.tab-bottom[visuallyselected="true"] {
   margin-bottom: 0;
   -moz-border-top-colors: -moz-Dialog;
   padding: 4px 6px 1px 6px;
 }
 
 .tab-bottom[beforeselected="true"]:-moz-locale-dir(ltr),
-.tab-bottom[selected="true"]:-moz-locale-dir(rtl) + .tab-bottom {
+.tab-bottom[visuallyselected="true"]:-moz-locale-dir(rtl) + .tab-bottom {
   border-bottom-right-radius: 0;
 }
 
-.tab-bottom[selected="true"]:-moz-locale-dir(ltr) + .tab-bottom,
+.tab-bottom[visuallyselected="true"]:-moz-locale-dir(ltr) + .tab-bottom,
 .tab-bottom[beforeselected="true"]:-moz-locale-dir(rtl) {
   border-bottom-left-radius: 0;
 }
 
 /* ::::: tabs-bottom ::::: */
 
 .tabs-bottom > .tabs-left,
 .tabs-bottom > .tabs-right {
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -3522,16 +3522,17 @@ nsNativeThemeCocoa::WidgetStateChanged(n
     *aShouldRepaint = true;
   } else {
     // Check the attribute to see if it's relevant.  
     // disabled, checked, dlgtype, default, etc.
     *aShouldRepaint = false;
     if (aAttribute == nsGkAtoms::disabled ||
         aAttribute == nsGkAtoms::checked ||
         aAttribute == nsGkAtoms::selected ||
+        aAttribute == nsGkAtoms::visuallyselected ||
         aAttribute == nsGkAtoms::menuactive ||
         aAttribute == nsGkAtoms::sortDirection ||
         aAttribute == nsGkAtoms::focused ||
         aAttribute == nsGkAtoms::_default ||
         aAttribute == nsGkAtoms::open ||
         aAttribute == nsGkAtoms::hover)
       *aShouldRepaint = true;
 
--- a/widget/gtk/nsNativeThemeGTK.cpp
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -1349,16 +1349,17 @@ nsNativeThemeGTK::WidgetStateChanged(nsI
   }
   else {
     // Check the attribute to see if it's relevant.  
     // disabled, checked, dlgtype, default, etc.
     *aShouldRepaint = false;
     if (aAttribute == nsGkAtoms::disabled ||
         aAttribute == nsGkAtoms::checked ||
         aAttribute == nsGkAtoms::selected ||
+        aAttribute == nsGkAtoms::visuallyselected ||
         aAttribute == nsGkAtoms::focused ||
         aAttribute == nsGkAtoms::readonly ||
         aAttribute == nsGkAtoms::_default ||
         aAttribute == nsGkAtoms::menuactive ||
         aAttribute == nsGkAtoms::open ||
         aAttribute == nsGkAtoms::parentfocused)
       *aShouldRepaint = true;
   }
--- a/widget/nsNativeTheme.h
+++ b/widget/nsNativeTheme.h
@@ -85,17 +85,17 @@ class nsNativeTheme : public nsITimerCal
     return CheckBooleanAttr(aFrame, nsGkAtoms::focused);
   }
 
   // scrollbar button:
   int32_t GetScrollbarButtonType(nsIFrame* aFrame);
 
   // tab:
   bool IsSelectedTab(nsIFrame* aFrame) {
-    return CheckBooleanAttr(aFrame, nsGkAtoms::selected);
+    return CheckBooleanAttr(aFrame, nsGkAtoms::visuallyselected);
   }
   
   bool IsNextToSelectedTab(nsIFrame* aFrame, int32_t aOffset);
   
   bool IsBeforeSelectedTab(nsIFrame* aFrame) {
     return IsNextToSelectedTab(aFrame, -1);
   }
   
--- a/widget/windows/nsNativeThemeWin.cpp
+++ b/widget/windows/nsNativeThemeWin.cpp
@@ -2568,16 +2568,17 @@ nsNativeThemeWin::WidgetStateChanged(nsI
   }
   else {
     // Check the attribute to see if it's relevant.  
     // disabled, checked, dlgtype, default, etc.
     *aShouldRepaint = false;
     if (aAttribute == nsGkAtoms::disabled ||
         aAttribute == nsGkAtoms::checked ||
         aAttribute == nsGkAtoms::selected ||
+        aAttribute == nsGkAtoms::visuallyselected ||
         aAttribute == nsGkAtoms::readonly ||
         aAttribute == nsGkAtoms::open ||
         aAttribute == nsGkAtoms::menuactive ||
         aAttribute == nsGkAtoms::focused)
       *aShouldRepaint = true;
   }
 
   return NS_OK;