Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 06 May 2015 16:28:53 -0400
changeset 273998 10a18e5ff1f478c19c50f505d0735b8f0685d402
parent 273997 687d646a4ca978ad222f91de9f7d868db3ed180a (current diff)
parent 273989 5593ac626826e11ea1a5f2e29970ec89db12982e (diff)
child 273999 af7357d7fd10f8e4becfecd0e455abc1850817c2
child 274151 5b6be53fffcb3d6c3804b978be2e8c3100d038a3
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.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 b2g-inbound. a=merge CLOSED TREE
--- a/.ycm_extra_conf.py
+++ b/.ycm_extra_conf.py
@@ -16,12 +16,19 @@ if not os.path.exists(path):
 mach_module = imp.load_module('_mach', open(path), path, ('', 'r', imp.PY_SOURCE))
 
 def FlagsForFile(filename):
     mach = mach_module.get_mach()
     out = StringIO()
     out.encoding = None
     mach.run(['compileflags', filename], stdout=out, stderr=out)
 
+    flag_list = shlex.split(out.getvalue())
+
+    # This flag is added by Fennec for android build and causes ycmd to fail to parse the file.
+    # Removing this flag is a workaround until ycmd starts to handle this flag properly.
+    # https://github.com/Valloric/YouCompleteMe/issues/1490
+    final_flags = [x for x in flag_list if not x.startswith('-march=armv')]
+
     return {
-        'flags': shlex.split(out.getvalue()),
+        'flags': final_flags,
         'do_cache': True
     }
--- a/accessible/windows/ia2/ia2Accessible.cpp
+++ b/accessible/windows/ia2/ia2Accessible.cpp
@@ -490,17 +490,17 @@ STDMETHODIMP
 ia2Accessible::get_uniqueID(long* aUniqueID)
 {
   A11Y_TRYBLOCK_BEGIN
 
   if (!aUniqueID)
     return E_INVALIDARG;
 
   AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
-  *aUniqueID = - reinterpret_cast<intptr_t>(acc->UniqueID());
+  *aUniqueID = AccessibleWrap::GetChildIDFor(acc);
   return S_OK;
 
   A11Y_TRYBLOCK_END
 }
 
 STDMETHODIMP
 ia2Accessible::get_windowHandle(HWND* aWindowHandle)
 {
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -51,26 +51,58 @@ const uint32_t USE_ROLE_STRING = 0;
  */
 
 //#define DEBUG_LEAKS
 
 #ifdef DEBUG_LEAKS
 static gAccessibles = 0;
 #endif
 
+#ifdef _WIN64
+IDSet AccessibleWrap::sIDGen;
+
+static const uint32_t kNoID = 0;
+#endif
+
 static const int32_t kIEnumVariantDisconnected = -1;
 
 ////////////////////////////////////////////////////////////////////////////////
 // AccessibleWrap
 ////////////////////////////////////////////////////////////////////////////////
+AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+  Accessible(aContent, aDoc)
+#ifdef _WIN64
+  , mID(kNoID)
+#endif
+{
+}
+
+AccessibleWrap::~AccessibleWrap()
+{
+#ifdef _WIN64
+  if (mID != kNoID)
+    sIDGen.ReleaseID(mID);
+#endif
+}
 
 ITypeInfo* AccessibleWrap::gTypeInfo = nullptr;
 
 NS_IMPL_ISUPPORTS_INHERITED0(AccessibleWrap, Accessible)
 
+void
+AccessibleWrap::Shutdown()
+{
+#ifdef _WIN64
+  if (mID != kNoID)
+    static_cast<DocAccessibleWrap*>(mDoc)->RemoveID(mID);
+#endif
+
+  Accessible::Shutdown();
+}
+
 //-----------------------------------------------------
 // IUnknown interface methods - see iunknown.h for documentation
 //-----------------------------------------------------
 
 // Microsoft COM QueryInterface
 STDMETHODIMP
 AccessibleWrap::QueryInterface(REFIID iid, void** ppv)
 {
@@ -1249,20 +1281,33 @@ AccessibleWrap::HandleAccEvent(AccEvent*
 
 int32_t
 AccessibleWrap::GetChildIDFor(Accessible* aAccessible)
 {
   // A child ID of the window is required, when we use NotifyWinEvent,
   // so that the 3rd party application can call back and get the IAccessible
   // the event occurred on.
 
-  // Yes, this means we're only compatibible with 32 bit
-  // MSAA is only available for 32 bit windows, so it's okay
-  // XXX: bug 606080
-  return aAccessible ? - NS_PTR_TO_INT32(aAccessible->UniqueID()) : 0;
+#ifdef _WIN64
+  if (!aAccessible || !aAccessible->Document())
+    return 0;
+
+  uint32_t* id = & static_cast<AccessibleWrap*>(aAccessible)->mID;
+  if (*id != kNoID)
+    return *id;
+
+  *id = sIDGen.GetID();
+  DocAccessibleWrap* doc =
+    static_cast<DocAccessibleWrap*>(aAccessible->Document());
+  doc->AddID(*id, static_cast<AccessibleWrap*>(aAccessible));
+
+  return *id;
+#else
+  return - reinterpret_cast<intptr_t>(aAccessible);
+#endif
 }
 
 HWND
 AccessibleWrap::GetHWNDFor(Accessible* aAccessible)
 {
   if (aAccessible) {
     DocAccessible* document = aAccessible->Document();
     if(!document)
@@ -1302,16 +1347,35 @@ AccessibleWrap::NativeAccessible(Accessi
     return nullptr;
   }
 
   IAccessible* msaaAccessible = nullptr;
   aAccessible->GetNativeInterface(reinterpret_cast<void**>(&msaaAccessible));
   return static_cast<IDispatch*>(msaaAccessible);
 }
 
+#ifdef _WIN64
+static Accessible*
+GetAccessibleInSubtree(DocAccessible* aDoc, uint32_t aID)
+{
+  Accessible* child = static_cast<DocAccessibleWrap*>(aDoc)->GetAccessibleByID(aID);
+  if (child)
+    return child;
+
+  uint32_t childDocCount = aDoc->ChildDocumentCount();
+  for (uint32_t i = 0; i < childDocCount; i++) {
+    child = GetAccessibleInSubtree(aDoc->GetChildDocumentAt(i), aID);
+    if (child)
+      return child;
+  }
+
+    return nullptr;
+  }
+#endif
+
 Accessible*
 AccessibleWrap::GetXPAccessibleFor(const VARIANT& aVarChild)
 {
   if (aVarChild.vt != VT_I4)
     return nullptr;
 
   // if its us real easy - this seems to always be the case
   if (aVarChild.lVal == CHILDID_SELF)
@@ -1337,17 +1401,21 @@ AccessibleWrap::GetXPAccessibleFor(const
   // Otherwise we treat lVal as index in parent.
 
   if (aVarChild.lVal < 0) {
     // Convert child ID to unique ID.
     void* uniqueID = reinterpret_cast<void*>(-aVarChild.lVal);
 
     DocAccessible* document = Document();
     Accessible* child =
+#ifdef _WIN64
+    GetAccessibleInSubtree(document, static_cast<uint32_t>(aVarChild.lVal));
+#else
       document->GetAccessibleByUniqueIDInSubtree(uniqueID);
+#endif
 
     // If it is a document then just return an accessible.
     if (IsDoc())
       return child;
 
     // Otherwise check whether the accessible is a child (this path works for
     // ARIA documents and popups).
     Accessible* parent = child;
--- a/accessible/windows/msaa/AccessibleWrap.h
+++ b/accessible/windows/msaa/AccessibleWrap.h
@@ -10,16 +10,17 @@
 #include "nsCOMPtr.h"
 #include "Accessible.h"
 #include "Accessible2.h"
 #include "ia2Accessible.h"
 #include "ia2AccessibleComponent.h"
 #include "ia2AccessibleHyperlink.h"
 #include "ia2AccessibleValue.h"
 #include "mozilla/a11y/ProxyAccessible.h"
+#include "mozilla/a11y/IDSet.h"
 
 #ifdef __GNUC__
 // Inheriting from both XPCOM and MSCOM interfaces causes a lot of warnings
 // about virtual functions being hidden by each other. This is done by
 // design, so silence the warning.
 #pragma GCC diagnostic ignored "-Woverloaded-virtual"
 #endif
 
@@ -28,18 +29,17 @@ namespace a11y {
 
 class AccessibleWrap : public Accessible,
                        public ia2Accessible,
                        public ia2AccessibleComponent,
                        public ia2AccessibleHyperlink,
                        public ia2AccessibleValue
 {
 public: // construction, destruction
-  AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
-    Accessible(aContent, aDoc) { }
+  AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   public: // IUnknown methods - see iunknown.h for documentation
     STDMETHODIMP QueryInterface(REFIID, void**);
 
   // Return the registered OLE class ID of this object's CfDataObj.
@@ -147,16 +147,17 @@ public: // construction, destruction
                                            LCID lcid, WORD wFlags,
                                            DISPPARAMS *pDispParams,
                                            VARIANT *pVarResult,
                                            EXCEPINFO *pExcepInfo,
                                            UINT *puArgErr);
 
   // Accessible
   virtual nsresult HandleAccEvent(AccEvent* aEvent);
+  virtual void Shutdown() override;
 
   // Helper methods
   static int32_t GetChildIDFor(Accessible* aAccessible);
   static HWND GetHWNDFor(Accessible* aAccessible);
 
   /**
    * System caret support: update the Windows caret position. 
    * The system caret works more universally than the MSAA caret
@@ -171,25 +172,32 @@ public: // construction, destruction
    */
   Accessible* GetXPAccessibleFor(const VARIANT& aVarChild);
 
   virtual void GetNativeInterface(void **aOutAccessible) override;
 
   static IDispatch* NativeAccessible(Accessible* aAccessible);
 
 protected:
-  virtual ~AccessibleWrap() { }
+  virtual ~AccessibleWrap();
+
+#ifdef _WIN64
+  uint32_t mID;
+#endif
 
   /**
    * Creates ITypeInfo for LIBID_Accessibility if it's needed and returns it.
    */
   static ITypeInfo* GetTI(LCID lcid);
 
   static ITypeInfo* gTypeInfo;
 
+#ifdef _WIN64
+  static IDSet sIDGen;
+#endif
 
   enum navRelations {
     NAVRELATION_CONTROLLED_BY = 0x1000,
     NAVRELATION_CONTROLLER_FOR = 0x1001,
     NAVRELATION_LABEL_FOR = 0x1002,
     NAVRELATION_LABELLED_BY = 0x1003,
     NAVRELATION_MEMBER_OF = 0x1004,
     NAVRELATION_NODE_CHILD_OF = 0x1005,
--- a/accessible/windows/msaa/DocAccessibleWrap.h
+++ b/accessible/windows/msaa/DocAccessibleWrap.h
@@ -29,20 +29,38 @@ public:
         /* [retval][out] */ BSTR __RPC_FAR *pszValue);
 
   // Accessible
   virtual void Shutdown();
 
   // DocAccessible
   virtual void* GetNativeWindow() const;
 
+  /**
+   * Manage the mapping from id to Accessible.
+   */
+#ifdef _WIN64
+  void AddID(uint32_t aID, AccessibleWrap* aAcc)
+    { mIDToAccessibleMap.Put(aID, aAcc); }
+  void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
+  AccessibleWrap* GetAccessibleByID(uint32_t aID) const
+    { return mIDToAccessibleMap.Get(aID); }
+#endif
+
 protected:
   // DocAccessible
   virtual void DoInitialUpdate();
 
 protected:
   void* mHWND;
+
+  /*
+   * This provides a mapping from 32 bit id to accessible objects.
+   */
+#ifdef _WIN64
+  nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
+#endif
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
new file mode 100644
--- /dev/null
+++ b/accessible/windows/msaa/IDSet.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * A class to generate unique IDs in the range [ - 2^31, 0 )
+ */
+
+#ifndef MOZILLA_A11Y_IDSet_h_
+#define MOZILLA_A11Y_IDSet_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/SplayTree.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * On windows an accessible's id must be a negative 32 bit integer.  It is
+ * important to support recycling arbitrary IDs because accessibles can be
+ * created and destroyed at any time in the life of a page.  IDSet provides 2
+ * operations: generate an ID in the range [ - 2^31, 0 ), and release an ID so
+ * it can be allocated again.  Allocated ID are tracked by a sparse bitmap
+ * implemented with a splay tree.  Nodes in the tree are keyed by the upper N
+ * bits of the bitwise negation of the ID, and the node contains a bitmap
+ * tracking the allocation of 2^(32 - N) IDs.
+ */
+class IDSet
+{
+public:
+  MOZ_CONSTEXPR IDSet() : mBitSet(), mIdx(0) {}
+
+  /**
+   * Return a new unique id.
+   */
+  uint32_t GetID()
+  {
+    uint32_t idx = mIdx;
+    while (true) {
+      BitSetElt* elt = mBitSet.findOrInsert(BitSetElt(idx));
+      if (elt->mBitvec[0] != UINT64_MAX) {
+        uint32_t i = CountTrailingZeroes64(~elt->mBitvec[0]);
+
+        elt->mBitvec[0] |= (1ull << i);
+        mIdx = idx;
+        return ~(elt->mIdx * bitsPerElt + i);
+      }
+
+      if (elt->mBitvec[1] != UINT64_MAX) {
+        uint32_t i = CountTrailingZeroes64(~elt->mBitvec[1]);
+
+        elt->mBitvec[1] |= (1ull << i);
+        mIdx = idx;
+        return ~(elt->mIdx * bitsPerElt + bitsPerWord + i);
+      }
+
+      idx++;
+      if (idx > sMaxIdx) {
+        idx = 0;
+      }
+
+      if (idx == mIdx) {
+        MOZ_CRASH("used up all the available ids");
+      }
+    }
+  }
+
+  /**
+   * Free a no longer required id so it may be allocated again.
+   */
+  void ReleaseID(uint32_t aID)
+  {
+    aID = ~aID;
+    MOZ_ASSERT(aID < static_cast<uint32_t>(INT32_MAX));
+
+    uint32_t idx = aID / bitsPerElt;
+    mIdx = idx;
+    BitSetElt* elt = mBitSet.find(BitSetElt(idx));
+    MOZ_ASSERT(elt);
+
+    uint32_t vecIdx = (aID % bitsPerElt) / bitsPerWord;
+    elt->mBitvec[vecIdx] &= ~(1ull << (aID % bitsPerWord));
+    if (elt->mBitvec[0] == 0 && elt->mBitvec[1] == 0) {
+      delete mBitSet.remove(*elt);
+    }
+  }
+
+private:
+  static const unsigned int wordsPerElt = 2;
+  static const unsigned int bitsPerWord = 64;
+  static const unsigned int bitsPerElt = wordsPerElt * bitsPerWord;
+  static const uint32_t sMaxIdx = INT32_MAX / bitsPerElt;
+
+  struct BitSetElt : mozilla::SplayTreeNode<BitSetElt>
+  {
+    explicit BitSetElt(uint32_t aIdx) :
+      mIdx(aIdx)
+    { mBitvec[0] = mBitvec[1] = 0; }
+
+    uint64_t mBitvec[wordsPerElt];
+    uint32_t mIdx;
+
+    static int compare(const BitSetElt& a, const BitSetElt& b)
+    {
+      if (a.mIdx == b.mIdx) {
+        return 0;
+      }
+
+      if (a.mIdx < b.mIdx) {
+        return -1;
+      }
+      return 1;
+    }
+  };
+
+  SplayTree<BitSetElt, BitSetElt> mBitSet;
+  uint32_t mIdx;
+};
+
+}
+}
+
+#endif
--- a/accessible/windows/msaa/moz.build
+++ b/accessible/windows/msaa/moz.build
@@ -7,16 +7,17 @@
 EXPORTS += [
     'IUnknownImpl.h',
 ]
 
 EXPORTS.mozilla.a11y += [
     'AccessibleWrap.h',
     'Compatibility.h',
     'HyperTextAccessibleWrap.h',
+    'IDSet.h',
 ]
 
 UNIFIED_SOURCES += [
     'AccessibleWrap.cpp',
     'ApplicationAccessibleWrap.cpp',
     'ARIAGridAccessibleWrap.cpp',
     'Compatibility.cpp',
     'DocAccessibleWrap.cpp',
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1438,26 +1438,25 @@ pref("devtools.performance.profiler.samp
 pref("devtools.performance.ui.invert-call-tree", true);
 pref("devtools.performance.ui.invert-flame-graph", false);
 pref("devtools.performance.ui.flatten-tree-recursion", true);
 pref("devtools.performance.ui.show-platform-data", false);
 pref("devtools.performance.ui.show-idle-blocks", true);
 pref("devtools.performance.ui.enable-memory", false);
 pref("devtools.performance.ui.enable-framerate", true);
 pref("devtools.performance.ui.show-jit-optimizations", false);
-// If in aurora (40.0, will revert for 40.1), set default
+// If in aurora/dev edition (40.0, will revert for 40.1), set default
 // to retro mode.
 // TODO bug 1160313
-#if MOZ_UPDATE_CHANNEL == aurora
+#ifdef MOZ_DEV_EDITION
   pref("devtools.performance.ui.retro-mode", true);
 #else
   pref("devtools.performance.ui.retro-mode", false);
 #endif
 
-
 // The default cache UI setting
 pref("devtools.cache.disabled", false);
 
 // The default service workers UI setting
 pref("devtools.serviceWorkers.testing.enabled", false);
 
 // Enable the Network Monitor
 pref("devtools.netmonitor.enabled", true);
--- a/browser/components/loop/.eslintrc
+++ b/browser/components/loop/.eslintrc
@@ -44,33 +44,30 @@
     "global-strict": 0,           // Leave as zero (this will be unsupported in eslint 1.0.0)
     "key-spacing": 0,             // TODO: Remove (use default)
     "new-cap": 0,                 // TODO: Remove (use default)
     "no-catch-shadow": 0,         // TODO: Remove (use default)
     "no-console": 0,              // Leave as 0. We use console logging in content code.
     "no-empty": 0,                // TODO: Remove (use default)
     "no-extra-bind": 0,           // Leave as 0
     "no-extra-boolean-cast": 0,   // TODO: Remove (use default)
-    "no-extra-semi": 0,           // TODO: Remove (use default)
     "no-multi-spaces": 0,         // TBD.
     "no-new": 0,                  // TODO: Remove (use default)
     "no-redeclare": 0,            // TODO: Remove (use default)
     "no-return-assign": 0,        // TODO: Remove (use default)
     "no-shadow": 0,               // TODO: Remove (use default)
     "no-spaced-func": 0,          // TODO: Remove (use default)
     "no-trailing-spaces": 0,      // TODO: Remove (use default)
     "no-undef": 0,                // TODO: Remove (use default)
     "no-underscore-dangle": 0,    // Leave as 0. Commonly used for private variables.
     "no-unused-expressions": 0,   // TODO: Remove (use default)
     "no-unused-vars": 0,          // TODO: Remove (use default)
     "no-use-before-define": 0,    // TODO: Remove (use default)
     "no-wrap-func": 0,            // TODO: Remove (use default)
     "quotes": 0,                  // [2, "double", "avoid-escape"],
-    "semi": 0,                    // TODO: Remove (use default)
-    "semi-spacing": 0,            // TODO: Remove (use default)
     "space-infix-ops": 0,         // TODO: Remove (use default)
     "space-return-throw-case": 0, // TODO: Remove (use default)
     "strict": 0,                  // [2, "function"],
     "yoda": 0,                    // [2, "never"],
     // eslint-plugin-react rules. These are documented at
     // <https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules>
     "react/jsx-quotes": [2, "double", "avoid-escape"],
     "react/jsx-no-undef": 2,
--- a/browser/components/loop/content/shared/js/utils.js
+++ b/browser/components/loop/content/shared/js/utils.js
@@ -13,17 +13,17 @@ var inChrome = typeof Components != "und
 
   var mozL10n;
   if (inChrome) {
     this.EXPORTED_SYMBOLS = ["utils"];
     mozL10n = { get: function() {
       throw new Error("mozL10n.get not availabled from chrome!");
     }};
   } else {
-    mozL10n = document.mozL10n || navigator.mozL10n
+    mozL10n = document.mozL10n || navigator.mozL10n;
   }
 
   /**
    * Call types used for determining if a call is audio/video or audio-only.
    */
   var CALL_TYPES = {
     AUDIO_VIDEO: "audio-video",
     AUDIO_ONLY: "audio"
--- a/browser/components/loop/content/shared/js/websocket.js
+++ b/browser/components/loop/content/shared/js/websocket.js
@@ -41,17 +41,17 @@ loop.CallConnectionWebSocket = (function
     this._lastServerState = "init";
 
     // Set loop.debug.sdk to true in the browser, or standalone:
     // localStorage.setItem("debug.websocket", true);
     this._debugWebSocket =
       loop.shared.utils.getBoolPreference("debug.websocket");
 
     _.extend(this, Backbone.Events);
-  };
+  }
 
   CallConnectionWebSocket.prototype = {
     /**
      * Start the connection to the websocket.
      *
      * @return {Promise} A promise that resolves when the websocket
      *                   server connection is open and "hello"s have been
      *                   exchanged. It is rejected if there is a failure in
--- a/browser/components/loop/modules/CardDavImporter.jsm
+++ b/browser/components/loop/modules/CardDavImporter.jsm
@@ -301,17 +301,17 @@ this.CardDavImporter.prototype = {
           }
 
           if (name === "NICKNAME") {
             value = value.replace(/\\;/g, ";");
             // We don't store nickname in contact because it's not
             // a supported field. We're saving it off here in case we
             // need to use it if the fullname is blank.
             nickname = value;
-          };
+          }
 
           if (name === "ADR") {
             value = value.replace(/\\;/g, "\r");
             value = value.replace(/;/g, "\n");
             value = value.replace(/\r/g, ";");
             let pobox, extra, street, locality, region, code, country;
             let values = value.split(/\n/);
             if (values.length >= 7) {
@@ -450,14 +450,14 @@ this.CardDavImporter.prototype = {
           resolve(req);
         } else {
           reject(new Error(req.status + " " + req.statusText));
         }
       };
 
       req.onerror = function(error) {
         reject(error);
-      }
+      };
 
       req.send(body);
     });
   }
 };
--- a/browser/components/loop/modules/GoogleImporter.jsm
+++ b/browser/components/loop/modules/GoogleImporter.jsm
@@ -320,17 +320,17 @@ this.GoogleImporter.prototype = {
           resolve(doc);
         } else {
           reject(new Error(request.status + " " + request.statusText));
         }
       };
 
       request.onerror = function(error) {
         reject(error);
-      }
+      };
 
       request.send();
     });
   },
 
   /**
    * Fetches all the contacts in a users' address book.
    *
@@ -509,17 +509,17 @@ this.GoogleImporter.prototype = {
 
     let orgNodes = entry.getElementsByTagNameNS(kNS_GD, "organization");
     if (orgNodes.length) {
       contact.org = [];
       contact.jobTitle = [];
       for (let [,orgNode] of Iterator(orgNodes)) {
         let orgElement = orgNode.getElementsByTagNameNS(kNS_GD, "orgName")[0];
         let titleElement = orgNode.getElementsByTagNameNS(kNS_GD, "orgTitle")[0];
-        contact.org.push(orgElement ? orgElement.textContent : "")
+        contact.org.push(orgElement ? orgElement.textContent : "");
         contact.jobTitle.push(titleElement ? titleElement.textContent : "");
       }
     }
 
     contact.category = ["google"];
 
     // Basic sanity checking: make sure the name field isn't empty
     if (!("name" in contact) || contact.name[0].length == 0) {
--- a/browser/components/loop/modules/LoopCalls.jsm
+++ b/browser/components/loop/modules/LoopCalls.jsm
@@ -48,18 +48,19 @@ CallProgressSocket.prototype = {
    * Sends a hello message to the server.
    *
    * @param {function} Callback used after a successful handshake
    *                   over the progressUrl.
    * @param {function} Callback used if an error is encountered
    */
   connect: function(onSuccess, onError) {
     this._onSuccess = onSuccess;
-    this._onError = onError ||
-      (reason => {MozLoopService.log.warn("LoopCalls::callProgessSocket - ", reason);});
+    this._onError = onError || (reason => {
+      MozLoopService.log.warn("LoopCalls::callProgessSocket - ", reason);
+    });
 
     if (!onSuccess) {
       this._onError("missing onSuccess argument");
       return;
     }
 
     if (Services.io.offline) {
       this._onError("IO offline");
@@ -232,17 +233,17 @@ let LoopCallsInternal = {
    * @param {Object} version - LoopPushService notification version
    *
    * @returns {Promise}
    *
    */
 
   _getCalls: function(sessionType, version) {
     return MozLoopService.hawkRequest(sessionType, "/calls?version=" + version, "GET").then(
-      response => {this._processCalls(response, sessionType);}
+      response => { this._processCalls(response, sessionType); }
     );
   },
 
   /**
    * Process the calls array returned from a GET/calls?version request.
    * Only one active call is permitted at this time.
    *
    * @param {Object} response - response payload from GET
@@ -304,17 +305,17 @@ let LoopCallsInternal = {
           if (contact.blocked) {
             // Blocked! Send a busy signal back to the caller.
             this._returnBusy(callData);
             return;
           }
         }
 
         openChat();
-      })
+      });
     } else {
       openChat();
     }
   },
 
   /**
    * Starts a direct call to the contact addresses.
    *
@@ -392,17 +393,17 @@ let LoopCallsInternal = {
       callData.progressURL,
       callData.callId,
       callData.websocketToken);
     if (this.mocks.webSocket) {
       callProgress._websocket = this.mocks.webSocket;
     }
     // This instance of CallProgressSocket should stay alive until the underlying
     // websocket is closed since it is passed to the websocket as the nsIWebSocketListener.
-    callProgress.connect(() => {callProgress.sendBusy();});
+    callProgress.connect(() => { callProgress.sendBusy(); });
   }
 };
 Object.freeze(LoopCallsInternal);
 
 /**
  * Public API
  */
 this.LoopCalls = {
--- a/browser/components/loop/modules/LoopContacts.jsm
+++ b/browser/components/loop/modules/LoopContacts.jsm
@@ -283,17 +283,17 @@ const batch = function(operation, data, 
     });
   }, err => {
     if (err) {
       callback(err, processed);
       return;
     }
     callback(null, processed);
   });
-}
+};
 
 /**
  * Extend a `target` object with the properties defined in `source`.
  *
  * @param {Object} target The target object to receive properties defined in `source`
  * @param {Object} source The source object to copy properties from
  */
 const extend = function(target, source) {
--- a/browser/components/loop/modules/LoopRooms.jsm
+++ b/browser/components/loop/modules/LoopRooms.jsm
@@ -2,30 +2,35 @@
  * 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
 const {MozLoopService, LOOP_SESSION_TYPE} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
-                                  "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
+                                  "resource://services-common/utils.js");
 XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
   const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
   return new EventEmitter();
 });
 XPCOMUtils.defineLazyGetter(this, "gLoopBundle", function() {
   return Services.strings.createBundle('chrome://browser/locale/loop/loop.properties');
 });
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoopRoomsCache",
+  "resource:///modules/loop/LoopRoomsCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "loopUtils",
-  "resource:///modules/loop/utils.js", "utils")
+  "resource:///modules/loop/utils.js", "utils");
 XPCOMUtils.defineLazyModuleGetter(this, "loopCrypto",
   "resource:///modules/loop/crypto.js", "LoopCrypto");
 
 
 this.EXPORTED_SYMBOLS = ["LoopRooms", "roomsPushNotification"];
 
 // The maximum number of clients that we support currently.
 const CLIENT_MAX_SIZE = 2;
@@ -36,16 +41,18 @@ const roomsPushNotification = function(v
 
 // Since the LoopRoomsInternal.rooms map as defined below is a local cache of
 // room objects that are retrieved from the server, this is list may become out
 // of date. The Push server may notify us of this event, which will set the global
 // 'dirty' flag to TRUE.
 let gDirty = true;
 // Global variable that keeps track of the currently used account.
 let gCurrentUser = null;
+// Global variable that keeps track of the room cache.
+let gRoomsCache = null;
 
 /**
  * Extend a `target` object with the properties defined in `source`.
  *
  * @param {Object} target The target object to receive properties defined in `source`
  * @param {Object} source The source object to copy properties from
  */
 const extend = function(target, source) {
@@ -118,16 +125,23 @@ const checkForParticipantsUpdate = funct
  * violated. You'll notice this as well in the documentation for each method.
  */
 let LoopRoomsInternal = {
   /**
    * @var {Map} rooms Collection of rooms currently in cache.
    */
   rooms: new Map(),
 
+  get roomsCache() {
+    if (!gRoomsCache) {
+      gRoomsCache = new LoopRoomsCache();
+    }
+    return gRoomsCache;
+  },
+
   /**
    * @var {String} sessionType The type of user session. May be 'FXA' or 'GUEST'.
    */
   get sessionType() {
     return MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
                                         LOOP_SESSION_TYPE.GUEST;
   },
 
@@ -270,22 +284,50 @@ let LoopRoomsInternal = {
     if (!roomData.context) {
       return roomData;
     }
 
     if (!roomData.context.wrappedKey) {
       throw new Error("Missing wrappedKey");
     }
 
-    // Bug 1152761 will cause us to additionally store keys locally. We'll
-    // need to add some code for recovery in case decryption fails.
-    let key = yield this.promiseDecryptRoomKey(roomData.context.wrappedKey);
+    let savedRoomKey = yield this.roomsCache.getKey(this.sessionType, roomData.roomToken);
+    let fallback = false;
+    let key;
+
+    try {
+      key = yield this.promiseDecryptRoomKey(roomData.context.wrappedKey);
+    } catch (error) {
+      // If we don't have a key saved, then we can't do anything.
+      if (!savedRoomKey) {
+        throw error;
+      }
+
+      // We failed to decrypt the room key, so has our FxA key changed?
+      // If so, we fall-back to the saved room key.
+      key = savedRoomKey;
+      fallback = true;
+    }
 
     let decryptedData = yield loopCrypto.decryptBytes(key, roomData.context.value);
 
+    if (fallback) {
+      // Fallback decryption succeeded, so we need to re-encrypt the room key and
+      // save the data back again.
+      // XXX Bug 1152764 will implement this or make it a separate bug.
+    } else if (!savedRoomKey || key != savedRoomKey) {
+      // Decryption succeeded, but we don't have the right key saved.
+      try {
+        yield this.roomsCache.setKey(this.sessionType, roomData.roomToken, key);
+      }
+      catch (error) {
+        MozLoopService.log.error("Failed to save room key:", error);
+      }
+    }
+
     roomData.roomKey = key;
     roomData.decryptedContext = JSON.parse(decryptedData);
 
     // Strip any existing key from the url.
     roomData.roomUrl = roomData.roomUrl.split("#")[0];
     // Now add the key to the url.
     roomData.roomUrl = roomData.roomUrl + "#" + roomData.roomKey;
 
@@ -332,21 +374,21 @@ let LoopRoomsInternal = {
       this.saveAndNotifyUpdate(room, isUpdate);
     } else {
       // XXX Don't decrypt if same?
       try {
         let roomData = yield this.promiseDecryptRoomData(room);
 
         this.saveAndNotifyUpdate(roomData, isUpdate);
       } catch (error) {
-        MozLoopService.log.error("Failed to decrypt room data: " + error);
+        MozLoopService.log.error("Failed to decrypt room data: ", error);
         // Do what we can to save the room data.
         room.decryptedContext = {};
         this.saveAndNotifyUpdate(room, isUpdate);
-      };
+      }
     }
   }),
 
   /**
    * Fetch a list of rooms that the currently registered user is a member of.
    *
    * @param {String}   [version] If set, we will fetch a list of changed rooms since
    *                             `version`. Optional.
@@ -485,16 +527,19 @@ let LoopRoomsInternal = {
       // Do not keep this value - it is a request to the server.
       delete room.expiresIn;
       this.rooms.set(room.roomToken, room);
 
       if (this.sessionType == LOOP_SESSION_TYPE.GUEST) {
         this.setGuestCreatedRoom(true);
       }
 
+      // Now we've got the room token, we can save the key to disk.
+      yield this.roomsCache.setKey(this.sessionType, room.roomToken, room.roomKey);
+
       eventEmitter.emit("add", room);
       callback(null, room);
     }.bind(this)).catch(callback);
   },
 
   /**
    * Sets whether or not the user has created a room in guest mode.
    *
@@ -695,16 +740,20 @@ let LoopRoomsInternal = {
       };
 
       // If we're not encrypting currently, then only send the roomName.
       // XXX This should go away once bug 1153788 is fixed.
       if (!sendData.context) {
         sendData = {
           roomName: newRoomName
         };
+      } else {
+        // This might be an upgrade to encrypted rename, so store the key
+        // just in case.
+        yield this.roomsCache.setKey(this.sessionType, all.roomToken, all.roomKey);
       }
 
       let response = yield MozLoopService.hawkRequest(this.sessionType,
           url, "PATCH", sendData);
 
       let newRoomData = all;
 
       extend(newRoomData, JSON.parse(response.body));
@@ -722,18 +771,22 @@ let LoopRoomsInternal = {
   onNotification: function(version, channelID) {
     // See if we received a notification for the channel that's currently active:
     let channelIDs = MozLoopService.channelIDs;
     if ((this.sessionType == LOOP_SESSION_TYPE.GUEST && channelID != channelIDs.roomsGuest) ||
         (this.sessionType == LOOP_SESSION_TYPE.FXA   && channelID != channelIDs.roomsFxA)) {
       return;
     }
 
+    let oldDirty = gDirty;
     gDirty = true;
-    this.getAll(version, () => {});
+    // If we were already dirty, then get the full set of rooms. For example,
+    // we'd already be dirty if we had started up but not got the list of rooms
+    // yet.
+    this.getAll(oldDirty ? null : version, () => {});
   },
 
   /**
    * When a user logs in or out, this method should be invoked to check whether
    * the rooms cache needs to be refreshed.
    *
    * @param {String|null} user The FxA userID or NULL
    */
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/modules/LoopRoomsCache.jsm
@@ -0,0 +1,159 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+const {MozLoopService, LOOP_SESSION_TYPE} =
+  Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
+XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
+                                  "resource://services-common/utils.js");
+XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
+
+this.EXPORTED_SYMBOLS = ["LoopRoomsCache"];
+
+const LOOP_ROOMS_CACHE_FILENAME = "loopRoomsCache.json";
+
+/**
+ * RoomsCache is a cache for saving simple rooms data to the disk in case we
+ * need it for back-up purposes, e.g. recording room keys for FxA if the user
+ * changes their password.
+ *
+ * The format of the data is:
+ *
+ * {
+ *   <sessionType>: {
+ *     <roomToken>: {
+ *       "key": <roomKey>
+ *     }
+ *   }
+ * }
+ *
+ * It is intended to try and keep the data forward and backwards compatible in
+ * a reasonable manner, hence why the structure is more complex than it needs
+ * to be to store tokens and keys.
+ *
+ * @param {Object} options The options for the RoomsCache, containing:
+ *   - {String} baseDir   The base directory in which to save the file.
+ *   - {String} filename  The filename for the cache file.
+ */
+function LoopRoomsCache(options) {
+  options = options || {};
+
+  this.baseDir = options.baseDir || OS.Constants.Path.profileDir;
+  this.path = OS.Path.join(
+    this.baseDir,
+    options.filename || LOOP_ROOMS_CACHE_FILENAME
+  );
+  this._cache = null;
+}
+
+LoopRoomsCache.prototype = {
+  /**
+   * Updates the local copy of the cache and saves it to disk.
+   *
+   * @param  {Object} contents An object to be saved in json format.
+   * @return {Promise} A promise that is resolved once the save is complete.
+   */
+  _setCache: function(contents) {
+    this._cache = contents;
+
+    return OS.File.makeDir(this.baseDir, {ignoreExisting: true}).then(() => {
+        return CommonUtils.writeJSON(contents, this.path);
+      });
+  },
+
+  /**
+   * Returns the local copy of the cache if there is one, otherwise it reads
+   * it from the disk.
+   *
+   * @return {Promise} A promise that is resolved once the read is complete.
+   */
+  _getCache: Task.async(function* () {
+    if (this._cache) {
+      return this._cache;
+    }
+
+    try {
+      return (this._cache = yield CommonUtils.readJSON(this.path));
+    } catch(error) {
+      // This is really complex due to OSFile's error handling, see bug 1160109.
+      if ((OS.Constants.libc && error.unixErrno != OS.Constants.libc.ENOENT) ||
+          (OS.Constants.Win && error.winLastError != OS.Constants.Win.ERROR_FILE_NOT_FOUND)) {
+        MozLoopService.log.debug("Error reading the cache:", error);
+      }
+      return (this._cache = {});
+    }
+  }),
+
+  /**
+   * Function for testability purposes. Clears the cache.
+   *
+   * @return {Promise} A promise that is resolved once the clear is complete.
+   */
+  clear: function() {
+    this._cache = null;
+    return OS.File.remove(this.path);
+  },
+
+  /**
+   * Gets a room key from the cache.
+   *
+   * @param {LOOP_SESSION_TYPE} sessionType  The session type for the room.
+   * @param {String}            roomToken    The token for the room.
+   * @return {Promise} A promise that is resolved when the data has been read
+   *                   with the value of the key, or null if it isn't present.
+   */
+  getKey: Task.async(function* (sessionType, roomToken) {
+    if (sessionType != LOOP_SESSION_TYPE.FXA) {
+      return null;
+    }
+
+    let sessionData = (yield this._getCache())[sessionType];
+
+    if (!sessionData || !sessionData[roomToken]) {
+      return null;
+    }
+    return sessionData[roomToken].key;
+  }),
+
+  /**
+   * Stores a room key into the cache. Note, if the key has not changed,
+   * the store will not be re-written.
+   *
+   * @param {LOOP_SESSION_TYPE} sessionType  The session type for the room.
+   * @param {String}            roomToken    The token for the room.
+   * @param {String}            roomKey      The encryption key for the room.
+   * @return {Promise} A promise that is resolved when the data has been stored.
+   */
+  setKey: Task.async(function* (sessionType, roomToken, roomKey) {
+    if (sessionType != LOOP_SESSION_TYPE.FXA) {
+      return;
+    }
+
+    let cache = yield this._getCache();
+
+    // Create these objects if they don't exist.
+    // We aim to do this creation and setting of the room key in a
+    // forwards-compatible way so that if new fields are added to rooms later
+    // then we don't mess them up (if there's no keys).
+    if (!cache[sessionType]) {
+      cache[sessionType] = {};
+    }
+
+    if (!cache[sessionType][roomToken]) {
+      cache[sessionType][roomToken] = {};
+    }
+
+    // Only save it if there's no key, or it is different.
+    if (!cache[sessionType][roomToken].key ||
+        cache[sessionType][roomToken].key != roomKey) {
+      cache[sessionType][roomToken].key = roomKey;
+      return yield this._setCache(cache);
+    }
+  })
+};
--- a/browser/components/loop/modules/MozLoopPushHandler.jsm
+++ b/browser/components/loop/modules/MozLoopPushHandler.jsm
@@ -5,17 +5,17 @@
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 
-const {MozLoopService} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
+const { MozLoopService } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
 const consoleLog = MozLoopService.log;
 
 this.EXPORTED_SYMBOLS = ["MozLoopPushHandler"];
 
 const CONNECTION_STATE_CLOSED = 0;
 const CONNECTION_STATE_CONNECTING = 1;
 const CONNECTION_STATE_OPEN = 2;
 
@@ -291,17 +291,17 @@ function PingMonitor(pingFunc, onTimeout
 
 PingMonitor.prototype = {
   /**
    * Function to restart the ping timeout and cancel any current timeout operation.
    */
   restart: function () {
     consoleLog.info("PushHandler: ping timeout restart");
     this.stop();
-    this._pingTimerID = setTimeout(() => {this._pingSend()}, this._pingInterval);
+    this._pingTimerID = setTimeout(() => { this._pingSend(); }, this._pingInterval);
   },
 
   /**
    * Function to stop the PingMonitor.
    */
   stop: function() {
     if (this._pingTimerID){
       clearTimeout(this._pingTimerID);
@@ -498,17 +498,17 @@ let MozLoopPushHandler = {
       return;
     }
 
     this.channels.set(channelID, {onRegistered: onRegistered,
                                   onNotification: onNotification});
     this._channelsToRegister.push(channelID);
     this._registerChannels();
   },
-  
+
   /**
    * Un-register a notification channel.
    *
    * @param {String} channelID Notification channel ID.
    */
   unregister: function(channelID) {
     consoleLog.info("MozLoopPushHandler: un-register channel ", channelID);
     if (!this.channels.has(channelID)) {
@@ -766,24 +766,24 @@ let MozLoopPushHandler = {
     this._pushSocket = new PushSocket(this._mockWebSocket);
 
     let performOpen = () => {
       consoleLog.info("PushHandler: attempt to open websocket to PushServer: ", this.pushServerUri);
       this._pushSocket.connect(this.pushServerUri,
                                (aMsg) => this._onMsg(aMsg),
                                () => this._onStart(),
                                (aCode, aReason) => this._onClose(aCode, aReason));
-    }
+    };
 
     let pushServerURLFetchError = () => {
       consoleLog.warn("PushHandler: Could not retrieve push server URL from Loop server, will retry");
       this._pushSocket = undefined;
       this._retryManager.retry(() => this._openSocket());
       return;
-    }
+    };
 
     try {
       this.pushServerUri = Services.prefs.getCharPref("loop.debug.pushserver");
     }
     catch (e) {}
 
     if (!this.pushServerUri) {
       // Get push server to use from the Loop server
@@ -866,9 +866,9 @@ let MozLoopPushHandler = {
    * @param {string} channelID - identification token to use in registration for this channel.
    */
   _sendRegistration: function(channelID) {
     if (channelID) {
       this._pushSocket.send({messageType: "register",
                              channelID: channelID});
     }
   },
-}
+};
--- a/browser/components/loop/modules/MozLoopService.jsm
+++ b/browser/components/loop/modules/MozLoopService.jsm
@@ -59,17 +59,17 @@ this.EXPORTED_SYMBOLS = ["MozLoopService
   "TWO_WAY_MEDIA_CONN_LENGTH", "SHARING_STATE_CHANGE"];
 
 XPCOMUtils.defineLazyModuleGetter(this, "injectLoopAPI",
   "resource:///modules/loop/MozLoopAPI.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "convertToRTCStatsReport",
   "resource://gre/modules/media/RTCStatsReport.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "loopUtils",
-  "resource:///modules/loop/utils.js", "utils")
+  "resource:///modules/loop/utils.js", "utils");
 XPCOMUtils.defineLazyModuleGetter(this, "loopCrypto",
   "resource:///modules/loop/crypto.js", "LoopCrypto");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Chat", "resource:///modules/Chat.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
                                   "resource://services-common/utils.js");
 
@@ -165,22 +165,22 @@ let MozLoopServiceInternal = {
   /**
    * The current deferreds for the registration processes. This is set if in progress
    * or the registration was successful. This is null if a registration attempt was
    * unsuccessful.
    */
   deferredRegistrations: new Map(),
 
   get pushHandler() {
-    return this.mocks.pushHandler || MozLoopPushHandler
+    return this.mocks.pushHandler || MozLoopPushHandler;
   },
 
   // The uri of the Loop server.
   get loopServerUri() {
-    return Services.prefs.getCharPref("loop.server")
+    return Services.prefs.getCharPref("loop.server");
   },
 
   /**
    * The initial delay for push registration. This ensures we don't start
    * kicking off straight after browser startup, just a few seconds later.
    */
   get initialRegistrationDelayMilliseconds() {
     try {
@@ -360,17 +360,17 @@ let MozLoopServiceInternal = {
     return new Promise((resolve, reject) => {
       let onRegistered = (error, pushURL, channelID) => {
         log.debug("createNotificationChannel onRegistered:", error, pushURL, channelID);
         if (error) {
           reject(Error(error));
         } else {
           resolve(this.registerWithLoopServer(sessionType, serviceType, pushURL));
         }
-      }
+      };
 
       this.pushHandler.register(channelID, onRegistered, onNotification);
     });
   },
 
   /**
    * Starts registration of Loop with the PushServer and the LoopServer.
    * Successful PushServer registration will automatically trigger the registration
@@ -438,30 +438,30 @@ let MozLoopServiceInternal = {
     if (!pushURL || !sessionType || !serviceType) {
       return Promise.reject(new Error("Invalid or missing parameters for registerWithLoopServer"));
     }
 
     let pushURLs = this.pushURLs.get(sessionType);
 
     // Create a blank URL record set if none exists for this sessionType.
     if (!pushURLs) {
-      pushURLs = {calls: undefined, rooms: undefined};
+      pushURLs = { calls: undefined, rooms: undefined };
       this.pushURLs.set(sessionType, pushURLs);
     }
 
     if (pushURLs[serviceType] == pushURL) {
       return Promise.resolve(pushURL);
     }
 
     let newURLs = {calls: pushURLs.calls,
                    rooms: pushURLs.rooms};
     newURLs[serviceType] = pushURL;
 
     return this.hawkRequestInternal(sessionType, "/registration", "POST",
-                                    {simplePushURLs: newURLs}).then(
+                                    { simplePushURLs: newURLs }).then(
       (response) => {
         // If this failed we got an invalid token.
         if (!this.storeSessionToken(sessionType, response.headers)) {
           throw new Error("session-token-wrong-size");
         }
 
         // Record the new push URL
         pushURLs[serviceType] = pushURL;
@@ -528,17 +528,17 @@ let MozLoopServiceInternal = {
             // Authorization failed, invalid token. This is fine since it may mean we already logged out.
             log.debug("already unregistered - invalid token", sessionType);
             return "already unregistered, sessionType = " + sessionType;
           }
 
           log.error("Failed to unregister with the loop server. Error: ", error);
           throw error;
         });
-    }
+    };
 
     return Promise.all([unregister(sessionType, callsPushURL), unregister(sessionType, roomsPushURL)]);
   },
 
   /**
    * Performs a hawk based request to the loop server - there is no pre-registration
    * for this request, if this is required, use hawkRequest.
    *
@@ -579,17 +579,17 @@ let MozLoopServiceInternal = {
       // mutating the values of the object passed in.
       let newPayloadObj = {};
       for (let property of Object.getOwnPropertyNames(payloadObj)) {
         if (typeof payloadObj[property] == "string") {
           newPayloadObj[property] = CommonUtils.encodeUTF8(payloadObj[property]);
         } else {
           newPayloadObj[property] = payloadObj[property];
         }
-      };
+      }
       payloadObj = newPayloadObj;
     }
 
     let handle401Error = (error) => {
       if (sessionType === LOOP_SESSION_TYPE.FXA) {
         return MozLoopService.logOutFromFxA().then(() => {
           // Set a user-visible error after logOutFromFxA clears existing ones.
           this.setError("login", error);
@@ -799,17 +799,17 @@ let MozLoopServiceInternal = {
         // Send job to worker to do log sanitation, transcoding and saving to
         // disk for pickup by telemetry on next startup, which then uploads it.
 
         let worker = new ChromeWorker("MozLoopWorker.js");
         worker.onmessage = function(e) {
           log.info(e.data.ok ?
             "Successfully staged loop report for telemetry upload." :
             ("Failed to stage loop report. Error: " + e.data.fail));
-        }
+        };
         worker.postMessage(job);
       });
     }, pc.id);
   },
 
   getChatWindowID: function(conversationWindowData) {
     // Try getting a window ID that can (re-)identify this conversation, or resort
     // to a globally unique one as a last resort.
@@ -937,17 +937,17 @@ let MozLoopServiceInternal = {
       }
       let prefType = Services.prefs.getPrefType(this.getSessionTokenPrefName(SESSION_TYPE));
       if (prefType == Services.prefs.PREF_INVALID) {
         throw new Error("No FxA hawk token returned and we don't have one saved");
       }
 
       return JSON.parse(response.body);
     },
-    error => {this._hawkRequestError(error);});
+    error => { this._hawkRequestError(error); });
   },
 
   /**
    * Get the OAuth client constructed with Loop OAauth parameters.
    *
    * @return {Promise}
    */
   promiseFxAOAuthClient: Task.async(function* () {
@@ -1020,17 +1020,17 @@ let MozLoopServiceInternal = {
 
     let payload = {
       code: code,
       state: state,
     };
     return this.hawkRequestInternal(LOOP_SESSION_TYPE.FXA, "/fxa-oauth/token", "POST", payload).then(response => {
       return JSON.parse(response.body);
     },
-    error => {this._hawkRequestError(error);});
+    error => { this._hawkRequestError(error); });
   },
 
   /**
    * Called once gFxAOAuthClient fires onComplete.
    *
    * @param {Deferred} deferred used to resolve the gFxAOAuthClientPromise
    * @param {Object} result (with code and state)
    */
@@ -1684,17 +1684,17 @@ this.MozLoopService = {
    * @returns {Promise}
    *        Returns a promise that resolves to the response of the API call,
    *        or is rejected with an error.  If the server response can be parsed
    *        as JSON and contains an 'error' property, the promise will be
    *        rejected with this JSON-parsed response.
    */
   hawkRequest: function(sessionType, path, method, payloadObj) {
     return MozLoopServiceInternal.hawkRequest(sessionType, path, method, payloadObj).catch(
-      error => {MozLoopServiceInternal._hawkRequestError(error);});
+      error => { MozLoopServiceInternal._hawkRequestError(error); });
   },
 
   /**
    * Returns the window data for a specific conversation window id.
    *
    * This data will be relevant to the type of window, e.g. rooms or calls.
    * See LoopRooms or LoopCalls for more information.
    *
--- a/browser/components/loop/moz.build
+++ b/browser/components/loop/moz.build
@@ -15,16 +15,17 @@ BROWSER_CHROME_MANIFESTS += [
 EXTRA_JS_MODULES.loop += [
     'content/shared/js/crypto.js',
     'content/shared/js/utils.js',
     'modules/CardDavImporter.jsm',
     'modules/GoogleImporter.jsm',
     'modules/LoopCalls.jsm',
     'modules/LoopContacts.jsm',
     'modules/LoopRooms.jsm',
+    'modules/LoopRoomsCache.jsm',
     'modules/LoopStorage.jsm',
     'modules/MozLoopAPI.jsm',
     'modules/MozLoopPushHandler.jsm',
     'modules/MozLoopService.jsm',
     'modules/MozLoopWorker.js',
 ]
 
 with Files('**'):
--- a/browser/components/loop/standalone/content/js/multiplexGum.js
+++ b/browser/components/loop/standalone/content/js/multiplexGum.js
@@ -53,17 +53,17 @@ loop.standaloneMedia = (function() {
         // Operate on a copy of the array in case any of the callbacks
         // calls reset, which would cause an infinite-recursion.
         this.userMedia.successCallbacks = [];
         this.userMedia.errorCallbacks = [];
         callbacks.forEach(function(cb) {
           if (typeof cb == "function") {
             cb(param);
           }
-        })
+        });
       }
       function handleSuccess(localStream) {
         this.userMedia.pending = false;
         this.userMedia.localStream = localStream;
         this.userMedia.error = null;
         handleResult.call(this, this.userMedia.successCallbacks.slice(0), localStream);
       }
 
@@ -129,17 +129,17 @@ loop.standaloneMedia = (function() {
     }
   };
 
   var singletonMultiplexGum = new _MultiplexGum();
   function myGetUserMedia() {
     // This function is needed to pull in the instance
     // of the singleton for tests to overwrite the used instance.
     singletonMultiplexGum.getPermsAndCacheMedia.apply(singletonMultiplexGum, arguments);
-  };
+  }
   patchSymbolIfExtant("navigator", "mozGetUserMedia", myGetUserMedia);
   patchSymbolIfExtant("navigator", "webkitGetUserMedia", myGetUserMedia);
   patchSymbolIfExtant("navigator", "getUserMedia", myGetUserMedia);
   patchSymbolIfExtant("TBPlugin", "getUserMedia", myGetUserMedia);
 
   return {
     multiplexGum: singletonMultiplexGum,
     _MultiplexGum: _MultiplexGum,
--- a/browser/components/loop/standalone/content/js/standaloneMetricsStore.js
+++ b/browser/components/loop/standalone/content/js/standaloneMetricsStore.js
@@ -31,17 +31,17 @@ loop.store.StandaloneMetricsStore = (fun
     audioMute: "audio mute",
     button: "button click",
     download: "download button click",
     faceMute: "face mute",
     link: "link click",
     pageLoad: "page load messages",
     success: "success",
     support: "support link click"
-  }
+  };
 
   var StandaloneMetricsStore = loop.store.createStore({
     actions: [
       "gotMediaPermission",
       "joinRoom",
       "leaveRoom",
       "mediaConnected",
       "recordClick"
@@ -127,17 +127,17 @@ loop.store.StandaloneMetricsStore = (fun
         "Leave conversation");
     },
 
     /**
      * Handles notification that two-way media has been achieved.
      */
     mediaConnected: function() {
       this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success,
-        "Media connected")
+        "Media connected");
     },
 
     /**
      * Handles recording link clicks.
      *
      * @param {sharedActions.RecordClick} actionData The data associated with
      *                                               the link.
      */
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -210,17 +210,17 @@ loop.standaloneRoomViews = (function(moz
     },
 
     recordClick: function(event) {
       // Check for valid href, as this is clicking on the paragraph -
       // so the user may be clicking on the text rather than the link.
       if (event.target && event.target.href) {
         this.props.dispatcher.dispatch(new sharedActions.RecordClick({
           linkInfo: event.target.href
-        }))
+        }));
       }
     },
 
     render: function() {
       return (
         React.createElement("footer", null, 
           React.createElement("p", {dangerouslySetInnerHTML: {__html: this._getContent()}, 
              onClick: this.recordClick}), 
@@ -283,17 +283,17 @@ loop.standaloneRoomViews = (function(moz
       roomContextUrls: React.PropTypes.array,
       roomName: React.PropTypes.string,
       roomInfoFailure: React.PropTypes.string
     },
 
     getInitialState: function() {
       return {
         failureLogged: false
-      }
+      };
     },
 
     _logFailure: function(message) {
       if (!this.state.failureLogged) {
         console.error(mozL10n.get(message));
         this.state.failureLogged = true;
       }
     },
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -210,17 +210,17 @@ loop.standaloneRoomViews = (function(moz
     },
 
     recordClick: function(event) {
       // Check for valid href, as this is clicking on the paragraph -
       // so the user may be clicking on the text rather than the link.
       if (event.target && event.target.href) {
         this.props.dispatcher.dispatch(new sharedActions.RecordClick({
           linkInfo: event.target.href
-        }))
+        }));
       }
     },
 
     render: function() {
       return (
         <footer>
           <p dangerouslySetInnerHTML={{__html: this._getContent()}}
              onClick={this.recordClick}></p>
@@ -283,17 +283,17 @@ loop.standaloneRoomViews = (function(moz
       roomContextUrls: React.PropTypes.array,
       roomName: React.PropTypes.string,
       roomInfoFailure: React.PropTypes.string
     },
 
     getInitialState: function() {
       return {
         failureLogged: false
-      }
+      };
     },
 
     _logFailure: function(message) {
       if (!this.state.failureLogged) {
         console.error(mozL10n.get(message));
         this.state.failureLogged = true;
       }
     },
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -675,11 +675,11 @@ describe("loop.roomViews", function () {
         view = mountTestComponent({
           roomData: { roomContextUrls: [fakeContextURL] }
         });
 
         var closeBtn = view.getDOMNode().querySelector(".room-context-btn-close");
         React.addons.TestUtils.Simulate.click(closeBtn);
         expect(view.getDOMNode()).to.eql(null);
       });
-    })
+    });
   });
 });
--- a/browser/components/loop/test/mochitest/browser_CardDavImporter.js
+++ b/browser/components/loop/test/mochitest/browser_CardDavImporter.js
@@ -4,17 +4,17 @@
 "use strict";
 
 const {CardDavImporter} = Cu.import("resource:///modules/loop/CardDavImporter.jsm", {});
 
 const kAuth = {
   "method": "basic",
   "user": "username",
   "password": "p455w0rd"
-}
+};
 
 
 // "pid" for "provider ID"
 let vcards = [
     "VERSION:3.0\n" +
     "N:Smith;John;;;\n" +
     "FN:John Smith\n" +
     "EMAIL;TYPE=work:john.smith@example.com\n" +
@@ -161,17 +161,17 @@ const monkeyPatchImporter = function(imp
         default:
           reject(new Error("404 Not Found"));
           return;
       }
       resolve({"responseXML": responseXML});
     });
   }.bind(importer);
   return importer;
-}
+};
 
 add_task(function* test_CardDavImport() {
   let importer = monkeyPatchImporter(new CardDavImporter());
   yield new Promise ((resolve, reject) => {
     info("Initiating import");
     importer.startImport({
         "host": "example.com",
         "auth": kAuth.method,
@@ -318,9 +318,9 @@ add_task(function* test_CardDavImport() 
 
   error = yield new Promise ((resolve, reject) => {
     info("Initiating import");
     importer.startImport({
         "host": "example.com",
       }, (err, result) => { err ? resolve(err) : reject(new Error("Should have failed")); }, mockDb);
   });
   Assert.equal(error.message, "No authentication specified", "Missing parameters should generate error");
-})
+});
--- a/browser/components/loop/test/mochitest/browser_LoopContacts.js
+++ b/browser/components/loop/test/mochitest/browser_LoopContacts.js
@@ -101,17 +101,17 @@ const promiseLoadContacts = function() {
 };
 
 // Get a copy of a contact without private properties.
 const normalizeContact = function(contact) {
   let result = {};
   // Get a copy of contact without private properties.
   for (let prop of Object.getOwnPropertyNames(contact)) {
     if (!prop.startsWith("_")) {
-      result[prop] = contact[prop]
+      result[prop] = contact[prop];
     }
   }
   return result;
 };
 
 const compareContacts = function(contact1, contact2) {
   Assert.ok("_guid" in contact1, "First contact should have an ID.");
   Assert.deepEqual(normalizeContact(contact1), normalizeContact(contact2));
@@ -184,17 +184,17 @@ add_task(function* () {
 
   yield new Promise((resolve, reject) => {
     LoopContacts.removeAll(function(err) {
       Assert.ok(!err, "There shouldn't be an error");
       LoopContacts.getAll(function(err, found) {
         Assert.ok(!err, "There shouldn't be an error");
         Assert.equal(found.length, 0, "There shouldn't be any contacts left");
         resolve();
-      })
+      });
     });
   });
 });
 
 // Test retrieving a contact.
 add_task(function* () {
   let contacts = yield promiseLoadContacts();
 
--- a/browser/components/loop/test/mochitest/browser_mozLoop_sharingListeners.js
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_sharingListeners.js
@@ -28,17 +28,17 @@ let handlers = [
   }
 ];
 
 function promiseWindowIdReceivedOnAdd(handler) {
   return new Promise(resolve => {
     handler.resolve = resolve;
     gMozLoopAPI.addBrowserSharingListener(handler.listener);
   });
-};
+}
 
 let createdTabs = [];
 
 function promiseWindowIdReceivedNewTab(handlers = []) {
   let promiseHandlers = [];
 
   handlers.forEach(handler => {
     promiseHandlers.push(new Promise(resolve => {
@@ -47,17 +47,17 @@ function promiseWindowIdReceivedNewTab(h
   });
 
   let createdTab = gBrowser.selectedTab = gBrowser.addTab();
   createdTabs.push(createdTab);
 
   promiseHandlers.push(promiseTabLoadEvent(createdTab, "about:mozilla"));
 
   return Promise.all(promiseHandlers);
-};
+}
 
 function promiseRemoveTab(tab) {
   return new Promise(resolve => {
     gBrowser.tabContainer.addEventListener("TabClose", function onTabClose() {
       gBrowser.tabContainer.removeEventListener("TabClose", onTabClose);
       resolve();
     });
     gBrowser.removeTab(tab);
@@ -165,17 +165,17 @@ add_task(function* test_infoBar() {
 
     let button = bar.querySelector(".notification-button");
     Assert.ok(button, "There should be a button present");
     Assert.strictEqual(button.type, "menu-button", "We're expecting a menu-button");
     Assert.strictEqual(button.getAttribute("anchor"), "dropmarker",
       "The popup should be opening anchored to the dropmarker");
     Assert.strictEqual(button.getElementsByTagNameNS(kNSXUL, "menupopup").length, 1,
       "There should be a popup attached to the button");
-  }
+  };
 
   testBarProps();
 
   // When we switch tabs, the infobar should move along with it. We use `selectedIndex`
   // here, because that's the setter that triggers the 'select' event. This event
   // is what LoopUI listens to and moves the infobar.
   gBrowser.selectedIndex = Array.indexOf(gBrowser.tabs, createdTabs[0]);
 
--- a/browser/components/loop/test/shared/otSdkDriver_test.js
+++ b/browser/components/loop/test/shared/otSdkDriver_test.js
@@ -14,19 +14,19 @@ describe("loop.OTSdkDriver", function ()
   var sandbox;
   var dispatcher, driver, mozLoop, publisher, sdk, session, sessionData;
   var fakeLocalElement, fakeRemoteElement, fakeScreenElement;
   var publisherConfig, fakeEvent;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
 
-    fakeLocalElement = {fake: 1};
-    fakeRemoteElement = {fake: 2};
-    fakeScreenElement = {fake: 3};
+    fakeLocalElement = { fake: 1 };
+    fakeRemoteElement = { fake: 2 };
+    fakeScreenElement = { fake: 3 };
     fakeEvent = {
       preventDefault: sinon.stub()
     };
     publisherConfig = {
       fake: "config"
     };
     sessionData = {
       apiKey: "1234567890",
@@ -93,47 +93,47 @@ describe("loop.OTSdkDriver", function ()
 
   afterEach(function() {
     sandbox.restore();
   });
 
   describe("Constructor", function() {
     it("should throw an error if the dispatcher is missing", function() {
       expect(function() {
-        new loop.OTSdkDriver({sdk: sdk});
+        new loop.OTSdkDriver({ sdk: sdk });
       }).to.Throw(/dispatcher/);
     });
 
     it("should throw an error if the sdk is missing", function() {
       expect(function() {
         new loop.OTSdkDriver({dispatcher: dispatcher});
       }).to.Throw(/sdk/);
     });
   });
 
   describe("#setupStreamElements", function() {
     it("should call initPublisher", function() {
       driver.setupStreamElements(new sharedActions.SetupStreamElements({
-        getLocalElementFunc: function() {return fakeLocalElement;},
-        getRemoteElementFunc: function() {return fakeRemoteElement;},
+        getLocalElementFunc: function() { return fakeLocalElement; },
+        getRemoteElementFunc: function() { return fakeRemoteElement; },
         publisherConfig: publisherConfig
       }));
 
       sinon.assert.calledOnce(sdk.initPublisher);
       sinon.assert.calledWith(sdk.initPublisher, fakeLocalElement, publisherConfig);
     });
   });
 
   describe("#retryPublishWithoutVideo", function() {
     beforeEach(function() {
       sdk.initPublisher.returns(publisher);
 
       driver.setupStreamElements(new sharedActions.SetupStreamElements({
-        getLocalElementFunc: function() {return fakeLocalElement;},
-        getRemoteElementFunc: function() {return fakeRemoteElement;},
+        getLocalElementFunc: function() { return fakeLocalElement; },
+        getRemoteElementFunc: function() { return fakeRemoteElement; },
         publisherConfig: publisherConfig
       }));
     });
 
     it("should make MediaStreamTrack.getSources return without a video source", function(done) {
       driver.retryPublishWithoutVideo();
 
       window.MediaStreamTrack.getSources(function(sources) {
@@ -153,18 +153,18 @@ describe("loop.OTSdkDriver", function ()
     });
   });
 
   describe("#setMute", function() {
     beforeEach(function() {
       sdk.initPublisher.returns(publisher);
 
       driver.setupStreamElements(new sharedActions.SetupStreamElements({
-        getLocalElementFunc: function() {return fakeLocalElement;},
-        getRemoteElementFunc: function() {return fakeRemoteElement;},
+        getLocalElementFunc: function() { return fakeLocalElement; },
+        getRemoteElementFunc: function() { return fakeRemoteElement; },
         publisherConfig: publisherConfig
       }));
     });
 
     it("should publishAudio with the correct enabled value", function() {
       driver.setMute(new sharedActions.SetMute({
         type: "audio",
         enabled: false
@@ -599,19 +599,19 @@ describe("loop.OTSdkDriver", function ()
     });
   });
 
   describe("Events (general media)", function() {
     beforeEach(function() {
       driver.connectSession(sessionData);
 
       driver.setupStreamElements(new sharedActions.SetupStreamElements({
-        getLocalElementFunc: function() {return fakeLocalElement;},
-        getScreenShareElementFunc: function() {return fakeScreenElement;},
-        getRemoteElementFunc: function() {return fakeRemoteElement;},
+        getLocalElementFunc: function() {return fakeLocalElement; },
+        getScreenShareElementFunc: function() {return fakeScreenElement; },
+        getRemoteElementFunc: function() {return fakeRemoteElement; },
         publisherConfig: publisherConfig
       }));
     });
 
     describe("connectionDestroyed", function() {
       it("should dispatch a remotePeerDisconnected action if the client" +
         "disconnected", function() {
           session.trigger("connectionDestroyed", {
@@ -727,17 +727,17 @@ describe("loop.OTSdkDriver", function ()
         fakeStream = {
           hasVideo: true,
           videoType: "camera",
           videoDimensions: {width: 1, height: 2}
         };
       });
 
       it("should dispatch a VideoDimensionsChanged action", function() {
-        publisher.trigger("streamCreated", {stream: fakeStream});
+        publisher.trigger("streamCreated", { stream: fakeStream });
 
         sinon.assert.called(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.VideoDimensionsChanged({
             isLocal: true,
             videoType: "camera",
             dimensions: {width: 1, height: 2}
           }));
@@ -768,103 +768,103 @@ describe("loop.OTSdkDriver", function ()
         fakeStream = {
           hasVideo: true,
           videoType: "camera",
           videoDimensions: {width: 1, height: 2}
         };
       });
 
       it("should dispatch a VideoDimensionsChanged action", function() {
-        session.trigger("streamCreated", {stream: fakeStream});
+        session.trigger("streamCreated", { stream: fakeStream });
 
         sinon.assert.called(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.VideoDimensionsChanged({
             isLocal: false,
             videoType: "camera",
-            dimensions: {width: 1, height: 2}
+            dimensions: { width: 1, height: 2 }
           }));
       });
 
       it("should dispatch a ConnectionStatus action", function() {
         driver._metrics.connections = 1;
 
-        session.trigger("streamCreated", {stream: fakeStream});
+        session.trigger("streamCreated", { stream: fakeStream });
 
         sinon.assert.called(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.ConnectionStatus({
             event: "Session.streamCreated",
             state: "receiving",
             connections: 1,
             recvStreams: 1,
             sendStreams: 0
           }));
       });
 
       it("should subscribe to a camera stream", function() {
-        session.trigger("streamCreated", {stream: fakeStream});
+        session.trigger("streamCreated", { stream: fakeStream });
 
         sinon.assert.calledOnce(session.subscribe);
         sinon.assert.calledWithExactly(session.subscribe,
           fakeStream, fakeRemoteElement, publisherConfig);
       });
 
       it("should subscribe to a screen sharing stream", function() {
         fakeStream.videoType = "screen";
 
-        session.trigger("streamCreated", {stream: fakeStream});
+        session.trigger("streamCreated", { stream: fakeStream });
 
         sinon.assert.calledOnce(session.subscribe);
         sinon.assert.calledWithExactly(session.subscribe,
           fakeStream, fakeScreenElement, publisherConfig);
       });
 
       it("should dispatch a mediaConnected action if both streams are up", function() {
         driver._publishedLocalStream = true;
 
-        session.trigger("streamCreated", {stream: fakeStream});
+        session.trigger("streamCreated", { stream: fakeStream });
 
         // Called twice due to the VideoDimensionsChanged above.
         sinon.assert.called(dispatcher.dispatch);
         sinon.assert.calledWithMatch(dispatcher.dispatch,
           new sharedActions.MediaConnected({}));
       });
 
       it("should store the start time when both streams are up and" +
       " driver._sendTwoWayMediaTelemetry is true", function() {
         driver._sendTwoWayMediaTelemetry = true;
         driver._publishedLocalStream = true;
         var startTime = 1;
         sandbox.stub(performance, "now").returns(startTime);
 
-        session.trigger("streamCreated", {stream: fakeStream});
+        session.trigger("streamCreated", { stream: fakeStream });
 
         expect(driver._getTwoWayMediaStartTime()).to.eql(startTime);
       });
 
       it("should not store the start time when both streams are up and" +
          " driver._isDesktop is false", function() {
-        driver._isDesktop = false ;
+        driver._isDesktop = false;
         driver._publishedLocalStream = true;
         var startTime = 73;
         sandbox.stub(performance, "now").returns(startTime);
 
-        session.trigger("streamCreated", {stream: fakeStream});
+        session.trigger("streamCreated", { stream: fakeStream });
 
         expect(driver._getTwoWayMediaStartTime()).to.not.eql(startTime);
       });
 
 
       it("should not dispatch a mediaConnected action for screen sharing streams",
         function() {
           driver._publishedLocalStream = true;
           fakeStream.videoType = "screen";
 
-          session.trigger("streamCreated", {stream: fakeStream});
+          session.trigger("streamCreated", { stream: fakeStream });
 
           sinon.assert.neverCalledWithMatch(dispatcher.dispatch,
             sinon.match.hasOwn("name", "mediaConnected"));
         });
 
       it("should not dispatch a ReceivingScreenShare action for camera streams",
         function() {
           session.trigger("streamCreated", {stream: fakeStream});
@@ -872,22 +872,22 @@ describe("loop.OTSdkDriver", function ()
           sinon.assert.neverCalledWithMatch(dispatcher.dispatch,
             new sharedActions.ReceivingScreenShare({receiving: true}));
         });
 
       it("should dispatch a ReceivingScreenShare action for screen sharing streams",
         function() {
           fakeStream.videoType = "screen";
 
-          session.trigger("streamCreated", {stream: fakeStream});
+          session.trigger("streamCreated", { stream: fakeStream });
 
           // Called twice due to the VideoDimensionsChanged above.
           sinon.assert.called(dispatcher.dispatch);
           sinon.assert.calledWithExactly(dispatcher.dispatch,
-            new sharedActions.ReceivingScreenShare({receiving: true}));
+            new sharedActions.ReceivingScreenShare({ receiving: true }));
         });
     });
 
     describe("streamDestroyed (publisher/local)", function() {
       it("should dispatch a ConnectionStatus action", function() {
         driver._metrics.sendStreams = 1;
         driver._metrics.recvStreams = 1;
         driver._metrics.connections = 2;
@@ -911,17 +911,17 @@ describe("loop.OTSdkDriver", function ()
 
       beforeEach(function() {
         fakeStream = {
           videoType: "screen"
         };
       });
 
       it("should dispatch a ReceivingScreenShare action", function() {
-        session.trigger("streamDestroyed", {stream: fakeStream});
+        session.trigger("streamDestroyed", { stream: fakeStream });
 
         sinon.assert.called(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.ReceivingScreenShare({
             receiving: false
           }));
       });
 
@@ -941,17 +941,17 @@ describe("loop.OTSdkDriver", function ()
             recvStreams: 0,
             sendStreams: 1
           }));
       });
 
       it("should not dispatch an action if the videoType is camera", function() {
         fakeStream.videoType = "camera";
 
-        session.trigger("streamDestroyed", {stream: fakeStream});
+        session.trigger("streamDestroyed", { stream: fakeStream });
 
         sinon.assert.neverCalledWithMatch(dispatcher.dispatch,
           sinon.match.hasOwn("name", "receivingScreenShare"));
       });
     });
 
     describe("streamPropertyChanged", function() {
       var fakeStream = {
@@ -996,75 +996,75 @@ describe("loop.OTSdkDriver", function ()
         session.connection = {
           id: "localUser"
         };
       });
 
       it("should dispatch a RemotePeerConnected action if this is for a remote user",
         function() {
           session.trigger("connectionCreated", {
-            connection: {id: "remoteUser"}
+            connection: { id: "remoteUser" }
           });
 
           sinon.assert.called(dispatcher.dispatch);
           sinon.assert.calledWithExactly(dispatcher.dispatch,
             new sharedActions.RemotePeerConnected());
         });
 
       it("should store the connection details for a remote user", function() {
         session.trigger("connectionCreated", {
-          connection: {id: "remoteUser"}
+          connection: { id: "remoteUser" }
         });
 
         expect(driver.connections).to.include.keys("remoteUser");
       });
 
       it("should dispatch a ConnectionStatus action for a remote user", function() {
         driver._metrics.connections = 1;
         driver._metrics.sendStreams = 1;
 
         session.trigger("connectionCreated", {
-          connection: {id: "remoteUser"}
+          connection: { id: "remoteUser" }
         });
 
         sinon.assert.called(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.ConnectionStatus({
           event: "Session.connectionCreated",
           state: "sending",
           connections: 2,
           recvStreams: 0,
           sendStreams: 1
         }));
       });
 
       it("should not dispatch an RemotePeerConnected action if this is for a local user",
         function() {
           session.trigger("connectionCreated", {
-            connection: {id: "localUser"}
+            connection: { id: "localUser" }
           });
 
           sinon.assert.neverCalledWithMatch(dispatcher.dispatch,
             sinon.match.hasOwn("name", "remotePeerConnected"));
         });
 
       it("should not store the connection details for a local user", function() {
         session.trigger("connectionCreated", {
-          connection: {id: "localUser"}
+          connection: { id: "localUser" }
         });
 
         expect(driver.connections).to.not.include.keys("localUser");
       });
 
       it("should dispatch a ConnectionStatus action for a remote user", function() {
         driver._metrics.connections = 0;
         driver._metrics.sendStreams = 0;
 
         session.trigger("connectionCreated", {
-          connection: {id: "localUser"}
+          connection: { id: "localUser" }
         });
 
         sinon.assert.called(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.ConnectionStatus({
             event: "Session.connectionCreated",
             state: "waiting",
             connections: 1,
--- a/browser/components/loop/test/standalone/standaloneMetricsStore_test.js
+++ b/browser/components/loop/test/standalone/standaloneMetricsStore_test.js
@@ -87,17 +87,17 @@ describe("loop.store.StandaloneMetricsSt
       store.recordClick(new sharedActions.RecordClick({
         linkInfo: "fake"
       }));
 
       sinon.assert.calledOnce(window.ga);
       sinon.assert.calledWithExactly(window.ga,
         "send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.linkClick,
         "fake");
-    })
+    });
   });
 
   describe("Store Change Handlers", function() {
     it("should log an event on room full", function() {
       fakeActiveRoomStore.setStoreState({roomState: ROOM_STATES.FULL});
 
       sinon.assert.calledOnce(window.ga);
       sinon.assert.calledWithExactly(window.ga,
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -244,17 +244,17 @@ describe("loop.standaloneRoomViews", fun
         var view = mountTestComponent();
 
         activeRoomStore.setStoreState({roomState: ROOM_STATES.GATHER});
 
         expect(view._videoDimensionsCache).eql({
           local: {},
           remote: {}
         });
-      })
+      });
     });
 
     describe("#publishStream", function() {
       var view;
 
       beforeEach(function() {
         view = mountTestComponent();
         view.setState({
--- a/browser/components/loop/test/standalone/standalone_client_test.js
+++ b/browser/components/loop/test/standalone/standalone_client_test.js
@@ -89,17 +89,17 @@ describe("loop.StandaloneClient", functi
           client.requestCallUrlInfo("fakeCallUrlToken", sandbox.stub());
           requests[0].respond(404, {"Content-Type": "application/json"},
                               error);
 
           sinon.assert.calledOnce(console.error);
           sinon.assert.calledWithExactly(console.error, "Server error",
                                         "HTTP 404 Not Found", serverResponse);
         });
-      })
+     });
     });
 
 
 
     describe("requestCallInfo", function() {
       var client, fakeServerErrorDescription;
 
       beforeEach(function() {
--- a/browser/components/loop/test/xpcshell/head.js
+++ b/browser/components/loop/test/xpcshell/head.js
@@ -1,24 +1,29 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
+// Initialize this before the imports, as some of them need it.
+do_get_profile();
+
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Http.jsm");
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource:///modules/loop/MozLoopService.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource:///modules/loop/LoopCalls.jsm");
 Cu.import("resource:///modules/loop/LoopRooms.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
 const { MozLoopServiceInternal } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
+const { LoopRoomsInternal } = Cu.import("resource:///modules/loop/LoopRooms.jsm", {});
 
 XPCOMUtils.defineLazyModuleGetter(this, "MozLoopPushHandler",
                                   "resource:///modules/loop/MozLoopPushHandler.jsm");
 
 const kMockWebSocketChannelName = "Mock WebSocket Channel";
 const kWebSocketChannelContractID = "@mozilla.org/network/protocol;1?name=wss";
 
 const kServerPushUrl = "ws://localhost";
@@ -130,17 +135,17 @@ let mockPushHandler = {
   }
 };
 
 /**
  * Mock nsIWebSocketChannel for tests. This mocks the WebSocketChannel, and
  * enables us to check parameters and return messages similar to the push
  * server.
  */
-function MockWebSocketChannel() {};
+function MockWebSocketChannel() {}
 
 MockWebSocketChannel.prototype = {
   QueryInterface: XPCOMUtils.generateQI(Ci.nsIWebSocketChannel),
 
   initRegStatus: 0,
 
   defaultMsgHandler: function(msg) {
     // Treat as a ping
@@ -206,8 +211,15 @@ MockWebSocketChannel.prototype = {
   stop: function (err) {
     this.listener.onStop(this.context, err || -1);
   },
 
   serverClose: function (err) {
     this.listener.onServerClose(this.context, err || -1);
   },
 };
+
+const extend = function(target, source) {
+  for (let key of Object.getOwnPropertyNames(source)) {
+    target[key] = source[key];
+  }
+  return target;
+};
--- a/browser/components/loop/test/xpcshell/test_loopapi_hawk_request.js
+++ b/browser/components/loop/test/xpcshell/test_loopapi_hawk_request.js
@@ -28,17 +28,17 @@ add_task(function* hawk_session_scope_co
 function generateSessionTypeVerificationStub(desiredSessionType) {
 
   function hawkRequestStub(sessionType, path, method, payloadObj, callback) {
     return new Promise(function (resolve, reject) {
       Assert.equal(desiredSessionType, sessionType);
 
       resolve();
     });
-  };
+  }
 
   return hawkRequestStub;
 }
 
 const origHawkRequest = MozLoopService.hawkRequest;
 do_register_cleanup(function() {
   MozLoopService.hawkRequest = origHawkRequest;
 });
--- a/browser/components/loop/test/xpcshell/test_looppush_initialize.js
+++ b/browser/components/loop/test/xpcshell/test_looppush_initialize.js
@@ -125,20 +125,20 @@ add_test(function test_reconnect_no_regi
 });
 
 add_test(function test_ping_websocket() {
   let pingReceived = false,
       socketClosed = false;
   mockWebSocket.defaultMsgHandler = (msg) => {
     pingReceived = true;
     // Do not send a ping response.
-  }
+  };
   mockWebSocket.close = () => {
     socketClosed = true;
-  }
+  };
 
   MozLoopPushHandler.shutdown();
   MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket});
   MozLoopPushHandler.register(
     "test-chan",
     function(err, url) {
       Assert.equal(err, null, "err should be null to indicate success");
       waitForCondition(() => pingReceived).then(() => {
@@ -215,9 +215,9 @@ function run_test() {
     Services.prefs.clearUserPref("services.push.serverULR");
     Services.prefs.clearUserPref("loop.retry_delay.start");
     Services.prefs.clearUserPref("loop.retry_delay.limit");
     Services.prefs.clearUserPref("loop.ping.interval");
     Services.prefs.clearUserPref("loop.ping.timeout");
   });
 
   run_next_test();
-};
+}
--- a/browser/components/loop/test/xpcshell/test_looprooms.js
+++ b/browser/components/loop/test/xpcshell/test_looprooms.js
@@ -177,23 +177,16 @@ const kCreateRoomData = {
   roomToken: "_nxD4V4FflQ",
   roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
   expiresAt: 1405534180
 };
 
 const kChannelGuest = MozLoopService.channelIDs.roomsGuest;
 const kChannelFxA = MozLoopService.channelIDs.roomsFxA;
 
-const extend = function(target, source) {
-  for (let key of Object.getOwnPropertyNames(source)) {
-    target[key] = source[key];
-  }
-  return target;
-};
-
 const normalizeRoom = function(room) {
   let newRoom = extend({}, room);
   let name = newRoom.decryptedContext.roomName;
 
   for (let key of Object.getOwnPropertyNames(roomDetail)) {
     // Handle sub-objects if necessary (e.g. context, decryptedContext).
     if (typeof roomDetail[key] == "object") {
       newRoom[key] = extend({}, roomDetail[key]);
@@ -465,57 +458,63 @@ add_task(function* test_refresh() {
 // Test if push updates function as expected.
 add_task(function* test_roomUpdates() {
   gExpectedUpdates.push("_nxD4V4FflQ");
   gExpectedLeaves["_nxD4V4FflQ"] = [
     "2a1787a6-4a73-43b5-ae3e-906ec1e763cb",
     "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7"
   ];
   roomsPushNotification("1", kChannelGuest);
-  yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedLeaves).length === 0);
+  yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedLeaves).length === 0 &&
+    gExpectedUpdates.length === 0);
 
   gExpectedUpdates.push("_nxD4V4FflQ");
   gExpectedJoins["_nxD4V4FflQ"] = ["2a1787a6-4a73-43b5-ae3e-906ec1e763cb"];
   roomsPushNotification("2", kChannelGuest);
-  yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedJoins).length === 0);
+  yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedJoins).length === 0 &&
+    gExpectedUpdates.length === 0);
 
   gExpectedUpdates.push("_nxD4V4FflQ");
   gExpectedJoins["_nxD4V4FflQ"] = ["781f012b-f1ea-4ce1-9105-7cfc36fb4ec7"];
   gExpectedLeaves["_nxD4V4FflQ"] = ["2a1787a6-4a73-43b5-ae3e-906ec1e763cb"];
   roomsPushNotification("3", kChannelGuest);
-  yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedLeaves).length === 0);
+  yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedLeaves).length === 0 &&
+    Object.getOwnPropertyNames(gExpectedJoins).length === 0 &&
+    gExpectedUpdates.length === 0);
 
   gExpectedUpdates.push("_nxD4V4FflQ");
   gExpectedJoins["_nxD4V4FflQ"] = [
     "2a1787a6-4a73-43b5-ae3e-906ec1e763cb",
     "5de6281c-6568-455f-af08-c0b0a973100e"];
   roomsPushNotification("4", kChannelGuest);
-  yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedJoins).length === 0);
+  yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedJoins).length === 0 &&
+    gExpectedUpdates.length === 0);
 });
 
 // Test if push updates' channelIDs are respected.
-add_task(function* () {
+add_task(function* test_channelIdsRespected() {
   function badRoomJoin() {
     Assert.ok(false, "Unexpected 'joined' event emitted!");
   }
-  LoopRooms.on("join", onRoomLeft);
+  LoopRooms.on("join", badRoomJoin);
   roomsPushNotification("4", kChannelFxA);
   LoopRooms.off("join", badRoomJoin);
 
   // Set the userProfile to look like we're logged into FxA.
   MozLoopServiceInternal.fxAOAuthTokenData = { token_type: "bearer" };
   MozLoopServiceInternal.fxAOAuthProfile = { email: "fake@invalid.com" };
 
   gExpectedUpdates.push("_nxD4V4FflQ");
   gExpectedLeaves["_nxD4V4FflQ"] = [
     "2a1787a6-4a73-43b5-ae3e-906ec1e763cb",
     "5de6281c-6568-455f-af08-c0b0a973100e"
   ];
   roomsPushNotification("3", kChannelFxA);
-  yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedLeaves).length === 0);
+  yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedLeaves).length === 0 &&
+    gExpectedUpdates.length === 0);
 });
 
 // Test if joining a room as Guest works as expected.
 add_task(function* test_joinRoomGuest() {
   MozLoopServiceInternal.fxAOAuthTokenData = null;
   MozLoopServiceInternal.fxAOAuthProfile = null;
 
   let roomToken = "_nxD4V4FflQ";
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/xpcshell/test_looprooms_encryption_in_fxa.js
@@ -0,0 +1,269 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Cu.import("resource://services-common/utils.js");
+const { LOOP_ROOMS_CACHE_FILENAME } = Cu.import("resource:///modules/loop/LoopRoomsCache.jsm", {});
+
+const kContextEnabledPref = "loop.contextInConverations.enabled";
+
+const kFxAKey = "uGIs-kGbYt1hBBwjyW7MLQ";
+
+// Rooms details as responded by the server.
+const kRoomsResponses = new Map([
+  ["_nxD4V4FflQ", {
+    // Encrypted with roomKey "FliIGLUolW-xkKZVWstqKw".
+    // roomKey is wrapped with kFxAKey.
+    context: {
+      wrappedKey: "F3V27oPB+FgjFbVPML2PupONYqoIZ53XRU4BqG46Lr3eyIGumgCEqgjSe/MXAXiQ//8=",
+      value: "df7B4SNxhOI44eJjQavCevADyCCxz6/DEZbkOkRUMVUxzS42FbzN6C2PqmCKDYUGyCJTwJ0jln8TLw==",
+      alg: "AES-GCM"
+    },
+    roomToken: "_nxD4V4FflQ",
+    roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ"
+  }],
+  ["QzBbvGmIZWU", {
+    context: {
+      wrappedKey: "AFu7WwFNjhWR5J6L8ks7S6H/1ktYVEw3yt1eIIWVaMabZaB3vh5612/FNzua4oS2oCM=",
+      value: "sqj+xRNEty8K3Q1gSMd5bIUYKu34JfiO2+LIMlJrOetFIbJdBoQ+U8JZNaTFl6Qp3RULZ41x0zeSBSk=",
+      alg: "AES-GCM"
+    },
+    roomToken: "QzBbvGmIZWU",
+    roomUrl: "http://localhost:3000/rooms/QzBbvGmIZWU"
+  }]
+]);
+
+const kExpectedRooms = new Map([
+  ["_nxD4V4FflQ", {
+    context: {
+      wrappedKey: "F3V27oPB+FgjFbVPML2PupONYqoIZ53XRU4BqG46Lr3eyIGumgCEqgjSe/MXAXiQ//8=",
+      value: "df7B4SNxhOI44eJjQavCevADyCCxz6/DEZbkOkRUMVUxzS42FbzN6C2PqmCKDYUGyCJTwJ0jln8TLw==",
+      alg: "AES-GCM"
+    },
+    decryptedContext: {
+      roomName: "First Room Name"
+    },
+    roomKey: "FliIGLUolW-xkKZVWstqKw",
+    roomToken: "_nxD4V4FflQ",
+    roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ#FliIGLUolW-xkKZVWstqKw"
+  }],
+  ["QzBbvGmIZWU", {
+    context: {
+      wrappedKey: "AFu7WwFNjhWR5J6L8ks7S6H/1ktYVEw3yt1eIIWVaMabZaB3vh5612/FNzua4oS2oCM=",
+      value: "sqj+xRNEty8K3Q1gSMd5bIUYKu34JfiO2+LIMlJrOetFIbJdBoQ+U8JZNaTFl6Qp3RULZ41x0zeSBSk=",
+      alg: "AES-GCM"
+    },
+    decryptedContext: {
+      roomName: "Loopy Discussion",
+    },
+    roomKey: "h2H8Sa9QxLCTTiXNmJVtRA",
+    roomToken: "QzBbvGmIZWU",
+    roomUrl: "http://localhost:3000/rooms/QzBbvGmIZWU"
+  }]
+]);
+
+const kCreateRoomProps = {
+  decryptedContext: {
+    roomName: "Say Hello",
+  },
+  roomOwner: "Gavin",
+  maxSize: 2
+};
+
+const kCreateRoomData = {
+  roomToken: "Vo2BFQqIaAM",
+  roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
+  expiresAt: 1405534180
+};
+
+function getCachePath() {
+  return OS.Path.join(OS.Constants.Path.profileDir, LOOP_ROOMS_CACHE_FILENAME);
+}
+
+function readRoomsCache() {
+  return CommonUtils.readJSON(getCachePath());
+}
+
+function saveRoomsCache(contents) {
+  delete LoopRoomsInternal.roomsCache._cache;
+  return CommonUtils.writeJSON(contents, getCachePath());
+}
+
+function clearRoomsCache() {
+  return LoopRoomsInternal.roomsCache.clear();
+}
+
+// This is a cut-down version of the one in test_looprooms.js.
+add_task(function* setup_server() {
+  loopServer.registerPathHandler("/registration", (req, res) => {
+    res.setStatusLine(null, 200, "OK");
+    res.processAsync();
+    res.finish();
+  });
+
+  loopServer.registerPathHandler("/rooms", (req, res) => {
+    res.setStatusLine(null, 200, "OK");
+
+    if (req.method == "POST") {
+      Assert.ok(req.bodyInputStream, "POST request should have a payload");
+      let body = CommonUtils.readBytesFromInputStream(req.bodyInputStream);
+      let data = JSON.parse(body);
+
+      Assert.ok(!("decryptedContext" in data), "should not have any decrypted data");
+      Assert.ok("context" in data, "should have context");
+
+      res.write(JSON.stringify(kCreateRoomData));
+    } else {
+      res.write(JSON.stringify([...kRoomsResponses.values()]));
+    }
+
+    res.processAsync();
+    res.finish();
+  });
+
+  function returnRoomDetails(res, roomName) {
+    roomDetail.roomName = roomName;
+    res.setStatusLine(null, 200, "OK");
+    res.write(JSON.stringify(roomDetail));
+    res.processAsync();
+    res.finish();
+  }
+
+  function getJSONData(body) {
+    return JSON.parse(CommonUtils.readBytesFromInputStream(body));
+  }
+
+  // Add a request handler for each room in the list.
+  [...kRoomsResponses.values()].forEach(function(room) {
+    loopServer.registerPathHandler("/rooms/" + encodeURIComponent(room.roomToken), (req, res) => {
+      if (req.method == "POST") {
+        let data = getJSONData(req.bodyInputStream);
+        res.setStatusLine(null, 200, "OK");
+        res.write(JSON.stringify(data));
+        res.processAsync();
+        res.finish();
+      } else if (req.method == "PATCH") {
+        let data = getJSONData(req.bodyInputStream);
+        Assert.ok("context" in data, "should have encrypted context");
+        // We return a fake encrypted name here as the context is
+        // encrypted.
+        returnRoomDetails(res, "fakeEncrypted");
+      } else {
+        res.setStatusLine(null, 200, "OK");
+        res.write(JSON.stringify(room));
+        res.processAsync();
+        res.finish();
+      }
+    });
+  });
+
+  loopServer.registerPathHandler("/rooms/error401", (req, res) => {
+    res.setStatusLine(null, 401, "Not Found");
+    res.processAsync();
+    res.finish();
+  });
+
+  loopServer.registerPathHandler("/rooms/errorMalformed", (req, res) => {
+    res.setStatusLine(null, 200, "OK");
+    res.write("{\"some\": \"Syntax Error!\"}}}}}}");
+    res.processAsync();
+    res.finish();
+  });
+
+  mockPushHandler.registrationPushURL = kEndPointUrl;
+
+  yield MozLoopService.promiseRegisteredWithServers();
+});
+
+
+// Test if getting rooms saves unknown keys correctly.
+add_task(function* test_get_rooms_saves_unknown_keys() {
+  let rooms = yield LoopRooms.promise("getAll");
+
+  // Check that we've saved the encryption keys correctly.
+  let roomsCache = yield readRoomsCache();
+  for (let room of [...kExpectedRooms.values()]) {
+    if (room.context.wrappedKey) {
+      Assert.equal(roomsCache[LOOP_SESSION_TYPE.FXA][room.roomToken].key, room.roomKey);
+    }
+  }
+
+  yield clearRoomsCache();
+});
+
+// Test that when we get a room it updates the saved key if it is different.
+add_task(function* test_get_rooms_saves_different_keys() {
+  let roomsCache = {};
+  roomsCache[LOOP_SESSION_TYPE.FXA] = {
+    QzBbvGmIZWU: {key: "fakeKey"}
+  };
+  yield saveRoomsCache(roomsCache);
+
+  const kRoomToken = "QzBbvGmIZWU";
+
+  let room = yield LoopRooms.promise("get", kRoomToken);
+
+  // Check that we've saved the encryption keys correctly.
+  roomsCache = yield readRoomsCache();
+
+  Assert.notEqual(roomsCache[LOOP_SESSION_TYPE.FXA][kRoomToken].key, "fakeKey");
+  Assert.equal(roomsCache[LOOP_SESSION_TYPE.FXA][kRoomToken].key, room.roomKey);
+
+  yield clearRoomsCache();
+});
+
+// Test that if roomKey decryption fails, the saved key is used for decryption.
+add_task(function* test_get_rooms_uses_saved_key() {
+  const kRoomToken = "_nxD4V4FflQ";
+  const kExpected = kExpectedRooms.get(kRoomToken);
+
+  let roomsCache = {};
+  roomsCache[LOOP_SESSION_TYPE.FXA] = {
+    "_nxD4V4FflQ": {key: kExpected.roomKey}
+  };
+  yield saveRoomsCache(roomsCache);
+
+  // Change the encryption key for FxA, so that decoding the room key will break.
+  Services.prefs.setCharPref("loop.key.fxa", "invalidKey");
+
+  let room = yield LoopRooms.promise("get", kRoomToken);
+
+  Assert.deepEqual(room, kExpected);
+
+  Services.prefs.setCharPref("loop.key.fxa", kFxAKey);
+  yield clearRoomsCache();
+});
+
+// Test that when a room is created the new key is saved.
+add_task(function* test_create_room_saves_key() {
+  let room = yield LoopRooms.promise("create", kCreateRoomProps);
+
+  let roomsCache = yield readRoomsCache();
+
+  Assert.equal(roomsCache[LOOP_SESSION_TYPE.FXA][room.roomToken].key, room.roomKey);
+
+  yield clearRoomsCache();
+});
+
+function run_test() {
+  setupFakeLoopServer();
+
+  Services.prefs.setCharPref("loop.key.fxa", kFxAKey);
+  Services.prefs.setBoolPref(kContextEnabledPref, true);
+
+  // Pretend we're signed into FxA.
+  MozLoopServiceInternal.fxAOAuthTokenData = { token_type: "bearer" };
+  MozLoopServiceInternal.fxAOAuthProfile = { email: "fake@invalid.com" };
+
+  do_register_cleanup(function () {
+    Services.prefs.clearUserPref(kContextEnabledPref);
+    Services.prefs.clearUserPref("loop.key.fxa");
+
+    MozLoopServiceInternal.fxAOAuthTokenData = null;
+    MozLoopServiceInternal.fxAOAuthProfile = null;
+  });
+
+  run_next_test();
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/xpcshell/test_looprooms_first_notification.js
@@ -0,0 +1,119 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource:///modules/loop/LoopRooms.jsm");
+Cu.import("resource:///modules/Chat.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+const kGuestKey = "uGIs-kGbYt1hBBwjyW7MLQ";
+const kChannelGuest = MozLoopService.channelIDs.roomsGuest;
+
+const kRoomsResponses = new Map([
+  ["_nxD4V4FflQ", {
+    roomToken: "_nxD4V4FflQ",
+    // Encrypted with roomKey "FliIGLUolW-xkKZVWstqKw".
+    // roomKey is wrapped with kGuestKey.
+    context: {
+      wrappedKey: "F3V27oPB+FgjFbVPML2PupONYqoIZ53XRU4BqG46Lr3eyIGumgCEqgjSe/MXAXiQ//8=",
+      value: "df7B4SNxhOI44eJjQavCevADyCCxz6/DEZbkOkRUMVUxzS42FbzN6C2PqmCKDYUGyCJTwJ0jln8TLw==",
+      alg: "AES-GCM"
+    },
+    roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
+    maxSize: 2,
+    ctime: 1405517546,
+    participants: [{
+      displayName: "Alexis",
+      account: "alexis@example.com",
+      roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb"
+    }, {
+      displayName: "Adam",
+      roomConnectionId: "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7"
+    }]
+  }],
+  ["QzBbvGmIZWU", {
+    roomToken: "QzBbvGmIZWU",
+    roomName: "Second Room Name",
+    roomUrl: "http://localhost:3000/rooms/QzBbvGmIZWU",
+    maxSize: 2,
+    ctime: 140551741
+  }],
+]);
+
+let gRoomsAdded = [];
+let gRoomsUpdated = [];
+
+const onRoomAdded = function(e, room) {
+  gRoomsAdded.push(room);
+};
+
+const onRoomUpdated = function(e, room) {
+  gRoomsUpdated.push(room);
+};
+
+let gQueryString = null;
+
+add_task(function* setup_server() {
+  loopServer.registerPathHandler("/registration", (req, res) => {
+    res.setStatusLine(null, 200, "OK");
+    res.processAsync();
+    res.finish();
+  });
+
+  loopServer.registerPathHandler("/rooms", (req, res) => {
+    gQueryString = req.queryString;
+
+    res.setStatusLine(null, 200, "OK");
+    // For this simple test, always send all rooms, even though this wouldn't
+    // happen normally.
+    res.write(JSON.stringify([...kRoomsResponses.values()]));
+    res.processAsync();
+    res.finish();
+  });
+
+  mockPushHandler.registrationPushURL = kEndPointUrl;
+
+  yield MozLoopService.promiseRegisteredWithServers();
+});
+
+// Test if notifying from push correctly gets all rooms the first time.
+add_task(function* test_notification_firstTime() {
+  roomsPushNotification("1", kChannelGuest);
+
+  // Wait for the notification to complete before we getAll, otherwise
+  // the getAll may cover up what we're testing.
+  yield waitForCondition(() => gRoomsAdded.length === 2);
+
+  let rooms = yield LoopRooms.promise("getAll");
+  Assert.equal(rooms.length, 2);
+  Assert.equal(gQueryString, "", "Query string should not be set.");
+});
+
+// Test if notifying from push correctly only gets the specific room the second time.
+add_task(function* test_notification_firstTime() {
+  roomsPushNotification("2", kChannelGuest);
+  yield waitForCondition(() => gRoomsUpdated.length === 2);
+
+  let rooms = yield LoopRooms.promise("getAll");
+  Assert.equal(rooms.length, 2);
+  Assert.equal(gQueryString, "version=2", "Query string should be set.");
+});
+
+function run_test() {
+  setupFakeLoopServer();
+
+  Services.prefs.setCharPref("loop.key", kGuestKey);
+  LoopRooms.on("add", onRoomAdded);
+  LoopRooms.on("update", onRoomUpdated);
+
+  do_register_cleanup(function () {
+    Services.prefs.clearUserPref("loop.key");
+
+    LoopRooms.off("add", onRoomAdded);
+    LoopRooms.off("update", onRoomUpdated);
+  });
+
+  run_next_test();
+}
--- a/browser/components/loop/test/xpcshell/test_loopservice_restart.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_restart.js
@@ -115,9 +115,9 @@ function run_test() {
     MozLoopServiceInternal.mocks.pushHandler = undefined;
     Services.prefs.clearUserPref(LOOP_INITIAL_DELAY_PREF);
     Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
     Services.prefs.clearUserPref(LOOP_FXA_PROFILE_PREF);
     Services.prefs.clearUserPref(LOOP_CREATED_ROOM_PREF);
   });
 
   run_next_test();
-};
+}
--- a/browser/components/loop/test/xpcshell/xpcshell.ini
+++ b/browser/components/loop/test/xpcshell/xpcshell.ini
@@ -2,16 +2,18 @@
 head = head.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'gonk'
 
 [test_loopapi_hawk_request.js]
 [test_looppush_initialize.js]
 [test_looprooms.js]
+[test_looprooms_encryption_in_fxa.js]
+[test_looprooms_first_notification.js]
 [test_loopservice_directcall.js]
 [test_loopservice_dnd.js]
 [test_loopservice_encryptionkey.js]
 [test_loopservice_hawk_errors.js]
 [test_loopservice_hawk_request.js]
 [test_loopservice_loop_prefs.js]
 [test_loopservice_initialize.js]
 [test_loopservice_locales.js]
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -627,17 +627,16 @@ BrowserGlue.prototype = {
       let notificationId = 'addon-slow:' + addonId;
       let notification = notificationBox.getNotificationWithValue(notificationId);
       if(notification) {
         notification.label = message;
       } else {
         let buttons = [
           {
             label: win.gNavigatorBundle.getFormattedString("addonwatch.disable.label", [addon.name]),
-            accessKey: win.gNavigatorBundle.getString("addonwatch.disable.accesskey"),
             callback: function() {
               addon.userDisabled = true;
               if (addon.pendingOperations != addon.PENDING_NONE) {
                 let restartMessage = win.gNavigatorBundle.getFormattedString("addonwatch.restart.message", [addon.name, brandShortName]);
                 let restartButton = [
                   {
                     label: win.gNavigatorBundle.getFormattedString("addonwatch.restart.label", [brandShortName]),
                     accessKey: win.gNavigatorBundle.getString("addonwatch.restart.accesskey"),
--- a/browser/devtools/performance/modules/actors.js
+++ b/browser/devtools/performance/modules/actors.js
@@ -14,24 +14,27 @@ const {
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
 loader.lazyRequireGetter(this, "RecordingUtils",
   "devtools/performance/recording-utils", true);
 loader.lazyRequireGetter(this, "TimelineFront",
   "devtools/server/actors/timeline", true);
 loader.lazyRequireGetter(this, "MemoryFront",
   "devtools/server/actors/memory", true);
-loader.lazyRequireGetter(this, "timers",
-  "resource://gre/modules/Timer.jsm");
+loader.lazyRequireGetter(this, "Poller",
+  "devtools/shared/poller", true);
 
 // how often do we pull allocation sites from the memory actor
 const ALLOCATION_SITE_POLL_TIMER = 200; // ms
 
+// how often do we check the status of the profiler's circular buffer
+const BUFFER_CHECK_TIMER = 5000; // ms
+
 const MEMORY_ACTOR_METHODS = [
-  "destroy", "attach", "detach", "getState", "getAllocationsSettings",
+  "attach", "detach", "getState", "getAllocationsSettings",
   "getAllocations", "startRecordingAllocations", "stopRecordingAllocations"
 ];
 
 const TIMELINE_ACTOR_METHODS = [
   "start", "stop",
 ];
 
 const PROFILER_ACTOR_METHODS = [
@@ -40,16 +43,19 @@ const PROFILER_ACTOR_METHODS = [
 ];
 
 /**
  * Constructor for a facade around an underlying ProfilerFront.
  */
 function ProfilerFrontFacade (target) {
   this._target = target;
   this._onProfilerEvent = this._onProfilerEvent.bind(this);
+  this._checkBufferStatus = this._checkBufferStatus.bind(this);
+  this._BUFFER_CHECK_TIMER = this._target.TEST_MOCK_BUFFER_CHECK_TIMER || BUFFER_CHECK_TIMER;
+
   EventEmitter.decorate(this);
 }
 
 ProfilerFrontFacade.prototype = {
   EVENTS: ["console-api-profiler", "profiler-stopped"],
 
   // Connects to the targets underlying real ProfilerFront.
   connect: Task.async(function*() {
@@ -67,46 +73,71 @@ ProfilerFrontFacade.prototype = {
     // TODO bug 1159389, listen directly to actor if supporting new front
     target.client.addListener("eventNotification", this._onProfilerEvent);
   }),
 
   /**
    * Unregisters events for the underlying profiler actor.
    */
   destroy: Task.async(function *() {
+    if (this._poller) {
+      yield this._poller.destroy();
+    }
     yield this.unregisterEventNotifications({ events: this.EVENTS });
     // TODO bug 1159389, listen directly to actor if supporting new front
     this._target.client.removeListener("eventNotification", this._onProfilerEvent);
   }),
 
   /**
    * Starts the profiler actor, if necessary.
+   *
+   * @option {number?} bufferSize
+   * @option {number?} sampleFrequency
    */
   start: Task.async(function *(options={}) {
+    // Check for poller status even if the profiler is already active --
+    // profiler can be activated via `console.profile` or another source, like
+    // the Gecko Profiler.
+    if (!this._poller) {
+      this._poller = new Poller(this._checkBufferStatus, this._BUFFER_CHECK_TIMER, false);
+    }
+    if (!this._poller.isPolling()) {
+      this._poller.on();
+    }
+
     // Start the profiler only if it wasn't already active. The built-in
     // nsIPerformance module will be kept recording, because it's the same instance
     // 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.isActive();
-    if (profilerStatus.isActive) {
+    let { isActive, currentTime, position, generation, totalSize } = yield this.isActive();
+    if (isActive) {
       this.emit("profiler-already-active");
-      return profilerStatus.currentTime;
+      return { startTime: currentTime, position, generation, totalSize };
     }
 
     // Translate options from the recording model into profiler-specific
     // options for the nsIProfiler
     let profilerOptions = {
       entries: options.bufferSize,
       interval: options.sampleFrequency ? (1000 / (options.sampleFrequency * 1000)) : void 0
     };
 
     yield this.startProfiler(profilerOptions);
 
     this.emit("profiler-activated");
-    return 0;
+    return { startTime: 0, position, generation, totalSize };
+  }),
+
+  /**
+   * Indicates the end of a recording -- does not actually stop the profiler
+   * (stopProfiler does that), but notes that we no longer need to poll
+   * for buffer status.
+   */
+  stop: Task.async(function *() {
+    yield this._poller.off();
   }),
 
   /**
    * Returns profile data from now since `startTime`.
    */
   getProfile: Task.async(function *(options) {
     let profilerData = yield (actorCompatibilityBridge("getProfile").call(this, options));
     // If the backend does not support filtering by start and endtime on platform (< Fx40),
@@ -131,16 +162,20 @@ ProfilerFrontFacade.prototype = {
       } else if (subject.action === "profileEnd") {
         this.emit("console-profile-end", details);
       }
     } else if (topic === "profiler-stopped") {
       this.emit("profiler-stopped");
     }
   },
 
+  _checkBufferStatus: Task.async(function *() {
+    this.emit("buffer-status", (yield this.isActive()));
+  }),
+
   toString: () => "[object ProfilerFrontFacade]"
 };
 
 // Bind all the methods that directly proxy to the actor
 PROFILER_ACTOR_METHODS.forEach(method => ProfilerFrontFacade.prototype[method] = actorCompatibilityBridge(method));
 exports.ProfilerFront = ProfilerFrontFacade;
 
 /**
@@ -195,30 +230,41 @@ TIMELINE_ACTOR_METHODS.forEach(method =>
 exports.TimelineFront = TimelineFrontFacade;
 
 /**
  * Constructor for a facade around an underlying ProfilerFront.
  */
 function MemoryFrontFacade (target) {
   this._target = target;
   this._pullAllocationSites = this._pullAllocationSites.bind(this);
+
   EventEmitter.decorate(this);
 }
 
 MemoryFrontFacade.prototype = {
   connect: Task.async(function*() {
     let supported = yield memoryActorSupported(this._target);
     this._actor = supported ?
                   new MemoryFront(this._target.client, this._target.form) :
                   new MockMemoryFront();
 
     this.IS_MOCK = !supported;
   }),
 
   /**
+   * Disables polling and destroys actor.
+   */
+  destroy: Task.async(function *() {
+    if (this._poller) {
+      yield this._poller.destroy();
+    }
+    yield this._actor.destroy();
+  }),
+
+  /**
    * Starts polling for allocation information.
    */
   start: Task.async(function *(options) {
     if (!options.withAllocations) {
       return 0;
     }
 
     yield this.attach();
@@ -230,17 +276,22 @@ MemoryFrontFacade.prototype = {
       allocationOptions.probability = options.allocationsSampleProbability;
     }
     if (options.allocationsMaxLogLength !== void 0) {
       allocationOptions.maxLogLength = options.allocationsMaxLogLength;
     }
 
     let startTime = yield this.startRecordingAllocations(allocationOptions);
 
-    yield this._pullAllocationSites();
+    if (!this._poller) {
+      this._poller = new Poller(this._pullAllocationSites, ALLOCATION_SITE_POLL_TIMER, false);
+    }
+    if (!this._poller.isPolling()) {
+      this._poller.on();
+    }
 
     return startTime;
   }),
 
   /**
    * Stops polling for allocation information.
    */
   stop: Task.async(function *(options) {
@@ -248,18 +299,18 @@ MemoryFrontFacade.prototype = {
       return 0;
     }
 
     // Since `_pullAllocationSites` is usually running inside a timeout, and
     // it's performing asynchronous requests to the server, a recording may
     // be stopped before that method finishes executing. Therefore, we need to
     // wait for the last request to `getAllocations` to finish before actually
     // stopping recording allocations.
+    yield this._poller.off();
     yield this._lastPullAllocationSitesFinished;
-    timers.clearTimeout(this._sitesPullTimeout);
 
     let endTime = yield this.stopRecordingAllocations();
     yield this.detach();
 
     return endTime;
   }),
 
   /**
@@ -282,18 +333,16 @@ MemoryFrontFacade.prototype = {
     // being the event name, and the second argument describing the type.
     this.emit("timeline-data", "allocations", {
       sites: memoryData.allocations,
       timestamps: memoryData.allocationsTimestamps,
       frames: memoryData.frames,
       counts: memoryData.counts
     });
 
-    this._sitesPullTimeout = timers.setTimeout(this._pullAllocationSites, ALLOCATION_SITE_POLL_TIMER);
-
     resolve();
   }),
 
   toString: () => "[object MemoryFrontFacade]"
 };
 
 // Bind all the methods that directly proxy to the actor
 MEMORY_ACTOR_METHODS.forEach(method => MemoryFrontFacade.prototype[method] = actorCompatibilityBridge(method));
--- a/browser/devtools/performance/modules/compatibility.js
+++ b/browser/devtools/performance/modules/compatibility.js
@@ -148,16 +148,23 @@ function legacyRequest (target, actor, m
 /**
  * Returns a function to be used as a method on an "Actor" in ./actors.
  * Calls the underlying actor's method, supporting the modern `Front`
  * interface if possible, otherwise, falling back to using
  * `legacyRequest`.
  */
 function actorCompatibilityBridge (method) {
   return function () {
+    // If there's no target or client on this actor facade,
+    // abort silently -- this occurs in tests when polling occurs
+    // after the test ends, when tests do not wait for toolbox destruction
+    // (which will destroy the actor facade, turning off the polling).
+    if (!this._target || !this._target.client) {
+      return;
+    }
     // Check to see if this is a modern ActorFront, which has its
     // own `request` method. Also, check if its a mock actor, as it mimicks
     // the ActorFront interface.
     // The profiler actor does not currently support the modern `Front`
     // interface, so we have to manually push packets to it.
     // TODO bug 1159389, fix up profiler actor to not need this, however
     // we will need it for backwards compat
     if (this.IS_MOCK || this._actor.request) {
--- a/browser/devtools/performance/modules/front.js
+++ b/browser/devtools/performance/modules/front.js
@@ -16,24 +16,21 @@ loader.lazyRequireGetter(this, "DevTools
 loader.lazyRequireGetter(this, "actors",
   "devtools/performance/actors");
 
 loader.lazyImporter(this, "gDevTools",
   "resource:///modules/devtools/gDevTools.jsm");
 loader.lazyImporter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 
-
-// How often do we pull allocation sites from the memory actor.
-const DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT = 200; // ms
-
 // Events to pipe from PerformanceActorsConnection to the PerformanceFront
 const CONNECTION_PIPE_EVENTS = [
   "timeline-data", "profiler-already-active", "profiler-activated",
-  "recording-starting", "recording-started", "recording-stopping", "recording-stopped"
+  "recording-starting", "recording-started", "recording-stopping", "recording-stopped",
+  "buffer-status"
 ];
 
 /**
  * A cache of all PerformanceActorsConnection instances.
  * The keys are Target objects.
  */
 let SharedPerformanceActors = new WeakMap();
 
@@ -74,16 +71,17 @@ function PerformanceActorsConnection(tar
   this._pendingConsoleRecordings = [];
   this._sitesPullTimeout = 0;
   this._recordings = [];
 
   this._pipeToConnection = this._pipeToConnection.bind(this);
   this._onTimelineData = this._onTimelineData.bind(this);
   this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this);
   this._onConsoleProfileEnd = this._onConsoleProfileEnd.bind(this);
+  this._onBufferStatus = this._onBufferStatus.bind(this);
   this._onProfilerUnexpectedlyStopped = this._onProfilerUnexpectedlyStopped.bind(this);
 
   Services.obs.notifyObservers(null, "performance-actors-connection-created", null);
 }
 
 PerformanceActorsConnection.prototype = {
 
   // Properties set based off of server actor support
@@ -168,29 +166,31 @@ PerformanceActorsConnection.prototype = 
   _registerListeners: function () {
     this._timeline.on("timeline-data", this._onTimelineData);
     this._memory.on("timeline-data", this._onTimelineData);
     this._profiler.on("console-profile-start", this._onConsoleProfileStart);
     this._profiler.on("console-profile-end", this._onConsoleProfileEnd);
     this._profiler.on("profiler-stopped", this._onProfilerUnexpectedlyStopped);
     this._profiler.on("profiler-already-active", this._pipeToConnection);
     this._profiler.on("profiler-activated", this._pipeToConnection);
+    this._profiler.on("buffer-status", this._onBufferStatus);
   },
 
   /**
    * Unregisters listeners on events on the underlying actors.
    */
   _unregisterListeners: function () {
     this._timeline.off("timeline-data", this._onTimelineData);
     this._memory.off("timeline-data", this._onTimelineData);
     this._profiler.off("console-profile-start", this._onConsoleProfileStart);
     this._profiler.off("console-profile-end", this._onConsoleProfileEnd);
     this._profiler.off("profiler-stopped", this._onProfilerUnexpectedlyStopped);
     this._profiler.off("profiler-already-active", this._pipeToConnection);
     this._profiler.off("profiler-activated", this._pipeToConnection);
+    this._profiler.off("buffer-status", this._onBufferStatus);
   },
 
   /**
    * Closes the connections to non-profiler actors.
    */
   _disconnectActors: Task.async(function* () {
     yield Promise.all([
       this._profiler.destroy(),
@@ -284,46 +284,64 @@ PerformanceActorsConnection.prototype = 
    * - frames
    * - memory
    * - ticks
    * - allocations
    *
    * Populate our internal store of recordings for all currently recording sessions.
    */
   _onTimelineData: function (_, ...data) {
-    this._recordings.forEach(e => e.addTimelineData.apply(e, data));
+    this._recordings.forEach(e => e._addTimelineData.apply(e, data));
     this.emit("timeline-data", ...data);
   },
 
   /**
+   * Called whenever the underlying profiler polls its buffer status.
+   */
+  _onBufferStatus: function (_, data) {
+    // If no buffer data emitted (whether from an older actor being destroyed
+    // from a previous test, or the server does not support it), just ignore.
+    // Also check for a value of buffer status (`position`) to see if it's
+    // because of an unsupported server.
+    if (!data || data.position === void 0) {
+      return;
+    }
+    this._recordings.forEach(e => e._addBufferStatusData.call(e, data));
+    this.emit("buffer-status", data);
+  },
+
+  /**
    * Begins a recording session
    *
    * @param object options
    *        An options object to pass to the actors. Supported properties are
    *        `withTicks`, `withMemory` and `withAllocations`, `probability`, and `maxLogLength`.
    * @return object
    *         A promise that is resolved once recording has started.
    */
   startRecording: Task.async(function*(options = {}) {
     let model = new RecordingModel(options);
     this.emit("recording-starting", model);
 
     // All actors are started asynchronously over the remote debugging protocol.
     // Get the corresponding start times from each one of them.
     // The timeline and memory actors are target-dependent, so start those as well,
     // even though these are mocked in older Geckos (FF < 35)
-    let profilerStartTime = yield this._profiler.start(options);
+    let { startTime, position, generation, totalSize } = yield this._profiler.start(options);
     let timelineStartTime = yield this._timeline.start(options);
     let memoryStartTime = yield this._memory.start(options);
 
-    let data = { profilerStartTime, timelineStartTime, memoryStartTime };
+    let data = {
+      profilerStartTime: startTime, timelineStartTime, memoryStartTime,
+      generation, position, totalSize
+    };
 
     // Signify to the model that the recording has started,
     // populate with data and store the recording model here.
-    model.populate(data);
+    model._populate(data);
     this._recordings.push(model);
 
     this.emit("recording-started", model);
     return model;
   }),
 
   /**
    * Manually ends the recording session for the corresponding RecordingModel.
@@ -363,16 +381,19 @@ PerformanceActorsConnection.prototype = 
     let memoryEndTime = Date.now();
     let timelineEndTime = Date.now();
 
     // Only if there are no more sessions recording do we stop
     // the underlying memory and timeline actors. If we're still recording,
     // juse use Date.now() for the memory and timeline end times, as those
     // are only used in tests.
     if (!this.isRecording()) {
+      // This doesn't stop the profiler, just turns off polling for
+      // events, and also turns off events on memory/timeline actors.
+      yield this._profiler.stop();
       memoryEndTime = yield this._memory.stop(config);
       timelineEndTime = yield this._timeline.stop(config);
     }
 
     // Set the results on the RecordingModel itself.
     model._onStopRecording({
       // Data available only at the end of a recording.
       profile: profilerData.profile,
--- a/browser/devtools/performance/modules/graphs.js
+++ b/browser/devtools/performance/modules/graphs.js
@@ -340,17 +340,21 @@ GraphsController.prototype = {
    * Sets a mapped selection on the graph that is the main controller
    * for keeping the graphs' selections in sync.
    */
   setMappedSelection: function (selection, { mapStart, mapEnd }) {
     return this._getPrimaryLink().setMappedSelection(selection, { mapStart, mapEnd });
   },
 
   getMappedSelection: function ({ mapStart, mapEnd }) {
-    return this._getPrimaryLink().getMappedSelection({ mapStart, mapEnd });
+    if (this._getPrimaryLink()) {
+      return this._getPrimaryLink().getMappedSelection({ mapStart, mapEnd });
+    } else {
+      return null;
+    }
   },
 
   /**
    * Returns an array of graphs that have been created, not necessarily
    * enabled currently.
    */
   getWidgets: function () {
     return Object.keys(this._graphs).map(name => this._graphs[name]);
--- a/browser/devtools/performance/modules/recording-model.js
+++ b/browser/devtools/performance/modules/recording-model.js
@@ -38,16 +38,18 @@ RecordingModel.prototype = {
   _console: false,
   _imported: false,
   _recording: false,
   _completed: false,
   _profilerStartTime: 0,
   _timelineStartTime: 0,
   _memoryStartTime: 0,
   _configuration: {},
+  _originalBufferStatus: null,
+  _bufferPercent: null,
 
   // Serializable fields, necessary and sufficient for import and export.
   _label: "",
   _duration: 0,
   _markers: null,
   _frames: null,
   _memory: null,
   _ticks: null,
@@ -85,26 +87,32 @@ RecordingModel.prototype = {
     let recordingData = this.getAllData();
     yield PerformanceIO.saveRecordingToFile(recordingData, file);
   }),
 
   /**
    * Sets up the instance with data from the SharedPerformanceConnection when
    * starting a recording. Should only be called by SharedPerformanceConnection.
    */
-  populate: function (info) {
+  _populate: function (info) {
     // Times must come from the actor in order to be self-consistent.
     // However, we also want to update the view with the elapsed time
     // even when the actor is not generating data. To do this we get
     // the local time and use it to compute a reasonable elapsed time.
     this._localStartTime = Date.now();
 
     this._profilerStartTime = info.profilerStartTime;
     this._timelineStartTime = info.timelineStartTime;
     this._memoryStartTime = info.memoryStartTime;
+    this._originalBufferStatus = {
+      position: info.position,
+      totalSize: info.totalSize,
+      generation: info.generation
+    };
+
     this._recording = true;
 
     this._markers = [];
     this._frames = [];
     this._memory = [];
     this._ticks = [];
     this._allocations = { sites: [], timestamps: [], frames: [], counts: [] };
   },
@@ -276,19 +284,45 @@ RecordingModel.prototype = {
    * A model may no longer be recording, yet still not have the profiler data. In that
    * case, use `isCompleted()`.
    */
   isRecording: function () {
     return this._recording;
   },
 
   /**
+   * Returns the percent (value between 0 and 1) of buffer used in this
+   * recording. Returns `null` for recordings that are no longer recording.
+   */
+  getBufferUsage: function () {
+    return this.isRecording() ? this._bufferPercent : null;
+  },
+
+  /**
+   * Fired whenever the PerformanceFront has new buffer data.
+   */
+  _addBufferStatusData: function (bufferStatus) {
+    // If this model isn't currently recording, or if the server does not
+    // support buffer status (or if this fires after actors are being destroyed),
+    // ignore this information.
+    if (!bufferStatus || !this.isRecording()) {
+      return;
+    }
+    let { position: currentPosition, totalSize, generation: currentGeneration } = bufferStatus;
+    let { position: origPosition, generation: origGeneration } = this._originalBufferStatus;
+
+    let normalizedCurrent = (totalSize * (currentGeneration - origGeneration)) + currentPosition;
+    let percent = (normalizedCurrent - origPosition) / totalSize;
+    this._bufferPercent = percent > 1 ? 1 : percent;
+  },
+
+  /**
    * Fired whenever the PerformanceFront emits markers, memory or ticks.
    */
-  addTimelineData: function (eventName, ...data) {
+  _addTimelineData: function (eventName, ...data) {
     // If this model isn't currently recording,
     // ignore the timeline data.
     if (!this.isRecording()) {
       return;
     }
 
     let config = this.getConfiguration();
 
@@ -338,12 +372,14 @@ RecordingModel.prototype = {
         RecordingUtils.offsetAndScaleTimestamps(timestamps, timeOffset, timeScale);
         Array.prototype.push.apply(this._allocations.sites, sites);
         Array.prototype.push.apply(this._allocations.timestamps, timestamps);
         Array.prototype.push.apply(this._allocations.frames, frames);
         Array.prototype.push.apply(this._allocations.counts, counts);
         break;
       }
     }
-  }
+  },
+
+  toString: () => "[object RecordingModel]"
 };
 
 exports.RecordingModel = RecordingModel;
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -14,16 +14,17 @@ support-files =
 [browser_markers-gc.js]
 [browser_markers-parse-html.js]
 [browser_perf-allocations-to-samples.js]
 [browser_perf-compatibility-01.js]
 [browser_perf-compatibility-02.js]
 [browser_perf-compatibility-03.js]
 [browser_perf-compatibility-04.js]
 [browser_perf-compatibility-05.js]
+[browser_perf-compatibility-06.js]
 [browser_perf-clear-01.js]
 [browser_perf-clear-02.js]
 [browser_perf-columns-js-calltree.js]
 [browser_perf-columns-memory-calltree.js]
 [browser_perf-console-record-01.js]
 [browser_perf-console-record-02.js]
 [browser_perf-console-record-03.js]
 [browser_perf-console-record-04.js]
@@ -85,16 +86,17 @@ support-files =
 [browser_perf-overview-selection-03.js]
 [browser_perf-overview-time-interval.js]
 [browser_perf-shared-connection-02.js]
 [browser_perf-shared-connection-03.js]
 [browser_perf-states.js]
 [browser_perf-refresh.js]
 [browser_perf-ui-recording.js]
 [browser_perf-recording-model-01.js]
+[browser_perf-recording-model-02.js]
 [browser_perf-recording-notices-01.js]
 [browser_perf-recording-notices-02.js]
 [browser_perf_recordings-io-01.js]
 [browser_perf_recordings-io-02.js]
 [browser_perf_recordings-io-03.js]
 [browser_perf_recordings-io-04.js]
 [browser_perf-range-changed-render.js]
 [browser_perf-recording-selected-01.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-compatibility-06.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that when using an older server (< Fx40) where the profiler actor does not
+ * have the `getBufferInfo` method that nothing breaks and RecordingModels have null
+ * `getBufferUsage()` values.
+ */
+
+function spawnTest () {
+  let { target, front } = yield initBackend(SIMPLE_URL, { TEST_MOCK_BUFFER_CHECK_TIMER: 10 });
+  let frontBufferStatusCalled = false;
+
+  // Explicitly override the profiler's `isActive` method, where
+  // all the buffer info is retrieved, and delete the buffer properties.
+  let isActive = front._connection._profiler.isActive;
+  front._connection._profiler.isActive = function () {
+    return isActive.apply(front._connection._profiler, arguments).then(res => {
+      return { isActive: res.isActive, currentTime: res.currentTime };
+    });
+  };
+
+  front.on("buffer-status", () => frontBufferStatusCalled = true);
+  let model = yield front.startRecording();
+  let [_, stats] = yield onceSpread(front._connection._profiler, "buffer-status");
+  is(stats.generation, void 0, "buffer-status has void `generation`");
+  is(stats.totalSize, void 0, "buffer-status has void `totalSize`");
+  is(stats.position, void 0, "buffer-status has void `position`");
+
+  let count = 0;
+  while (count < 5) {
+    let [_, stats] = yield onceSpread(front._connection._profiler, "buffer-status");
+    is(stats.generation, void 0, "buffer-status has void `generation`");
+    is(stats.totalSize, void 0, "buffer-status has void `totalSize`");
+    is(stats.position, void 0, "buffer-status has void `position`");
+    count++;
+  }
+
+  is(model.getBufferUsage(), null, "model should have `null` for its buffer usage");
+  yield front.stopRecording(model);
+  is(model.getBufferUsage(), null, "after recording, model should still have `null` for its buffer usage");
+  ok(!frontBufferStatusCalled, "the front should never emit a buffer-status event when not supported.");
+
+  yield removeTab(target.tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-recording-model-02.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that buffer status is correctly updated in recording models.
+ */
+
+let BUFFER_SIZE = 20000;
+
+function spawnTest () {
+  let { target, front } = yield initBackend(SIMPLE_URL, { TEST_MOCK_BUFFER_CHECK_TIMER: 10 });
+  let config = { bufferSize: BUFFER_SIZE };
+
+  let model = yield front.startRecording(config);
+  let [_, stats] = yield onceSpread(front, "buffer-status");
+  is(stats.totalSize, BUFFER_SIZE, `buffer-status event has correct totalSize: ${stats.totalSize}`);
+  ok(stats.position < BUFFER_SIZE, `buffer-status event has correct position: ${stats.position}`);
+  is(stats.generation, 0, `buffer-status event has correct generation: ${stats.generation}`);
+
+  // Halt once more for a buffer status to ensure we're beyond 0
+  yield once(front, "buffer-status");
+
+  let lastBufferStatus = 0;
+  let checkCount = 0;
+  while (lastBufferStatus < 1) {
+    let currentBufferStatus = model.getBufferUsage();
+    ok(currentBufferStatus > lastBufferStatus, `buffer is more filled than before: ${currentBufferStatus}`);
+    lastBufferStatus = currentBufferStatus;
+    checkCount++;
+    yield once(front, "buffer-status");
+  }
+
+  ok(checkCount >= 1, "atleast 1 event were fired until the buffer was filled");
+  is(lastBufferStatus, 1, "buffer usage cannot surpass 100%");
+  yield front.stopRecording(model);
+
+  is(model.getBufferUsage(), null, "getBufferUsage() should be null when no longer recording.");
+
+  yield removeTab(target.tab);
+  finish();
+}
--- a/browser/devtools/performance/test/head.js
+++ b/browser/devtools/performance/test/head.js
@@ -190,16 +190,17 @@ function initBackend(aUrl, targetOps={})
 
     yield target.makeRemote();
 
     // Attach addition options to `target`. This is used to force mock fronts
     // to smokescreen test different servers where memory or timeline actors
     // may not exist. Possible options that will actually work:
     // TEST_MOCK_MEMORY_ACTOR = true
     // TEST_MOCK_TIMELINE_ACTOR = true
+    // TEST_MOCK_BUFFER_CHECK_TIMER = number
     merge(target, targetOps);
 
     let connection = getPerformanceActorsConnection(target);
     yield connection.open();
 
     let front = new PerformanceFront(connection);
     return { target, front };
   });
--- a/browser/devtools/performance/views/overview.js
+++ b/browser/devtools/performance/views/overview.js
@@ -150,16 +150,21 @@ let OverviewView = {
       throw new Error("A recording should be available in order to get the selection.");
     }
     if (this.isDisabled()) {
       return { startTime: 0, endTime: recording.getDuration() };
     }
     let mapStart = () => 0;
     let mapEnd = () => recording.getDuration();
     let selection = this.graphs.getMappedSelection({ mapStart, mapEnd });
+    // If no selection returned, this means the overview graphs have not been rendered
+    // yet, so act as if we have no selection (the full recording).
+    if (!selection) {
+      return { startTime: 0, endTime: recording.getDuration() };
+    }
     return { startTime: selection.min, endTime: selection.max };
   },
 
   /**
    * Method for handling all the set up for rendering the overview graphs.
    *
    * @param number resolution
    *        The fps graph resolution. @see Graphs.jsm
--- a/browser/devtools/webconsole/console-output.js
+++ b/browser/devtools/webconsole/console-output.js
@@ -1057,26 +1057,26 @@ Messages.Extended.prototype = Heritage.e
    * Render one piece/element of the message array.
    *
    * @private
    * @param mixed piece
    *        Message element to display - this can be a LongString, ObjectActor,
    *        DOM node or a function to invoke.
    * @return Element
    */
-  _renderBodyPiece: function(piece)
+  _renderBodyPiece: function(piece, options = {})
   {
     if (piece instanceof Ci.nsIDOMNode) {
       return piece;
     }
     if (typeof piece == "function") {
       return piece(this);
     }
 
-    return this._renderValueGrip(piece);
+    return this._renderValueGrip(piece, options);
   },
 
   /**
    * Render a grip that represents a value received from the server. This method
    * picks the appropriate widget to render the value with.
    *
    * @private
    * @param object grip
@@ -1405,41 +1405,44 @@ Messages.ConsoleGeneric.prototype = Heri
     let body = Messages.Simple.prototype._renderBody.apply(this, arguments);
     body.classList.remove("devtools-monospace", "message-body");
     return body;
   },
 
   _renderBodyPieces: function(container)
   {
     let lastStyle = null;
+    let stylePieces = this._styles.length > 0 ? this._styles.length : 1;
 
     for (let i = 0; i < this._messagePieces.length; i++) {
-      let separator = i > 0 ? this._renderBodyPieceSeparator() : null;
-      if (separator) {
-        container.appendChild(separator);
+      // Pieces with an associated style definition come from "%c" formatting.
+      // For body pieces beyond that, add a separator before each one.
+      if (i >= stylePieces) {
+        container.appendChild(this._renderBodyPieceSeparator());
       }
 
       let piece = this._messagePieces[i];
       let style = this._styles[i];
 
       // No long string support.
-      if (style && typeof style == "string" ) {
-        lastStyle = this.cleanupStyle(style);
-      }
+      lastStyle = (style && typeof style == "string") ?
+                  this.cleanupStyle(style) : null;
 
       container.appendChild(this._renderBodyPiece(piece, lastStyle));
     }
 
     this._messagePieces = null;
     this._styles = null;
   },
 
   _renderBodyPiece: function(piece, style)
   {
-    let elem = Messages.Extended.prototype._renderBodyPiece.call(this, piece);
+    // Skip quotes for top-level strings.
+    let options = { noStringQuotes: true };
+    let elem = Messages.Extended.prototype._renderBodyPiece.call(this, piece, options);
     let result = elem;
 
     if (style) {
       if (elem.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
         elem.style = style;
       } else {
         let span = this.document.createElementNS(XHTML_NS, "span");
         span.style = style;
@@ -3293,21 +3296,26 @@ Widgets.ObjectRenderers.add({
 /**
  * The long string widget.
  *
  * @constructor
  * @param object message
  *        The owning message.
  * @param object longStringActor
  *        The LongStringActor to display.
+ * @param object options
+ *        Options, such as noStringQuotes
  */
-Widgets.LongString = function(message, longStringActor)
+Widgets.LongString = function(message, longStringActor, options)
 {
   Widgets.BaseWidget.call(this, message);
   this.longStringActor = longStringActor;
+  this.noStringQuotes = (options && "noStringQuotes" in options) ?
+    options.noStringQuotes : !this.message._quoteStrings;
+
   this._onClick = this._onClick.bind(this);
   this._onSubstring = this._onSubstring.bind(this);
 };
 
 Widgets.LongString.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
 {
   /**
    * The LongStringActor displayed by the widget.
@@ -3333,17 +3341,17 @@ Widgets.LongString.prototype = Heritage.
    * Render the long string in the widget element.
    * @private
    * @param string str
    *        The string to display.
    */
   _renderString: function(str)
   {
     this.element.textContent = VariablesView.getString(str, {
-      noStringQuotes: !this.message._quoteStrings,
+      noStringQuotes: this.noStringQuotes,
       noEllipsis: true,
     });
   },
 
   /**
    * Render the anchor ellipsis that allows the user to expand the long string.
    *
    * @private
--- a/browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js
@@ -84,17 +84,17 @@ function* testMethod(aMethod, aHud, aOut
   aHud.jsterm.clearOutput();
 
   // test for multiple arguments.
   console[aMethod]("foo", "bar");
 
   yield waitForMessages({
     webconsole: aHud,
     messages: [{
-      text: '"foo" "bar"',
+      text: 'foo bar',
       category: CATEGORY_WEBDEV,
     }],
   })
 }
 
 function setStringFilter(aValue, aHud) {
   aHud.ui.filterBox.value = aValue;
   aHud.ui.adjustVisibilityOnSearchStringChange();
--- a/browser/devtools/webconsole/test/browser_webconsole_output_01.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_01.js
@@ -24,61 +24,67 @@ DebuggerServer.LONG_STRING_INITIAL_LENGT
 let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 4)).join("a");
 let initialString = longString.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
 
 let inputTests = [
   // 0
   {
     input: "'hello \\nfrom \\rthe \\\"string world!'",
     output: "\"hello \nfrom \rthe \"string world!\"",
+    consoleOutput: "hello \nfrom \rthe \"string world!",
   },
 
   // 1
   {
     // unicode test
     input: "'\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165'",
     output: "\"\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165\"",
+    consoleOutput: "\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165",
   },
 
   // 2
   {
     input: "'" + longString + "'",
     output: '"' + initialString + "\"[\u2026]",
+    consoleOutput: initialString + "[\u2026]",
     printOutput: initialString,
   },
 
   // 3
   {
     input: "''",
     output: '""',
+    consoleOutput: "",
     printOutput: '""',
   },
 
   // 4
   {
     input: "0",
     output: "0",
   },
 
   // 5
   {
     input: "'0'",
     output: '"0"',
+    consoleOutput: "0",
   },
 
   // 6
   {
     input: "42",
     output: "42",
   },
 
   // 7
   {
     input: "'42'",
     output: '"42"',
+    consoleOutput: "42",
   },
 
   // 8
   {
     input: "/foobar/",
     output: "/foobar/",
     inspectable: true,
   },
--- a/browser/devtools/webconsole/test/browser_webconsole_output_events.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_events.js
@@ -30,24 +30,24 @@ let test = asyncTest(function* () {
       category: CATEGORY_OUTPUT,
     }],
   });
 
   yield waitForMessages({
     webconsole: hud,
     messages: [{
       name: "console.log() output for mousemove",
-      text: /"eventLogger" mousemove { target: .+, buttons: 0, clientX: \d+, clientY: \d+, layerX: \d+, layerY: \d+ }/,
+      text: /eventLogger mousemove { target: .+, buttons: 0, clientX: \d+, clientY: \d+, layerX: \d+, layerY: \d+ }/,
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
     }],
   });
 
   yield waitForMessages({
     webconsole: hud,
     messages: [{
       name: "console.log() output for keypress",
-      text: /"eventLogger" keypress Shift { target: .+, key: .+, charCode: \d+, keyCode: \d+ }/,
+      text: /eventLogger keypress Shift { target: .+, key: .+, charCode: \d+, keyCode: \d+ }/,
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
     }],
   });
 });
\ No newline at end of file
--- a/browser/devtools/webconsole/test/browser_webconsole_output_order.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_order.js
@@ -26,17 +26,17 @@ let test = asyncTest(function*() {
       text: "console.log('foo', 'bar');",
       category: CATEGORY_INPUT,
     },
     {
       text: "undefined",
       category: CATEGORY_OUTPUT,
     },
     {
-      text: '"foo" "bar"',
+      text: 'foo bar',
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
     }],
   });
 
   let fncall_node = [...function_call.matched][0];
   let result_node = [...result.matched][0];
   let console_message_node = [...console_message.matched][0];
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -1416,16 +1416,19 @@ function whenDelayedStartupFinished(aWin
  *        - inspectable: boolean, when true, the test runner expects the JS eval
  *        result is an object that can be clicked for inspection.
  *
  *        - noClick: boolean, when true, the test runner does not click the JS
  *        eval result. Some objects, like |window|, have a lot of properties and
  *        opening vview for them is very slow (they can cause timeouts in debug
  *        builds).
  *
+ *        - consoleOutput: string|RegExp, optional, expected consoleOutput
+ *        If not provided consoleOuput = output;
+ *
  *        - printOutput: string|RegExp, optional, expected output for
  *        |print(input)|. If this is not provided, printOutput = output.
  *
  *        - variablesViewLabel: string|RegExp, optional, the expected variables
  *        view label when the object is inspected. If this is not provided, then
  *        |output| is used.
  *
  *        - inspectorIcon: boolean, when true, the test runner expects the
@@ -1457,21 +1460,24 @@ function checkOutputForInputs(hud, input
   }
 
   function* checkConsoleLog(entry)
   {
     info("Logging: " + entry.input);
     hud.jsterm.clearOutput();
     hud.jsterm.execute("console.log(" + entry.input + ")");
 
+    let consoleOutput = "consoleOutput" in entry ?
+                        entry.consoleOutput : entry.output;
+
     let [result] = yield waitForMessages({
       webconsole: hud,
       messages: [{
-        name: "console.log() output: " + entry.output,
-        text: entry.output,
+        name: "console.log() output: " + consoleOutput,
+        text: consoleOutput,
         category: CATEGORY_WEBDEV,
         severity: SEVERITY_LOG,
       }],
     });
 
     if (typeof entry.inspectorIcon == "boolean") {
       let msg = [...result.matched][0];
       info("Checking Inspector Link: " + entry.input);
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -47,18 +47,16 @@ const INSECURE_PASSWORDS_LEARN_MORE = "h
 const STRICT_TRANSPORT_SECURITY_LEARN_MORE = "https://developer.mozilla.org/docs/Security/HTTP_Strict_Transport_Security";
 
 const WEAK_SIGNATURE_ALGORITHM_LEARN_MORE = "https://developer.mozilla.org/docs/Security/Weak_Signature_Algorithm";
 
 const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
 
 const VARIABLES_VIEW_URL = "chrome://browser/content/devtools/widgets/VariablesView.xul";
 
-const CONSOLE_DIR_VIEW_HEIGHT = 0.6;
-
 const IGNORED_SOURCE_URLS = ["debugger eval code"];
 
 // The amount of time in milliseconds that we wait before performing a live
 // search.
 const SEARCH_DELAY = 200;
 
 // The number of lines that are displayed in the console output by default, for
 // each category. The user can change this number by adjusting the hidden
@@ -2659,19 +2657,16 @@ WebConsoleFrame.prototype = {
     }
 
     node.appendChild(timestampNode);
     node.appendChild(indentNode);
     node.appendChild(iconContainer);
 
     // Display the variables view after the message node.
     if (aLevel == "dir") {
-      bodyNode.style.height = (this.window.innerHeight *
-                               CONSOLE_DIR_VIEW_HEIGHT) + "px";
-
       let options = {
         objectActor: body.arguments[0],
         targetElement: bodyNode,
         hideFilterInput: true,
       };
       this.jsterm.openVariablesView(options).then((aView) => {
         node._variablesView = aView;
         if (node.classList.contains("hidden-message")) {
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -46,17 +46,16 @@ addonInstall.acceptButton.accesskey=I
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 is brandShortName
 # #2 is the number of add-ons being installed
 addonConfirmInstall.message=This site would like to install an add-on in #1:;This site would like to install #2 add-ons in #1:
 
 addonwatch.slow=%1$S might be making %2$S run slowly
 addonwatch.disable.label=Disable %S
-addonwatch.disable.accesskey=D
 addonwatch.ignoreSession.label=Ignore for now
 addonwatch.ignoreSession.accesskey=I
 addonwatch.ignorePerm.label=Ignore permanently
 addonwatch.ignorePerm.accesskey=p
 addonwatch.restart.message=To disable %1$S you must restart %2$S
 addonwatch.restart.label=Restart %S
 addonwatch.restart.accesskey=R
 
--- a/configure.in
+++ b/configure.in
@@ -8480,16 +8480,19 @@ AC_SUBST(MOZ_LOCALE_SWITCHER)
 AC_SUBST(MOZ_DISABLE_GECKOVIEW)
 AC_SUBST(MOZ_ANDROID_GECKOLIBS_AAR)
 AC_SUBST(MOZ_ANDROID_READING_LIST_SERVICE)
 AC_SUBST(MOZ_ANDROID_SEARCH_ACTIVITY)
 AC_SUBST(MOZ_ANDROID_SHARE_OVERLAY)
 AC_SUBST(MOZ_ANDROID_TAB_QUEUE)
 AC_SUBST(MOZ_ANDROID_MLS_STUMBLER)
 AC_SUBST(MOZ_ANDROID_DOWNLOADS_INTEGRATION)
+AC_SUBST(MOZ_ANDROID_APPLICATION_CLASS)
+AC_SUBST(MOZ_ANDROID_BROWSER_INTENT_CLASS)
+AC_SUBST(MOZ_ANDROID_SEARCH_INTENT_CLASS)
 AC_SUBST(MOZ_INSTALL_TRACKING)
 AC_SUBST(ENABLE_STRIP)
 AC_SUBST(PKG_SKIP_STRIP)
 AC_SUBST(STRIP_FLAGS)
 AC_SUBST(USE_ELF_HACK)
 AC_SUBST(INCREMENTAL_LINKER)
 AC_SUBST(MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS)
 AC_SUBST(MOZ_COMPONENT_NSPR_LIBS)
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -1356,16 +1356,37 @@ Console::ProcessCallData(ConsoleCallData
     innerID.AppendInt(aData->mInnerIDNumber);
   }
 
   if (NS_FAILED(mStorage->RecordEvent(innerID, outerID, eventValue))) {
     NS_WARNING("Failed to record a console event.");
   }
 }
 
+namespace {
+
+// Helper method for ProcessArguments. Flushes output, if non-empty, to aSequence.
+void
+FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence, nsString &output)
+{
+  if (!output.IsEmpty()) {
+    JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
+                                                       output.get(),
+                                                       output.Length()));
+    if (!str) {
+      return;
+    }
+
+    aSequence.AppendElement(JS::StringValue(str));
+    output.Truncate();
+  }
+}
+
+} // anonymous namespace
+
 void
 Console::ProcessArguments(JSContext* aCx,
                           const nsTArray<JS::Heap<JS::Value>>& aData,
                           Sequence<JS::Value>& aSequence,
                           Sequence<JS::Value>& aStyles)
 {
   if (aData.IsEmpty()) {
     return;
@@ -1467,50 +1488,30 @@ Console::ProcessArguments(JSContext* aCx
     char ch = *start;
     tmp.Append(ch);
     ++start;
 
     switch (ch) {
       case 'o':
       case 'O':
       {
-        if (!output.IsEmpty()) {
-          JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
-                                                             output.get(),
-                                                             output.Length()));
-          if (!str) {
-            return;
-          }
-
-          aSequence.AppendElement(JS::StringValue(str));
-          output.Truncate();
-        }
+        FlushOutput(aCx, aSequence, output);
 
         JS::Rooted<JS::Value> v(aCx);
         if (index < aData.Length()) {
           v = aData[index++];
         }
 
         aSequence.AppendElement(v);
         break;
       }
 
       case 'c':
       {
-        if (!output.IsEmpty()) {
-          JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
-                                                             output.get(),
-                                                             output.Length()));
-          if (!str) {
-            return;
-          }
-
-          aSequence.AppendElement(JS::StringValue(str));
-          output.Truncate();
-        }
+        FlushOutput(aCx, aSequence, output);
 
         if (index < aData.Length()) {
           JS::Rooted<JS::Value> v(aCx, aData[index++]);
           JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
           if (!jsString) {
             return;
           }
 
@@ -1574,24 +1575,21 @@ Console::ProcessArguments(JSContext* aCx
         break;
 
       default:
         output.Append(tmp);
         break;
     }
   }
 
-  if (!output.IsEmpty()) {
-    JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx, output.get(),
-                                                       output.Length()));
-    if (!str) {
-      return;
-    }
+  FlushOutput(aCx, aSequence, output);
 
-    aSequence.AppendElement(JS::StringValue(str));
+  // Discard trailing style element if there is no output to apply it to.
+  if (aStyles.Length() > aSequence.Length()) {
+    aStyles.TruncateLength(aSequence.Length());
   }
 
   // The rest of the array, if unused by the format string.
   for (; index < aData.Length(); ++index) {
     aSequence.AppendElement(aData[index]);
   }
 }
 
--- a/dom/broadcastchannel/BroadcastChannel.cpp
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -11,20 +11,17 @@
 #include "mozilla/dom/StructuredCloneUtils.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/Preferences.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 
-#include "nsIAppsService.h"
 #include "nsIDocument.h"
-#include "nsIScriptSecurityManager.h"
-#include "nsServiceManagerUtils.h"
 #include "nsISupportsPrimitives.h"
 
 #ifdef XP_WIN
 #undef PostMessage
 #endif
 
 namespace mozilla {
 
@@ -52,46 +49,16 @@ private:
 
 namespace {
 
 void
 GetOrigin(nsIPrincipal* aPrincipal, nsAString& aOrigin, ErrorResult& aRv)
 {
   MOZ_ASSERT(aPrincipal);
 
-  bool unknownAppId;
-  aRv = aPrincipal->GetUnknownAppId(&unknownAppId);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return;
-  }
-
-  if (!unknownAppId) {
-    uint32_t appId;
-    aRv = aPrincipal->GetAppId(&appId);
-    if (NS_WARN_IF(aRv.Failed())) {
-      return;
-    }
-
-    if (appId != nsIScriptSecurityManager::NO_APP_ID) {
-      // If we are in "app code", use manifest URL as unique origin since
-      // multiple apps can share the same origin but not same broadcast
-      // messages.
-      nsresult rv;
-      nsCOMPtr<nsIAppsService> appsService =
-        do_GetService("@mozilla.org/AppsService;1", &rv);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        aRv.Throw(rv);
-        return;
-      }
-
-      appsService->GetManifestURLByLocalId(appId, aOrigin);
-      return;
-    }
-  }
-
   nsAutoString tmp;
   aRv = nsContentUtils::GetUTFOrigin(aPrincipal, tmp);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   // 'null' means an unknown origin (it can be chrome code or it can be some
   // about: page).
--- a/dom/broadcastchannel/BroadcastChannelParent.cpp
+++ b/dom/broadcastchannel/BroadcastChannelParent.cpp
@@ -5,51 +5,63 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "BroadcastChannelParent.h"
 #include "BroadcastChannelService.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/ipc/BlobParent.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/unused.h"
+#include "nsIScriptSecurityManager.h"
 
 namespace mozilla {
 
 using namespace ipc;
 
 namespace dom {
 
 BroadcastChannelParent::BroadcastChannelParent(
+                                            const PrincipalInfo& aPrincipalInfo,
                                             const nsAString& aOrigin,
                                             const nsAString& aChannel,
                                             bool aPrivateBrowsing)
   : mService(BroadcastChannelService::GetOrCreate())
   , mOrigin(aOrigin)
   , mChannel(aChannel)
+  , mAppId(nsIScriptSecurityManager::UNKNOWN_APP_ID)
+  , mIsInBrowserElement(false)
   , mPrivateBrowsing(aPrivateBrowsing)
 {
   AssertIsOnBackgroundThread();
   mService->RegisterActor(this);
+
+  if (aPrincipalInfo.type() ==PrincipalInfo::TContentPrincipalInfo) {
+    const ContentPrincipalInfo& info =
+      aPrincipalInfo.get_ContentPrincipalInfo();
+    mAppId = info.appId();
+    mIsInBrowserElement = info.isInBrowserElement();
+  }
 }
 
 BroadcastChannelParent::~BroadcastChannelParent()
 {
   AssertIsOnBackgroundThread();
 }
 
 bool
 BroadcastChannelParent::RecvPostMessage(const ClonedMessageData& aData)
 {
   AssertIsOnBackgroundThread();
 
   if (NS_WARN_IF(!mService)) {
     return false;
   }
 
-  mService->PostMessage(this, aData, mOrigin, mChannel, mPrivateBrowsing);
+  mService->PostMessage(this, aData, mOrigin, mAppId, mIsInBrowserElement,
+                        mChannel, mPrivateBrowsing);
   return true;
 }
 
 bool
 BroadcastChannelParent::RecvClose()
 {
   AssertIsOnBackgroundThread();
 
@@ -75,22 +87,26 @@ BroadcastChannelParent::ActorDestroy(Act
     // released too.
     mService->UnregisterActor(this);
   }
 }
 
 void
 BroadcastChannelParent::CheckAndDeliver(const ClonedMessageData& aData,
                                         const nsString& aOrigin,
+                                        uint64_t aAppId,
+                                        bool aInBrowserElement,
                                         const nsString& aChannel,
                                         bool aPrivateBrowsing)
 {
   AssertIsOnBackgroundThread();
 
   if (aOrigin == mOrigin &&
+      aAppId == mAppId &&
+      aInBrowserElement == mIsInBrowserElement &&
       aChannel == mChannel &&
       aPrivateBrowsing == mPrivateBrowsing) {
     // We need to duplicate data only if we have blobs or if the manager of
     // them is different than the manager of this parent actor.
     if (aData.blobsParent().IsEmpty() ||
         static_cast<BlobParent*>(aData.blobsParent()[0])->GetBackgroundManager() == Manager()) {
       unused << SendNotify(aData);
       return;
--- a/dom/broadcastchannel/BroadcastChannelParent.h
+++ b/dom/broadcastchannel/BroadcastChannelParent.h
@@ -8,47 +8,55 @@
 #define mozilla_dom_BroadcastChannelParent_h
 
 #include "mozilla/dom/PBroadcastChannelParent.h"
 
 namespace mozilla {
 
 namespace ipc {
 class BackgroundParentImpl;
+class PrincipalInfo;
 }
 
 namespace dom {
 
 class BroadcastChannelService;
 
 class BroadcastChannelParent final : public PBroadcastChannelParent
 {
   friend class mozilla::ipc::BackgroundParentImpl;
 
+  typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
+
 public:
   void CheckAndDeliver(const ClonedMessageData& aData,
                        const nsString& aOrigin,
+                       const uint64_t aAppId,
+                       const bool aIsInBrowserElement,
                        const nsString& aChannel,
                        bool aPrivateBrowsing);
 
 private:
-  BroadcastChannelParent(const nsAString& aOrigin,
+  BroadcastChannelParent(const PrincipalInfo& aPrincipalInfo,
+                         const nsAString& aOrigin,
                          const nsAString& aChannel,
                          bool aPrivateBrowsing);
   ~BroadcastChannelParent();
 
   virtual bool
   RecvPostMessage(const ClonedMessageData& aData) override;
 
   virtual bool RecvClose() override;
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   nsRefPtr<BroadcastChannelService> mService;
   nsString mOrigin;
   nsString mChannel;
+  uint64_t mAppId;
+  bool mIsInBrowserElement;
   bool mPrivateBrowsing;
 };
 
 } // dom namespace
 } // mozilla namespace
 
 #endif // mozilla_dom_BroadcastChannelParent_h
--- a/dom/broadcastchannel/BroadcastChannelService.cpp
+++ b/dom/broadcastchannel/BroadcastChannelService.cpp
@@ -79,22 +79,26 @@ BroadcastChannelService::UnregisterActor
 
 namespace {
 
 struct MOZ_STACK_CLASS PostMessageData final
 {
   PostMessageData(BroadcastChannelParent* aParent,
                   const ClonedMessageData& aData,
                   const nsAString& aOrigin,
+                  uint64_t aAppId,
+                  bool aIsInBrowserElement,
                   const nsAString& aChannel,
                   bool aPrivateBrowsing)
     : mParent(aParent)
     , mData(aData)
     , mOrigin(aOrigin)
     , mChannel(aChannel)
+    , mAppId(aAppId)
+    , mIsInBrowserElement(aIsInBrowserElement)
     , mPrivateBrowsing(aPrivateBrowsing)
   {
     MOZ_ASSERT(aParent);
     MOZ_COUNT_CTOR(PostMessageData);
 
     // We need to keep the array alive for the life-time of this
     // PostMessageData.
     if (!aData.blobsParent().IsEmpty()) {
@@ -114,47 +118,53 @@ struct MOZ_STACK_CLASS PostMessageData f
     MOZ_COUNT_DTOR(PostMessageData);
   }
 
   BroadcastChannelParent* mParent;
   const ClonedMessageData& mData;
   nsTArray<nsRefPtr<FileImpl>> mFiles;
   const nsString mOrigin;
   const nsString mChannel;
+  uint64_t mAppId;
+  bool mIsInBrowserElement;
   bool mPrivateBrowsing;
 };
 
 PLDHashOperator
 PostMessageEnumerator(nsPtrHashKey<BroadcastChannelParent>* aKey, void* aPtr)
 {
   AssertIsOnBackgroundThread();
 
   auto* data = static_cast<PostMessageData*>(aPtr);
   BroadcastChannelParent* parent = aKey->GetKey();
   MOZ_ASSERT(parent);
 
   if (parent != data->mParent) {
-    parent->CheckAndDeliver(data->mData, data->mOrigin, data->mChannel,
+    parent->CheckAndDeliver(data->mData, data->mOrigin, data->mAppId,
+                            data->mIsInBrowserElement, data->mChannel,
                             data->mPrivateBrowsing);
   }
 
   return PL_DHASH_NEXT;
 }
 
 } // anonymous namespace
 
 void
 BroadcastChannelService::PostMessage(BroadcastChannelParent* aParent,
                                      const ClonedMessageData& aData,
                                      const nsAString& aOrigin,
+                                     uint64_t aAppId,
+                                     bool aIsInBrowserElement,
                                      const nsAString& aChannel,
                                      bool aPrivateBrowsing)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aParent);
   MOZ_ASSERT(mAgents.Contains(aParent));
 
-  PostMessageData data(aParent, aData, aOrigin, aChannel, aPrivateBrowsing);
+  PostMessageData data(aParent, aData, aOrigin, aAppId, aIsInBrowserElement,
+                       aChannel, aPrivateBrowsing);
   mAgents.EnumerateEntries(PostMessageEnumerator, &data);
 }
 
 } // dom namespace
 } // mozilla namespace
--- a/dom/broadcastchannel/BroadcastChannelService.h
+++ b/dom/broadcastchannel/BroadcastChannelService.h
@@ -29,16 +29,18 @@ public:
   static already_AddRefed<BroadcastChannelService> GetOrCreate();
 
   void RegisterActor(BroadcastChannelParent* aParent);
   void UnregisterActor(BroadcastChannelParent* aParent);
 
   void PostMessage(BroadcastChannelParent* aParent,
                    const ClonedMessageData& aData,
                    const nsAString& aOrigin,
+                   uint64_t aAppId,
+                   bool aIsInBrowserElement,
                    const nsAString& aChannel,
                    bool aPrivateBrowsing);
 
 private:
   BroadcastChannelService();
   ~BroadcastChannelService();
 
   nsTHashtable<nsPtrHashKey<BroadcastChannelParent>> mAgents;
--- a/dom/browser-element/mochitest/file_browserElement_XFrameOptionsAllowFrom.html
+++ b/dom/browser-element/mochitest/file_browserElement_XFrameOptionsAllowFrom.html
@@ -14,30 +14,30 @@
 
 var frame_src = 'http://example.com/tests/dom/browser-element/mochitest/file_browserElement_XFrameOptionsAllowFrom.sjs';
 
 var iframe1 = document.createElement('iframe');
 iframe1.height = '300px';
 var iframe2 = document.createElement('iframe');
 iframe2.height = '300px';
 document.body.appendChild(iframe1);
-document.body.appendChild(iframe2);
 
 iframe1.addEventListener('load', function iframe1Load() {
   iframe1.removeEventListener('load', iframe1Load);
   // This causes our embedder to take a screenshot (and blocks until the
   // screenshot is completed).
   var iframe2Loaded = false;
   iframe2.addEventListener('load', function iframe2Load() {
     iframe2.removeEventListener('load', iframe2Load);
     iframe2Loaded = true;
     alert('finish');
   });
 
-  setTimeout(function() { iframe2.src = frame_src; }, 1000);
+  document.body.appendChild(iframe2);
+  iframe2.src = frame_src;
 });
 
 
 iframe1.src = frame_src + '?iframe1';
 </script>
 
 </body>
 </html>
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -35,16 +35,18 @@
 #include "nsIDOMEvent.h"
 #include "nsIEventTarget.h"
 #include "nsPIDOMWindow.h"
 #include "nsThreadUtils.h"
 #include "nsTraceRefcnt.h"
 #include "PermissionRequestBase.h"
 #include "ProfilerHelpers.h"
 #include "ReportInternalError.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
 
 #ifdef DEBUG
 #include "IndexedDatabaseManager.h"
 #endif
 
 #define GC_ON_IPC_MESSAGES 0
 
 #if defined(DEBUG) || GC_ON_IPC_MESSAGES
@@ -52,17 +54,23 @@
 #include "js/GCAPI.h"
 #include "nsJSEnvironment.h"
 
 #define BUILD_GC_ON_IPC_MESSAGES
 
 #endif // DEBUG || GC_ON_IPC_MESSAGES
 
 namespace mozilla {
+
+using ipc::PrincipalInfo;
+
 namespace dom {
+
+using namespace workers;
+
 namespace indexedDB {
 
 /*******************************************************************************
  * ThreadLocal
  ******************************************************************************/
 
 ThreadLocal::ThreadLocal(const nsID& aBackgroundChildLoggingId)
   : mLoggingInfo(aBackgroundChildLoggingId, 1, -1, 1)
@@ -772,16 +780,245 @@ DispatchSuccessEvent(ResultHelper* aResu
 
   if (transaction &&
       transaction->IsOpen() &&
       internalEvent->mFlags.mExceptionHasBeenRisen) {
     transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
   }
 }
 
+class WorkerPermissionChallenge;
+
+// This class calles WorkerPermissionChallenge::OperationCompleted() in the
+// worker thread.
+class WorkerPermissionOperationCompleted final : public WorkerRunnable
+{
+  nsRefPtr<WorkerPermissionChallenge> mChallenge;
+
+public:
+  WorkerPermissionOperationCompleted(WorkerPrivate* aWorkerPrivate,
+                                     WorkerPermissionChallenge* aChallenge)
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+    , mChallenge(aChallenge)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  virtual bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+};
+
+// This class used to do prompting in the main thread and main process.
+class WorkerPermissionRequest final : public PermissionRequestBase
+{
+  nsRefPtr<WorkerPermissionChallenge> mChallenge;
+
+public:
+  WorkerPermissionRequest(Element* aElement,
+                          nsIPrincipal* aPrincipal,
+                          WorkerPermissionChallenge* aChallenge)
+    : PermissionRequestBase(aElement, aPrincipal)
+    , mChallenge(aChallenge)
+  {
+    MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(aChallenge);
+  }
+
+private:
+  ~WorkerPermissionRequest()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  virtual void
+  OnPromptComplete(PermissionValue aPermissionValue) override;
+};
+
+// This class is used in the main thread of all child processes.
+class WorkerPermissionRequestChildProcessActor final
+  : public PIndexedDBPermissionRequestChild
+{
+  nsRefPtr<WorkerPermissionChallenge> mChallenge;
+
+public:
+  explicit WorkerPermissionRequestChildProcessActor(
+                                          WorkerPermissionChallenge* aChallenge)
+    : mChallenge(aChallenge)
+  {
+    MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default);
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(aChallenge);
+  }
+
+protected:
+  ~WorkerPermissionRequestChildProcessActor()
+  {}
+
+  virtual bool
+  Recv__delete__(const uint32_t& aPermission) override;
+};
+
+class WorkerPermissionChallenge final : public nsRunnable
+                                      , public WorkerFeature
+{
+public:
+  WorkerPermissionChallenge(WorkerPrivate* aWorkerPrivate,
+                            BackgroundFactoryRequestChild* aActor,
+                            IDBFactory* aFactory,
+                            const PrincipalInfo& aPrincipalInfo)
+    : mWorkerPrivate(aWorkerPrivate)
+    , mActor(aActor)
+    , mFactory(aFactory)
+    , mPrincipalInfo(aPrincipalInfo)
+  {
+    MOZ_ASSERT(mWorkerPrivate);
+    MOZ_ASSERT(aActor);
+    MOZ_ASSERT(aFactory);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    bool completed = RunInternal();
+    if (completed) {
+      OperationCompleted();
+    }
+
+    return NS_OK;
+  }
+
+  virtual bool
+  Notify(JSContext* aCx, workers::Status aStatus) override
+  {
+    // We don't care about the notification. We just want to keep the
+    // mWorkerPrivate alive.
+    return true;
+  }
+
+  void
+  OperationCompleted()
+  {
+    if (NS_IsMainThread()) {
+      nsRefPtr<WorkerPermissionOperationCompleted> runnable =
+        new WorkerPermissionOperationCompleted(mWorkerPrivate, this);
+
+      if (!runnable->Dispatch(nullptr)) {
+        NS_WARNING("Failed to dispatch a runnable to the worker thread.");
+        return;
+      }
+
+      return;
+    }
+
+    MOZ_ASSERT(mActor);
+    mActor->AssertIsOnOwningThread();
+
+    MaybeCollectGarbageOnIPCMessage();
+
+    nsRefPtr<IDBFactory> factory;
+    mFactory.swap(factory);
+
+    mActor->SendPermissionRetry();
+    mActor = nullptr;
+
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    JSContext* cx = mWorkerPrivate->GetJSContext();
+    mWorkerPrivate->RemoveFeature(cx, this);
+  }
+
+private:
+  bool
+  RunInternal()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // Walk up to our containing page
+    WorkerPrivate* wp = mWorkerPrivate;
+    while (wp->GetParent()) {
+      wp = wp->GetParent();
+    }
+
+    nsPIDOMWindow* window = wp->GetWindow();
+    if (!window) {
+      return true;
+    }
+
+    nsresult rv;
+    nsCOMPtr<nsIPrincipal> principal =
+      mozilla::ipc::PrincipalInfoToPrincipal(mPrincipalInfo, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return true;
+    }
+
+    if (XRE_GetProcessType() == GeckoProcessType_Default) {
+      nsCOMPtr<Element> ownerElement =
+        do_QueryInterface(window->GetChromeEventHandler());
+      if (NS_WARN_IF(!ownerElement)) {
+        return true;
+      }
+
+      nsRefPtr<WorkerPermissionRequest> helper =
+        new WorkerPermissionRequest(ownerElement, principal, this);
+
+      PermissionRequestBase::PermissionValue permission;
+      if (NS_WARN_IF(NS_FAILED(helper->PromptIfNeeded(&permission)))) {
+        return true;
+      }
+
+      MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed ||
+                 permission == PermissionRequestBase::kPermissionDenied ||
+                 permission == PermissionRequestBase::kPermissionPrompt);
+
+      return permission != PermissionRequestBase::kPermissionPrompt;
+    }
+
+    TabChild* tabChild = TabChild::GetFrom(window);
+    MOZ_ASSERT(tabChild);
+
+    IPC::Principal ipcPrincipal(principal);
+
+    auto* actor = new WorkerPermissionRequestChildProcessActor(this);
+    tabChild->SendPIndexedDBPermissionRequestConstructor(actor, ipcPrincipal);
+    return false;
+  }
+
+private:
+  WorkerPrivate* mWorkerPrivate;
+  BackgroundFactoryRequestChild* mActor;
+  nsRefPtr<IDBFactory> mFactory;
+  PrincipalInfo mPrincipalInfo;
+};
+
+void
+WorkerPermissionRequest::OnPromptComplete(PermissionValue aPermissionValue)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mChallenge->OperationCompleted();
+}
+
+bool
+WorkerPermissionOperationCompleted::WorkerRun(JSContext* aCx,
+                                              WorkerPrivate* aWorkerPrivate)
+{
+  aWorkerPrivate->AssertIsOnWorkerThread();
+  mChallenge->OperationCompleted();
+  return true;
+}
+
+bool
+WorkerPermissionRequestChildProcessActor::Recv__delete__(
+                                              const uint32_t& /* aPermission */)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mChallenge->OperationCompleted();
+  return true;
+}
+
 } // anonymous namespace
 
 /*******************************************************************************
  * Local class implementations
  ******************************************************************************/
 
 void
 PermissionRequestMainProcessHelper::OnPromptComplete(
@@ -1119,17 +1356,33 @@ bool
 BackgroundFactoryRequestChild::RecvPermissionChallenge(
                                             const PrincipalInfo& aPrincipalInfo)
 {
   AssertIsOnOwningThread();
 
   MaybeCollectGarbageOnIPCMessage();
 
   if (!NS_IsMainThread()) {
-    MOZ_CRASH("Implement me for workers!");
+    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(workerPrivate);
+    workerPrivate->AssertIsOnWorkerThread();
+
+    nsRefPtr<WorkerPermissionChallenge> challenge =
+      new WorkerPermissionChallenge(workerPrivate, this, mFactory,
+                                    aPrincipalInfo);
+
+    JSContext* cx = workerPrivate->GetJSContext();
+    MOZ_ASSERT(cx);
+
+    if (!workerPrivate->AddFeature(cx, challenge)) {
+      return false;
+    }
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(challenge)));
+    return true;
   }
 
   nsresult rv;
   nsCOMPtr<nsIPrincipal> principal =
     mozilla::ipc::PrincipalInfoToPrincipal(aPrincipalInfo, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
--- a/dom/indexedDB/test/browser.ini
+++ b/dom/indexedDB/test/browser.ini
@@ -1,16 +1,21 @@
 [DEFAULT]
 skip-if = (buildapp != "browser") || e10s
 support-files =
   head.js
   browser_forgetThisSiteAdd.html
   browser_forgetThisSiteGet.html
   browserHelpers.js
   browser_permissionsPrompt.html
+  browser_permissionsSharedWorker.html
+  browser_permissionsSharedWorker.js
+  browser_permissionsWorker.html
+  browser_permissionsWorker.js
   bug839193.js
   bug839193.xul
 
 [browser_forgetThisSite.js]
 [browser_permissionsPromptAllow.js]
 [browser_permissionsPromptDeny.js]
+[browser_permissionsPromptWorker.js]
 [browser_perwindow_privateBrowsing.js]
 [browser_bug839193.js]
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/browser_permissionsPromptWorker.js
@@ -0,0 +1,91 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const testWorkerURL = "http://mochi.test:8888/browser/" +
+  "dom/indexedDB/test/browser_permissionsWorker.html";
+const testSharedWorkerURL = "http://mochi.test:8888/browser/" +
+  "dom/indexedDB/test/browser_permissionsSharedWorker.html";
+const notificationID = "indexedDB-permissions-prompt";
+
+function test()
+{
+  waitForExplicitFinish();
+  executeSoon(test1);
+}
+
+function test1()
+{
+  // We want a prompt.
+  removePermission(testWorkerURL, "indexedDB");
+
+  info("creating tab");
+  gBrowser.selectedTab = gBrowser.addTab();
+
+  gBrowser.selectedBrowser.addEventListener("load", function () {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    setFinishedCallback(function(isIDBDatabase, exception) {
+      ok(isIDBDatabase, "First database creation was successful");
+      ok(!exception, "No exception");
+      is(getPermission(testWorkerURL, "indexedDB"),
+         Components.interfaces.nsIPermissionManager.ALLOW_ACTION,
+         "Correct permission set");
+      gBrowser.removeCurrentTab();
+      executeSoon(test2);
+    });
+
+    registerPopupEventHandler("popupshowing", function () {
+      ok(true, "prompt showing");
+    });
+    registerPopupEventHandler("popupshown", function () {
+      ok(true, "prompt shown");
+      triggerMainCommand(this);
+    });
+    registerPopupEventHandler("popuphidden", function () {
+      ok(true, "prompt hidden");
+    });
+
+  }, true);
+
+  info("loading test page: " + testWorkerURL);
+  content.location = testWorkerURL;
+}
+
+function test2()
+{
+  // We want a prompt.
+  removePermission(testSharedWorkerURL, "indexedDB");
+
+  info("creating tab");
+  gBrowser.selectedTab = gBrowser.addTab();
+
+  gBrowser.selectedBrowser.addEventListener("load", function () {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+    setFinishedCallback(function(isIDBDatabase, exception) {
+      ok(!isIDBDatabase, "First database creation was successful");
+      ok(exception, "No exception");
+      is(getPermission(testSharedWorkerURL, "indexedDB"),
+         Components.interfaces.nsIPermissionManager.UNKNOWN_ACTION,
+         "Correct permission set");
+      gBrowser.removeCurrentTab();
+      executeSoon(finish);
+    });
+
+    registerPopupEventHandler("popupshowing", function () {
+      ok(false, "prompt showing");
+    });
+    registerPopupEventHandler("popupshown", function () {
+      ok(false, "prompt shown");
+    });
+    registerPopupEventHandler("popuphidden", function () {
+      ok(false, "prompt hidden");
+    });
+
+  }, true);
+
+  info("loading test page: " + testSharedWorkerURL);
+  content.location = testSharedWorkerURL;
+}
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/browser_permissionsSharedWorker.html
@@ -0,0 +1,34 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <head>
+    <title>Indexed Database Test</title>
+
+    <script type="text/javascript;version=1.7">
+    let testIsIDBDatabase;
+    let testException;
+
+    function runTest() {
+      let w = new SharedWorker('browser_permissionsSharedWorker.js');
+      w.port.onmessage = function(e) {
+        if (e.data.status == 'success') {
+          testIsIDBDatabase = e.data.isIDBDatabase;
+        } else {
+          testException = e.data.error;
+        }
+
+        setTimeout(testFinishedCallback, 0, testIsIDBDatabase, testException);
+      }
+
+      const name = window.location.pathname + "_sharedWorker";
+      w.port.postMessage(name);
+    }
+    </script>
+
+  </head>
+
+  <body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/browser_permissionsSharedWorker.js
@@ -0,0 +1,14 @@
+onconnect = function(e) {
+  e.ports[0].onmessage = function(e) {
+    var request = indexedDB.open(e.data, { version: 1,
+                                           storage: "persistent" });
+    request.onsuccess = function(event) {
+      e.target.postMessage({ status: 'success',
+                             isIDBDatabase: (event.target.result instanceof IDBDatabase) });
+    }
+
+    request.onerror = function(event) {
+      e.target.postMessage({ status: 'error', error: event.target.error.name });
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/browser_permissionsWorker.html
@@ -0,0 +1,34 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <head>
+    <title>Indexed Database Test</title>
+
+    <script type="text/javascript;version=1.7">
+    let testIsIDBDatabase;
+    let testException;
+
+    function runTest() {
+      let w = new Worker('browser_permissionsWorker.js');
+      w.onmessage = function(e) {
+        if (e.data.status == 'success') {
+          testIsIDBDatabase = e.data.isIDBDatabase;
+        } else {
+          testException = e.data.error;
+        }
+
+        setTimeout(testFinishedCallback, 0, testIsIDBDatabase, testException);
+      }
+
+      const name = window.location.pathname;
+      w.postMessage(name);
+    }
+    </script>
+
+  </head>
+
+  <body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/browser_permissionsWorker.js
@@ -0,0 +1,12 @@
+onmessage = function(e) {
+  var request = indexedDB.open(e.data, { version: 1,
+                                         storage: "persistent" });
+  request.onsuccess = function(event) {
+    postMessage({ status: 'success',
+                  isIDBDatabase: (event.target.result instanceof IDBDatabase) });
+  }
+
+  request.onerror = function(event) {
+    postMessage({ status: 'error', error: event.target.error.name });
+  }
+}
--- a/dom/plugins/ipc/TaskFactory.h
+++ b/dom/plugins/ipc/TaskFactory.h
@@ -2,16 +2,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_plugins_TaskFactory_h
 #define mozilla_plugins_TaskFactory_h
 
 #include <base/task.h>
 
+#include "mozilla/Move.h"
+
 /*
  * This is based on the ScopedRunnableMethodFactory from ipc/chromium/src/base/task.h
  * Chromium's factories assert if tasks are created and run on different threads,
  * which is something we need to do in PluginModuleParent (hang UI vs. main thread).
  * TaskFactory just provides cancellable tasks that don't assert this.
  * This version also allows both ScopedMethod and regular Tasks to be generated
  * by the same Factory object.
  */
@@ -22,35 +24,40 @@ namespace plugins {
 template<class T>
 class TaskFactory : public RevocableStore
 {
 private:
   template<class TaskType>
   class TaskWrapper : public TaskType
   {
   public:
-    explicit TaskWrapper(RevocableStore* store) : revocable_(store) { }
+    template<typename... Args>
+    explicit TaskWrapper(RevocableStore* store, Args&&... args)
+      : TaskType(mozilla::Forward<Args>(args)...)
+      , revocable_(store)
+    {
+    }
 
     virtual void Run() {
       if (!revocable_.revoked())
         TaskType::Run();
     }
 
   private:
     Revocable revocable_;
   };
 
 public:
   explicit TaskFactory(T* object) : object_(object) { }
 
-  template <class TaskParamType>
-  inline TaskParamType* NewTask()
+  template <typename TaskParamType, typename... Args>
+  inline TaskParamType* NewTask(Args&&... args)
   {
     typedef TaskWrapper<TaskParamType> TaskWrapper;
-    TaskWrapper* task = new TaskWrapper(this);
+    TaskWrapper* task = new TaskWrapper(this, mozilla::Forward<Args>(args)...);
     return task;
   }
 
   template <class Method>
   inline Task* NewRunnableMethod(Method method) {
     typedef TaskWrapper<RunnableMethod<Method, Tuple0> > TaskWrapper;
 
     TaskWrapper* task = new TaskWrapper(this);
--- a/dom/workers/test/serviceworkers/fetch/context/index.html
+++ b/dom/workers/test/serviceworkers/fetch/context/index.html
@@ -321,20 +321,16 @@
           iframe.parentNode.removeChild(iframe);
           resolve();
         }
       }, false);
     });
   }
 
   function testCache() {
-    if (isAndroid) {
-      // FIXME: Re-enable this test on Android once bug 1148818 gets fixed.
-      return Promise.resolve();
-    }
     return new Promise(function(resolve, reject) {
       // Issue an XHR that will be intercepted by the SW in order to start off
       // the test with a RequestContext value that is not the default ("fetch").
       // This needs to run inside a fetch event handler because synthesized
       // RequestContext objects can only have the "fetch" context, and we'd
       // prefer to test the more general case of some other RequestContext value.
       var xhr = new XMLHttpRequest();
       xhr.open("get", "cache", true);
--- a/gfx/layers/GrallocImages.cpp
+++ b/gfx/layers/GrallocImages.cpp
@@ -293,16 +293,17 @@ ConvertOmxYUVFormatToRGB565(android::sp<
     return BAD_VALUE;
   }
 
   GraphicBufferAutoUnlock unlock(aBuffer);
 
   uint32_t format = aBuffer->getPixelFormat();
   uint32_t width = aSurface->GetSize().width;
   uint32_t height = aSurface->GetSize().height;
+  uint32_t stride = aBuffer->getStride();
 
   if (format == GrallocImage::HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO) {
     // The Adreno hardware decoder aligns image dimensions to a multiple of 32,
     // so we have to account for that here
     uint32_t alignedWidth = ALIGN(width, 32);
     uint32_t alignedHeight = ALIGN(height, 32);
     uint32_t uvOffset = ALIGN(alignedHeight * alignedWidth, 4096);
     uint32_t uvStride = 2 * ALIGN(width / 2, 32);
@@ -311,21 +312,21 @@ ConvertOmxYUVFormatToRGB565(android::sp<
                             buffer + uvOffset,
                             uvStride,
                             aMappedSurface->mData,
                             width, height);
     return OK;
   }
 
   if (format == HAL_PIXEL_FORMAT_YCrCb_420_SP) {
-    uint32_t uvOffset = height * width;
-    ConvertYVU420SPToRGB565(buffer, width,
+    uint32_t uvOffset = height * stride;
+    ConvertYVU420SPToRGB565(buffer, stride,
                             buffer + uvOffset + 1,
                             buffer + uvOffset,
-                            width,
+                            stride,
                             aMappedSurface->mData,
                             width, height);
     return OK;
   }
 
   if (format == HAL_PIXEL_FORMAT_YV12) {
     // Depend on platforms, it is possible for HW decoder to output YV12 format.
     // It means the mData won't be configured during the SetData API because the
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -257,17 +257,18 @@ BackgroundParentImpl::AllocPBroadcastCha
                                             const PrincipalInfo& aPrincipalInfo,
                                             const nsString& aOrigin,
                                             const nsString& aChannel,
                                             const bool& aPrivateBrowsing)
 {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
 
-  return new BroadcastChannelParent(aOrigin, aChannel, aPrivateBrowsing);
+  return new BroadcastChannelParent(aPrincipalInfo, aOrigin, aChannel,
+                                    aPrivateBrowsing);
 }
 
 namespace {
 
 class CheckPrincipalRunnable final : public nsRunnable
 {
 public:
   CheckPrincipalRunnable(already_AddRefed<ContentParent> aParent,
@@ -310,57 +311,16 @@ public:
 
     bool isNullPrincipal;
     nsresult rv = principal->GetIsNullPrincipal(&isNullPrincipal);
     if (NS_WARN_IF(NS_FAILED(rv)) || isNullPrincipal) {
       mContentParent->KillHard("BroadcastChannel killed: no null principal.");
       return NS_OK;
     }
 
-    bool unknownAppId;
-    rv = principal->GetUnknownAppId(&unknownAppId);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      mContentParent->KillHard("BroadcastChannel killed: failed to get the app status.");
-      return NS_OK;
-    }
-
-    if (!unknownAppId) {
-      uint32_t appId;
-      rv = principal->GetAppId(&appId);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        mContentParent->KillHard("BroadcastChannel killed: failed to get the app id.");
-        return NS_OK;
-      }
-
-      // If the broadcastChannel is used by an app, the origin is the manifest URL.
-      if (appId != nsIScriptSecurityManager::NO_APP_ID) {
-        nsresult rv;
-        nsCOMPtr<nsIAppsService> appsService =
-          do_GetService("@mozilla.org/AppsService;1", &rv);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          mContentParent->KillHard("BroadcastChannel killed: appService getter failed.");
-          return NS_OK;
-        }
-
-        nsAutoString origin;
-        rv = appsService->GetManifestURLByLocalId(appId, origin);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          mContentParent->KillHard("BroadcastChannel killed: failed to retrieve the manifestURL.");
-          return NS_OK;
-        }
-
-        if (!origin.Equals(mOrigin)) {
-          mContentParent->KillHard("BroadcastChannel killed: origins do not match.");
-          return NS_OK;
-        }
-
-        return NS_OK;
-      }
-    }
-
     nsCOMPtr<nsIURI> uri;
     rv = NS_NewURI(getter_AddRefs(uri), mOrigin);
     if (NS_FAILED(rv) || !uri) {
       mContentParent->KillHard("BroadcastChannel killed: invalid origin URI.");
       return NS_OK;
     }
 
     rv = principal->CheckMayLoad(uri, false, false);
--- a/js/public/TrackedOptimizationInfo.h
+++ b/js/public/TrackedOptimizationInfo.h
@@ -26,16 +26,17 @@ namespace JS {
     _(GetProp_Innerize)                                 \
     _(GetProp_InlineCache)                              \
                                                         \
     _(SetProp_CommonSetter)                             \
     _(SetProp_TypedObject)                              \
     _(SetProp_DefiniteSlot)                             \
     _(SetProp_Unboxed)                                  \
     _(SetProp_InlineAccess)                             \
+    _(SetProp_InlineCache)                              \
                                                         \
     _(GetElem_TypedObject)                              \
     _(GetElem_Dense)                                    \
     _(GetElem_TypedStatic)                              \
     _(GetElem_TypedArray)                               \
     _(GetElem_String)                                   \
     _(GetElem_Arguments)                                \
     _(GetElem_ArgumentsInlined)                         \
--- a/js/src/asmjs/AsmJSLink.cpp
+++ b/js/src/asmjs/AsmJSLink.cpp
@@ -794,17 +794,17 @@ CallAsmJS(JSContext* cx, unsigned argc, 
 static JSFunction*
 NewExportedFunction(JSContext* cx, const AsmJSModule::ExportedFunction& func,
                     HandleObject moduleObj, unsigned exportIndex)
 {
     RootedPropertyName name(cx, func.name());
     unsigned numArgs = func.isChangeHeap() ? 1 : func.numArgs();
     JSFunction* fun =
         NewNativeConstructor(cx, CallAsmJS, numArgs, name,
-                             JSFunction::ExtendedFinalizeKind, GenericObject,
+                             gc::AllocKind::FUNCTION_EXTENDED, GenericObject,
                              JSFunction::ASMJS_CTOR);
     if (!fun)
         return nullptr;
 
     fun->setExtendedSlot(ASM_MODULE_SLOT, ObjectValue(*moduleObj));
     fun->setExtendedSlot(ASM_EXPORT_INDEX_SLOT, Int32Value(exportIndex));
     return fun;
 }
@@ -827,17 +827,17 @@ HandleDynamicLinkFailure(JSContext* cx, 
 
     uint32_t begin = module.srcBodyStart();  // starts right after 'use asm'
     uint32_t end = module.srcEndBeforeCurly();
     Rooted<JSFlatString*> src(cx, module.scriptSource()->substringDontDeflate(cx, begin, end));
     if (!src)
         return false;
 
     RootedFunction fun(cx, NewScriptedFunction(cx, 0, JSFunction::INTERPRETED,
-                                               name, JSFunction::FinalizeKind,
+                                               name, gc::AllocKind::FUNCTION,
                                                TenuredObject));
     if (!fun)
         return false;
 
     AutoNameVector formals(cx);
     if (!formals.reserve(3))
         return false;
 
@@ -1090,17 +1090,17 @@ JSFunction*
 js::NewAsmJSModuleFunction(ExclusiveContext* cx, JSFunction* origFun, HandleObject moduleObj)
 {
     RootedPropertyName name(cx, origFun->name());
 
     JSFunction::Flags flags = origFun->isLambda() ? JSFunction::ASMJS_LAMBDA_CTOR
                                                   : JSFunction::ASMJS_CTOR;
     JSFunction* moduleFun =
         NewNativeConstructor(cx, LinkAsmJS, origFun->nargs(), name,
-                             JSFunction::ExtendedFinalizeKind, TenuredObject,
+                             gc::AllocKind::FUNCTION_EXTENDED, TenuredObject,
                              flags);
     if (!moduleFun)
         return nullptr;
 
     moduleFun->setExtendedSlot(MODULE_FUN_SLOT, ObjectValue(*moduleObj));
     return moduleFun;
 }
 
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -5979,29 +5979,29 @@ CheckSimdOperationCall(FunctionCompiler&
 
       case AsmJSSimdOperation_swizzle:
         return CheckSimdSwizzle(f, call, opType, def, type);
       case AsmJSSimdOperation_shuffle:
         return CheckSimdShuffle(f, call, opType, def, type);
 
       case AsmJSSimdOperation_load:
         return CheckSimdLoad(f, call, opType, 4, def, type);
-      case AsmJSSimdOperation_loadX:
+      case AsmJSSimdOperation_load1:
         return CheckSimdLoad(f, call, opType, 1, def, type);
-      case AsmJSSimdOperation_loadXY:
+      case AsmJSSimdOperation_load2:
         return CheckSimdLoad(f, call, opType, 2, def, type);
-      case AsmJSSimdOperation_loadXYZ:
+      case AsmJSSimdOperation_load3:
         return CheckSimdLoad(f, call, opType, 3, def, type);
       case AsmJSSimdOperation_store:
         return CheckSimdStore(f, call, opType, 4, def, type);
-      case AsmJSSimdOperation_storeX:
+      case AsmJSSimdOperation_store1:
         return CheckSimdStore(f, call, opType, 1, def, type);
-      case AsmJSSimdOperation_storeXY:
+      case AsmJSSimdOperation_store2:
         return CheckSimdStore(f, call, opType, 2, def, type);
-      case AsmJSSimdOperation_storeXYZ:
+      case AsmJSSimdOperation_store3:
         return CheckSimdStore(f, call, opType, 3, def, type);
 
       case AsmJSSimdOperation_bitselect:
         return CheckSimdSelect(f, call, opType, /*isElementWise */ false, def, type);
       case AsmJSSimdOperation_select:
         return CheckSimdSelect(f, call, opType, /*isElementWise */ true, def, type);
 
       case AsmJSSimdOperation_splat: {
@@ -7580,17 +7580,17 @@ ParseFunction(ModuleCompiler& m, ParseNo
 
     ParseNode* fn = m.parser().handler.newFunctionDefinition();
     if (!fn)
         return false;
 
     // This flows into FunctionBox, so must be tenured.
     RootedFunction fun(m.cx(),
                        NewScriptedFunction(m.cx(), 0, JSFunction::INTERPRETED,
-                                           name, JSFunction::FinalizeKind,
+                                           name, gc::AllocKind::FUNCTION,
                                            TenuredObject));
     if (!fun)
         return false;
 
     AsmJSParseContext* outerpc = m.parser().pc;
 
     Directives directives(outerpc);
     FunctionBox* funbox = m.parser().newFunctionBox(fn, fun, outerpc, directives, NotGenerator);
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -1091,17 +1091,17 @@ static JSObject*
 CreateObjectConstructor(JSContext* cx, JSProtoKey key)
 {
     Rooted<GlobalObject*> self(cx, cx->global());
     if (!GlobalObject::ensureConstructor(cx, self, JSProto_Function))
         return nullptr;
 
     /* Create the Object function now that we have a [[Prototype]] for it. */
     return NewNativeConstructor(cx, obj_construct, 1, HandlePropertyName(cx->names().Object),
-                                JSFunction::FinalizeKind, SingletonObject);
+                                gc::AllocKind::FUNCTION, SingletonObject);
 }
 
 static JSObject*
 CreateObjectPrototype(JSContext* cx, JSProtoKey key)
 {
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     MOZ_ASSERT(cx->global()->isNative());
 
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -73,18 +73,23 @@ ErrorBadArgs(JSContext* cx)
 static inline bool
 ErrorWrongTypeArg(JSContext* cx, size_t argIndex, Handle<TypeDescr*> typeDescr)
 {
     MOZ_ASSERT(argIndex < 10);
     char charArgIndex[2];
     JS_snprintf(charArgIndex, sizeof charArgIndex, "%d", argIndex);
 
     HeapSlot& typeNameSlot = typeDescr->getReservedSlotRef(JS_DESCR_SLOT_STRING_REPR);
+    char* typeNameStr = JS_EncodeString(cx, typeNameSlot.toString());
+    if (!typeNameStr)
+        return false;
+
     JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SIMD_NOT_A_VECTOR,
-                         JS_EncodeString(cx, typeNameSlot.toString()), charArgIndex);
+                         typeNameStr, charArgIndex);
+    JS_free(cx, typeNameStr);
     return false;
 }
 
 template<typename V>
 bool
 js::ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out)
 {
     typedef typename V::Elem Elem;
--- a/js/src/builtin/SIMD.h
+++ b/js/src/builtin/SIMD.h
@@ -38,31 +38,31 @@
   V(add, (BinaryFunc<Float32x4, Add, Float32x4>), 2)                                  \
   V(and, (CoercedBinaryFunc<Float32x4, Int32x4, And, Float32x4>), 2)                  \
   V(div, (BinaryFunc<Float32x4, Div, Float32x4>), 2)                                  \
   V(equal, (CompareFunc<Float32x4, Equal>), 2)                                        \
   V(greaterThan, (CompareFunc<Float32x4, GreaterThan>), 2)                            \
   V(greaterThanOrEqual, (CompareFunc<Float32x4, GreaterThanOrEqual>), 2)              \
   V(lessThan, (CompareFunc<Float32x4, LessThan>), 2)                                  \
   V(lessThanOrEqual, (CompareFunc<Float32x4, LessThanOrEqual>), 2)                    \
-  V(load,    (Load<Float32x4, 4>), 2)                                                 \
-  V(loadXYZ, (Load<Float32x4, 3>), 2)                                                 \
-  V(loadXY,  (Load<Float32x4, 2>), 2)                                                 \
-  V(loadX,   (Load<Float32x4, 1>), 2)                                                 \
+  V(load,  (Load<Float32x4, 4>), 2)                                                   \
+  V(load3, (Load<Float32x4, 3>), 2)                                                   \
+  V(load2, (Load<Float32x4, 2>), 2)                                                   \
+  V(load1, (Load<Float32x4, 1>), 2)                                                   \
   V(max, (BinaryFunc<Float32x4, Maximum, Float32x4>), 2)                              \
   V(maxNum, (BinaryFunc<Float32x4, MaxNum, Float32x4>), 2)                            \
   V(min, (BinaryFunc<Float32x4, Minimum, Float32x4>), 2)                              \
   V(minNum, (BinaryFunc<Float32x4, MinNum, Float32x4>), 2)                            \
   V(mul, (BinaryFunc<Float32x4, Mul, Float32x4>), 2)                                  \
   V(notEqual, (CompareFunc<Float32x4, NotEqual>), 2)                                  \
   V(or, (CoercedBinaryFunc<Float32x4, Int32x4, Or, Float32x4>), 2)                    \
-  V(store,    (Store<Float32x4, 4>), 3)                                               \
-  V(storeXYZ, (Store<Float32x4, 3>), 3)                                               \
-  V(storeXY,  (Store<Float32x4, 2>), 3)                                               \
-  V(storeX,   (Store<Float32x4, 1>), 3)                                               \
+  V(store,  (Store<Float32x4, 4>), 3)                                                 \
+  V(store3, (Store<Float32x4, 3>), 3)                                                 \
+  V(store2, (Store<Float32x4, 2>), 3)                                                 \
+  V(store1, (Store<Float32x4, 1>), 3)                                                 \
   V(sub, (BinaryFunc<Float32x4, Sub, Float32x4>), 2)                                  \
   V(withX, (FuncWith<Float32x4, WithX>), 2)                                           \
   V(withY, (FuncWith<Float32x4, WithY>), 2)                                           \
   V(withZ, (FuncWith<Float32x4, WithZ>), 2)                                           \
   V(withW, (FuncWith<Float32x4, WithW>), 2)                                           \
   V(xor, (CoercedBinaryFunc<Float32x4, Int32x4, Xor, Float32x4>), 2)
 
 #define FLOAT32X4_TERNARY_FUNCTION_LIST(V)                                            \
@@ -96,26 +96,26 @@
 #define FLOAT64X2_BINARY_FUNCTION_LIST(V)                                             \
   V(add, (BinaryFunc<Float64x2, Add, Float64x2>), 2)                                  \
   V(div, (BinaryFunc<Float64x2, Div, Float64x2>), 2)                                  \
   V(equal, (CompareFunc<Float64x2, Equal>), 2)                                        \
   V(greaterThan, (CompareFunc<Float64x2, GreaterThan>), 2)                            \
   V(greaterThanOrEqual, (CompareFunc<Float64x2, GreaterThanOrEqual>), 2)              \
   V(lessThan, (CompareFunc<Float64x2, LessThan>), 2)                                  \
   V(lessThanOrEqual, (CompareFunc<Float64x2, LessThanOrEqual>), 2)                    \
-  V(load,    (Load<Float64x2, 2>), 2)                                                 \
-  V(loadX,   (Load<Float64x2, 1>), 2)                                                 \
+  V(load,  (Load<Float64x2, 2>), 2)                                                   \
+  V(load1, (Load<Float64x2, 1>), 2)                                                   \
   V(max, (BinaryFunc<Float64x2, Maximum, Float64x2>), 2)                              \
   V(maxNum, (BinaryFunc<Float64x2, MaxNum, Float64x2>), 2)                            \
   V(min, (BinaryFunc<Float64x2, Minimum, Float64x2>), 2)                              \
   V(minNum, (BinaryFunc<Float64x2, MinNum, Float64x2>), 2)                            \
   V(mul, (BinaryFunc<Float64x2, Mul, Float64x2>), 2)                                  \
   V(notEqual, (CompareFunc<Float64x2, NotEqual>), 2)                                  \
-  V(store,    (Store<Float64x2, 2>), 3)                                               \
-  V(storeX,   (Store<Float64x2, 1>), 3)                                               \
+  V(store,  (Store<Float64x2, 2>), 3)                                                 \
+  V(store1, (Store<Float64x2, 1>), 3)                                                 \
   V(sub, (BinaryFunc<Float64x2, Sub, Float64x2>), 2)                                  \
   V(withX, (FuncWith<Float64x2, WithX>), 2)                                           \
   V(withY, (FuncWith<Float64x2, WithY>), 2)
 
 #define FLOAT64X2_TERNARY_FUNCTION_LIST(V)                                            \
   V(bitselect, BitSelect<Float64x2>, 3)                                               \
   V(clamp, Clamp<Float64x2>, 3)                                                       \
   V(select, Select<Float64x2>, 3)
@@ -143,31 +143,31 @@
 #define INT32X4_BINARY_FUNCTION_LIST(V)                                               \
   V(add, (BinaryFunc<Int32x4, Add, Int32x4>), 2)                                      \
   V(and, (BinaryFunc<Int32x4, And, Int32x4>), 2)                                      \
   V(equal, (CompareFunc<Int32x4, Equal>), 2)                                          \
   V(greaterThan, (CompareFunc<Int32x4, GreaterThan>), 2)                              \
   V(greaterThanOrEqual, (CompareFunc<Int32x4, GreaterThanOrEqual>), 2)                \
   V(lessThan, (CompareFunc<Int32x4, LessThan>), 2)                                    \
   V(lessThanOrEqual, (CompareFunc<Int32x4, LessThanOrEqual>), 2)                      \
-  V(load,    (Load<Int32x4, 4>), 2)                                                   \
-  V(loadXYZ, (Load<Int32x4, 3>), 2)                                                   \
-  V(loadXY,  (Load<Int32x4, 2>), 2)                                                   \
-  V(loadX,   (Load<Int32x4, 1>), 2)                                                   \
+  V(load,  (Load<Int32x4, 4>), 2)                                                     \
+  V(load3, (Load<Int32x4, 3>), 2)                                                     \
+  V(load2, (Load<Int32x4, 2>), 2)                                                     \
+  V(load1, (Load<Int32x4, 1>), 2)                                                     \
   V(mul, (BinaryFunc<Int32x4, Mul, Int32x4>), 2)                                      \
   V(notEqual, (CompareFunc<Int32x4, NotEqual>), 2)                                    \
   V(or, (BinaryFunc<Int32x4, Or, Int32x4>), 2)                                        \
   V(sub, (BinaryFunc<Int32x4, Sub, Int32x4>), 2)                                      \
   V(shiftLeftByScalar, (Int32x4BinaryScalar<ShiftLeft>), 2)                           \
   V(shiftRightArithmeticByScalar, (Int32x4BinaryScalar<ShiftRightArithmetic>), 2)     \
   V(shiftRightLogicalByScalar, (Int32x4BinaryScalar<ShiftRightLogical>), 2)           \
-  V(store,    (Store<Int32x4, 4>), 3)                                                 \
-  V(storeXYZ, (Store<Int32x4, 3>), 3)                                                 \
-  V(storeXY,  (Store<Int32x4, 2>), 3)                                                 \
-  V(storeX,   (Store<Int32x4, 1>), 3)                                                 \
+  V(store,  (Store<Int32x4, 4>), 3)                                                   \
+  V(store3, (Store<Int32x4, 3>), 3)                                                   \
+  V(store2, (Store<Int32x4, 2>), 3)                                                   \
+  V(store1, (Store<Int32x4, 1>), 3)                                                   \
   V(withX, (FuncWith<Int32x4, WithX>), 2)                                             \
   V(withY, (FuncWith<Int32x4, WithY>), 2)                                             \
   V(withZ, (FuncWith<Int32x4, WithZ>), 2)                                             \
   V(withW, (FuncWith<Int32x4, WithW>), 2)                                             \
   V(xor, (BinaryFunc<Int32x4, Xor, Int32x4>), 2)
 
 #define INT32X4_TERNARY_FUNCTION_LIST(V)                                              \
   V(bitselect, BitSelect<Int32x4>, 3)                                                 \
@@ -239,23 +239,23 @@
     _(bitselect)                     \
     _(select)                        \
     _(splat)                         \
     _(not)                           \
     _(neg)                           \
     _(swizzle)                       \
     _(shuffle)                       \
     _(load)                          \
-    _(loadX)                         \
-    _(loadXY)                        \
-    _(loadXYZ)                       \
+    _(load1)                         \
+    _(load2)                         \
+    _(load3)                         \
     _(store)                         \
-    _(storeX)                        \
-    _(storeXY)                       \
-    _(storeXYZ)                      \
+    _(store1)                        \
+    _(store2)                        \
+    _(store3)                        \
     _(check)
 #define ION_ONLY_INT32X4_SIMD_OP(_)  \
     _(bool)
 #define FOREACH_COMMONX4_SIMD_OP(_)  \
     ION_COMMONX4_SIMD_OP(_)          \
     COMP_COMMONX4_TO_INT32X4_SIMD_OP(_)
 #define FORALL_SIMD_OP(_)            \
     FOREACH_INT32X4_SIMD_OP(_)       \
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -1223,20 +1223,20 @@ Parser<ParseHandler>::newFunction(Handle
         break;
       case Method:
         flags = JSFunction::INTERPRETED_METHOD;
         break;
       default:
         flags = JSFunction::INTERPRETED;
         break;
     }
-    
-    gc::AllocKind allocKind = JSFunction::FinalizeKind;
+
+    gc::AllocKind allocKind = gc::AllocKind::FUNCTION;
     if (kind == Arrow || kind == Method)
-        allocKind = JSFunction::ExtendedFinalizeKind;
+        allocKind = gc::AllocKind::FUNCTION_EXTENDED;
     fun = NewFunctionWithProto(context, nullptr, 0, flags, NullPtr(), atom, proto,
                                allocKind, TenuredObject);
     if (!fun)
         return nullptr;
     if (options().selfHostingMode)
         fun->setIsSelfHostedBuiltin();
     return fun;
 }
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -74,17 +74,20 @@ enum InitialHeap {
     TenuredHeap
 };
 
 /* The GC allocation kinds. */
 // FIXME: uint8_t would make more sense for the underlying type, but causes
 // miscompilations in GCC (fixed in 4.8.5 and 4.9.3). See also bug 1143966.
 enum class AllocKind {
     FIRST,
-    OBJECT0 = FIRST,
+    OBJECT_FIRST = FIRST,
+    FUNCTION = FIRST,
+    FUNCTION_EXTENDED,
+    OBJECT0,
     OBJECT0_BACKGROUND,
     OBJECT2,
     OBJECT2_BACKGROUND,
     OBJECT4,
     OBJECT4_BACKGROUND,
     OBJECT8,
     OBJECT8_BACKGROUND,
     OBJECT12,
@@ -105,23 +108,23 @@ enum class AllocKind {
     SYMBOL,
     JITCODE,
     LIMIT,
     LAST = LIMIT - 1
 };
 
 static_assert(int(AllocKind::FIRST) == 0, "Various places depend on AllocKind starting at 0, "
                                           "please audit them carefully!");
-static_assert(int(AllocKind::OBJECT0) == 0, "Various places depend on AllocKind::OBJECT0 being 0, "
-                                            "please audit them carefully!");
+static_assert(int(AllocKind::OBJECT_FIRST) == 0, "Various places depend on AllocKind::OBJECT_FIRST "
+                                                 "being 0, please audit them carefully!");
 
 inline bool
 IsObjectAllocKind(AllocKind kind)
 {
-    return kind >= AllocKind::OBJECT0 && kind <= AllocKind::OBJECT_LAST;
+    return kind >= AllocKind::OBJECT_FIRST && kind <= AllocKind::OBJECT_LAST;
 }
 
 inline bool
 IsValidAllocKind(AllocKind kind)
 {
     return kind >= AllocKind::FIRST && kind <= AllocKind::LAST;
 }
 
@@ -135,20 +138,20 @@ inline bool IsAllocKind(AllocKind kind)
 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))
+inline decltype(mozilla::MakeEnumeratedRange<int>(AllocKind::OBJECT_FIRST, AllocKind::OBJECT_LIMIT))
 ObjectAllocKinds()
 {
-    return mozilla::MakeEnumeratedRange<int>(AllocKind::OBJECT0, AllocKind::OBJECT_LIMIT);
+    return mozilla::MakeEnumeratedRange<int>(AllocKind::OBJECT_FIRST, 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(IsAllocKind(first), "|first| is not a valid AllocKind!");
@@ -165,16 +168,18 @@ template<typename ValueType> using AllAl
 // 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::FUNCTION */
+        JSTRACE_OBJECT,       /* AllocKind::FUNCTION_EXTENDED */
         JSTRACE_OBJECT,       /* AllocKind::OBJECT0 */
         JSTRACE_OBJECT,       /* AllocKind::OBJECT0_BACKGROUND */
         JSTRACE_OBJECT,       /* AllocKind::OBJECT2 */
         JSTRACE_OBJECT,       /* AllocKind::OBJECT2_BACKGROUND */
         JSTRACE_OBJECT,       /* AllocKind::OBJECT4 */
         JSTRACE_OBJECT,       /* AllocKind::OBJECT4_BACKGROUND */
         JSTRACE_OBJECT,       /* AllocKind::OBJECT8 */
         JSTRACE_OBJECT,       /* AllocKind::OBJECT8_BACKGROUND */
@@ -1019,36 +1024,40 @@ struct Chunk
 
     bool hasAvailableArenas() const {
         return info.numArenasFree != 0;
     }
 
     ArenaHeader* allocateArena(JSRuntime* rt, JS::Zone* zone, AllocKind kind,
                                const AutoLockGC& lock);
 
-    enum ArenaDecommitState { IsCommitted = false, IsDecommitted = true };
-    void releaseArena(JSRuntime* rt, ArenaHeader* aheader, const AutoLockGC& lock,
-                      ArenaDecommitState state = IsCommitted);
+    void releaseArena(JSRuntime* rt, ArenaHeader* aheader, const AutoLockGC& lock);
     void recycleArena(ArenaHeader* aheader, SortedArenaList& dest, AllocKind thingKind,
                       size_t thingsPerArena);
 
-    static Chunk* allocate(JSRuntime* rt);
+    bool decommitOneFreeArena(JSRuntime* rt, AutoLockGC& lock);
+    void decommitAllArenasWithoutUnlocking(const AutoLockGC& lock);
 
-    void decommitAllArenas(JSRuntime* rt);
+    static Chunk* allocate(JSRuntime* rt);
 
   private:
     inline void init(JSRuntime* rt);
 
+    void decommitAllArenas(JSRuntime* rt);
+
     /* Search for a decommitted arena to allocate. */
     unsigned findDecommittedArenaOffset();
     ArenaHeader* fetchNextDecommittedArena();
 
     void addArenaToFreeList(JSRuntime* rt, ArenaHeader* aheader);
     void addArenaToDecommittedList(JSRuntime* rt, const ArenaHeader* aheader);
 
+    void updateChunkListAfterAlloc(JSRuntime* rt, const AutoLockGC& lock);
+    void updateChunkListAfterFree(JSRuntime* rt, const AutoLockGC& lock);
+
   public:
     /* Unlink and return the freeArenasHead. */
     inline ArenaHeader* fetchNextFreeArena(JSRuntime* rt);
 };
 
 static_assert(sizeof(Chunk) == ChunkSize,
               "Ensure the hardcoded chunk size definition actually matches the struct.");
 static_assert(js::gc::ChunkMarkBitmapOffset == offsetof(Chunk, bitmap),
--- a/js/src/jit-test/tests/SIMD/load.js
+++ b/js/src/jit-test/tests/SIMD/load.js
@@ -28,75 +28,75 @@ function f() {
         assertEqX4(SIMD.float32x4.load(f32, 16 - 4),               [13,14,15,16]);
         assertEqX4(SIMD.float32x4.load(i32, 16 - 4),               [13,14,15,16]);
         assertEqX4(SIMD.float32x4.load(i16, (16 << 1) - (4 << 1)), [13,14,15,16]);
         assertEqX4(SIMD.float32x4.load(u16, (16 << 1) - (4 << 1)), [13,14,15,16]);
         assertEqX4(SIMD.float32x4.load(i8,  (16 << 2) - (4 << 2)), [13,14,15,16]);
         assertEqX4(SIMD.float32x4.load(u8,  (16 << 2) - (4 << 2)), [13,14,15,16]);
     }
 
-    function testLoadX() {
-        assertEqX4(SIMD.float32x4.loadX(f64, 0),      [1,0,0,0]);
-        assertEqX4(SIMD.float32x4.loadX(f32, 1),      [2,0,0,0]);
-        assertEqX4(SIMD.float32x4.loadX(i32, 2),      [3,0,0,0]);
-        assertEqX4(SIMD.float32x4.loadX(i16, 3 << 1), [4,0,0,0]);
-        assertEqX4(SIMD.float32x4.loadX(u16, 4 << 1), [5,0,0,0]);
-        assertEqX4(SIMD.float32x4.loadX(i8 , 5 << 2), [6,0,0,0]);
-        assertEqX4(SIMD.float32x4.loadX(u8 , 6 << 2), [7,0,0,0]);
+    function testLoad1() {
+        assertEqX4(SIMD.float32x4.load1(f64, 0),      [1,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(f32, 1),      [2,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(i32, 2),      [3,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(i16, 3 << 1), [4,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(u16, 4 << 1), [5,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(i8 , 5 << 2), [6,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(u8 , 6 << 2), [7,0,0,0]);
 
-        assertEqX4(SIMD.float32x4.loadX(f64, (16 >> 1) - (4 >> 1)), [13,0,0,0]);
-        assertEqX4(SIMD.float32x4.loadX(f32, 16 - 4),               [13,0,0,0]);
-        assertEqX4(SIMD.float32x4.loadX(i32, 16 - 4),               [13,0,0,0]);
-        assertEqX4(SIMD.float32x4.loadX(i16, (16 << 1) - (4 << 1)), [13,0,0,0]);
-        assertEqX4(SIMD.float32x4.loadX(u16, (16 << 1) - (4 << 1)), [13,0,0,0]);
-        assertEqX4(SIMD.float32x4.loadX(i8,  (16 << 2) - (4 << 2)), [13,0,0,0]);
-        assertEqX4(SIMD.float32x4.loadX(u8,  (16 << 2) - (4 << 2)), [13,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(f64, (16 >> 1) - (4 >> 1)), [13,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(f32, 16 - 4),               [13,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(i32, 16 - 4),               [13,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(i16, (16 << 1) - (4 << 1)), [13,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(u16, (16 << 1) - (4 << 1)), [13,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(i8,  (16 << 2) - (4 << 2)), [13,0,0,0]);
+        assertEqX4(SIMD.float32x4.load1(u8,  (16 << 2) - (4 << 2)), [13,0,0,0]);
     }
 
-    function testLoadXY() {
-        assertEqX4(SIMD.float32x4.loadXY(f64, 0),      [1,2,0,0]);
-        assertEqX4(SIMD.float32x4.loadXY(f32, 1),      [2,3,0,0]);
-        assertEqX4(SIMD.float32x4.loadXY(i32, 2),      [3,4,0,0]);
-        assertEqX4(SIMD.float32x4.loadXY(i16, 3 << 1), [4,5,0,0]);
-        assertEqX4(SIMD.float32x4.loadXY(u16, 4 << 1), [5,6,0,0]);
-        assertEqX4(SIMD.float32x4.loadXY(i8 , 5 << 2), [6,7,0,0]);
-        assertEqX4(SIMD.float32x4.loadXY(u8 , 6 << 2), [7,8,0,0]);
+    function testLoad2() {
+        assertEqX4(SIMD.float32x4.load2(f64, 0),      [1,2,0,0]);
+        assertEqX4(SIMD.float32x4.load2(f32, 1),      [2,3,0,0]);
+        assertEqX4(SIMD.float32x4.load2(i32, 2),      [3,4,0,0]);
+        assertEqX4(SIMD.float32x4.load2(i16, 3 << 1), [4,5,0,0]);
+        assertEqX4(SIMD.float32x4.load2(u16, 4 << 1), [5,6,0,0]);
+        assertEqX4(SIMD.float32x4.load2(i8 , 5 << 2), [6,7,0,0]);
+        assertEqX4(SIMD.float32x4.load2(u8 , 6 << 2), [7,8,0,0]);
 
-        assertEqX4(SIMD.float32x4.loadXY(f64, (16 >> 1) - (4 >> 1)), [13,14,0,0]);
-        assertEqX4(SIMD.float32x4.loadXY(f32, 16 - 4),               [13,14,0,0]);
-        assertEqX4(SIMD.float32x4.loadXY(i32, 16 - 4),               [13,14,0,0]);
-        assertEqX4(SIMD.float32x4.loadXY(i16, (16 << 1) - (4 << 1)), [13,14,0,0]);
-        assertEqX4(SIMD.float32x4.loadXY(u16, (16 << 1) - (4 << 1)), [13,14,0,0]);
-        assertEqX4(SIMD.float32x4.loadXY(i8,  (16 << 2) - (4 << 2)), [13,14,0,0]);
-        assertEqX4(SIMD.float32x4.loadXY(u8,  (16 << 2) - (4 << 2)), [13,14,0,0]);
+        assertEqX4(SIMD.float32x4.load2(f64, (16 >> 1) - (4 >> 1)), [13,14,0,0]);
+        assertEqX4(SIMD.float32x4.load2(f32, 16 - 4),               [13,14,0,0]);
+        assertEqX4(SIMD.float32x4.load2(i32, 16 - 4),               [13,14,0,0]);
+        assertEqX4(SIMD.float32x4.load2(i16, (16 << 1) - (4 << 1)), [13,14,0,0]);
+        assertEqX4(SIMD.float32x4.load2(u16, (16 << 1) - (4 << 1)), [13,14,0,0]);
+        assertEqX4(SIMD.float32x4.load2(i8,  (16 << 2) - (4 << 2)), [13,14,0,0]);
+        assertEqX4(SIMD.float32x4.load2(u8,  (16 << 2) - (4 << 2)), [13,14,0,0]);
     }
 
-    function testLoadXYZ() {
-        assertEqX4(SIMD.float32x4.loadXYZ(f64, 0),      [1,2,3,0]);
-        assertEqX4(SIMD.float32x4.loadXYZ(f32, 1),      [2,3,4,0]);
-        assertEqX4(SIMD.float32x4.loadXYZ(i32, 2),      [3,4,5,0]);
-        assertEqX4(SIMD.float32x4.loadXYZ(i16, 3 << 1), [4,5,6,0]);
-        assertEqX4(SIMD.float32x4.loadXYZ(u16, 4 << 1), [5,6,7,0]);
-        assertEqX4(SIMD.float32x4.loadXYZ(i8 , 5 << 2), [6,7,8,0]);
-        assertEqX4(SIMD.float32x4.loadXYZ(u8 , 6 << 2), [7,8,9,0]);
+    function testLoad3() {
+        assertEqX4(SIMD.float32x4.load3(f64, 0),      [1,2,3,0]);
+        assertEqX4(SIMD.float32x4.load3(f32, 1),      [2,3,4,0]);
+        assertEqX4(SIMD.float32x4.load3(i32, 2),      [3,4,5,0]);
+        assertEqX4(SIMD.float32x4.load3(i16, 3 << 1), [4,5,6,0]);
+        assertEqX4(SIMD.float32x4.load3(u16, 4 << 1), [5,6,7,0]);
+        assertEqX4(SIMD.float32x4.load3(i8 , 5 << 2), [6,7,8,0]);
+        assertEqX4(SIMD.float32x4.load3(u8 , 6 << 2), [7,8,9,0]);
 
-        assertEqX4(SIMD.float32x4.loadXYZ(f64, (16 >> 1) - (4 >> 1)), [13,14,15,0]);
-        assertEqX4(SIMD.float32x4.loadXYZ(f32, 16 - 4),               [13,14,15,0]);
-        assertEqX4(SIMD.float32x4.loadXYZ(i32, 16 - 4),               [13,14,15,0]);
-        assertEqX4(SIMD.float32x4.loadXYZ(i16, (16 << 1) - (4 << 1)), [13,14,15,0]);
-        assertEqX4(SIMD.float32x4.loadXYZ(u16, (16 << 1) - (4 << 1)), [13,14,15,0]);
-        assertEqX4(SIMD.float32x4.loadXYZ(i8,  (16 << 2) - (4 << 2)), [13,14,15,0]);
-        assertEqX4(SIMD.float32x4.loadXYZ(u8,  (16 << 2) - (4 << 2)), [13,14,15,0]);
+        assertEqX4(SIMD.float32x4.load3(f64, (16 >> 1) - (4 >> 1)), [13,14,15,0]);
+        assertEqX4(SIMD.float32x4.load3(f32, 16 - 4),               [13,14,15,0]);
+        assertEqX4(SIMD.float32x4.load3(i32, 16 - 4),               [13,14,15,0]);
+        assertEqX4(SIMD.float32x4.load3(i16, (16 << 1) - (4 << 1)), [13,14,15,0]);
+        assertEqX4(SIMD.float32x4.load3(u16, (16 << 1) - (4 << 1)), [13,14,15,0]);
+        assertEqX4(SIMD.float32x4.load3(i8,  (16 << 2) - (4 << 2)), [13,14,15,0]);
+        assertEqX4(SIMD.float32x4.load3(u8,  (16 << 2) - (4 << 2)), [13,14,15,0]);
     }
 
     for (var i = 0; i < 150; i++) {
         testLoad();
-        testLoadX();
-        testLoadXY();
-        testLoadXYZ();
+        testLoad1();
+        testLoad2();
+        testLoad3();
     }
 }
 
 f();
 
 function testBailout(uglyDuckling) {
     var f32 = new Float32Array(16);
     for (var i = 0; i < 16; i++)
--- a/js/src/jit-test/tests/SIMD/store.js
+++ b/js/src/jit-test/tests/SIMD/store.js
@@ -43,78 +43,78 @@ function f() {
         SIMD.float32x4.store(u16, 0, f4);
         check(4);
         SIMD.float32x4.store(i8, 0, f4);
         check(4);
         SIMD.float32x4.store(u8, 0, f4);
         check(4);
     }
 
-    function testStoreX() {
-        SIMD.float32x4.storeX(f64, 0, f4);
+    function testStore1() {
+        SIMD.float32x4.store1(f64, 0, f4);
         check(1);
-        SIMD.float32x4.storeX(f32, 0, f4);
+        SIMD.float32x4.store1(f32, 0, f4);
         check(1);
-        SIMD.float32x4.storeX(i32, 0, f4);
+        SIMD.float32x4.store1(i32, 0, f4);
         check(1);
-        SIMD.float32x4.storeX(u32, 0, f4);
+        SIMD.float32x4.store1(u32, 0, f4);
         check(1);
-        SIMD.float32x4.storeX(i16, 0, f4);
+        SIMD.float32x4.store1(i16, 0, f4);
         check(1);
-        SIMD.float32x4.storeX(u16, 0, f4);
+        SIMD.float32x4.store1(u16, 0, f4);
         check(1);
-        SIMD.float32x4.storeX(i8, 0, f4);
+        SIMD.float32x4.store1(i8, 0, f4);
         check(1);
-        SIMD.float32x4.storeX(u8, 0, f4);
+        SIMD.float32x4.store1(u8, 0, f4);
         check(1);
     }
 
-    function testStoreXY() {
-        SIMD.float32x4.storeXY(f64, 0, f4);
+    function testStore2() {
+        SIMD.float32x4.store2(f64, 0, f4);
         check(2);
-        SIMD.float32x4.storeXY(f32, 0, f4);
+        SIMD.float32x4.store2(f32, 0, f4);
         check(2);
-        SIMD.float32x4.storeXY(i32, 0, f4);
+        SIMD.float32x4.store2(i32, 0, f4);
         check(2);
-        SIMD.float32x4.storeXY(u32, 0, f4);
+        SIMD.float32x4.store2(u32, 0, f4);
         check(2);
-        SIMD.float32x4.storeXY(i16, 0, f4);
+        SIMD.float32x4.store2(i16, 0, f4);
         check(2);
-        SIMD.float32x4.storeXY(u16, 0, f4);
+        SIMD.float32x4.store2(u16, 0, f4);
         check(2);
-        SIMD.float32x4.storeXY(i8, 0, f4);
+        SIMD.float32x4.store2(i8, 0, f4);
         check(2);
-        SIMD.float32x4.storeXY(u8, 0, f4);
+        SIMD.float32x4.store2(u8, 0, f4);
         check(2);
     }
 
-    function testStoreXYZ() {
-        SIMD.float32x4.storeXYZ(f64, 0, f4);
+    function testStore3() {
+        SIMD.float32x4.store3(f64, 0, f4);
         check(3);
-        SIMD.float32x4.storeXYZ(f32, 0, f4);
+        SIMD.float32x4.store3(f32, 0, f4);
         check(3);
-        SIMD.float32x4.storeXYZ(i32, 0, f4);
+        SIMD.float32x4.store3(i32, 0, f4);
         check(3);
-        SIMD.float32x4.storeXYZ(u32, 0, f4);
+        SIMD.float32x4.store3(u32, 0, f4);
         check(3);
-        SIMD.float32x4.storeXYZ(i16, 0, f4);
+        SIMD.float32x4.store3(i16, 0, f4);
         check(3);
-        SIMD.float32x4.storeXYZ(u16, 0, f4);
+        SIMD.float32x4.store3(u16, 0, f4);
         check(3);
-        SIMD.float32x4.storeXYZ(i8, 0, f4);
+        SIMD.float32x4.store3(i8, 0, f4);
         check(3);
-        SIMD.float32x4.storeXYZ(u8, 0, f4);
+        SIMD.float32x4.store3(u8, 0, f4);
         check(3);
     }
 
     for (var i = 0; i < 150; i++) {
         testStore();
-        testStoreX();
-        testStoreXY();
-        testStoreXYZ();
+        testStore1();
+        testStore2();
+        testStore3();
     }
 }
 
 f();
 
 function testBailout(uglyDuckling) {
     var f32 = new Float32Array(16);
     for (var i = 0; i < 16; i++)
@@ -136,9 +136,8 @@ function testBailout(uglyDuckling) {
         assertEq(i < 149 || caught, true);
     }
 }
 
 print('Testing range checks...');
 testBailout(-1);
 testBailout(-15);
 testBailout(12 * 4 + 1);
-
--- a/js/src/jit-test/tests/asm.js/testSIMD-load-store.js
+++ b/js/src/jit-test/tests/asm.js/testSIMD-load-store.js
@@ -275,55 +275,55 @@ for (var i = 0; i < SIZE; i++)
 
 //      Variable indexes
 function MakeCodeFor(typeName) {
     return `
     "use asm";
     var type = glob.SIMD.${typeName};
     var c = type.check;
 
-    var lx = type.loadX;
-    var lxy = type.loadXY;
-    var lxyz = type.loadXYZ;
+    var l1 = type.load1;
+    var l2 = type.load2;
+    var l3 = type.load3;
 
-    var sx = type.storeX;
-    var sxy = type.storeXY;
-    var sxyz = type.storeXYZ;
+    var s1 = type.store1;
+    var s2 = type.store2;
+    var s3 = type.store3;
 
     var u8 = new glob.Uint8Array(heap);
 
-    function loadX(i) { i=i|0; return lx(u8, i); }
-    function loadXY(i) { i=i|0; return lxy(u8, i); }
-    function loadXYZ(i) { i=i|0; return lxyz(u8, i); }
+    function load1(i) { i=i|0; return l1(u8, i); }
+    function load2(i) { i=i|0; return l2(u8, i); }
+    function load3(i) { i=i|0; return l3(u8, i); }
 
-    function loadCstX() { return lx(u8, 41 << 2); }
-    function loadCstXY() { return lxy(u8, 41 << 2); }
-    function loadCstXYZ() { return lxyz(u8, 41 << 2); }
+    function loadCst1() { return l1(u8, 41 << 2); }
+    function loadCst2() { return l2(u8, 41 << 2); }
+    function loadCst3() { return l3(u8, 41 << 2); }
 
-    function storeX(i, x) { i=i|0; x=c(x); return sx(u8, i, x); }
-    function storeXY(i, x) { i=i|0; x=c(x); return sxy(u8, i, x); }
-    function storeXYZ(i, x) { i=i|0; x=c(x); return sxyz(u8, i, x); }
+    function store1(i, x) { i=i|0; x=c(x); return s1(u8, i, x); }
+    function store2(i, x) { i=i|0; x=c(x); return s2(u8, i, x); }
+    function store3(i, x) { i=i|0; x=c(x); return s3(u8, i, x); }
 
-    function storeCstX(x) { x=c(x); return sx(u8, 41 << 2, x); }
-    function storeCstXY(x) { x=c(x); return sxy(u8, 41 << 2, x); }
-    function storeCstXYZ(x) { x=c(x); return sxyz(u8, 41 << 2, x); }
+    function storeCst1(x) { x=c(x); return s1(u8, 41 << 2, x); }
+    function storeCst2(x) { x=c(x); return s2(u8, 41 << 2, x); }
+    function storeCst3(x) { x=c(x); return s3(u8, 41 << 2, x); }
 
     return {
-        loadX: loadX,
-        loadXY: loadXY,
-        loadXYZ: loadXYZ,
-        loadCstX: loadCstX,
-        loadCstXY: loadCstXY,
-        loadCstXYZ: loadCstXYZ,
-        storeX: storeX,
-        storeXY: storeXY,
-        storeXYZ: storeXYZ,
-        storeCstX: storeCstX,
-        storeCstXY: storeCstXY,
-        storeCstXYZ: storeCstXYZ,
+        load1: load1,
+        load2: load2,
+        load3: load3,
+        loadCst1: loadCst1,
+        loadCst2: loadCst2,
+        loadCst3: loadCst3,
+        store1: store1,
+        store2: store2,
+        store3: store3,
+        storeCst1: storeCst1,
+        storeCst2: storeCst2,
+        storeCst3: storeCst3,
     }
 `;
 }
 
 var SIZE = 0x10000;
 
 function TestPartialLoads(m, typedArray, x, y, z, w) {
     // Fill array with predictable values
@@ -331,90 +331,90 @@ function TestPartialLoads(m, typedArray,
         typedArray[i] =     x(i);
         typedArray[i + 1] = y(i);
         typedArray[i + 2] = z(i);
         typedArray[i + 3] = w(i);
     }
 
     // Test correct loads
     var i = 0, j = 0; // i in elems, j in bytes
-    assertEqX4(m.loadX(j),   [x(i), 0, 0, 0]);
-    assertEqX4(m.loadXY(j),  [x(i), y(i), 0, 0]);
-    assertEqX4(m.loadXYZ(j), [x(i), y(i), z(i), 0]);
+    assertEqX4(m.load1(j), [x(i), 0, 0, 0]);
+    assertEqX4(m.load2(j), [x(i), y(i), 0, 0]);
+    assertEqX4(m.load3(j), [x(i), y(i), z(i), 0]);
 
     j += 4;
-    assertEqX4(m.loadX(j),   [y(i), 0, 0, 0]);
-    assertEqX4(m.loadXY(j),  [y(i), z(i), 0, 0]);
-    assertEqX4(m.loadXYZ(j), [y(i), z(i), w(i), 0]);
+    assertEqX4(m.load1(j), [y(i), 0, 0, 0]);
+    assertEqX4(m.load2(j), [y(i), z(i), 0, 0]);
+    assertEqX4(m.load3(j), [y(i), z(i), w(i), 0]);
 
     j += 4;
-    assertEqX4(m.loadX(j),   [z(i), 0, 0, 0]);
-    assertEqX4(m.loadXY(j),  [z(i), w(i), 0, 0]);
-    assertEqX4(m.loadXYZ(j), [z(i), w(i), x(i+4), 0]);
+    assertEqX4(m.load1(j), [z(i), 0, 0, 0]);
+    assertEqX4(m.load2(j), [z(i), w(i), 0, 0]);
+    assertEqX4(m.load3(j), [z(i), w(i), x(i+4), 0]);
 
     j += 4;
-    assertEqX4(m.loadX(j),   [w(i), 0, 0, 0]);
-    assertEqX4(m.loadXY(j),  [w(i), x(i+4), 0, 0]);
-    assertEqX4(m.loadXYZ(j), [w(i), x(i+4), y(i+4), 0]);
+    assertEqX4(m.load1(j), [w(i), 0, 0, 0]);
+    assertEqX4(m.load2(j), [w(i), x(i+4), 0, 0]);
+    assertEqX4(m.load3(j), [w(i), x(i+4), y(i+4), 0]);
 
     j += 4;
     i += 4;
-    assertEqX4(m.loadX(j),   [x(i), 0, 0, 0]);
-    assertEqX4(m.loadXY(j),  [x(i), y(i), 0, 0]);
-    assertEqX4(m.loadXYZ(j), [x(i), y(i), z(i), 0]);
+    assertEqX4(m.load1(j), [x(i), 0, 0, 0]);
+    assertEqX4(m.load2(j), [x(i), y(i), 0, 0]);
+    assertEqX4(m.load3(j), [x(i), y(i), z(i), 0]);
 
     // Test loads with constant indexes (41)
-    assertEqX4(m.loadCstX(),   [y(40), 0, 0, 0]);
-    assertEqX4(m.loadCstXY(),  [y(40), z(40), 0, 0]);
-    assertEqX4(m.loadCstXYZ(), [y(40), z(40), w(40), 0]);
+    assertEqX4(m.loadCst1(), [y(40), 0, 0, 0]);
+    assertEqX4(m.loadCst2(), [y(40), z(40), 0, 0]);
+    assertEqX4(m.loadCst3(), [y(40), z(40), w(40), 0]);
 
     // Test limit and OOB accesses
-    assertEqX4(m.loadX((SIZE - 1) << 2), [w(SIZE - 4), 0, 0, 0]);
-    assertThrowsInstanceOf(() => m.loadX(((SIZE - 1) << 2) + 1), RangeError);
+    assertEqX4(m.load1((SIZE - 1) << 2), [w(SIZE - 4), 0, 0, 0]);
+    assertThrowsInstanceOf(() => m.load1(((SIZE - 1) << 2) + 1), RangeError);
 
-    assertEqX4(m.loadXY((SIZE - 2) << 2), [z(SIZE - 4), w(SIZE - 4), 0, 0]);
-    assertThrowsInstanceOf(() => m.loadXY(((SIZE - 2) << 2) + 1), RangeError);
+    assertEqX4(m.load2((SIZE - 2) << 2), [z(SIZE - 4), w(SIZE - 4), 0, 0]);
+    assertThrowsInstanceOf(() => m.load2(((SIZE - 2) << 2) + 1), RangeError);
 
-    assertEqX4(m.loadXYZ((SIZE - 3) << 2), [y(SIZE - 4), z(SIZE - 4), w(SIZE - 4), 0]);
-    assertThrowsInstanceOf(() => m.loadXYZ(((SIZE - 3) << 2) + 1), RangeError);
+    assertEqX4(m.load3((SIZE - 3) << 2), [y(SIZE - 4), z(SIZE - 4), w(SIZE - 4), 0]);
+    assertThrowsInstanceOf(() => m.load3(((SIZE - 3) << 2) + 1), RangeError);
 }
 
 // Partial stores
 function TestPartialStores(m, typedArray, typeName, x, y, z, w) {
     var val = SIMD[typeName](x, y, z, w);
 
     function Reset() {
         for (var i = 0; i < SIZE; i++)
             typedArray[i] = i + 1;
     }
     function CheckNotModified(low, high) {
         for (var i = low; i < high; i++)
             assertEq(typedArray[i], i + 1);
     }
 
-    function TestStoreX(i) {
-        m.storeX(i, val);
+    function TestStore1(i) {
+        m.store1(i, val);
         CheckNotModified(0, i >> 2);
         assertEq(typedArray[i >> 2], x);
         CheckNotModified((i >> 2) + 1, SIZE);
         typedArray[i >> 2] = (i >> 2) + 1;
     }
 
-    function TestStoreXY(i) {
-        m.storeXY(i, val);
+    function TestStore2(i) {
+        m.store2(i, val);
         CheckNotModified(0, i >> 2);
         assertEq(typedArray[i >> 2], x);
         assertEq(typedArray[(i >> 2) + 1], y);
         CheckNotModified((i >> 2) + 2, SIZE);
         typedArray[i >> 2] = (i >> 2) + 1;
         typedArray[(i >> 2) + 1] = (i >> 2) + 2;
     }
 
-    function TestStoreXYZ(i) {
-        m.storeXYZ(i, val);
+    function TestStore3(i) {
+        m.store3(i, val);
         CheckNotModified(0, i >> 2);
         assertEq(typedArray[i >> 2], x);
         assertEq(typedArray[(i >> 2) + 1], y);
         assertEq(typedArray[(i >> 2) + 2], z);
         CheckNotModified((i >> 2) + 3, SIZE);
         typedArray[i >> 2] = (i >> 2) + 1;
         typedArray[(i >> 2) + 1] = (i >> 2) + 2;
         typedArray[(i >> 2) + 2] = (i >> 2) + 3;
@@ -422,66 +422,66 @@ function TestPartialStores(m, typedArray
 
     function TestOOBStore(f) {
         assertThrowsInstanceOf(f, RangeError);
         CheckNotModified(0, SIZE);
     }
 
     Reset();
 
-    TestStoreX(0);
-    TestStoreX(1 << 2);
-    TestStoreX(2 << 2);
-    TestStoreX(3 << 2);
-    TestStoreX(1337 << 2);
+    TestStore1(0);
+    TestStore1(1 << 2);
+    TestStore1(2 << 2);
+    TestStore1(3 << 2);
+    TestStore1(1337 << 2);
 
     var i = (SIZE - 1) << 2;
-    TestStoreX(i);
-    TestOOBStore(() => m.storeX(i + 1, val));
-    TestOOBStore(() => m.storeX(-1, val));
+    TestStore1(i);
+    TestOOBStore(() => m.store1(i + 1, val));
+    TestOOBStore(() => m.store1(-1, val));
 
-    TestStoreXY(0);
-    TestStoreXY(1 << 2);
-    TestStoreXY(2 << 2);
-    TestStoreXY(3 << 2);
-    TestStoreXY(1337 << 2);
+    TestStore2(0);
+    TestStore2(1 << 2);
+    TestStore2(2 << 2);
+    TestStore2(3 << 2);
+    TestStore2(1337 << 2);
 
     var i = (SIZE - 2) << 2;
-    TestStoreXY(i);
-    TestOOBStore(() => m.storeXY(i + 1, val));
-    TestOOBStore(() => m.storeXY(-1, val));
+    TestStore2(i);
+    TestOOBStore(() => m.store2(i + 1, val));
+    TestOOBStore(() => m.store2(-1, val));
 
-    TestStoreXYZ(0);
-    TestStoreXYZ(1 << 2);
-    TestStoreXYZ(2 << 2);
-    TestStoreXYZ(3 << 2);
-    TestStoreXYZ(1337 << 2);
+    TestStore3(0);
+    TestStore3(1 << 2);
+    TestStore3(2 << 2);
+    TestStore3(3 << 2);
+    TestStore3(1337 << 2);
 
     var i = (SIZE - 3) << 2;
-    TestStoreXYZ(i);
-    TestOOBStore(() => m.storeXYZ(i + 1, val));
-    TestOOBStore(() => m.storeXYZ(-1, val));
-    TestOOBStore(() => m.storeXYZ(-9, val));
+    TestStore3(i);
+    TestOOBStore(() => m.store3(i + 1, val));
+    TestOOBStore(() => m.store3(-1, val));
+    TestOOBStore(() => m.store3(-9, val));
 
     // Constant indexes (41)
-    m.storeCstX(val);
+    m.storeCst1(val);
     CheckNotModified(0, 41);
     assertEq(typedArray[41], x);
     CheckNotModified(42, SIZE);
     typedArray[41] = 42;
 
-    m.storeCstXY(val);
+    m.storeCst2(val);
     CheckNotModified(0, 41);
     assertEq(typedArray[41], x);
     assertEq(typedArray[42], y);
     CheckNotModified(43, SIZE);
     typedArray[41] = 42;
     typedArray[42] = 43;
 
-    m.storeCstXYZ(val);
+    m.storeCst3(val);
     CheckNotModified(0, 41);
     assertEq(typedArray[41], x);
     assertEq(typedArray[42], y);
     assertEq(typedArray[43], z);
     CheckNotModified(44, SIZE);
     typedArray[41] = 42;
     typedArray[42] = 43;
     typedArray[43] = 44;
--- a/js/src/jit-test/tests/asm.js/testZOOB.js
+++ b/js/src/jit-test/tests/asm.js/testZOOB.js
@@ -99,117 +99,117 @@ function testSimdX4(ctor, shift, scale, 
     var arr = new ctor(ab);
 
     var c = asmCompile('glob', 'imp', 'b',
                        USE_ASM +
                        'var arr=new glob.' + ctor.name + '(b); ' +
                        'var SIMD_' + simdName + ' = glob.SIMD.' + simdName + '; ' +
                        'var SIMD_' + simdName + '_check = SIMD_' + simdName + '.check; ' +
                        'var SIMD_' + simdName + '_load = SIMD_' + simdName + '.load; ' +
-                       'var SIMD_' + simdName + '_loadXYZ = SIMD_' + simdName + '.loadXYZ; ' +
-                       'var SIMD_' + simdName + '_loadXY = SIMD_' + simdName + '.loadXY; ' +
-                       'var SIMD_' + simdName + '_loadX = SIMD_' + simdName + '.loadX; ' +
+                       'var SIMD_' + simdName + '_load3 = SIMD_' + simdName + '.load3; ' +
+                       'var SIMD_' + simdName + '_load2 = SIMD_' + simdName + '.load2; ' +
+                       'var SIMD_' + simdName + '_load1 = SIMD_' + simdName + '.load1; ' +
                        'var SIMD_' + simdName + '_store = SIMD_' + simdName + '.store; ' +
-                       'var SIMD_' + simdName + '_storeXYZ = SIMD_' + simdName + '.storeXYZ; ' +
-                       'var SIMD_' + simdName + '_storeXY = SIMD_' + simdName + '.storeXY; ' +
-                       'var SIMD_' + simdName + '_storeX = SIMD_' + simdName + '.storeX; ' +
+                       'var SIMD_' + simdName + '_store3 = SIMD_' + simdName + '.store3; ' +
+                       'var SIMD_' + simdName + '_store2 = SIMD_' + simdName + '.store2; ' +
+                       'var SIMD_' + simdName + '_store1 = SIMD_' + simdName + '.store1; ' +
                        'function load(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_load(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' +
-                       'function loadXYZ(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_loadXYZ(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' +
-                       'function loadXY(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_loadXY(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' +
-                       'function loadX(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_loadX(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' +
+                       'function load3(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_load3(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' +
+                       'function load2(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_load2(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' +
+                       'function load1(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_load1(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' +
                        'function store(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_store(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' +
-                       'function storeXYZ(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_storeXYZ(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' +
-                       'function storeXY(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_storeXY(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' +
-                       'function storeX(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_storeX(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' +
-                       'return { load: load, loadXYZ: loadXYZ, loadXY: loadXY, loadX: loadX, store: store, storeXYZ: storeXYZ, storeXY : storeXY, storeX : storeX }');
+                       'function store3(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_store3(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' +
+                       'function store2(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_store2(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' +
+                       'function store1(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_store1(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' +
+                       'return { load: load, load3: load3, load2: load2, load1: load1, store: store, store3: store3, store2 : store2, store1 : store1 }');
     var f = asmLink(c, this, null, ab);
 
     for (var i of indices) {
         var index = ((i<<scale)+disp)>>shift;
 
-        var v, vXYZ, vXY, vX;
-        var t = false, tXYZ = false, tXY = false, tX = false;
+        var v, v3, v2, v1;
+        var t = false, t3 = false, t2 = false, t1 = false;
         try { v = simdCtor.load(arr, index); }
         catch (e) {
             assertEq(e instanceof RangeError, true);
             t = true;
         }
-        try { vXYZ = simdCtor.loadXYZ(arr, index); }
+        try { v3 = simdCtor.load3(arr, index); }
         catch (e) {
             assertEq(e instanceof RangeError, true);
-            tXYZ = true;
+            t3 = true;
         }
-        try { vXY = simdCtor.loadXY(arr, index); }
+        try { v2 = simdCtor.load2(arr, index); }
         catch (e) {
             assertEq(e instanceof RangeError, true);
-            tXY = true;
+            t2 = true;
         }
-        try { vX = simdCtor.loadX(arr, index); }
+        try { v1 = simdCtor.load1(arr, index); }
         catch (e) {
             assertEq(e instanceof RangeError, true);
-            tX = true;
+            t1 = true;
         }
 
         // Loads
-        var l, lXYZ, lXY, lX;
-        var r = false, rXYZ = false, rXY = false, rX = false;
+        var l, l3, l2, l1;
+        var r = false, r3 = false, r2 = false, r1 = false;
         try { l = f.load(i); }
         catch (e) {
             assertEq(e instanceof RangeError, true);
             r = true;
         }
-        try { lXYZ = f.loadXYZ(i); }
+        try { l3 = f.load3(i); }
         catch (e) {
             assertEq(e instanceof RangeError, true);
-            rXYZ = true;
+            r3 = true;
         }
-        try { lXY = f.loadXY(i); }
+        try { l2 = f.load2(i); }
         catch (e) {
             assertEq(e instanceof RangeError, true);
-            rXY = true;
+            r2 = true;
         }
-        try { lX = f.loadX(i); }
+        try { l1 = f.load1(i); }
         catch (e) {
             assertEq(e instanceof RangeError, true);
-            rX = true;
+            r1 = true;
         }
         assertEq(t, r);
-        assertEq(tXYZ, rXYZ);
-        assertEq(tXY, rXY);
-        assertEq(tX, rX);
+        assertEq(t3, r3);
+        assertEq(t2, r2);
+        assertEq(t1, r1);
         if (!t) assertEqX4(v, l);
-        if (!tXYZ) assertEqX4(vXYZ, lXYZ);
-        if (!tXY) assertEqX4(vXY, lXY);
-        if (!tX) assertEqX4(vX, lX);
+        if (!t3) assertEqX4(v3, l3);
+        if (!t2) assertEqX4(v2, l2);
+        if (!t1) assertEqX4(v1, l1);
 
         // Stores
         if (!t) {
             simdCtor.store(arr, index, simdCtor.not(v));
             f.store(i, v);
             assertEqX4(simdCtor.load(arr, index), v);
         } else
             assertThrowsInstanceOf(() => f.store(i, simdCtor()), RangeError);
-        if (!tXYZ) {
-            simdCtor.storeXYZ(arr, index, simdCtor.not(vXYZ));
-            f.storeXYZ(i, vXYZ);
-            assertEqX4(simdCtor.loadXYZ(arr, index), vXYZ);
+        if (!t3) {
+            simdCtor.store3(arr, index, simdCtor.not(v3));
+            f.store3(i, v3);
+            assertEqX4(simdCtor.load3(arr, index), v3);
         } else
-            assertThrowsInstanceOf(() => f.storeXYZ(i, simdCtor()), RangeError);
-        if (!tXY) {
-            simdCtor.storeXY(arr, index, simdCtor.not(vXY));
-            f.storeXY(i, vXY);
-            assertEqX4(simdCtor.loadXY(arr, index), vXY);
+            assertThrowsInstanceOf(() => f.store3(i, simdCtor()), RangeError);
+        if (!t2) {
+            simdCtor.store2(arr, index, simdCtor.not(v2));
+            f.store2(i, v2);
+            assertEqX4(simdCtor.load2(arr, index), v2);
         } else
-            assertThrowsInstanceOf(() => f.storeXY(i, simdCtor()), RangeError);
-        if (!tX) {
-            simdCtor.storeX(arr, index, simdCtor.not(vX));
-            f.storeX(i, vX);
-            assertEqX4(simdCtor.loadX(arr, index), vX);
+            assertThrowsInstanceOf(() => f.store2(i, simdCtor()), RangeError);
+        if (!t1) {
+            simdCtor.store1(arr, index, simdCtor.not(v1));
+            f.store1(i, v1);
+            assertEqX4(simdCtor.load1(arr, index), v1);
         } else
-            assertThrowsInstanceOf(() => f.storeX(i, simdCtor()), RangeError);
+            assertThrowsInstanceOf(() => f.store1(i, simdCtor()), RangeError);
     }
 }
 
 function testFloat32x4(ctor, shift, scale, disp) {
     testSimdX4(ctor, shift, scale, disp, 'float32x4', SIMD.float32x4);
 }
 function testInt32x4(ctor, shift, scale, disp) {
     testSimdX4(ctor, shift, scale, disp, 'int32x4', SIMD.int32x4);
--- a/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js
+++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-object.js
@@ -74,10 +74,10 @@ assertEq(tByteSize([1, 2, 3, 4]),       
 assertEq(tByteSize([1, 2, 3, 4, 5]),                    s(80,  96));
 assertEq(tByteSize([1, 2, 3, 4, 5, 6]),                 s(80,  96));
 assertEq(tByteSize([1, 2, 3, 4, 5, 6, 7]),              s(112, 128));
 assertEq(tByteSize([1, 2, 3, 4, 5, 6, 7, 8]),           s(112, 128));
 
 // Various forms of functions.
 assertEq(tByteSize(function () {}),                     s(32,  64));
 assertEq(tByteSize(function () {}.bind()),              s(96,  128));
-assertEq(tByteSize(() => 1),                            s(48,  96));
+assertEq(tByteSize(() => 1),                            s(48,  80));
 assertEq(tByteSize(Math.sin),                           s(32,  64));
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -11194,16 +11194,17 @@ IonBuilder::jsop_setprop(PropertyName* n
         return emitted;
 
     // Try to emit a monomorphic/polymorphic store based on baseline caches.
     trackOptimizationAttempt(TrackedStrategy::SetProp_InlineAccess);
     if (!setPropTryInlineAccess(&emitted, obj, name, value, barrier, objTypes) || emitted)
         return emitted;
 
     // Emit a polymorphic cache.
+    trackOptimizationAttempt(TrackedStrategy::SetProp_InlineCache);
     return setPropTryCache(&emitted, obj, name, value, barrier, objTypes);
 }
 
 bool
 IonBuilder::setPropTryCommonSetter(bool* emitted, MDefinition* obj,
                                    PropertyName* name, MDefinition* value)
 {
     MOZ_ASSERT(*emitted == false);
@@ -11711,16 +11712,17 @@ IonBuilder::setPropTryCache(bool* emitte
         ins->setNeedsBarrier();
 
     current->add(ins);
     current->push(value);
 
     if (!resumeAfter(ins))
         return false;
 
+    trackOptimizationSuccess();
     *emitted = true;
     return true;
 }
 
 bool
 IonBuilder::jsop_delprop(PropertyName* name)
 {
     MDefinition* obj = current->pop();
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -378,47 +378,47 @@ IonBuilder::inlineNativeCall(CallInfo& c
         return inlineSimdShuffle(callInfo, native, SimdTypeDescr::Float32x4, 1, 4);
     if (native == js::simd_int32x4_shuffle)
         return inlineSimdShuffle(callInfo, native, SimdTypeDescr::Int32x4, 2, 4);
     if (native == js::simd_float32x4_shuffle)
         return inlineSimdShuffle(callInfo, native, SimdTypeDescr::Float32x4, 2, 4);
 
     if (native == js::simd_int32x4_load)
         return inlineSimdLoad(callInfo, native, SimdTypeDescr::Int32x4, 4);
-    if (native == js::simd_int32x4_loadX)
+    if (native == js::simd_int32x4_load1)
         return inlineSimdLoad(callInfo, native, SimdTypeDescr::Int32x4, 1);
-    if (native == js::simd_int32x4_loadXY)
+    if (native == js::simd_int32x4_load2)
         return inlineSimdLoad(callInfo, native, SimdTypeDescr::Int32x4, 2);
-    if (native == js::simd_int32x4_loadXYZ)
+    if (native == js::simd_int32x4_load3)
         return inlineSimdLoad(callInfo, native, SimdTypeDescr::Int32x4, 3);
 
     if (native == js::simd_float32x4_load)
         return inlineSimdLoad(callInfo, native, SimdTypeDescr::Float32x4, 4);
-    if (native == js::simd_float32x4_loadX)
+    if (native == js::simd_float32x4_load1)
         return inlineSimdLoad(callInfo, native, SimdTypeDescr::Float32x4, 1);
-    if (native == js::simd_float32x4_loadXY)
+    if (native == js::simd_float32x4_load2)
         return inlineSimdLoad(callInfo, native, SimdTypeDescr::Float32x4, 2);
-    if (native == js::simd_float32x4_loadXYZ)
+    if (native == js::simd_float32x4_load3)
         return inlineSimdLoad(callInfo, native, SimdTypeDescr::Float32x4, 3);
 
     if (native == js::simd_int32x4_store)
         return inlineSimdStore(callInfo, native, SimdTypeDescr::Int32x4, 4);
-    if (native == js::simd_int32x4_storeX)
+    if (native == js::simd_int32x4_store1)
         return inlineSimdStore(callInfo, native, SimdTypeDescr::Int32x4, 1);
-    if (native == js::simd_int32x4_storeXY)
+    if (native == js::simd_int32x4_store2)
         return inlineSimdStore(callInfo, native, SimdTypeDescr::Int32x4, 2);
-    if (native == js::simd_int32x4_storeXYZ)
+    if (native == js::simd_int32x4_store3)
         return inlineSimdStore(callInfo, native, SimdTypeDescr::Int32x4, 3);
     if (native == js::simd_float32x4_store)
         return inlineSimdStore(callInfo, native, SimdTypeDescr::Float32x4, 4);
-    if (native == js::simd_float32x4_storeX)
+    if (native == js::simd_float32x4_store1)
         return inlineSimdStore(callInfo, native, SimdTypeDescr::Float32x4, 1);
-    if (native == js::simd_float32x4_storeXY)
+    if (native == js::simd_float32x4_store2)
         return inlineSimdStore(callInfo, native, SimdTypeDescr::Float32x4, 2);
-    if (native == js::simd_float32x4_storeXYZ)
+    if (native == js::simd_float32x4_store3)
         return inlineSimdStore(callInfo, native, SimdTypeDescr::Float32x4, 3);
 
     if (native == js::simd_int32x4_bool)
         return inlineSimdBool(callInfo, native, SimdTypeDescr::Int32x4);
 
     // Reaching here means we tried to inline a native for which there is no
     // Ion specialization.
     trackOptimizationOutcome(TrackedOutcome::CantInlineNativeNoSpecialization);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3477,17 +3477,17 @@ JS_DefineFunctions(JSContext* cx, Handle
             JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(obj->getClass());
             MOZ_ASSERT(obj == &obj->global().getPrototype(key).toObject());
             RootedObject ctor(cx, &obj->global().getConstructor(key).toObject());
 
             flags &= ~JSFUN_GENERIC_NATIVE;
             JSFunction* fun = DefineFunction(cx, ctor, id,
                                              GenericNativeMethodDispatcher,
                                              fs->nargs + 1, flags,
-                                             JSFunction::ExtendedFinalizeKind);
+                                             gc::AllocKind::FUNCTION_EXTENDED);
             if (!fun)
                 return false;
 
             /*
              * As jsapi.h notes, fs must point to storage that lives as long
              * as fun->object lives.
              */
             fun->setExtendedSlot(0, PrivateValue(const_cast<JSFunctionSpec*>(fs)));
@@ -4036,17 +4036,17 @@ CompileFunction(JSContext* cx, const Rea
     AutoNameVector formals(cx);
     for (unsigned i = 0; i < nargs; i++) {
         RootedAtom argAtom(cx, Atomize(cx, argnames[i], strlen(argnames[i])));
         if (!argAtom || !formals.append(argAtom->asPropertyName()))
             return false;
     }
 
     fun.set(NewScriptedFunction(cx, 0, JSFunction::INTERPRETED, funAtom,
-                                JSFunction::FinalizeKind, TenuredObject,
+                                gc::AllocKind::FUNCTION, TenuredObject,
                                 enclosingDynamicScope));
     if (!fun)
         return false;
 
     // Make sure to handle cases when we have a polluted scopechain.
     CompileOptions options(cx, optionsArg);
     if (!enclosingDynamicScope->is<GlobalObject>())
         options.setHasPollutedScope(true);
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3257,17 +3257,17 @@ const Class ArrayObject::class_ = {
     nullptr, /* mayResolve */
     nullptr, /* convert */
     nullptr, /* finalize */
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     nullptr, /* trace */
     {
-        GenericCreateConstructor<ArrayConstructor, 1, JSFunction::FinalizeKind>,
+        GenericCreateConstructor<ArrayConstructor, 1, gc::AllocKind::FUNCTION>,
         CreateArrayPrototype,
         array_static_methods,
         nullptr,
         array_methods
     }
 };
 
 /*
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -760,30 +760,28 @@ CreateLazyScriptsForCompartment(JSContex
     // script. The last condition is so that we don't compile lazy scripts
     // whose enclosing scripts failed to compile, indicating that the lazy
     // script did not escape the script.
     //
     // Note that while we ideally iterate over LazyScripts, LazyScripts do not
     // currently stand in 1-1 relation with JSScripts; JSFunctions with the
     // same LazyScript may create different JSScripts due to relazification of
     // clones. See bug 1105306.
-    for (gc::ZoneCellIter i(cx->zone(), JSFunction::FinalizeKind); !i.done(); i.next()) {
-        JSObject* obj = i.get<JSObject>();
+    for (gc::ZoneCellIter i(cx->zone(), AllocKind::FUNCTION); !i.done(); i.next()) {
+        JSFunction* fun = &i.get<JSObject>()->as<JSFunction>();
 
         // Sweeping is incremental; take care to not delazify functions that
         // are about to be finalized. GC things referenced by objects that are
         // about to be finalized (e.g., in slots) may already be freed.
-        if (gc::IsAboutToBeFinalizedUnbarriered(&obj) ||
-            obj->compartment() != cx->compartment() ||
-            !obj->is<JSFunction>())
+        if (gc::IsAboutToBeFinalizedUnbarriered(&fun) ||
+            fun->compartment() != cx->compartment())
         {
             continue;
         }
 
-        JSFunction* fun = &obj->as<JSFunction>();
         if (fun->isInterpretedLazy()) {
             LazyScript* lazy = fun->lazyScriptOrNull();
             if (lazy && lazy->sourceObject() && !lazy->maybeScript() &&
                 !lazy->hasUncompiledEnclosingScript())
             {
                 if (!lazyFunctions.append(fun))
                     return false;
             }
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -3158,17 +3158,17 @@ const Class DateObject::class_ = {
     nullptr, /* mayResolve */
     date_convert,
     nullptr, /* finalize */
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     nullptr, /* trace */
     {
-        GenericCreateConstructor<DateConstructor, 7, JSFunction::FinalizeKind>,
+        GenericCreateConstructor<DateConstructor, 7, gc::AllocKind::FUNCTION>,
         GenericCreatePrototype,
         date_static_methods,
         nullptr,
         date_methods,
         nullptr,
         FinishDateClassInit
     }
 };
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -485,17 +485,17 @@ ErrorObject::createProto(JSContext* cx, 
 
     return errorProto;
 }
 
 /* static */ JSObject*
 ErrorObject::createConstructor(JSContext* cx, JSProtoKey key)
 {
     RootedObject ctor(cx);
-    ctor = GenericCreateConstructor<Error, 1, JSFunction::ExtendedFinalizeKind>(cx, key);
+    ctor = GenericCreateConstructor<Error, 1, gc::AllocKind::FUNCTION_EXTENDED>(cx, key);
     if (!ctor)
         return nullptr;
 
     ctor->as<JSFunction>().setExtendedSlot(0, Int32Value(ExnTypeFromProtoKey(key)));
     return ctor;
 }
 
 JS_FRIEND_API(JSFlatString*)
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -419,17 +419,17 @@ js::DefineFunctionWithReserved(JSContext
     RootedObject obj(cx, objArg);
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
     JSAtom* atom = Atomize(cx, name, strlen(name));
     if (!atom)
         return nullptr;
     Rooted<jsid> id(cx, AtomToId(atom));
-    return DefineFunction(cx, obj, id, call, nargs, attrs, JSFunction::ExtendedFinalizeKind);
+    return DefineFunction(cx, obj, id, call, nargs, attrs, gc::AllocKind::FUNCTION_EXTENDED);
 }
 
 JS_FRIEND_API(JSFunction*)
 js::NewFunctionWithReserved(JSContext* cx, JSNative native, unsigned nargs, unsigned flags,
                             const char* name)
 {
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
 
@@ -438,32 +438,32 @@ js::NewFunctionWithReserved(JSContext* c
     RootedAtom atom(cx);
     if (name) {
         atom = Atomize(cx, name, strlen(name));
         if (!atom)
             return nullptr;
     }
 
     return (flags & JSFUN_CONSTRUCTOR) ?
-        NewNativeConstructor(cx, native, nargs, atom, JSFunction::ExtendedFinalizeKind) :
-        NewNativeFunction(cx, native, nargs, atom, JSFunction::ExtendedFinalizeKind);
+        NewNativeConstructor(cx, native, nargs, atom, gc::AllocKind::FUNCTION_EXTENDED) :
+        NewNativeFunction(cx, native, nargs, atom, gc::AllocKind::FUNCTION_EXTENDED);
 }
 
 JS_FRIEND_API(JSFunction*)
 js::NewFunctionByIdWithReserved(JSContext* cx, JSNative native, unsigned nargs, unsigned flags,
                                 jsid id)
 {
     MOZ_ASSERT(JSID_IS_STRING(id));
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     CHECK_REQUEST(cx);
 
     RootedAtom atom(cx, JSID_TO_ATOM(id));
     return (flags & JSFUN_CONSTRUCTOR) ?
-        NewNativeConstructor(cx, native, nargs, atom, JSFunction::ExtendedFinalizeKind) :
-        NewNativeFunction(cx, native, nargs, atom, JSFunction::ExtendedFinalizeKind);
+        NewNativeConstructor(cx, native, nargs, atom, gc::AllocKind::FUNCTION_EXTENDED) :
+        NewNativeFunction(cx, native, nargs, atom, gc::AllocKind::FUNCTION_EXTENDED);
 }
 
 JS_FRIEND_API(const Value&)
 js::GetFunctionNativeReserved(JSObject* fun, size_t which)
 {
     MOZ_ASSERT(fun->as<JSFunction>().isNative());
     return fun->as<JSFunction>().getExtendedSlot(which);
 }
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -591,19 +591,19 @@ js::XDRInterpretedFunction(XDRState<mode
     if (mode == XDR_DECODE) {
         RootedObject proto(cx);
         if (firstword & IsStarGenerator) {
             proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global());
             if (!proto)
                 return false;
         }
 
-        gc::AllocKind allocKind = JSFunction::FinalizeKind;
+        gc::AllocKind allocKind = gc::AllocKind::FUNCTION;
         if (uint16_t(flagsword) & JSFunction::EXTENDED)
-            allocKind = JSFunction::ExtendedFinalizeKind;
+            allocKind = gc::AllocKind::FUNCTION_EXTENDED;
         fun = NewFunctionWithProto(cx, nullptr, 0, JSFunction::INTERPRETED,
                                    /* enclosingDynamicScope = */ NullPtr(), NullPtr(), proto,
                                    allocKind, TenuredObject);
         if (!fun)
             return false;
         script = nullptr;
     }
 
@@ -649,19 +649,17 @@ js::CloneFunctionAndScript(JSContext* cx
     /* NB: Keep this in sync with XDRInterpretedFunction. */
     RootedObject cloneProto(cx);
     if (srcFun->isStarGenerator()) {
         cloneProto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global());
         if (!cloneProto)
             return nullptr;
     }
 
-    gc::AllocKind allocKind = JSFunction::FinalizeKind;
-    if (srcFun->isExtended())
-        allocKind = JSFunction::ExtendedFinalizeKind;
+    gc::AllocKind allocKind = srcFun->getAllocKind();
     RootedFunction clone(cx, NewFunctionWithProto(cx, nullptr, 0,
                                                   JSFunction::INTERPRETED, NullPtr(), NullPtr(),
                                                   cloneProto, allocKind, TenuredObject));
     if (!clone)
         return nullptr;
 
     JSScript::AutoDelazify srcScript(cx, srcFun);
     if (!srcScript)
@@ -779,17 +777,17 @@ static JSObject*
 CreateFunctionConstructor(JSContext* cx, JSProtoKey key)
 {
     Rooted<GlobalObject*> global(cx, cx->global());
     RootedObject functionProto(cx, &global->getPrototype(JSProto_Function).toObject());
 
     RootedObject functionCtor(cx,
       NewFunctionWithProto(cx, Function, 1, JSFunction::NATIVE_CTOR,
                            NullPtr(), HandlePropertyName(cx->names().Function),
-                           functionProto, JSFunction::FinalizeKind, SingletonObject));
+                           functionProto, AllocKind::FUNCTION, SingletonObject));
     if (!functionCtor)
         return nullptr;
 
     return functionCtor;
 
 }
 
 static JSObject*
@@ -799,17 +797,17 @@ CreateFunctionPrototype(JSContext* cx, J
 
     RootedObject objectProto(cx, &self->getPrototype(JSProto_Object).toObject());
     /*
      * Bizarrely, |Function.prototype| must be an interpreted function, so
      * give it the guts to be one.
      */
     JSObject* functionProto_ =
         NewFunctionWithProto(cx, nullptr, 0, JSFunction::INTERPRETED,
-                             self, NullPtr(), objectProto, JSFunction::FinalizeKind,
+                             self, NullPtr(), objectProto, AllocKind::FUNCTION,
                              SingletonObject);
     if (!functionProto_)
         return nullptr;
 
     RootedFunction functionProto(cx, &functionProto_->as<JSFunction>());
     functionProto->setIsFunctionPrototype();
 
     const char* rawSource = "() {\n}";
@@ -867,17 +865,17 @@ CreateFunctionPrototype(JSContext* cx, J
     // |Function.prototype| right now.)
     //
     // Note that we can't use NewFunction here, even though we want the normal
     // Function.prototype for our proto, because we're still in the middle of
     // creating that as far as the world is concerned, so things will get all
     // confused.
     RootedFunction throwTypeError(cx,
       NewFunctionWithProto(cx, ThrowTypeError, 0, JSFunction::NATIVE_FUN,
-                           NullPtr(), NullPtr(), functionProto, JSFunction::FinalizeKind,
+                           NullPtr(), NullPtr(), functionProto, AllocKind::FUNCTION,
                            SingletonObject));
     if (!throwTypeError || !PreventExtensions(cx, throwTypeError))
         return nullptr;
 
     self->setThrowTypeError(throwTypeError);
 
     return functionProto;
 }
@@ -1949,17 +1947,17 @@ FunctionConstructor(JSContext* cx, unsig
     if (isStarGenerator) {
         proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, global);
         if (!proto)
             return false;
     }
     RootedFunction fun(cx, NewFunctionWithProto(cx, nullptr, 0,
                                                 JSFunction::INTERPRETED_LAMBDA, global,
                                                 anonymousAtom, proto,
-                                                JSFunction::FinalizeKind, TenuredObject));
+                                                AllocKind::FUNCTION, TenuredObject));
     if (!fun)
         return false;
 
     if (!JSFunction::setTypeForScriptedFunction(cx, fun))
         return false;
 
     if (hasRest)
         fun->setHasRest();
@@ -1998,56 +1996,54 @@ js::Generator(JSContext* cx, unsigned ar
 bool
 JSFunction::isBuiltinFunctionConstructor()
 {
     return maybeNative() == Function || maybeNative() == Generator;
 }
 
 JSFunction*
 js::NewNativeFunction(ExclusiveContext* cx, Native native, unsigned nargs, HandleAtom atom,
-                      gc::AllocKind allocKind /* = JSFunction::FinalizeKind */,
+                      gc::AllocKind allocKind /* = AllocKind::FUNCTION */,
                       NewObjectKind newKind /* = GenericObject */)
 {
     return NewFunctionWithProto(cx, native, nargs, JSFunction::NATIVE_FUN,
                                 NullPtr(), atom, NullPtr(), allocKind, newKind);
 }
 
 JSFunction*
 js::NewNativeConstructor(ExclusiveContext* cx, Native native, unsigned nargs, HandleAtom atom,
-                         gc::AllocKind allocKind /* = JSFunction::FinalizeKind */,
+                         gc::AllocKind allocKind /* = AllocKind::FUNCTION */,
                          NewObjectKind newKind /* = GenericObject */,
                          JSFunction::Flags flags /* = JSFunction::NATIVE_CTOR */)
 {
     MOZ_ASSERT(flags & JSFunction::NATIVE_CTOR);
     return NewFunctionWithProto(cx, native, nargs, flags, NullPtr(), atom,
                                 NullPtr(), allocKind, newKind);
 }
 
 JSFunction*
 js::NewScriptedFunction(ExclusiveContext* cx, unsigned nargs,
                         JSFunction::Flags flags, HandleAtom atom,
-                        gc::AllocKind allocKind /* = JSFunction::FinalizeKind */,
+                        gc::AllocKind allocKind /* = AllocKind::FUNCTION */,
                         NewObjectKind newKind /* = GenericObject */,
                         HandleObject enclosingDynamicScope /* = NullPtr() */)
 {
     return NewFunctionWithProto(cx, nullptr, nargs, flags,
                                 enclosingDynamicScope ? enclosingDynamicScope : cx->global(),
                                 atom, NullPtr(), allocKind, newKind);
 }
 
 JSFunction*
 js::NewFunctionWithProto(ExclusiveContext* cx, Native native,
                          unsigned nargs, JSFunction::Flags flags, HandleObject enclosingDynamicScope,
                          HandleAtom atom, HandleObject proto,
-                         gc::AllocKind allocKind /* = JSFunction::FinalizeKind */,
+                         gc::AllocKind allocKind /* = AllocKind::FUNCTION */,
                          NewObjectKind newKind /* = GenericObject */)
 {
-    MOZ_ASSERT(allocKind == JSFunction::FinalizeKind || allocKind == JSFunction::ExtendedFinalizeKind);
-    MOZ_ASSERT(sizeof(JSFunction) <= gc::Arena::thingSize(JSFunction::FinalizeKind));
-    MOZ_ASSERT(sizeof(FunctionExtended) <= gc::Arena::thingSize(JSFunction::ExtendedFinalizeKind));
+    MOZ_ASSERT(allocKind == AllocKind::FUNCTION || allocKind == AllocKind::FUNCTION_EXTENDED);
     MOZ_ASSERT_IF(native, !enclosingDynamicScope);
 
     RootedObject funobj(cx);
     // Don't mark asm.js module functions as singleton since they are
     // cloned (via CloneFunctionObjectIfNotSingleton) which assumes that
     // isSingleton implies isInterpreted.
     if (native && !IsAsmJSModuleNative(native))
         newKind = SingletonObject;
@@ -2064,17 +2060,17 @@ js::NewFunctionWithProto(ExclusiveContex
 #endif
     funobj = NewObjectWithClassProto(cx, &JSFunction::class_, proto, allocKind,
                                      newKind);
     if (!funobj)
         return nullptr;
 
     RootedFunction fun(cx, &funobj->as<JSFunction>());
 
-    if (allocKind == JSFunction::ExtendedFinalizeKind)
+    if (allocKind == AllocKind::FUNCTION_EXTENDED)
         flags = JSFunction::Flags(flags | JSFunction::EXTENDED);
 
     /* Initialize all function members. */
     fun->setArgCount(uint16_t(nargs));
     fun->setFlags(flags);
     if (fun->isInterpreted()) {
         MOZ_ASSERT(!native);
         if (fun->isInterpretedLazy())
@@ -2082,17 +2078,17 @@ js::NewFunctionWithProto(ExclusiveContex
         else
             fun->initScript(nullptr);
         fun->initEnvironment(enclosingDynamicScope);
     } else {
         MOZ_ASSERT(fun->isNative());
         MOZ_ASSERT(native);
         fun->initNative(native, nullptr);
     }
-    if (allocKind == JSFunction::ExtendedFinalizeKind)
+    if (allocKind == AllocKind::FUNCTION_EXTENDED)
         fun->initializeExtended();
     fun->initAtom(atom);
 
     return fun;
 }
 
 bool
 js::CloneFunctionObjectUseSameScript(JSCompartment* compartment, HandleFunction fun,
@@ -2164,17 +2160,17 @@ js::CloneFunctionObject(JSContext* cx, H
                                                  allocKind, newKind);
     if (!cloneobj)
         return nullptr;
     RootedFunction clone(cx, &cloneobj->as<JSFunction>());
 
     MOZ_ASSERT(useSameScript || !fun->isInterpretedLazy());
 
     uint16_t flags = fun->flags() & ~JSFunction::EXTENDED;
-    if (allocKind == JSFunction::ExtendedFinalizeKind)
+    if (allocKind == AllocKind::FUNCTION_EXTENDED)
         flags |= JSFunction::EXTENDED;
 
     clone->setArgCount(fun->nargs());
     clone->setFlags(flags);
     if (fun->hasScript()) {
         clone->initScript(fun->nonLazyScript());
         clone->initEnvironment(parent);
     } else if (fun->isInterpretedLazy()) {
@@ -2183,17 +2179,17 @@ js::CloneFunctionObject(JSContext* cx, H
         LazyScript* lazy = fun->lazyScriptOrNull();
         clone->initLazyScript(lazy);
         clone->initEnvironment(parent);
     } else {
         clone->initNative(fun->native(), fun->jitInfo());
     }
     clone->initAtom(fun->displayAtom());
 
-    if (allocKind == JSFunction::ExtendedFinalizeKind) {
+    if (allocKind == AllocKind::FUNCTION_EXTENDED) {
         if (fun->isExtended() && fun->compartment() == cx->compartment()) {
             for (unsigned i = 0; i < FunctionExtended::NUM_EXTENDED_SLOTS; i++)
                 clone->initExtendedSlot(i, fun->getExtendedSlot(i));
         } else {
             clone->initializeExtended();
         }
     }
 
@@ -2251,17 +2247,17 @@ js::IdToFunctionName(JSContext* cx, Hand
     }
 
     RootedValue idv(cx, IdToValue(id));
     return ToAtom<CanGC>(cx, idv);
 }
 
 JSFunction*
 js::DefineFunction(JSContext* cx, HandleObject obj, HandleId id, Native native,
-                   unsigned nargs, unsigned flags, AllocKind allocKind /* = FinalizeKind */,
+                   unsigned nargs, unsigned flags, AllocKind allocKind /* = AllocKind::FUNCTION */,
                    NewObjectKind newKind /* = GenericObject */)
 {
     GetterOp gop;
     SetterOp sop;
     if (flags & JSFUN_STUB_GSOPS) {
         /*
          * JSFUN_STUB_GSOPS is a request flag only, not stored in fun->flags or
          * the defined property's attributes. This allows us to encode another,
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -449,24 +449,16 @@ class JSFunction : public js::NativeObje
                       "for offsetOfNativeOrScript() have any sense");
         static_assert(offsetof(U, n.native) == offsetof(U, nativeOrScript),
                       "U::nativeOrScript must be at the same offset as "
                       "native");
 
         return offsetof(JSFunction, u.nativeOrScript);
     }
 
-#if JS_BITS_PER_WORD == 32
-    static const js::gc::AllocKind FinalizeKind = js::gc::AllocKind::OBJECT2_BACKGROUND;
-    static const js::gc::AllocKind ExtendedFinalizeKind = js::gc::AllocKind::OBJECT4_BACKGROUND;
-#else
-    static const js::gc::AllocKind FinalizeKind = js::gc::AllocKind::OBJECT4_BACKGROUND;
-    static const js::gc::AllocKind ExtendedFinalizeKind = js::gc::AllocKind::OBJECT8_BACKGROUND;
-#endif
-
     inline void trace(JSTracer* trc);
 
     /* Bound function accessors. */
 
     inline bool initBoundFunction(JSContext* cx, js::HandleObject target, js::HandleValue thisArg,
                                   const js::Value* args, unsigned argslen);
 
     JSObject* getBoundFunctionTarget() const;
@@ -480,18 +472,20 @@ class JSFunction : public js::NativeObje
         return *(js::HeapPtrScript*)&u.i.s.script_;
     }
 
     inline js::FunctionExtended* toExtended();
     inline const js::FunctionExtended* toExtended() const;
 
   public:
     inline bool isExtended() const {
-        MOZ_ASSERT_IF(isTenured(), !!(flags() & EXTENDED) == (asTenured().getAllocKind() == ExtendedFinalizeKind));
-        return !!(flags() & EXTENDED);
+        bool extended = !!(flags() & EXTENDED);
+        MOZ_ASSERT_IF(isTenured(),
+                      extended == (asTenured().getAllocKind() == js::gc::AllocKind::FUNCTION_EXTENDED));
+        return extended;
     }
 
     /*
      * Accessors for data stored in extended functions. Use setExtendedSlot if
      * the function has already been initialized. Otherwise use
      * initExtendedSlot.
      */
     inline void initializeExtended();
@@ -500,23 +494,23 @@ class JSFunction : public js::NativeObje
     inline const js::Value& getExtendedSlot(size_t which) const;
 
     /* Constructs a new type for the function if necessary. */
     static bool setTypeForScriptedFunction(js::ExclusiveContext* cx, js::HandleFunction fun,
                                            bool singleton = false);
 
     /* GC support. */
     js::gc::AllocKind getAllocKind() const {
-        static_assert(FinalizeKind != ExtendedFinalizeKind,
+        static_assert(js::gc::AllocKind::FUNCTION != js::gc::AllocKind::FUNCTION_EXTENDED,
                       "extended/non-extended AllocKinds have to be different "
                       "for getAllocKind() to have a reason to exist");
 
-        js::gc::AllocKind kind = FinalizeKind;
+        js::gc::AllocKind kind = js::gc::AllocKind::FUNCTION;
         if (isExtended())
-            kind = ExtendedFinalizeKind;
+            kind = js::gc::AllocKind::FUNCTION_EXTENDED;
         MOZ_ASSERT_IF(isTenured(), kind == asTenured().getAllocKind());
         return kind;
     }
 };
 
 static_assert(sizeof(JSFunction) == sizeof(js::shadow::Function),
               "shadow interface must match actual interface");
 
@@ -529,52 +523,52 @@ extern bool
 Function(JSContext* cx, unsigned argc, Value* vp);
 
 extern bool
 Generator(JSContext* cx, unsigned argc, Value* vp);
 
 // Allocate a new function backed by a JSNative.
 extern JSFunction*
 NewNativeFunction(ExclusiveContext* cx, JSNative native, unsigned nargs, HandleAtom atom,
-                  gc::AllocKind allocKind = JSFunction::FinalizeKind,
+                  gc::AllocKind allocKind = gc::AllocKind::FUNCTION,
                   NewObjectKind newKind = GenericObject);
 
 // Allocate a new constructor backed by a JSNative.
 extern JSFunction*
 NewNativeConstructor(ExclusiveContext* cx, JSNative native, unsigned nargs, HandleAtom atom,
-                     gc::AllocKind allocKind = JSFunction::FinalizeKind,
+                     gc::AllocKind allocKind = gc::AllocKind::FUNCTION,
                      NewObjectKind newKind = GenericObject,
                      JSFunction::Flags flags = JSFunction::NATIVE_CTOR);
 
 // Allocate a new scripted function.  If enclosingDynamicScope is null, the
 // global will be used.  In all cases the parent of the resulting object will be
 // the global.
 extern JSFunction*
 NewScriptedFunction(ExclusiveContext* cx, unsigned nargs, JSFunction::Flags flags,
-                    HandleAtom atom, gc::AllocKind allocKind = JSFunction::FinalizeKind,
+                    HandleAtom atom, gc::AllocKind allocKind = gc::AllocKind::FUNCTION,
                     NewObjectKind newKind = GenericObject,
                     HandleObject enclosingDynamicScope = NullPtr());
 
 // If proto is nullptr, Function.prototype is used instead.  If
 // enclosingDynamicScope is null, the function will have a null environment()
 // (yes, null, not the global).  In all cases, the global will be used as the
 // parent.
 extern JSFunction*
 NewFunctionWithProto(ExclusiveContext* cx, JSNative native, unsigned nargs,
                      JSFunction::Flags flags, HandleObject enclosingDynamicScope, HandleAtom atom,
-                     HandleObject proto, gc::AllocKind allocKind = JSFunction::FinalizeKind,
+                     HandleObject proto, gc::AllocKind allocKind = gc::AllocKind::FUNCTION,
                      NewObjectKind newKind = GenericObject);
 
 extern JSAtom*
 IdToFunctionName(JSContext* cx, HandleId id);
 
 extern JSFunction*
 DefineFunction(JSContext* cx, HandleObject obj, HandleId id, JSNative native,
                unsigned nargs, unsigned flags,
-               gc::AllocKind allocKind = JSFunction::FinalizeKind,
+               gc::AllocKind allocKind = gc::AllocKind::FUNCTION,
                NewObjectKind newKind = GenericObject);
 
 bool
 FunctionHasResolveHook(const JSAtomState& atomState, jsid id);
 
 extern bool
 fun_toString(JSContext* cx, unsigned argc, Value* vp);
 
@@ -612,17 +606,17 @@ class FunctionExtended : public JSFuncti
 };
 
 extern bool
 CloneFunctionObjectUseSameScript(JSCompartment* compartment, HandleFunction fun,
                                  HandleObject newParent);
 
 extern JSFunction*
 CloneFunctionObject(JSContext* cx, HandleFunction fun, HandleObject parent,
-                    gc::AllocKind kind = JSFunction::FinalizeKind,
+                    gc::AllocKind kind = gc::AllocKind::FUNCTION,
                     NewObjectKind newKindArg = GenericObject,
                     HandleObject proto = NullPtr());
 
 extern bool
 FindBody(JSContext* cx, HandleFunction fun, HandleLinearString src, size_t* bodyStart,
          size_t* bodyEnd);
 
 } // namespace js
--- a/js/src/jsfuninlines.h
+++ b/js/src/jsfuninlines.h
@@ -75,18 +75,18 @@ CloneFunctionObjectIfNotSingleton(JSCont
             return nullptr;
         MOZ_ASSERT(!proto || succeeded);
         fun->setEnvironment(parent);
         return fun;
     }
 
     // These intermediate variables are needed to avoid link errors on some
     // platforms.  Sigh.
-    gc::AllocKind finalizeKind = JSFunction::FinalizeKind;
-    gc::AllocKind extendedFinalizeKind = JSFunction::ExtendedFinalizeKind;
+    gc::AllocKind finalizeKind = gc::AllocKind::FUNCTION;
+    gc::AllocKind extendedFinalizeKind = gc::AllocKind::FUNCTION_EXTENDED;
     gc::AllocKind kind = fun->isExtended()
                          ? extendedFinalizeKind
                          : finalizeKind;
     return CloneFunctionObject(cx, fun, parent, kind, newKind, proto);
 }
 
 } /* namespace js */
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -265,16 +265,18 @@ static_assert(JS_ARRAY_LENGTH(slotsToThi
 // Assert that SortedArenaList::MinThingSize is <= the real minimum thing size.
 #define CHECK_MIN_THING_SIZE_INNER(x_)                                         \
     static_assert(x_ >= SortedArenaList::MinThingSize,                         \
     #x_ " is less than SortedArenaList::MinThingSize!");
 #define CHECK_MIN_THING_SIZE(...) { __VA_ARGS__ }; /* Define the array. */     \
     MOZ_FOR_EACH(CHECK_MIN_THING_SIZE_INNER, (), (__VA_ARGS__ UINT32_MAX))
 
 const uint32_t Arena::ThingSizes[] = CHECK_MIN_THING_SIZE(
+    sizeof(JSFunction),         /* AllocKind::FUNCTION            */
+    sizeof(FunctionExtended),   /* AllocKind::FUNCTION_EXTENDED   */
     sizeof(JSObject_Slots0),    /* AllocKind::OBJECT0             */
     sizeof(JSObject_Slots0),    /* AllocKind::OBJECT0_BACKGROUND  */
     sizeof(JSObject_Slots2),    /* AllocKind::OBJECT2             */
     sizeof(JSObject_Slots2),    /* AllocKind::OBJECT2_BACKGROUND  */
     sizeof(JSObject_Slots4),    /* AllocKind::OBJECT4             */
     sizeof(JSObject_Slots4),    /* AllocKind::OBJECT4_BACKGROUND  */
     sizeof(JSObject_Slots8),    /* AllocKind::OBJECT8             */
     sizeof(JSObject_Slots8),    /* AllocKind::OBJECT8_BACKGROUND  */
@@ -296,16 +298,18 @@ const uint32_t Arena::ThingSizes[] = CHE
 );
 
 #undef CHECK_MIN_THING_SIZE_INNER
 #undef CHECK_MIN_THING_SIZE
 
 #define OFFSET(type) uint32_t(sizeof(ArenaHeader) + (ArenaSize - sizeof(ArenaHeader)) % sizeof(type))
 
 const uint32_t Arena::FirstThingOffsets[] = {
+    OFFSET(JSFunction),         /* AllocKind::FUNCTION            */
+    OFFSET(FunctionExtended),   /* AllocKind::FUNCTION_EXTENDED   */
     OFFSET(JSObject_Slots0),    /* AllocKind::OBJECT0             */
     OFFSET(JSObject_Slots0),    /* AllocKind::OBJECT0_BACKGROUND  */
     OFFSET(JSObject_Slots2),    /* AllocKind::OBJECT2             */
     OFFSET(JSObject_Slots2),    /* AllocKind::OBJECT2_BACKGROUND  */
     OFFSET(JSObject_Slots4),    /* AllocKind::OBJECT4             */
     OFFSET(JSObject_Slots4),    /* AllocKind::OBJECT4_BACKGROUND  */
     OFFSET(JSObject_Slots8),    /* AllocKind::OBJECT8             */
     OFFSET(JSObject_Slots8),    /* AllocKind::OBJECT8_BACKGROUND  */
@@ -360,16 +364,18 @@ static const FinalizePhase IncrementalFi
     PHASE(IncrementalPhaseJitCode, gcstats::PHASE_SWEEP_JITCODE)
 };
 
 /*
  * Finalization order for things swept in the background.
  */
 
 static const AllocKind BackgroundPhaseObjects[] = {
+    AllocKind::FUNCTION,
+    AllocKind::FUNCTION_EXTENDED,
     AllocKind::OBJECT0_BACKGROUND,
     AllocKind::OBJECT2_BACKGROUND,
     AllocKind::OBJECT4_BACKGROUND,
     AllocKind::OBJECT8_BACKGROUND,
     AllocKind::OBJECT12_BACKGROUND,
     AllocKind::OBJECT16_BACKGROUND
 };
 
@@ -583,16 +589,18 @@ static bool
 FinalizeArenas(FreeOp* fop,
                ArenaHeader** src,
                SortedArenaList& dest,
                AllocKind thingKind,
                SliceBudget& budget,
                ArenaLists::KeepArenasEnum keepArenas)
 {
     switch (thingKind) {
+      case AllocKind::FUNCTION:
+      case AllocKind::FUNCTION_EXTENDED:
       case AllocKind::OBJECT0:
       case AllocKind::OBJECT0_BACKGROUND:
       case AllocKind::OBJECT2:
       case AllocKind::OBJECT2_BACKGROUND:
       case AllocKind::OBJECT4:
       case AllocKind::OBJECT4_BACKGROUND:
       case AllocKind::OBJECT8:
       case AllocKind::OBJECT8_BACKGROUND:
@@ -887,20 +895,17 @@ Chunk::fetchNextFreeArena(JSRuntime* rt)
 
 ArenaHeader*
 Chunk::allocateArena(JSRuntime* rt, Zone* zone, AllocKind thingKind, const AutoLockGC& lock)
 {
     ArenaHeader* aheader = info.numArenasFreeCommitted > 0
                            ? fetchNextFreeArena(rt)
                            : fetchNextDecommittedArena();
     aheader->init(zone, thingKind);
-    if (MOZ_UNLIKELY(!hasAvailableArenas())) {
-        rt->gc.availableChunks(lock).remove(this);
-        rt->gc.fullChunks(lock).push(this);
-    }
+    updateChunkListAfterAlloc(rt, lock);
     return aheader;
 }
 
 inline void
 GCRuntime::updateOnArenaFree(const ChunkInfo& info)
 {
     ++numArenasFreeCommitted;
 }
@@ -927,29 +932,74 @@ void
 Chunk::recycleArena(ArenaHeader* aheader, SortedArenaList& dest, AllocKind thingKind,
                     size_t thingsPerArena)
 {
     aheader->getArena()->setAsFullyUnused(thingKind);
     dest.insertAt(aheader, thingsPerArena);
 }
 
 void
-Chunk::releaseArena(JSRuntime* rt, ArenaHeader* aheader, const AutoLockGC& lock,
-                    ArenaDecommitState state /* = IsCommitted */)
+Chunk::releaseArena(JSRuntime* rt, ArenaHeader* aheader, const AutoLockGC& lock)
 {
     MOZ_ASSERT(aheader->allocated());
     MOZ_ASSERT(!aheader->hasDelayedMarking);
 
-    if (state == IsCommitted) {
-        aheader->setAsNotAllocated();
+    aheader->setAsNotAllocated();
+    addArenaToFreeList(rt, aheader);
+    updateChunkListAfterFree(rt, lock);
+}
+
+bool
+Chunk::decommitOneFreeArena(JSRuntime* rt, AutoLockGC& lock)
+{
+    MOZ_ASSERT(info.numArenasFreeCommitted > 0);
+    ArenaHeader* aheader = fetchNextFreeArena(rt);
+    updateChunkListAfterAlloc(rt, lock);
+
+    bool ok;
+    {
+        AutoUnlockGC unlock(lock);
+        ok = MarkPagesUnused(aheader->getArena(), ArenaSize);
+    }
+
+    if (ok)
+        addArenaToDecommittedList(rt, aheader);
+    else
         addArenaToFreeList(rt, aheader);
-    } else {
-        addArenaToDecommittedList(rt, aheader);
-    }
-
+    updateChunkListAfterFree(rt, lock);
+
+    return ok;
+}
+
+void
+Chunk::decommitAllArenasWithoutUnlocking(const AutoLockGC& lock)
+{
+    for (size_t i = 0; i < ArenasPerChunk; ++i) {
+        if (decommittedArenas.get(i) || arenas[i].aheader.allocated())
+            continue;
+
+        if (MarkPagesUnused(&arenas[i], ArenaSize)) {
+            info.numArenasFreeCommitted--;
+            decommittedArenas.set(i);
+        }
+    }
+}
+
+void
+Chunk::updateChunkListAfterAlloc(JSRuntime* rt, const AutoLockGC& lock)
+{
+    if (MOZ_UNLIKELY(!hasAvailableArenas())) {
+        rt->gc.availableChunks(lock).remove(this);
+        rt->gc.fullChunks(lock).push(this);
+    }
+}
+
+void
+Chunk::updateChunkListAfterFree(JSRuntime* rt, const AutoLockGC& lock)
+{
     if (info.numArenasFree == 1) {
         rt->gc.fullChunks(lock).remove(this);
         rt->gc.availableChunks(lock).push(this);
     } else if (!unused()) {
         MOZ_ASSERT(!rt->gc.fullChunks(lock).contains(this));
         MOZ_ASSERT(rt->gc.availableChunks(lock).contains(this));
         MOZ_ASSERT(!rt->gc.emptyChunks(lock).contains(this));
     } else {
@@ -2239,16 +2289,18 @@ UpdateCellPointersTyped(MovingTracer* tr
  */
 static void
 UpdateCellPointers(MovingTracer* trc, ArenaHeader* arena)
 {
     AllocKind kind = arena->getAllocKind();
     JSGCTraceKind traceKind = MapAllocToTraceKind(kind);
 
     switch (kind) {
+      case AllocKind::FUNCTION:
+      case AllocKind::FUNCTION_EXTENDED:
       case AllocKind::OBJECT0:
       case AllocKind::OBJECT0_BACKGROUND:
       case AllocKind::OBJECT2:
       case AllocKind::OBJECT2_BACKGROUND:
       case AllocKind::OBJECT4:
       case AllocKind::OBJECT4_BACKGROUND:
       case AllocKind::OBJECT8:
       case AllocKind::OBJECT8_BACKGROUND:
@@ -3144,27 +3196,18 @@ GCRuntime::maybePeriodicFullGC()
 }
 
 // Do all possible decommit immediately from the current thread without
 // releasing the GC lock or allocating any memory.
 void
 GCRuntime::decommitAllWithoutUnlocking(const AutoLockGC& lock)
 {
     MOZ_ASSERT(emptyChunks(lock).count() == 0);
-    for (ChunkPool::Iter chunk(availableChunks(lock)); !chunk.done(); chunk.next()) {
-        for (size_t i = 0; i < ArenasPerChunk; ++i) {
-            if (chunk->decommittedArenas.get(i) || chunk->arenas[i].aheader.allocated())
-                continue;
-
-            if (MarkPagesUnused(&chunk->arenas[i], ArenaSize)) {
-                chunk->info.numArenasFreeCommitted--;
-                chunk->decommittedArenas.set(i);
-            }
-        }
-    }
+    for (ChunkPool::Iter chunk(availableChunks(lock)); !chunk.done(); chunk.next())
+        chunk->decommitAllArenasWithoutUnlocking(lock);
     MOZ_ASSERT(availableChunks(lock).verify());
 }
 
 void
 GCRuntime::decommitArenas(AutoLockGC& lock)
 {
     // Verify that all entries in the empty chunks pool are decommitted.
     for (ChunkPool::Iter chunk(emptyChunks(lock)); !chunk.done(); chunk.next())
@@ -3187,23 +3230,17 @@ GCRuntime::decommitArenas(AutoLockGC& lo
     // head and don't want to thrash with the mutator.
     for (size_t i = toDecommit.length(); i > 1; --i) {
         Chunk* chunk = toDecommit[i - 1];
         MOZ_ASSERT(chunk);
 
         // The arena list is not doubly-linked, so we have to work in the free
         // list order and not in the natural order.
         while (chunk->info.numArenasFreeCommitted) {
-            ArenaHeader* aheader = chunk->allocateArena(rt, nullptr, AllocKind::OBJECT0, lock);
-            bool ok;
-            {
-                AutoUnlockGC unlock(lock);
-                ok = MarkPagesUnused(aheader->getArena(), ArenaSize);
-            }
-            chunk->releaseArena(rt, aheader, lock, Chunk::ArenaDecommitState(ok));
+            bool ok = chunk->decommitOneFreeArena(rt, lock);
 
             // FIXME Bug 1095620: add cancellation support when this becomes
             // a ParallelTask.
             if (/* cancel_ || */ !ok)
                 return;
         }
     }
     MOZ_ASSERT(availableChunks(lock).verify());
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -91,16 +91,18 @@ template <typename T> struct Participate
 FOR_EACH_GC_LAYOUT(EXPAND_PARTICIPATES_IN_CC)
 #undef EXPAND_PARTICIPATES_IN_CC
 
 static inline bool
 IsNurseryAllocable(AllocKind kind)
 {
     MOZ_ASSERT(IsValidAllocKind(kind));
     static const bool map[] = {
+        true,      /* AllocKind::FUNCTION */
+        true,      /* AllocKind::FUNCTION_EXTENDED */
         false,     /* AllocKind::OBJECT0 */
         true,      /* AllocKind::OBJECT0_BACKGROUND */
         false,     /* AllocKind::OBJECT2 */
         true,      /* AllocKind::OBJECT2_BACKGROUND */
         false,     /* AllocKind::OBJECT4 */
         true,      /* AllocKind::OBJECT4_BACKGROUND */
         false,     /* AllocKind::OBJECT8 */
         true,      /* AllocKind::OBJECT8_BACKGROUND */
@@ -124,16 +126,18 @@ IsNurseryAllocable(AllocKind kind)
     return map[size_t(kind)];
 }
 
 static inline bool
 IsBackgroundFinalized(AllocKind kind)
 {
     MOZ_ASSERT(IsValidAllocKind(kind));
     static const bool map[] = {
+        true,      /* AllocKind::FUNCTION */
+        true,      /* AllocKind::FUNCTION_EXTENDED */
         false,     /* AllocKind::OBJECT0 */
         true,      /* AllocKind::OBJECT0_BACKGROUND */
         false,     /* AllocKind::OBJECT2 */
         true,      /* AllocKind::OBJECT2_BACKGROUND */
         false,     /* AllocKind::OBJECT4 */
         true,      /* AllocKind::OBJECT4_BACKGROUND */
         false,     /* AllocKind::OBJECT8 */
         true,      /* AllocKind::OBJECT8_BACKGROUND */
@@ -282,19 +286,21 @@ GetBackgroundAllocKind(AllocKind kind)
 }
 
 /* Get the number of fixed slots and initial capacity associated with a kind. */
 static inline size_t
 GetGCKindSlots(AllocKind thingKind)
 {
     /* Using a switch in hopes that thingKind will usually be a compile-time constant. */
     switch (thingKind) {
+      case AllocKind::FUNCTION:
       case AllocKind::OBJECT0:
       case AllocKind::OBJECT0_BACKGROUND:
         return 0;
+      case AllocKind::FUNCTION_EXTENDED:
       case AllocKind::OBJECT2:
       case AllocKind::OBJECT2_BACKGROUND:
         return 2;
       case AllocKind::OBJECT4:
       case AllocKind::OBJECT4_BACKGROUND:
         return 4;
       case AllocKind::OBJECT8:
       case AllocKind::OBJECT8_BACKGROUND:
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -14,17 +14,17 @@
 
 namespace js {
 namespace gc {
 
 static inline AllocKind
 GetGCObjectKind(const Class* clasp)
 {
     if (clasp == FunctionClassPtr)
-        return JSFunction::FinalizeKind;
+        return AllocKind::FUNCTION;
     uint32_t nslots = JSCLASS_RESERVED_SLOTS(clasp);
     if (clasp->flags & JSCLASS_HAS_PRIVATE)
         nslots++;
     return GetGCObjectKind(nslots);
 }
 
 inline JSGCTraceKind
 GetGCThingTraceKind(const void* thing)
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1080,17 +1080,17 @@ NewObjectGCKind(const js::Class* clasp)
 static inline JSObject*
 NewObject(ExclusiveContext* cx, HandleObjectGroup group, gc::AllocKind kind,
           NewObjectKind newKind)
 {
     const Class* clasp = group->clasp();
 
     MOZ_ASSERT(clasp != &ArrayObject::class_);
     MOZ_ASSERT_IF(clasp == &JSFunction::class_,
-                  kind == JSFunction::FinalizeKind || kind == JSFunction::ExtendedFinalizeKind);
+                  kind == AllocKind::FUNCTION || kind == AllocKind::FUNCTION_EXTENDED);
 
     // For objects which can have fixed data following the object, only use
     // enough fixed slots to cover the number of reserved slots in the object,
     // regardless of the allocation kind specified.
     size_t nfixed = ClassCanHaveFixedData(clasp)
                     ? GetGCKindSlots(gc::GetGCObjectKind(clasp), clasp)
                     : GetGCKindSlots(kind, clasp);
 
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -818,13 +818,13 @@ Unbox(JSContext* cx, HandleObject obj, M
 }
 
 extern NativeObject*
 InitClass(JSContext* cx, HandleObject obj, HandleObject parent_proto,
           const Class* clasp, JSNative constructor, unsigned nargs,
           const JSPropertySpec* ps, const JSFunctionSpec* fs,
           const JSPropertySpec* static_ps, const JSFunctionSpec* static_fs,
           NativeObject** ctorp = nullptr,
-          gc::AllocKind ctorKind = JSFunction::FinalizeKind);
+          gc::AllocKind ctorKind = gc::AllocKind::FUNCTION);
 
 } /* namespace js */
 
 #endif /* jsobjinlines_h */
--- a/js/src/tests/ecma_7/SIMD/load.js
+++ b/js/src/tests/ecma_7/SIMD/load.js
@@ -46,43 +46,43 @@ function MakeComparator(kind, arr, share
     // these bytes as a typed array equivalent to the typed SIMD vector.
     var slice = function(start, numElemToRead) {
         // Read enough bytes
         var startBytes = start * bpe;
         var endBytes = startBytes + numElemToRead * sizeOfLaneElem;
         var asArray = Array.prototype.slice.call(uint8, startBytes, endBytes);
 
         // If length is less than SIZE_BYTES bytes, fill with 0.
-        // This is needed for loadX, loadXY, loadXYZ which do only partial
+        // This is needed for load1, load2, load3 which do only partial
         // reads.
         for (var i = asArray.length; i < SIZE_BYTES; i++) asArray[i] = 0;
         assertEq(asArray.length, SIZE_BYTES);
 
         return new typedArrayCtor(new Uint8Array(asArray).buffer);
     }
 
     var assertFunc = (lanes == 2) ? assertEqX2 : assertEqX4;
     var type = SIMD[kind];
     return {
-        loadX: function(index) {
-            var v = type.loadX(arr, index);
+        load1: function(index) {
+            var v = type.load1(arr, index);
             assertFunc(v, slice(index, 1));
         },
 
-        loadXY: function(index) {
+        load2: function(index) {
             if (lanes < 4)
                 return;
-            var v = type.loadXY(arr, index);
+            var v = type.load2(arr, index);
             assertFunc(v, slice(index, 2));
         },
 
-       loadXYZ: function(index) {
+       load3: function(index) {
            if (lanes < 4)
                return;
-           var v = type.loadXYZ(arr, index);
+           var v = type.load3(arr, index);
            assertFunc(v, slice(index, 3));
         },
 
         load: function(index) {
            var v = type.load(arr, index);
            assertFunc(v, slice(index, 4));
         }
     }
@@ -109,50 +109,50 @@ function testLoad(kind, TA) {
         assertThrowsInstanceOf(() => SIMD[kind].load(ta), TypeError);
         assertThrowsInstanceOf(() => SIMD[kind].load("hello", 0), TypeError);
         assertThrowsInstanceOf(() => SIMD[kind].load(ta, -1), RangeError);
 
         // Valid and invalid reads
         var C = MakeComparator(kind, ta);
         var bpe = ta.BYTES_PER_ELEMENT;
 
-        var lastValidArgLoadX   = (SIZE_BYTES - (lanes == 4 ? 4 : 8))  / bpe | 0;
-        var lastValidArgLoadXY  = (SIZE_BYTES - 8)  / bpe | 0;
-        var lastValidArgLoadXYZ = (SIZE_BYTES - 12) / bpe | 0;
+        var lastValidArgLoad1   = (SIZE_BYTES - (lanes == 4 ? 4 : 8))  / bpe | 0;
+        var lastValidArgLoad2   = (SIZE_BYTES - 8)  / bpe | 0;
+        var lastValidArgLoad3   = (SIZE_BYTES - 12) / bpe | 0;
         var lastValidArgLoad    = (SIZE_BYTES - 16) / bpe | 0;
 
         C.load(0);
         C.load(1);
         C.load(2);
         C.load(3);
         C.load(lastValidArgLoad);
         assertThrowsInstanceOf(() => SIMD[kind].load(ta, lastValidArgLoad + 1), RangeError);
 
-        C.loadX(0);
-        C.loadX(1);
-        C.loadX(2);
-        C.loadX(3);
-        C.loadX(lastValidArgLoadX);
-        assertThrowsInstanceOf(() => SIMD[kind].loadX(ta, lastValidArgLoadX + 1), RangeError);
+        C.load1(0);
+        C.load1(1);
+        C.load1(2);
+        C.load1(3);
+        C.load1(lastValidArgLoad1);
+        assertThrowsInstanceOf(() => SIMD[kind].load1(ta, lastValidArgLoad1 + 1), RangeError);
 
-        C.loadXY(0);
-        C.loadXY(1);
-        C.loadXY(2);
-        C.loadXY(3);
-        C.loadXY(lastValidArgLoadXY);
+        C.load2(0);
+        C.load2(1);
+        C.load2(2);
+        C.load2(3);
+        C.load2(lastValidArgLoad2);
 
-        C.loadXYZ(0);
-        C.loadXYZ(1);
-        C.loadXYZ(2);
-        C.loadXYZ(3);
-        C.loadXYZ(lastValidArgLoadXYZ);
+        C.load3(0);
+        C.load3(1);
+        C.load3(2);
+        C.load3(3);
+        C.load3(lastValidArgLoad3);
 
         if (lanes >= 4) {
-            assertThrowsInstanceOf(() => SIMD[kind].loadXY(ta, lastValidArgLoadXY + 1), RangeError);
-            assertThrowsInstanceOf(() => SIMD[kind].loadXYZ(ta, lastValidArgLoadXYZ + 1), RangeError);
+            assertThrowsInstanceOf(() => SIMD[kind].load2(ta, lastValidArgLoad2 + 1), RangeError);
+            assertThrowsInstanceOf(() => SIMD[kind].load3(ta, lastValidArgLoad3 + 1), RangeError);
         }
     }
 
     if (lanes == 4) {
         // Test ToInt32 behavior
         var v = SIMD[kind].load(TA, 12.5);
         assertEqX4(v, [12, 13, 14, 15]);
 
@@ -186,24 +186,24 @@ function testSharedArrayBufferCompat() {
                     new SharedInt32Array(TA.buffer),
                     new SharedFloat32Array(TA.buffer),
                     new SharedFloat64Array(TA.buffer)
                    ])
     {
         for (var kind of ['int32x4', 'float32x4', 'float64x2']) {
             var comp = MakeComparator(kind, ta);
             comp.load(0);
-            comp.loadX(0);
-            comp.loadXY(0);
-            comp.loadXYZ(0);
+            comp.load1(0);
+            comp.load2(0);
+            comp.load3(0);
 
             comp.load(3);
-            comp.loadX(3);
-            comp.loadXY(3);
-            comp.loadXYZ(3);
+            comp.load1(3);
+            comp.load2(3);
+            comp.load3(3);
         }
 
         assertThrowsInstanceOf(() => SIMD.int32x4.load(ta, 1024), RangeError);
         assertThrowsInstanceOf(() => SIMD.float32x4.load(ta, 1024), RangeError);
         assertThrowsInstanceOf(() => SIMD.float64x2.load(ta, 1024), RangeError);
     }
 }
 
--- a/js/src/tests/ecma_7/SIMD/store.js
+++ b/js/src/tests/ecma_7/SIMD/store.js
@@ -23,30 +23,30 @@ function assertChanged(ta, from, expecte
     for (; i < from + expected.length; i++)
         assertEq(ta[i], expected[i - from]);
     for (; i < ta.length; i++)
         assertEq(ta[i], POISON + i);
 }
 
 function testStore(ta, kind, i, v) {
     reset(ta);
-    SIMD[kind].storeX(ta, i, v);
+    SIMD[kind].store1(ta, i, v);
     assertChanged(ta, i, [v.x]);
 
     reset(ta);
     SIMD[kind].store(ta, i, v);
     assertChanged(ta, i, simdToArray(v));
 
     if (simdLength(v) > 2) {
         reset(ta);
-        SIMD[kind].storeXY(ta, i, v);
+        SIMD[kind].store2(ta, i, v);
         assertChanged(ta, i, [v.x, v.y]);
 
         reset(ta);
-        SIMD[kind].storeXYZ(ta, i, v);
+        SIMD[kind].store3(ta, i, v);
         assertChanged(ta, i, [v.x, v.y, v.z]);
     }
 }
 
 function testStoreInt32x4() {
     var I32 = new Int32Array(16);
 
     var v = SIMD.int32x4(0, 1, Math.pow(2,31) - 1, -Math.pow(2, 31));
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -5996,17 +5996,17 @@ DebuggerFrame_getArguments(JSContext* cx
         {
             return false;
         }
 
         Rooted<jsid> id(cx);
         for (unsigned i = 0; i < fargc; i++) {
             RootedFunction getobj(cx);
             getobj = NewNativeFunction(cx, DebuggerArguments_getArg, 0, js::NullPtr(),
-                                       JSFunction::ExtendedFinalizeKind);
+                                       gc::AllocKind::FUNCTION_EXTENDED);
             if (!getobj)
                 return false;
             id = INT_TO_JSID(i);
             if (!getobj ||
                 !NativeDefineProperty(cx, argsobj, id, UndefinedHandleValue,
                                       JS_DATA_TO_FUNC_PTR(GetterOp, getobj.get()), nullptr,
                                       JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER))
             {
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -602,17 +602,17 @@ GlobalObject::getSelfHostedFunction(JSCo
     RootedId shId(cx, AtomToId(selfHostedName));
     RootedObject holder(cx, cx->global()->intrinsicsHolder());
 
     if (cx->global()->maybeGetIntrinsicValue(shId, funVal.address()))
         return true;
 
     JSFunction* fun =
         NewScriptedFunction(cx, nargs, JSFunction::INTERPRETED_LAZY,
-                            name, JSFunction::ExtendedFinalizeKind, SingletonObject);
+                            name, gc::AllocKind::FUNCTION_EXTENDED, SingletonObject);
     if (!fun)
         return false;
     fun->setIsSelfHostedBuiltin();
     fun->setExtendedSlot(0, StringValue(selfHostedName));
     funVal.setObject(*fun);
 
     return cx->global()->addIntrinsicValue(cx, shId, funVal);
 }
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -294,17 +294,17 @@ class GlobalObject : public NativeObject
          JS::OnNewGlobalHookOption hookOption, const JS::CompartmentOptions& options);
 
     /*
      * Create a constructor function with the specified name and length using
      * ctor, a method which creates objects with the given class.
      */
     JSFunction*
     createConstructor(JSContext* cx, JSNative ctor, JSAtom* name, unsigned length,
-                      gc::AllocKind kind = JSFunction::FinalizeKind);
+                      gc::AllocKind kind = gc::AllocKind::FUNCTION);
 
     /*
      * Create an object to serve as [[Prototype]] for instances of the given
      * class, using |Object.prototype| as its [[Prototype]].  Users creating
      * prototype objects with particular internal structure (e.g. reserved
      * slots guaranteed to contain values of particular types) must immediately
      * complete the minimal initialization to make the returned object safe to
      * touch.
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -274,17 +274,17 @@ const Class RegExpObject::class_ = {
     nullptr, /* finalize */
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     RegExpObject::trace,
 
     // ClassSpec
     {
-        GenericCreateConstructor<js::regexp_construct, 2, JSFunction::FinalizeKind>,
+        GenericCreateConstructor<js::regexp_construct, 2, gc::AllocKind::FUNCTION>,
         CreateRegExpPrototype,
         nullptr,
         js::regexp_static_props,
         js::regexp_methods,
         js::regexp_properties
     }
 };
 
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -191,17 +191,17 @@ SavedFrame::finishSavedFrameInit(JSConte
     SavedFrame::finalize,       // finalize
     nullptr,                    // call
     nullptr,                    // hasInstance
     nullptr,                    // construct
     nullptr,                    // trace
 
     // ClassSpec
     {
-        GenericCreateConstructor<SavedFrame::construct, 0, JSFunction::FinalizeKind>,
+        GenericCreateConstructor<SavedFrame::construct, 0, gc::AllocKind::FUNCTION>,
         GenericCreatePrototype,
         SavedFrame::staticFunctions,
         nullptr,
         SavedFrame::protoFunctions,
         SavedFrame::protoAccessors,
         SavedFrame::finishSavedFrameInit,
         ClassSpec::DontDefineConstructor
     }
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1776,17 +1776,17 @@ CloneObject(JSContext* cx, HandleNativeO
 
     RootedObject clone(cx);
     if (selfHostedObject->is<JSFunction>()) {
         RootedFunction selfHostedFunction(cx, &selfHostedObject->as<JSFunction>());
         bool hasName = selfHostedFunction->atom() != nullptr;
         // Arrow functions use the first extended slot for their lexical |this| value.
         MOZ_ASSERT(!selfHostedFunction->isArrow());
         js::gc::AllocKind kind = hasName
-                                 ? JSFunction::ExtendedFinalizeKind
+                                 ? gc::AllocKind::FUNCTION_EXTENDED
                                  : selfHostedFunction->getAllocKind();
         clone = CloneFunctionObject(cx, selfHostedFunction, cx->global(), kind, TenuredObject);
         // To be able to re-lazify the cloned function, its name in the
         // self-hosting compartment has to be stored on the clone.
         if (clone && hasName)
             clone->as<JSFunction>().setExtendedSlot(0, StringValue(selfHostedFunction->atom()));
     } else if (selfHostedObject->is<RegExpObject>()) {
         RegExpObject& reobj = selfHostedObject->as<RegExpObject>();
--- a/js/src/vm/SharedTypedArrayObject.cpp
+++ b/js/src/vm/SharedTypedArrayObject.cpp
@@ -685,17 +685,17 @@ IMPL_SHARED_TYPED_ARRAY_COMBINED_UNWRAPP
 IMPL_SHARED_TYPED_ARRAY_COMBINED_UNWRAPPERS(Int32, int32_t, int32_t)
 IMPL_SHARED_TYPED_ARRAY_COMBINED_UNWRAPPERS(Uint32, uint32_t, uint32_t)
 IMPL_SHARED_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float32, float, float)
 IMPL_SHARED_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float64, double, double)
 
 #define SHARED_TYPED_ARRAY_CLASS_SPEC(_typedArray)                             \
 {                                                                              \
     GenericCreateConstructor<Shared##_typedArray##Object::class_constructor, 3, \
-                             JSFunction::FinalizeKind>,                        \
+                             gc::AllocKind::FUNCTION>,                         \
     Shared##_typedArray##Object::CreatePrototype,                              \
     nullptr,                                                                   \
     nullptr,                                                                   \
     Shared##_typedArray##Object::jsfuncs,                                      \
     Shared##_typedArray##Object::jsprops,                                      \
     Shared##_typedArray##Object::FinishClassInit                               \
 }
 
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -625,23 +625,16 @@ js::ConcatStrings<CanGC>(ExclusiveContex
 
 template JSString*
 js::ConcatStrings<NoGC>(ExclusiveContext* cx, JSString* left, JSString* right);
 
 template <typename CharT>
 JSFlatString*
 JSDependentString::undependInternal(ExclusiveContext* cx)
 {
-    /*
-     * We destroy the base() pointer in undepend, so we need a pre-barrier. We
-     * don't need a post-barrier because there aren't any outgoing pointers
-     * afterwards.
-     */
-    JSString::writeBarrierPre(base());
-
     size_t n = length();
     CharT* s = cx->pod_malloc<CharT>(n + 1);
     if (!s)
         return nullptr;
 
     AutoCheckCannotGC nogc;
     PodCopy(s, nonInlineChars<CharT>(nogc), n);
     s[n] = '\0';
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -230,17 +230,17 @@ class TypedArrayObjectTemplate : public 
         Handle<GlobalObject*> global = cx->global();
         RootedFunction ctorProto(cx, GlobalObject::getOrCreateTypedArrayConstructor(cx, global));
         if (!ctorProto)
             return nullptr;
 
         return NewFunctionWithProto(cx, class_constructor, 3,
                                     JSFunction::NATIVE_CTOR, NullPtr(),
                                     ClassName(key, cx),
-                                    ctorProto, JSFunction::FinalizeKind,
+                                    ctorProto, gc::AllocKind::FUNCTION,
                                     SingletonObject);
     }
 
     static bool
     finishClassInit(JSContext* cx, HandleObject ctor, HandleObject proto)
     {
         RootedValue bytesValue(cx, Int32Value(BYTES_PER_ELEMENT));
         if (!DefineProperty(cx, ctor, cx->names().BYTES_PER_ELEMENT, bytesValue,
@@ -842,17 +842,17 @@ TypedArrayObject::sharedTypedArrayProtot
     nullptr,                /* mayResolve */
     nullptr,                /* convert */
     nullptr,                /* finalize */
     nullptr,                /* call */
     nullptr,                /* hasInstance */
     nullptr,                /* construct */
     nullptr,                /* trace */
     {
-        GenericCreateConstructor<TypedArrayConstructor, 3, JSFunction::FinalizeKind>,
+        GenericCreateConstructor<TypedArrayConstructor, 3, gc::AllocKind::FUNCTION>,
         GenericCreatePrototype,
         TypedArrayObject::staticFunctions,
         nullptr,
         TypedArrayObject::protoFunctions,
         TypedArrayObject::protoAccessors,
         FinishTypedArrayInit,
         ClassSpec::DontDefineConstructor
     }
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -4480,17 +4480,19 @@ ChooseScaleAndSetTransform(FrameLayerBui
   gfxSize scale;
   // XXX Should we do something for 3D transforms?
   if (canDraw2D) {
     // If the container's transform is animated off main thread, fix a suitable scale size
     // for animation
     if (aContainerFrame->GetContent() &&
         nsLayoutUtils::HasAnimationsForCompositor(
           aContainerFrame->GetContent(), eCSSProperty_transform)) {
-      scale = nsLayoutUtils::ComputeSuitableScaleForAnimation(aContainerFrame->GetContent());
+      scale = nsLayoutUtils::ComputeSuitableScaleForAnimation(
+                aContainerFrame->GetContent(), aVisibleRect.Size(),
+                aContainerFrame->PresContext()->GetVisibleArea().Size());
     } else {
       // Scale factors are normalized to a power of 2 to reduce the number of resolution changes
       scale = RoundToFloatPrecision(ThebesMatrix(transform2d).ScaleFactors(true));
       // For frames with a changing transform that's not just a translation,
       // round scale factors up to nearest power-of-2 boundary so that we don't
       // keep having to redraw the content as it scales up and down. Rounding up to nearest
       // power-of-2 boundary ensures we never scale up, only down --- avoiding
       // jaggies. It also ensures we never scale down by more than a factor of 2,
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -476,28 +476,26 @@ GetScaleForValue(const StyleAnimationVal
   if (!canDraw2D) {
     return gfxSize();
   }
 
   return transform2d.ScaleFactors(true);
 }
 
 static float
-GetSuitableScale(float aMaxScale, float aMinScale)
-{
-  // If the minimum scale >= 1.0f, use it; if the maximum <= 1.0f, use it;
-  // otherwise use 1.0f.
-  if (aMinScale >= 1.0f) {
-    return aMinScale;
-  }
-  else if (aMaxScale <= 1.0f) {
-    return aMaxScale;
-  }
-
-  return 1.0f;
+GetSuitableScale(float aMaxScale, float aMinScale,
+                 nscoord aVisibleDimension, nscoord aDisplayDimension)
+{
+  float displayVisibleRatio = float(aDisplayDimension) /
+                              float(aVisibleDimension);
+  // We want to rasterize based on the largest scale used during the
+  // transform animation, unless that would make us rasterize something
+  // larger than the screen.  But we never want to go smaller than the
+  // minimum scale over the animation.
+  return std::max(std::min(aMaxScale, displayVisibleRatio), aMinScale);
 }
 
 static void
 GetMinAndMaxScaleForAnimationProperty(nsIContent* aContent,
                                       AnimationCollection* aAnimations,
                                       gfxSize& aMaxScale,
                                       gfxSize& aMinScale)
 {
@@ -526,17 +524,19 @@ GetMinAndMaxScaleForAnimationProperty(ns
           aMinScale.height = std::min<float>(aMinScale.height, to.height);
         }
       }
     }
   }
 }
 
 gfxSize
-nsLayoutUtils::ComputeSuitableScaleForAnimation(nsIContent* aContent)
+nsLayoutUtils::ComputeSuitableScaleForAnimation(nsIContent* aContent,
+                                                const nsSize& aVisibleSize,
+                                                const nsSize& aDisplaySize)
 {
   gfxSize maxScale(std::numeric_limits<gfxFloat>::min(),
                    std::numeric_limits<gfxFloat>::min());
   gfxSize minScale(std::numeric_limits<gfxFloat>::max(),
                    std::numeric_limits<gfxFloat>::max());
 
   AnimationCollection* animations =
     nsAnimationManager::GetAnimationsForCompositor(aContent,
@@ -551,21 +551,23 @@ nsLayoutUtils::ComputeSuitableScaleForAn
                                                     eCSSProperty_transform);
   if (animations) {
     GetMinAndMaxScaleForAnimationProperty(aContent, animations,
                                           maxScale, minScale);
   }
 
   if (maxScale.width == std::numeric_limits<gfxFloat>::min()) {
     // We didn't encounter a transform
-    maxScale = minScale = gfxSize(1.0, 1.0);
-  }
-
-  return gfxSize(GetSuitableScale(maxScale.width, minScale.width),
-                 GetSuitableScale(maxScale.height, minScale.height));
+    return gfxSize(1.0, 1.0);
+  }
+
+  return gfxSize(GetSuitableScale(maxScale.width, minScale.width,
+                                  aVisibleSize.width, aDisplaySize.width),
+                 GetSuitableScale(maxScale.height, minScale.height,
+                                  aVisibleSize.height, aDisplaySize.height));
 }
 
 bool
 nsLayoutUtils::AreAsyncAnimationsEnabled()
 {
   static bool sAreAsyncAnimationsEnabled;
   static bool sAsyncPrefCached = false;
 
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2142,23 +2142,28 @@ public:
 
   /**
    * Checks if we should warn about animations that can't be async
    */
   static bool IsAnimationLoggingEnabled();
 
   /**
    * Find a suitable scale for an element (aContent) over the course of any
-   * animations and transitions on the element.
+   * animations and transitions of the CSS transform property on the
+   * element that run on the compositor thread.
    * It will check the maximum and minimum scale during the animations and
    * transitions and return a suitable value for performance and quality.
-   * Will return scale(1,1) if there is no animated scaling.
-   * Always return positive value.
+   * Will return scale(1,1) if there are no such animations.
+   * Always returns a positive value.
+   * @param aVisibleSize is the size of the area we want to paint
+   * @param aDisplaySize is the size of the display area of the pres context
    */
-  static gfxSize ComputeSuitableScaleForAnimation(nsIContent* aContent);
+  static gfxSize ComputeSuitableScaleForAnimation(nsIContent* aContent,
+                                                  const nsSize& aVisibleSize,
+                                                  const nsSize& aDisplaySize);
 
   /**
    * Checks if we should forcibly use nearest pixel filtering for the
    * background.
    */
   static bool UseBackgroundNearestFiltering();
 
   /**
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -95,47 +95,37 @@ using namespace mozilla::layers;
 
 // static icon information
 nsImageFrame::IconLoad* nsImageFrame::gIconLoad = nullptr;
 
 // cached IO service for loading icons
 nsIIOService* nsImageFrame::sIOService;
 
 // test if the width and height are fixed, looking at the style data
-static bool HaveFixedSize(const nsStylePosition* aStylePosition)
+// This is used by nsImageFrame::ShouldCreateImageFrameFor and should
+// not be used for layout decisions.
+static bool HaveSpecifiedSize(const nsStylePosition* aStylePosition)
 {
   // check the width and height values in the reflow state's style struct
   // - if width and height are specified as either coord or percentage, then
   //   the size of the image frame is constrained
   return aStylePosition->mWidth.IsCoordPercentCalcUnit() &&
          aStylePosition->mHeight.IsCoordPercentCalcUnit();
 }
 
-// use the data in the reflow state to decide if the image has a constrained size
-// (i.e. width and height that are based on the containing block size and not the image size)
-// so we can avoid animated GIF related reflows
+// Decide whether we can optimize away reflows that result from the
+// image's intrinsic size changing.
 inline bool HaveFixedSize(const nsHTMLReflowState& aReflowState)
 {
   NS_ASSERTION(aReflowState.mStylePosition, "crappy reflowState - null stylePosition");
-  // when an image has percent css style height or width, but ComputedHeight()
-  // or ComputedWidth() of reflow state is  NS_UNCONSTRAINEDSIZE
-  // it needs to return false to cause an incremental reflow later
-  // if an image is inside table like bug 156731 simple testcase III,
-  // during pass 1 reflow, ComputedWidth() is NS_UNCONSTRAINEDSIZE
-  // in pass 2 reflow, ComputedWidth() is 0, it also needs to return false
-  // see bug 156731
-  const nsStyleCoord &height = aReflowState.mStylePosition->mHeight;
-  const nsStyleCoord &width = aReflowState.mStylePosition->mWidth;
-  return ((height.HasPercent() &&
-           NS_UNCONSTRAINEDSIZE == aReflowState.ComputedHeight()) ||
-          (width.HasPercent() &&
-           (NS_UNCONSTRAINEDSIZE == aReflowState.ComputedWidth() ||
-            0 == aReflowState.ComputedWidth())))
-          ? false
-          : HaveFixedSize(aReflowState.mStylePosition);
+  // Don't try to make this optimization when an image has percentages
+  // in its 'width' or 'height'.  The percentages might be treated like
+  // auto (especially for intrinsic width calculations and for heights).
+  return aReflowState.mStylePosition->mHeight.ConvertsToLength() &&
+         aReflowState.mStylePosition->mWidth.ConvertsToLength();
 }
 
 nsIFrame*
 NS_NewImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsImageFrame(aContext);
 }
 
@@ -446,17 +436,17 @@ nsImageFrame::SourceRectToDest(const nsI
 
 /* static */
 bool
 nsImageFrame::ShouldCreateImageFrameFor(Element* aElement,
                                         nsStyleContext* aStyleContext)
 {
   EventStates state = aElement->State();
   if (IMAGE_OK(state,
-               HaveFixedSize(aStyleContext->StylePosition()))) {
+               HaveSpecifiedSize(aStyleContext->StylePosition()))) {
     // Image is fine; do the image frame thing
     return true;
   }
 
   // Check if we want to use a placeholder box with an icon or just
   // let the presShell make us into inline text.  Decide as follows:
   //
   //  - if our special "force icons" style is set, show an icon
@@ -484,18 +474,18 @@ nsImageFrame::ShouldCreateImageFrameFor(
     // text).
     useSizedBox = true;
   }
   else if (aStyleContext->PresContext()->CompatibilityMode() !=
            eCompatibility_NavQuirks) {
     useSizedBox = false;
   }
   else {
-    // check whether we have fixed size
-    useSizedBox = HaveFixedSize(aStyleContext->StylePosition());
+    // check whether we have specified size
+    useSizedBox = HaveSpecifiedSize(aStyleContext->StylePosition());
   }
 
   return useSizedBox;
 }
 
 nsresult
 nsImageFrame::Notify(imgIRequest* aRequest,
                      int32_t aType,
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..85d980424c5791bfefe08d3b89b163070f26bc80
GIT binary patch
literal 219
zc%17D@N?(olHy`uVBq!ia0vp^DL`z*!2~2#!(YAxQk(@Ik;M!Q+`=Ht$S`Y;1W=H@
z#M9T6{XQ!zhb-HPd5=4RLXst}5hc#~xw)x%B@E6*sfi`2DGKG8B^e6tp1uL$jeO!j
zMWLQ9jv*QM-d<qjWnkbryx^Dl-{=_(LLvdO#;luCPEJzgCY{>Mex<H-8jorJ6rcqR
Mp00i_>zopr0J4-n_5c6?
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d09f03b25f5d955ffa5bcbf8955637a5e1dc90af
GIT binary patch
literal 220
zc%17D@N?(olHy`uVBq!ia0vp^MnIgx!2~3|yT0%MDb50q$YKTtZeb8+WSBKa0w~B{
z;_2(kexH?<L&h-EqWm~eNV3E=qQp5rH#aq}gu%HeHL)Z$MWH;iBtya7(>EZzkxv|`
zD9qEvF(l*O+jES(3=AC13-;G%tbM4avf$*Cd0Clcg1_0iCCgG6b&gH$1KPmg>FVdQ
I&MBb@020_kN&o-=
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bdb91e34c89484610a5a057065a9e5768e675f78
GIT binary patch
literal 191
zc%17D@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~AoCO|{#S9GG!XV7ZFl&wkP>{XE
z)7O>#J}WDSjMUMVzILFHWQl7;iF1B#Zfaf$gL6@8Vo7R>LV0FMhJw4NZ$Nk>pEyvF
zrKgKyNW|f{=NNez7&w?0?61#QyHHwX!O17{vNCalzgfBmMU5E6pTw2|4Po$f^>bP0
Hl+XkK<LNeO
new file mode 100644
--- /dev/null
+++ b/layout/reftests/image/image-resize-percent-height.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<script>
+function run() {
+  var img = document.getElementsByTagName("img")[0];
+  img.offsetWidth; // flush layout
+  img.onload = imgload;
+  img.src = "blue-50x50.png";
+}
+function imgload() {
+  document.documentElement.classList.remove("reftest-wait");
+}
+</script>
+<body onload="run()">
+<div><img src="blue-50x100.png" style="width: 50px; height: 100%"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/image/image-resize-percent-width.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<script>
+function run() {
+  var img = document.getElementsByTagName("img")[0];
+  img.offsetWidth; // flush layout
+  img.onload = imgload;
+  img.src = "blue-50x50.png";
+}
+function imgload() {
+  document.documentElement.classList.remove("reftest-wait");
+}
+</script>
+<body onload="run()">
+<div style="width: -moz-max-content"><img src="blue-100x50.png" style="width: 100%; height: 50px"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/image/image-resize-ref.html
@@ -0,0 +1,3 @@
+<!DOCTYPE HTML>
+<body>
+<div><img src="blue-50x50.png"></div>
--- a/layout/reftests/image/reftest.list
+++ b/layout/reftests/image/reftest.list
@@ -118,8 +118,11 @@ pref(dom.image.srcset.enabled,true) == i
 pref(dom.image.srcset.enabled,true) == image-srcset-default-src-1x.html image-srcset-default-src-1x-ref.html
 pref(dom.image.srcset.enabled,true) == image-srcset-orientation-2x.html image-srcset-orientation-2x-ref.html
 pref(dom.image.srcset.enabled,true) == image-srcset-orientation-1x.html image-srcset-orientation-1x-ref.html
 pref(dom.image.srcset.enabled,true) == image-srcset-svg-3x.html image-srcset-svg-3x-ref.html
 pref(dom.image.srcset.enabled,true) == image-srcset-svg-2x.html image-srcset-svg-2x-ref.html
 pref(dom.image.srcset.enabled,true) == image-srcset-svg-1x.html image-srcset-svg-1x-ref.html
 pref(dom.image.srcset.enabled,true) == image-srcset-svg-default-2x.html image-srcset-svg-default-2x-ref.html
 pref(dom.image.srcset.enabled,true) == image-srcset-svg-default-1x.html image-srcset-svg-default-1x-ref.html
+
+== image-resize-percent-height.html image-resize-ref.html
+== image-resize-percent-width.html image-resize-ref.html
--- a/mfbt/SplayTree.h
+++ b/mfbt/SplayTree.h
@@ -8,16 +8,17 @@
  * A sorted tree with optimal access times, where recently-accessed elements
  * are faster to access again.
  */
 
 #ifndef mozilla_SplayTree_h
 #define mozilla_SplayTree_h
 
 #include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
 
 namespace mozilla {
 
 template<class T, class C>
 class SplayTree;
 
 template<typename T>
 class SplayTreeNode
@@ -50,17 +51,17 @@ private:
  * method must be free from side effects.
  */
 template<typename T, class Comparator>
 class SplayTree
 {
   T* mRoot;
 
 public:
-  SplayTree()
+  MOZ_CONSTEXPR SplayTree()
     : mRoot(nullptr)
   {}
 
   bool empty() const
   {
     return !mRoot;
   }
 
@@ -81,25 +82,22 @@ public:
 
     if (!mRoot) {
       mRoot = aValue;
       return true;
     }
     T* last = lookup(*aValue);
     int cmp = Comparator::compare(*aValue, *last);
 
-    T** parentPointer = (cmp < 0) ? &last->mLeft : &last->mRight;
-    MOZ_ASSERT(!*parentPointer);
-    *parentPointer = aValue;
-    aValue->mParent = last;
-
-    splay(aValue);
+    finishInsertion(last, cmp, aValue);
     return true;
   }
 
+  T* findOrInsert(const T& aValue);
+
   T* remove(const T& aValue)
   {
     T* last = lookup(aValue);
     MOZ_ASSERT(last, "This tree must contain the element being removed.");
     MOZ_ASSERT(Comparator::compare(aValue, *last) == 0);
 
     // Splay the tree so that the item to remove is the root.
     splay(last);
@@ -191,16 +189,29 @@ private:
         node = node->mLeft;
       } else {
         node = node->mRight;
       }
     } while (node);
     return parent;
   }
 
+  T* finishInsertion(T* aLast, int32_t aCmp, T* aNew)
+  {
+    MOZ_ASSERT(aCmp, "Nodes shouldn't be equal!");
+
+    T** parentPointer = (aCmp < 0) ? &aLast->mLeft : &aLast->mRight;
+    MOZ_ASSERT(!*parentPointer);
+    *parentPointer = aNew;
+    aNew->mParent = aLast;
+
+    splay(aNew);
+    return aNew;
+  }
+
   /**
    * Rotate the tree until |node| is at the root of the tree. Performing
    * the rotations in this fashion preserves the amortized balancing of
    * the tree.
    */
   void splay(T* aNode)
   {
     MOZ_ASSERT(aNode);
@@ -290,11 +301,29 @@ private:
     }
     return aNode;
   }
 
   SplayTree(const SplayTree&) = delete;
   void operator=(const SplayTree&) = delete;
 };
 
+template<typename T, class Comparator>
+T*
+SplayTree<T, Comparator>::findOrInsert(const T& aValue)
+{
+  if (!mRoot) {
+    mRoot = new T(aValue);
+    return mRoot;
+  }
+
+  T* last = lookup(aValue);
+  int cmp = Comparator::compare(aValue, *last);
+  if (!cmp) {
+    return last;
+  }
+
+  return finishInsertion(last, cmp, new T(aValue));
+}
+
 }  /* namespace mozilla */
 
 #endif /* mozilla_SplayTree_h */
--- a/mfbt/tests/TestSplayTree.cpp
+++ b/mfbt/tests/TestSplayTree.cpp
@@ -100,37 +100,81 @@ struct SplayInt : SplayTreeNode<SplayInt
       return 1;
     }
     return 0;
   }
 
   int mValue;
 };
 
+struct SplayNoCopy : SplayTreeNode<SplayNoCopy>
+{
+  SplayNoCopy(const SplayNoCopy&) = delete;
+  SplayNoCopy(SplayNoCopy&&) = delete;
+
+  static int compare(const SplayNoCopy&, const SplayNoCopy&) { return 0; }
+};
+
+static SplayTree<SplayNoCopy, SplayNoCopy> testNoCopy;
+
 int
 main()
 {
   SplayTree<SplayInt, SplayInt> tree;
 
   MOZ_RELEASE_ASSERT(tree.empty());
 
   MOZ_RELEASE_ASSERT(!tree.find(SplayInt(0)));
 
   static const int N = mozilla::ArrayLength(gValues);
 
   // Insert the values, and check each one is findable just after insertion.
   for (int i = 0; i < N; i++) {
     tree.insert(new SplayInt(gValues[i]));
-    MOZ_RELEASE_ASSERT(tree.find(SplayInt(gValues[i])));
+    SplayInt* inserted = tree.find(SplayInt(gValues[i]));
+    MOZ_RELEASE_ASSERT(inserted);
+    MOZ_RELEASE_ASSERT(tree.findOrInsert(SplayInt(gValues[i])) == inserted);
     tree.checkCoherency();
   }
 
   // Check they're all findable after all insertions.
   for (int i = 0; i < N; i++) {
     MOZ_RELEASE_ASSERT(tree.find(SplayInt(gValues[i])));
+    MOZ_RELEASE_ASSERT(tree.findOrInsert(SplayInt(gValues[i])));
+    tree.checkCoherency();
+  }
+
+  // Check that non-inserted values cannot be found.
+  MOZ_RELEASE_ASSERT(!tree.find(SplayInt(-1)));
+  MOZ_RELEASE_ASSERT(!tree.find(SplayInt(N)));
+  MOZ_RELEASE_ASSERT(!tree.find(SplayInt(0x7fffffff)));
+
+  // Remove the values, and check each one is not findable just after removal.
+  for (int i = 0; i < N; i++) {
+    SplayInt* removed = tree.remove(SplayInt(gValues[i]));
+    MOZ_RELEASE_ASSERT(removed->mValue == gValues[i]);
+    MOZ_RELEASE_ASSERT(!tree.find(*removed));
+    delete removed;
+    tree.checkCoherency();
+  }
+
+  MOZ_RELEASE_ASSERT(tree.empty());
+
+  // Insert the values, and check each one is findable just after insertion.
+  for (int i = 0; i < N; i++) {
+    SplayInt* inserted = tree.findOrInsert(SplayInt(gValues[i]));
+    MOZ_RELEASE_ASSERT(tree.find(SplayInt(gValues[i])) == inserted);
+    MOZ_RELEASE_ASSERT(tree.findOrInsert(SplayInt(gValues[i])) == inserted);
+    tree.checkCoherency();
+  }
+
+  // Check they're all findable after all insertions.
+  for (int i = 0; i < N; i++) {
+    MOZ_RELEASE_ASSERT(tree.find(SplayInt(gValues[i])));
+    MOZ_RELEASE_ASSERT(tree.findOrInsert(SplayInt(gValues[i])));
     tree.checkCoherency();
   }
 
   // Check that non-inserted values cannot be found.
   MOZ_RELEASE_ASSERT(!tree.find(SplayInt(-1)));
   MOZ_RELEASE_ASSERT(!tree.find(SplayInt(N)));
   MOZ_RELEASE_ASSERT(!tree.find(SplayInt(0x7fffffff)));
 
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -82,17 +82,17 @@
     <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
 
     <!-- App requires OpenGL ES 2.0 -->
     <uses-feature android:glEsVersion="0x00020000" android:required="true" />
 
     <application android:label="@string/moz_app_displayname"
                  android:icon="@drawable/icon"
                  android:logo="@drawable/logo"
-                 android:name="org.mozilla.gecko.GeckoApplication"
+                 android:name="@MOZ_ANDROID_APPLICATION_CLASS@"
                  android:hardwareAccelerated="true"
 # The preprocessor does not yet support arbitrary parentheses, so this cannot
 # be parenthesized thus to clarify that the logical AND operator has precedence:
 #   !defined(MOZILLA_OFFICIAL) || (defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG))
 #if !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG)
                  android:debuggable="true">
 #else
                  android:debuggable="false">
@@ -102,17 +102,17 @@
 
 #ifdef MOZ_NATIVE_DEVICES
         <!-- This resources comes from Google Play Services. Required for casting support. -->
         <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
 #endif
 
         <!-- If the windowSoftInputMode adjust* flag changes below, the
              setSoftInputMode call in BrowserSearch#onStop must also be updated. -->
-        <activity android:name="org.mozilla.gecko.BrowserApp"
+        <activity android:name="@MOZ_ANDROID_BROWSER_INTENT_CLASS@"
                   android:label="@string/moz_app_displayname"
                   android:taskAffinity="@ANDROID_PACKAGE_NAME@.BROWSER"
                   android:alwaysRetainTaskState="true"
                   android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection"
                   android:windowSoftInputMode="stateUnspecified|adjustResize"
                   android:launchMode="singleTask"
                   android:exported="true"
                   android:theme="@style/Gecko.App">
@@ -129,17 +129,17 @@
              org.mozilla.{fennec,firefox,firefox_beta} subtree *and* an
              org.mozilla.gecko subtree.  The non-org.mozilla.gecko is deprecated
              and we would like to get rid of it entirely.  Until that happens, we
              have external consumers (such as intents and bookmarks) of
              non-org.mozilla.gecko Activity classes, so we define activity aliases
              for backwards compatibility. -->
         <activity-alias android:name=".App"
                         android:label="@MOZ_APP_DISPLAYNAME@"
-                        android:targetActivity="org.mozilla.gecko.BrowserApp">
+                        android:targetActivity="@MOZ_ANDROID_BROWSER_INTENT_CLASS@">
             <!-- android:priority ranges between -1000 and 1000.  We never want
                  another activity to usurp the MAIN action, so we ratchet our
                  priority up. -->
             <intent-filter android:priority="999">
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
                 <category android:name="android.intent.category.MULTIWINDOW_LAUNCHER"/>
                 <category android:name="android.intent.category.DEFAULT" />
@@ -351,17 +351,17 @@
              WebappAllocator. -->
 
 #define FRAGMENT WebappManifestFragment.xml.frag.in
 #include WebappFragmentRepeater.inc
 
         <!-- Masquerade as the Resolver so that we can be opened from the Marketplace. -->
         <activity-alias
             android:name="com.android.internal.app.ResolverActivity"
-            android:targetActivity="org.mozilla.gecko.BrowserApp"
+            android:targetActivity="@MOZ_ANDROID_BROWSER_INTENT_CLASS@"
             android:exported="true" />
 
         <receiver android:name="org.mozilla.gecko.GeckoUpdateReceiver">
             <intent-filter>
                 <action android:name="@ANDROID_PACKAGE_NAME@.CHECK_UPDATE_RESULT" />
             </intent-filter>
         </receiver>
 
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -76,20 +76,27 @@ public class AppConstants {
         public static final boolean preJB = MAX_SDK_VERSION < 16 || (MIN_SDK_VERSION < 16 && Build.VERSION.SDK_INT < 16);
         public static final boolean preICS = MAX_SDK_VERSION < 14 || (MIN_SDK_VERSION < 14 && Build.VERSION.SDK_INT < 14);
         public static final boolean preHCMR2 = MAX_SDK_VERSION < 13 || (MIN_SDK_VERSION < 13 && Build.VERSION.SDK_INT < 13);
         public static final boolean preHCMR1 = MAX_SDK_VERSION < 12 || (MIN_SDK_VERSION < 12 && Build.VERSION.SDK_INT < 12);
         public static final boolean preHC = MAX_SDK_VERSION < 11 || (MIN_SDK_VERSION < 11 && Build.VERSION.SDK_INT < 11);
     }
 
     /**
-     * The name of the Java class that launches the browser.
+     * The name of the Java class that represents the android application.
+     */
+    public static final String MOZ_ANDROID_APPLICATION_CLASS = "@MOZ_ANDROID_APPLICATION_CLASS@";
+    /**
+     * The name of the Java class that launches the browser activity.
      */
-    public static final String BROWSER_INTENT_CLASS_NAME = "org.mozilla.gecko.BrowserApp";
-    public static final String SEARCH_INTENT_CLASS_NAME = "org.mozilla.search.SearchActivity";
+    public static final String MOZ_ANDROID_BROWSER_INTENT_CLASS = "@MOZ_ANDROID_BROWSER_INTENT_CLASS@";
+    /**
+     * The name of the Java class that launches the search activity.
+     */
+    public static final String MOZ_ANDROID_SEARCH_INTENT_CLASS = "@MOZ_ANDROID_SEARCH_INTENT_CLASS@";
 
     public static final String GRE_MILESTONE = "@GRE_MILESTONE@";
 
     public static final String MOZ_APP_ABI = "@MOZ_APP_ABI@";
     public static final String MOZ_APP_BASENAME = "@MOZ_APP_BASENAME@";
 
     // For the benefit of future archaeologists: APP_BUILDID and
     // MOZ_APP_BUILDID are *exactly* the same.
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1864,17 +1864,17 @@ public class BrowserApp extends GeckoApp
                     DataReportingNotification.checkAndNotifyPolicy(GeckoAppShell.getContext());
                 }
 
             } else if (event.equals("Search:Keyword")) {
                 storeSearchQuery(message.getString("query"));
             } else if (event.equals("Prompt:ShowTop")) {
                 // Bring this activity to front so the prompt is visible..
                 Intent bringToFrontIntent = new Intent();
-                bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS_NAME);
+                bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
                 bringToFrontIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
                 startActivity(bringToFrontIntent);
             } else if (event.equals("Accounts:Exist")) {
                 final String kind = message.getString("kind");
                 final JSONObject response = new JSONObject();
 
                 if ("any".equals(kind)) {
                     response.put("exists", SyncAccounts.syncAccountsExist(getContext()) ||
--- a/mobile/android/base/CrashReporter.java
+++ b/mobile/android/base/CrashReporter.java
@@ -444,17 +444,17 @@ public class CrashReporter extends Activ
         doFinish();
     }
 
     private void doRestart() {
         try {
             String action = "android.intent.action.MAIN";
             Intent intent = new Intent(action);
             intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
-                                AppConstants.BROWSER_INTENT_CLASS_NAME);
+                                AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
             intent.putExtra("didRestart", true);
             Log.i(LOGTAG, intent.toString());
             startActivity(intent);
         } catch (Exception e) {
             Log.e(LOGTAG, "error while trying to restart", e);
         }
     }
 
--- a/mobile/android/base/DataReportingNotification.java
+++ b/mobile/android/base/DataReportingNotification.java
@@ -71,17 +71,17 @@ public class DataReportingNotification {
     /**
      * Launch a notification of the data policy, and record notification time and version.
      */
     private static void notifyDataPolicy(Context context, SharedPreferences sharedPrefs) {
         boolean result = false;
         try {
             // Launch main App to launch Data choices when notification is clicked.
             Intent prefIntent = new Intent(GeckoApp.ACTION_LAUNCH_SETTINGS);
-            prefIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS_NAME);
+            prefIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
 
             GeckoPreferences.setResourceToOpen(prefIntent, "preferences_vendor");
             prefIntent.putExtra(ALERT_NAME_DATAREPORTING_NOTIFICATION, true);
 
             PendingIntent contentIntent = PendingIntent.getActivity(context, 0, prefIntent, PendingIntent.FLAG_UPDATE_CURRENT);
             final Resources resources = context.getResources();
 
             // Create and send notification.
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -842,17 +842,17 @@ public class GeckoAppShell
      * Call this method only on the background thread.
      */
     private static void doCreateShortcut(final String aTitle, final String aURI, final Bitmap aIcon) {
         // The intent to be launched by the shortcut.
         Intent shortcutIntent = new Intent();
         shortcutIntent.setAction(GeckoApp.ACTION_HOMESCREEN_SHORTCUT);
         shortcutIntent.setData(Uri.parse(aURI));
         shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
-                                    AppConstants.BROWSER_INTENT_CLASS_NAME);
+                                    AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
 
         Intent intent = new Intent();
         intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
         intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, getLauncherIcon(aIcon));
 
         if (aTitle != null) {
             intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
         } else {
@@ -1200,17 +1200,17 @@ public class GeckoAppShell
 
         if ("vnd.youtube".equals(scheme) &&
             !hasHandlersForIntent(intent) &&
             !TextUtils.isEmpty(uri.getSchemeSpecificPart())) {
 
             // Return an intent with a URI that will open the YouTube page in the
             // current Fennec instance.
             final Class<?> c;
-            final String browserClassName = AppConstants.BROWSER_INTENT_CLASS_NAME;
+            final String browserClassName = AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS;
             try {
                 c = Class.forName(browserClassName);
             } catch (ClassNotFoundException e) {
                 // This should never occur.
                 Log.wtf(LOGTAG, "Class " + browserClassName + " not found!");
                 return null;
             }
 
--- a/mobile/android/base/GuestSession.java
+++ b/mobile/android/base/GuestSession.java
@@ -36,17 +36,17 @@ public class GuestSession {
             return false;
         }
 
         return profile.locked();
     }
 
     private static PendingIntent getNotificationIntent(Context context) {
         Intent intent = new Intent(NOTIFICATION_INTENT);
-        intent.setClassName(context, AppConstants.BROWSER_INTENT_CLASS_NAME);
+        intent.setClassName(context, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
         return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
     public static void showNotification(Context context) {
         final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
         final Resources res = context.getResources();
         builder.setContentTitle(res.getString(R.string.guest_browsing_notification_title))
                .setContentText(res.getString(R.string.guest_browsing_notification_text))
--- a/mobile/android/base/Restarter.java
+++ b/mobile/android/base/Restarter.java
@@ -26,17 +26,17 @@ public class Restarter extends Service {
             Thread.sleep(100);
         } catch (final InterruptedException e) {
         }
 
         final Intent restartIntent = (Intent)intent.getParcelableExtra(Intent.EXTRA_INTENT);
         restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                      .putExtra("didRestart", true)
                      .setClassName(getApplicationContext(),
-                                   AppConstants.BROWSER_INTENT_CLASS_NAME);
+                                   AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
         startActivity(restartIntent);
         Log.d(LOGTAG, "Launched " + restartIntent);
     }
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         doRestart(intent);
         stopSelf(startId);
--- a/mobile/android/base/background/common/GlobalConstants.java
+++ b/mobile/android/base/background/common/GlobalConstants.java
@@ -7,17 +7,17 @@ package org.mozilla.gecko.background.com
 import org.mozilla.gecko.AppConstants;
 
 /**
  * Preprocessed class for storing preprocessed values common to all
  * Android services.
  */
 public class GlobalConstants {
   public static final String BROWSER_INTENT_PACKAGE = AppConstants.ANDROID_PACKAGE_NAME;
-  public static final String BROWSER_INTENT_CLASS = AppConstants.BROWSER_INTENT_CLASS_NAME;
+  public static final String BROWSER_INTENT_CLASS = AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS;
 
   /**
    * Bug 800244: this signing-level permission protects broadcast intents that
    * should be received only by the Firefox versions with the given Android
    * package name.
    */
   public static final String PER_ANDROID_PACKAGE_PERMISSION = AppConstants.ANDROID_PACKAGE_NAME + ".permission.PER_ANDROID_PACKAGE";
 
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -748,16 +748,17 @@ for var in ('MOZ_ANDROID_ANR_REPORTER', 
 for var in ('MOZ_UPDATER', 'MOZ_PKG_SPECIAL'):
     if CONFIG[var]:
         DEFINES[var] = CONFIG[var]
 
 for var in ('ANDROID_PACKAGE_NAME', 'ANDROID_CPU_ARCH',
             'GRE_MILESTONE', 'MOZ_APP_BASENAME', 'MOZ_MOZILLA_API_KEY',
             'MOZ_APP_DISPLAYNAME', 'MOZ_APP_ID', 'MOZ_APP_NAME',
             'MOZ_APP_VENDOR', 'MOZ_APP_VERSION', 'MOZ_CHILD_PROCESS_NAME',
+            'MOZ_ANDROID_APPLICATION_CLASS', 'MOZ_ANDROID_BROWSER_INTENT_CLASS', 'MOZ_ANDROID_SEARCH_INTENT_CLASS',
             'MOZ_CRASHREPORTER', 'MOZ_UPDATE_CHANNEL', 'OMNIJAR_NAME',
             'OS_TARGET', 'TARGET_XPCOM_ABI'):
     DEFINES[var] = CONFIG[var]
 
 # Mangle our package name to avoid Bug 750548.
 DEFINES['MANGLED_ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME'].replace('fennec', 'f3nn3c')
 DEFINES['MOZ_APP_ABI'] = CONFIG['TARGET_XPCOM_ABI']
 if not CONFIG['COMPILE_ENVIRONMENT']:
--- a/mobile/android/base/overlays/ui/ShareDialog.java
+++ b/mobile/android/base/overlays/ui/ShareDialog.java
@@ -403,17 +403,17 @@ public class ShareDialog extends Locales
 
         Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.SHARE_OVERLAY, "bookmark");
     }
 
     public void launchBrowser() {
         try {
             // This can launch in the guest profile. Sorry.
             final Intent i = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
-            i.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS_NAME);
+            i.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
             startActivity(i);
         } catch (URISyntaxException e) {
             // Nothing much we can do.
         } finally {
             slideOut();
         }
     }
 
--- a/mobile/android/base/tabqueue/TabQueueDispatcher.java
+++ b/mobile/android/base/tabqueue/TabQueueDispatcher.java
@@ -63,17 +63,17 @@ public class TabQueueDispatcher extends 
         startService(intent);
         finish();
     }
 
     /**
      * Start fennec with the supplied intent.
      */
     private void loadNormally(Intent intent) {
-        intent.setClassName(getApplicationContext(), AppConstants.BROWSER_INTENT_CLASS_NAME);
+        intent.setClassName(getApplicationContext(), AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
         startActivity(intent);
         finish();
     }
 
     /**
      * Abort as we were started with no URL.
      * @param dataString
      */
--- a/mobile/android/base/tabqueue/TabQueueHelper.java
+++ b/mobile/android/base/tabqueue/TabQueueHelper.java
@@ -150,17 +150,17 @@ public class TabQueueHelper {
      *
      * @param context
      * @param tabsQueued
      */
     public static void showNotification(final Context context, final int tabsQueued) {
         ThreadUtils.assertNotOnUiThread();
 
         Intent resultIntent = new Intent();
-        resultIntent.setClassName(context, AppConstants.BROWSER_INTENT_CLASS_NAME);
+        resultIntent.setClassName(context, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
         resultIntent.setAction(TabQueueHelper.LOAD_URLS_ACTION);
 
         PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, resultIntent, PendingIntent.FLAG_CANCEL_CURRENT);
 
         final String text;
         final Resources resources = context.getResources();
         if (tabsQueued == 1) {
             text = resources.getString(R.string.tab_queue_notification_text_singular);
--- a/mobile/android/base/tabqueue/TabQueueService.java
+++ b/mobile/android/base/tabqueue/TabQueueService.java
@@ -185,17 +185,17 @@ public class TabQueueService extends Ser
 
         tabQueueHandler.postDelayed(stopServiceRunnable, TOAST_TIMEOUT);
 
         return START_REDELIVER_INTENT;
     }
 
     private void openNow(Intent intent) {
         Intent forwardIntent = new Intent(intent);
-        forwardIntent.setClassName(getApplicationContext(), AppConstants.BROWSER_INTENT_CLASS_NAME);
+        forwardIntent.setClassName(getApplicationContext(), AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
         forwardIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         startActivity(forwardIntent);
 
         TabQueueHelper.removeNotification(getApplicationContext());
 
         GeckoSharedPrefs.forApp(getApplicationContext()).edit().remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE)
                                                                .remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME)
                                                                .apply();
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -11,16 +11,22 @@ MOZ_APP_UA_NAME=Firefox
 MOZ_BRANDING_DIRECTORY=mobile/android/branding/unofficial
 MOZ_OFFICIAL_BRANDING_DIRECTORY=mobile/android/branding/official
 # MOZ_APP_DISPLAYNAME is set by branding/configure.sh
 
 # We support Android SDK version 9 and up by default.
 # See the --enable-android-min-sdk and --enable-android-max-sdk arguments in configure.in.
 MOZ_ANDROID_MIN_SDK_VERSION=9
 
+# There are several entry points into the Firefox application.  These are the names of some of the classes that are
+# listed in the Android manifest.  They are specified in here to avoid hard-coding them in source code files.
+MOZ_ANDROID_APPLICATION_CLASS=org.mozilla.gecko.GeckoApplication
+MOZ_ANDROID_BROWSER_INTENT_CLASS=org.mozilla.gecko.BrowserApp
+MOZ_ANDROID_SEARCH_INTENT_CLASS=org.mozilla.search.SearchActivity
+
 MOZ_SAFE_BROWSING=1
 
 MOZ_NO_SMART_CARDS=1
 
 # Enable getUserMedia
 MOZ_MEDIA_NAVIGATOR=1
 
 # Enable NFC permission
--- a/mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
+++ b/mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
@@ -125,17 +125,17 @@ public class PostSearchFragment extends 
 
             try {
                 // If the url URI does not have an intent scheme, the intent data will be the entire
                 // URI and its action will be ACTION_VIEW.
                 final Intent i = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
 
                 // If the intent URI didn't specify a package, open this in Fennec.
                 if (i.getPackage() == null) {
-                    i.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS_NAME);
+                    i.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
                     Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL,
                             TelemetryContract.Method.CONTENT, "search-result");
                 } else {
                     Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH,
                             TelemetryContract.Method.INTENT, "search-result");
                 }
 
                 startActivity(i);
--- a/mobile/android/search/java/org/mozilla/search/SearchWidget.java
+++ b/mobile/android/search/java/org/mozilla/search/SearchWidget.java
@@ -63,33 +63,33 @@ public class SearchWidget extends AppWid
     @Override
     public void onReceive(final Context context, final Intent intent) {
         // This will hold the intent to redispatch.
         final Intent redirect;
         switch (intent.getAction()) {
             case ACTION_LAUNCH_BROWSER:
                 redirect = buildRedirectIntent(Intent.ACTION_MAIN,
                                                AppConstants.ANDROID_PACKAGE_NAME,
-                                               AppConstants.BROWSER_INTENT_CLASS_NAME,
+                                               AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS,
                                                intent);
                 Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH,
                                       TelemetryContract.Method.WIDGET, "browser");
                 break;
             case ACTION_LAUNCH_NEW_TAB:
                 redirect = buildRedirectIntent(Intent.ACTION_VIEW,
                                                AppConstants.ANDROID_PACKAGE_NAME,
-                                               AppConstants.BROWSER_INTENT_CLASS_NAME,
+                                               AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS,
                                                intent);
                 Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH,
                                       TelemetryContract.Method.WIDGET, "new-tab");
                 break;
             case ACTION_LAUNCH_SEARCH:
                 redirect = buildRedirectIntent(Intent.ACTION_VIEW,
                                                AppConstants.ANDROID_PACKAGE_NAME,
-                                               AppConstants.SEARCH_INTENT_CLASS_NAME,
+                                               AppConstants.MOZ_ANDROID_SEARCH_INTENT_CLASS,
                                                intent);
                 Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH,
                                       TelemetryContract.Method.WIDGET, "search");
                 break;
             default:
                 redirect = null;
         }
 
--- a/mobile/android/search/manifests/SearchAndroidManifest_activities.xml.in
+++ b/mobile/android/search/manifests/SearchAndroidManifest_activities.xml.in
@@ -1,10 +1,10 @@
         <activity
-            android:name="org.mozilla.search.SearchActivity"
+            android:name="@MOZ_ANDROID_SEARCH_INTENT_CLASS@"
             android:launchMode="singleTop"
             android:icon="@drawable/search_launcher"
             android:label="@string/search_app_name"
             android:configChanges="orientation|screenSize"
             android:theme="@style/AppTheme">
             <intent-filter>
                 <action android:name="android.intent.action.ASSIST"/>
 
@@ -35,14 +35,14 @@
 
             <meta-data android:name="android.appwidget.provider" android:resource="@xml/search_widget_info" />
         </receiver>
 
         <activity
             android:name="org.mozilla.search.SearchPreferenceActivity"
             android:logo="@drawable/search_launcher"
             android:label="@string/search_pref_title"
-            android:parentActivityName="org.mozilla.search.SearchActivity"
+            android:parentActivityName="@MOZ_ANDROID_SEARCH_INTENT_CLASS@"
             android:theme="@style/SettingsTheme" >
             <meta-data
                 android:name="android.support.PARENT_ACTIVITY"
-                android:value="org.mozilla.search.SearchActivity"/>
+                android:value="@MOZ_ANDROID_SEARCH_INTENT_CLASS@"/>
         </activity>
--- a/mobile/android/tests/browser/junit3/src/BrowserTestCase.java
+++ b/mobile/android/tests/browser/junit3/src/BrowserTestCase.java
@@ -15,27 +15,27 @@ import android.test.ActivityInstrumentat
 @SuppressWarnings("unchecked")
 public class BrowserTestCase extends ActivityInstrumentationTestCase2<Activity> {
     @SuppressWarnings("unused")
     private static String LOG_TAG = "BrowserTestCase";
 
     /**
      * The Java Class instance that launches the browser.
      * <p>
-     * This should always agree with {@link AppConstants#BROWSER_INTENT_CLASS_NAME}.
+     * This should always agree with {@link AppConstants#MOZ_ANDROID_BROWSER_INTENT_CLASS}.
      */
     public static final Class<? extends Activity> BROWSER_INTENT_CLASS;
 
     // Use reflection here so we don't have to either (a) preprocess this
     // file, or (b) get access to Robocop's TestConstants class from these
     // instrumentation tests.
     static {
         Class<? extends Activity> cl;
         try {
-            cl = (Class<? extends Activity>) Class.forName(AppConstants.BROWSER_INTENT_CLASS_NAME);
+            cl = (Class<? extends Activity>) Class.forName(AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
         } catch (ClassNotFoundException e) {
             // Oh well.
             cl = Activity.class;
         }
         BROWSER_INTENT_CLASS = cl;
     }
 
     public BrowserTestCase() {
--- a/mobile/android/tests/browser/robocop/BaseRobocopTest.java
+++ b/mobile/android/tests/browser/robocop/BaseRobocopTest.java
@@ -38,25 +38,25 @@ public abstract class BaseRobocopTest ex
         TALOS
     }
 
     private static final String DEFAULT_ROOT_PATH = "/mnt/sdcard/tests";
 
     /**
      * The Java Class instance that launches the browser.
      * <p>
-     * This should always agree with {@link AppConstants#BROWSER_INTENT_CLASS_NAME}.
+     * This should always agree with {@link AppConstants#MOZ_ANDROID_BROWSER_INTENT_CLASS}.
      */
     public static final Class<? extends Activity> BROWSER_INTENT_CLASS;
 
     // Use reflection here so we don't have to preprocess this file.
     static {
         Class<? extends Activity> cl;
         try {
-            cl = (Class<? extends Activity>) Class.forName(AppConstants.BROWSER_INTENT_CLASS_NAME);
+            cl = (Class<? extends Activity>) Class.forName(AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
         } catch (ClassNotFoundException e) {
             // Oh well.
             cl = Activity.class;
         }
         BROWSER_INTENT_CLASS = cl;
     }
 
     protected Assert mAsserter;
--- a/netwerk/dns/effective_tld_names.dat
+++ b/netwerk/dns/effective_tld_names.dat
@@ -821,16 +821,17 @@ edu.es
 et
 com.et
 gov.et
 org.et
 edu.et
 biz.et
 name.et
 info.et
+net.et
 
 // eu : http://en.wikipedia.org/wiki/.eu
 eu
 
 // fi : http://en.wikipedia.org/wiki/.fi
 fi
 // aland.fi : http://en.wikipedia.org/wiki/.ax
 // This domain is being phased out in favor of .ax. As there are still many
@@ -3571,16 +3572,17 @@ org.lk
 edu.lk
 ngo.lk
 soc.lk
 web.lk
 ltd.lk
 assn.lk
 grp.lk
 hotel.lk
+ac.lk
 
 // lr : http://psg.com/dns/lr/lr.txt
 // Submitted by registry <randy@psg.com> 2008-06-17
 lr
 com.lr
 edu.lr
 gov.lr
 org.lr
@@ -6659,189 +6661,220 @@ net.ws
 org.ws
 gov.ws
 edu.ws
 
 // yt : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf
 yt
 
 // IDN ccTLDs
-// Please sort by ISO 3166 ccTLD, then punicode string
-// when submitting patches and follow this format:
-// <Punicode> ("<english word>" <language>) : <ISO 3166 ccTLD>
-// [optional sponsoring org]
-// <URL>
-
-// xn--mgbaam7a8h ("Emerat" Arabic) : AE
+// When submitting patches, please maintain a sort by ISO 3166 ccTLD, then
+// U-label, and follow this format:
+// // A-Label ("<Latin renderings>", <language name>[, variant info]) : <ISO 3166 ccTLD>
+// // [sponsoring org]
+// U-Label
+
+// xn--mgbaam7a8h ("Emerat", Arabic) : AE
 // http://nic.ae/english/arabicdomain/rules.jsp
 امارات
 
-// xn--54b7fta0cc ("Bangla" Bangla) : BD
+// xn--y9a3aq ("hye", Armenian) : AM
+// ISOC AM (operated by .am Registry)
+հայ
+
+// xn--54b7fta0cc ("Bangla", Bangla) : BD
 বাংলা
 
-// xn--fiqs8s ("China" Chinese-Han-Simplified <.Zhongguo>) : CN
+// xn--90ais ("bel", Belarusian/Russian Cyrillic) : BY
+// Operated by .by registry
+бел
+
+// xn--fiqs8s ("Zhongguo/China", Chinese, Simplified) : CN
 // CNNIC
 // http://cnnic.cn/html/Dir/2005/10/11/3218.htm
 中国
 
-// xn--fiqz9s ("China" Chinese-Han-Traditional <.Zhongguo>) : CN
+// xn--fiqz9s ("Zhongguo/China", Chinese, Traditional) : CN
 // CNNIC
 // http://cnnic.cn/html/Dir/2005/10/11/3218.htm
 中國
 
-// xn--lgbbat1ad8j ("Algeria / Al Jazair" Arabic) : DZ
+// xn--lgbbat1ad8j ("Algeria/Al Jazair", Arabic) : DZ
 الجزائر
 
-// xn--wgbh1c ("Egypt" Arabic .masr) : EG
+// xn--wgbh1c ("Egypt/Masr", Arabic) : EG
 // http://www.dotmasr.eg/
 مصر
 
-// xn--node ("ge" Georgian (Mkhedruli)) : GE
+// xn--node ("ge", Georgian Mkhedruli) : GE
 გე
 
-// xn--j6w193g ("Hong Kong" Chinese-Han) : HK
+// xn--qxam ("el", Greek) : GR
+// Hellenic Ministry of Infrastructure, Transport, and Networks
+ελ
+
+// xn--j6w193g ("Hong Kong", Chinese) : HK
 // https://www2.hkirc.hk/register/rules.jsp
 香港
 
-// xn--h2brj9c ("Bharat" Devanagari) : IN
+// xn--h2brj9c ("Bharat", Devanagari) : IN
 // India
 भारत
 
-// xn--mgbbh1a71e ("Bharat" Arabic) : IN
+// xn--mgbbh1a71e ("Bharat", Arabic) : IN
 // India
 بھارت
 
-// xn--fpcrj9c3d ("Bharat" Telugu) : IN
+// xn--fpcrj9c3d ("Bharat", Telugu) : IN
 // India
 భారత్
 
-// xn--gecrj9c ("Bharat" Gujarati) : IN
+// xn--gecrj9c ("Bharat", Gujarati) : IN
 // India
 ભારત
 
-// xn--s9brj9c ("Bharat" Gurmukhi) : IN
+// xn--s9brj9c ("Bharat", Gurmukhi) : IN
 // India
 ਭਾਰਤ
 
-// xn--45brj9c ("Bharat" Bengali) : IN
+// xn--45brj9c ("Bharat", Bengali) : IN
 // India
 ভারত
 
-// xn--xkc2dl3a5ee0h ("India" Tamil) : IN
+// xn--xkc2dl3a5ee0h ("India", Tamil) : IN
 // India
 இந்தியா
 
-// xn--mgba3a4f16a ("Iran" Persian) : IR
+// xn--mgba3a4f16a ("Iran", Persian) : IR
 ایران
 
-// xn--mgba3a4fra ("Iran" Arabic) : IR
+// xn--mgba3a4fra ("Iran", Arabic) : IR
 ايران
 
-// xn--mgbayh7gpa ("al-Ordon" Arabic) : JO
+// xn--mgbtx2b ("Iraq", Arabic) : IQ
+// Communications and Media Commission
+عراق
+
+// xn--mgbayh7gpa ("al-Ordon", Arabic) : JO
 // National Information Technology Center (NITC)
 // Royal Scientific Society, Al-Jubeiha
 الاردن
 
-// xn--3e0b707e ("Republic of Korea" Hangul) : KR
+// xn--3e0b707e ("Republic of Korea", Hangul) : KR
 한국
 
-// xn--80ao21a ("Kaz" Kazakh) : KZ
+// xn--80ao21a ("Kaz", Kazakh) : KZ
 қаз
 
-// xn--fzc2c9e2c ("Lanka" Sinhalese-Sinhala) : LK
+// xn--fzc2c9e2c ("Lanka", Sinhalese-Sinhala) : LK
 // http://nic.lk
 ලංකා
 
-// xn--xkc2al3hye2a ("Ilangai" Tamil) : LK
+// xn--xkc2al3hye2a ("Ilangai", Tamil) : LK
 // http://nic.lk
 இலங்கை
 
-// xn--mgbc0a9azcg ("Morocco / al-Maghrib" Arabic) : MA
+// xn--mgbc0a9azcg ("Morocco/al-Maghrib", Arabic) : MA
 المغرب
 
-// xn--l1acc ("mon" Mongolian) : MN
+// xn--d1alf ("mkd", Macedonian) : MK
+// MARnet
+мкд
+
+// xn--l1acc ("mon", Mongolian) : MN
 мон
 
-// xn--mgbx4cd0ab ("Malaysia" Malay) : MY
+// xn--mix891f ("Macao", Chinese, Traditional) : MO
+// MONIC / HNET Asia (Registry Operator for .mo)
+澳門
+
+// xn--mix082f ("Macao", Chinese, Simplified) : MO
+澳门
+
+// xn--mgbx4cd0ab ("Malaysia", Malay) : MY
 مليسيا
 
-// xn--mgb9awbf ("Oman" Arabic) : OM
+// xn--mgb9awbf ("Oman", Arabic) : OM
 عمان
 
-// xn--ygbi2ammx ("Falasteen" Arabic) : PS
+// xn--ygbi2ammx ("Falasteen", Arabic) : PS
 // The Palestinian National Internet Naming Authority (PNINA)
 // http://www.pnina.ps
 فلسطين
 
-// xn--90a3ac ("srb" Cyrillic) : RS
+// xn--90a3ac ("srb", Cyrillic) : RS
 // http://www.rnids.rs/en/the-.срб-domain
 срб
 пр.срб
 орг.срб
 обр.срб
 од.срб
 упр.срб
 ак.срб
 
-// xn--p1ai ("rf" Russian-Cyrillic) : RU
+// xn--p1ai ("rf", Russian-Cyrillic) : RU
 // http://www.cctld.ru/en/docs/rulesrf.php
 рф
 
-// xn--wgbl6a ("Qatar" Arabic) : QA
+// xn--wgbl6a ("Qatar", Arabic) : QA
 // http://www.ict.gov.qa/
 قطر
 
-// xn--mgberp4a5d4ar ("AlSaudiah" Arabic) : SA
+// xn--mgberp4a5d4ar ("AlSaudiah", Arabic) : SA
 // http://www.nic.net.sa/
 السعودية
 
-// xn--mgberp4a5d4a87g ("AlSaudiah" Arabic) variant : SA
+// xn--mgberp4a5d4a87g ("AlSaudiah", Arabic, variant)  : SA
 السعودیة
 
-// xn--mgbqly7c0a67fbc ("AlSaudiah" Arabic) variant : SA
+// xn--mgbqly7c0a67fbc ("AlSaudiah", Arabic, variant) : SA
 السعودیۃ
 
-// xn--mgbqly7cvafr ("AlSaudiah" Arabic) variant : SA
+// xn--mgbqly7cvafr ("AlSaudiah", Arabic, variant) : SA
 السعوديه
 
-// xn--ogbpf8fl ("Syria" Arabic) : SY
+// xn--mgbpl2fh ("sudan", Arabic) : SD
+// Operated by .sd registry
+سودان
+
+// xn--yfro4i67o Singapore ("Singapore", Chinese) : SG
+新加坡
+
+// xn--clchc0ea0b2g2a9gcd ("Singapore", Tamil) : SG
+சிங்கப்பூர்
+
+// xn--ogbpf8fl ("Syria", Arabic) : SY
 سورية
 
-// xn--mgbtf8fl ("Syria" Arabic) variant : SY
+// xn--mgbtf8fl ("Syria", Arabic, variant) : SY
 سوريا
 
-// xn--yfro4i67o Singapore ("Singapore" Chinese-Han) : SG
-新加坡
-
-// xn--clchc0ea0b2g2a9gcd ("Singapore" Tamil) : SG
-சிங்கப்பூர்
-
-// xn--o3cw4h ("Thai" Thai) : TH
+// xn--o3cw4h ("Thai", Thai) : TH
 // http://www.thnic.co.th
 ไทย
 
-// xn--pgbs0dh ("Tunis") : TN
+// xn--pgbs0dh ("Tunisia", Arabic) : TN
 // http://nic.tn
 تونس
 
-// xn--kpry57d ("Taiwan" Chinese-Han-Traditional) : TW
+// xn--kpry57d ("Taiwan", Chinese, Traditional) : TW
 // http://www.twnic.net/english/dn/dn_07a.htm
 台灣
 
-// xn--kprw13d ("Taiwan" Chinese-Han-Simplified) : TW
+// xn--kprw13d ("Taiwan", Chinese, Simplified) : TW
 // http://www.twnic.net/english/dn/dn_07a.htm
 台湾
 
-// xn--nnx388a ("Taiwan") variant : TW
+// xn--nnx388a ("Taiwan", Chinese, variant) : TW
 臺灣
 
-// xn--j1amh ("ukr" Cyrillic) : UA
+// xn--j1amh ("ukr", Cyrillic) : UA
 укр
 
-// xn--mgb2ddes ("AlYemen" Arabic) : YE
+// xn--mgb2ddes ("AlYemen", Arabic) : YE
 اليمن
 
 // xxx : http://icmregistry.com
 xxx
 
 // ye : http://www.y.net.ye/services/domain_name.htm
 *.ye
 
@@ -6850,17 +6883,17 @@ xxx
 
 // zm : http://en.wikipedia.org/wiki/.zm
 *.zm
 
 // zw : http://en.wikipedia.org/wiki/.zw
 *.zw
 
 
-// List of new gTLDs imported from https://newgtlds.icann.org/newgtlds.csv on 2015-04-07T06:02:08Z
+// List of new gTLDs imported from https://newgtlds.icann.org/newgtlds.csv on 2015-05-06T09:31:08Z
 
 // aaa : 2015-02-26 American Automobile Association, Inc.
 aaa
 
 // abb : 2014-10-24 ABB Ltd
 abb
 
 // abbott : 2014-07-24 Abbott Laboratories, Inc.
@@ -6903,28 +6936,34 @@ aeg
 afl
 
 // africa : 2014-03-24 ZA Central Registry NPC trading as Registry.Africa
 africa
 
 // africamagic : 2015-03-05 Electronic Media Network (Pty) Ltd
 africamagic
 
+// agakhan : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation)
+agakhan
+
 // agency : 2013-11-14 Steel Falls, LLC
 agency
 
 // aig : 2014-12-18 American International Group, Inc.
 aig
 
 // airforce : 2014-03-06 United TLD Holdco Ltd.
 airforce
 
 // airtel : 2014-10-24 Bharti Airtel Limited
 airtel
 
+// akdn : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation)
+akdn
+
 // alibaba : 2015-01-15 Alibaba Group Holding Limited
 alibaba
 
 // alipay : 2015-01-15 Alibaba Group Holding Limited
 alipay
 
 // allfinanz : 2014-07-03 Allfinanz Deutsche Vermögensberatung Aktiengesellschaft
 allfinanz
@@ -6987,16 +7026,19 @@ autos
 avianca
 
 // axa : 2013-12-19 AXA SA
 axa
 
 // azure : 2014-12-18 Microsoft Corporation
 azure
 
+// baby : 2015-04-09 Johnson & Johnson Services, Inc.
+baby
+
 // baidu : 2015-01-08 Baidu, Inc.
 baidu
 
 // band : 2014-06-12
 band
 
 // bank : 2014-09-25 fTLD Registry Services LLC
 bank
@@ -7218,34 +7260,43 @@ casino
 catering
 
 // cba : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA
 cba
 
 // cbn : 2014-08-22 The Christian Broadcasting Network, Inc.
 cbn
 
+// ceb : 2015-04-09 The Corporate Executive Board Company
+ceb
+
 // center : 2013-11-07 Tin Mill, LLC
 center
 
 // ceo : 2013-11-07 CEOTLD Pty Ltd
 ceo
 
-// cern : 2014-06-05 European Organization for Nuclear Research (\
+// cern : 2014-06-05 European Organization for Nuclear Research ("CERN")
 cern
 
 // cfa : 2014-08-28 CFA Institute
 cfa
 
 // cfd : 2014-12-11 IG Group Holdings PLC
 cfd
 
+// chanel : 2015-04-09 Chanel International B.V.
+chanel
+
 // channel : 2014-05-08 Charleston Road Registry Inc.
 channel
 
+// chase : 2015-04-30 JPMorgan Chase & Co.
+chase
+
 // chat : 2014-12-04 Sand Fields, LLC
 chat
 
 // cheap : 2013-11-14 Sand Cover, LLC
 cheap
 
 // chloe : 2014-10-16 Richemont DNS Inc.
 chloe
@@ -7287,16 +7338,19 @@ cleaning
 click
 
 // clinic : 2014-03-20 Goose Park, LLC
 clinic
 
 // clothing : 2013-08-27 Steel Lake, LLC
 clothing
 
+// cloud : 2015-04-16 ARUBA S.p.A.
+cloud
+
 // club : 2013-11-08 .CLUB DOMAINS, LLC
 club
 
 // coach : 2014-10-09 Koko Island, LLC
 coach
 
 // codes : 2013-10-31 Puff Willow, LLC
 codes
@@ -7713,17 +7767,17 @@ gallery
 gallup
 
 // garden : 2014-06-26 Top Level Domain Holdings Limited
 garden
 
 // gbiz : 2014-07-17 Charleston Road Registry Inc.
 gbiz
 
-// gdn : 2014-07-31 Joint Stock Company \
+// gdn : 2014-07-31 Joint Stock Company "Navigation-information systems"
 gdn
 
 // gea : 2014-12-04 GEA Group Aktiengesellschaft
 gea
 
 // gent : 2014-01-23 COMBELL GROUP NV/SA
 gent
 
@@ -7983,16 +8037,19 @@ iwc
 jaguar
 
 // java : 2014-06-19 Oracle Corporation
 java
 
 // jcb : 2014-11-20 JCB Co., Ltd.
 jcb
 
+// jcp : 2015-04-23 JCP Media, Inc.
+jcp
+
 // jetzt : 2014-01-09 New TLD Company AB
 jetzt
 
 // jewelry : 2015-03-05 Wild Bloom, LLC
 jewelry
 
 // jio : 2015-04-02 Affinity Names, Inc.
 jio
@@ -8010,28 +8067,40 @@ jmp
 joburg
 
 // jot : 2014-12-18 Amazon EU S.à r.l.
 jot
 
 // joy : 2014-12-18 Amazon EU S.à r.l.
 joy
 
+// jpmorgan : 2015-04-30 JPMorgan Chase & Co.
+jpmorgan
+
 // jprs : 2014-09-18 Japan Registry Services Co., Ltd.
 jprs
 
 // juegos : 2014-03-20 Uniregistry, Corp.
 juegos
 
 // kaufen : 2013-11-07 United TLD Holdco Ltd.
 kaufen
 
 // kddi : 2014-09-12 KDDI CORPORATION
 kddi
 
+// kerryhotels : 2015-04-30 Kerry Trading Co. Limited
+kerryhotels
+
+// kerrylogistics : 2015-04-09 Kerry Trading Co. Limited
+kerrylogistics
+
+// kerryproperties : 2015-04-09 Kerry Trading Co. Limited
+kerryproperties
+
 // kfh : 2014-12-04 Kuwait Finance House
 kfh
 
 // kim : 2013-09-23 Afilias Limited
 kim
 
 // kinder : 2014-11-07 Ferrero Trading Lux S.A.
 kinder
@@ -8043,25 +8112,31 @@ kitchen
 kiwi
 
 // koeln : 2014-01-09 NetCologne Gesellschaft für Telekommunikation mbH
 koeln
 
 // komatsu : 2015-01-08 Komatsu Ltd.
 komatsu
 
+// kpmg : 2015-04-23 KPMG International Cooperative (KPMG International Genossenschaft)
+kpmg
+
 // kpn : 2015-01-08 Koninklijke KPN N.V.
 kpn
 
 // krd : 2013-12-05 KRG Department of Information Technology
 krd
 
 // kred : 2013-12-19 KredTLD Pty Ltd
 kred
 
+// kuokgroup : 2015-04-09 Kerry Trading Co. Limited
+kuokgroup
+
 // kyknet : 2015-03-05 Electronic Media Network (Pty) Ltd
 kyknet
 
 // kyoto : 2014-11-07 Academic Institution: Kyoto Jyoho Gakuen
 kyoto
 
 // lacaixa : 2014-01-09 CAIXA D'ESTALVIS I PENSIONS DE BARCELONA
 lacaixa
@@ -8085,28 +8160,31 @@ lat
 latrobe
 
 // law : 2015-01-22 Minds + Machines Group Limited
 law
 
 // lawyer : 2014-03-20
 lawyer
 
-// lds : 2014-03-20 IRI Domain Management, LLC (\
+// lds : 2014-03-20 IRI Domain Management, LLC ("Applicant")
 lds
 
 // lease : 2014-03-06 Victor Trail, LLC
 lease
 
 // leclerc : 2014-08-07 A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc
 leclerc
 
 // legal : 2014-10-16 Blue Falls, LLC
 legal
 
+// lexus : 2015-04-23 TOYOTA MOTOR CORPORATION
+lexus
+
 // lgbt : 2014-05-08 Afilias Limited
 lgbt
 
 // liaison : 2014-10-02 Liaison Technologies, Incorporated
 liaison
 
 // lidl : 2014-09-18 Schwarz Domains und Services GmbH & Co. KG
 lidl
@@ -8247,16 +8325,19 @@ meo
 miami
 
 // microsoft : 2014-12-18 Microsoft Corporation
 microsoft
 
 // mini : 2014-01-09 Bayerische Motoren Werke Aktiengesellschaft
 mini
 
+// mls : 2015-04-23 The Canadian Real Estate Association
+mls
+
 // mma : 2014-11-07 MMA IARD
 mma
 
 // mnet : 2015-03-05 Electronic Media Network (Pty) Ltd
 mnet
 
 // mobily : 2014-12-18 GreenTech Consultancy Company W.L.L.
 mobily
@@ -8265,26 +8346,29 @@ mobily
 moda
 
 // moe : 2013-11-13 Interlink Co., Ltd.
 moe
 
 // moi : 2014-12-18 Amazon EU S.à r.l.
 moi
 
+// mom : 2015-04-16 Uniregistry, Corp.
+mom
+
 // monash : 2013-09-30 Monash University
 monash
 
 // money : 2014-10-16 Outer McCook, LLC
 money
 
 // montblanc : 2014-06-23 Richemont DNS Inc.
 montblanc
 
-// mormon : 2013-12-05 IRI Domain Management, LLC (\
+// mormon : 2013-12-05 IRI Domain Management, LLC ("Applicant")
 mormon
 
 // mortgage : 2014-03-20
 mortgage
 
 // moscow : 2013-12-19 Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID)
 moscow
 
@@ -8388,16 +8472,19 @@ nrw
 ntt
 
 // nyc : 2014-01-23 The City of New York by and through the New York City Department of Information Technology & Telecommunications
 nyc
 
 // obi : 2014-09-25 OBI Group Holding SE & Co. KGaA
 obi
 
+// observer : 2015-04-30 Guardian News and Media Limited
+observer
+
 // office : 2015-03-12 Microsoft Corporation
 office
 
 // okinawa : 2013-12-05 BusinessRalliart Inc.
 okinawa
 
 // omega : 2015-01-08 The Swatch Group Ltd
 omega
@@ -8550,16 +8637,19 @@ prof
 promo
 
 // properties : 2013-12-05 Big Pass, LLC
 properties
 
 // property : 2014-05-22 Uniregistry, Corp.
 property
 
+// protection : 2015-04-23 Symantec Corporation
+protection
+
 // pub : 2013-12-12 United TLD Holdco Ltd.
 pub
 
 // qpon : 2013-11-14 dotCOOL, Inc.
 qpon
 
 // quebec : 2013-12-19 PointQuébec Inc
 quebec
@@ -8727,17 +8817,17 @@ saxo
 sbi
 
 // sbs : 2014-11-07 SPECIAL BROADCASTING SERVICE CORPORATION
 sbs
 
 // sca : 2014-03-13 SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ)
 sca
 
-// scb : 2014-02-20 The Siam Commercial Bank Public Company Limited (\
+// scb : 2014-02-20 The Siam Commercial Bank Public Company Limited ("SCB")
 scb
 
 // schmidt : 2014-04-03 SALM S.A.S.
 schmidt
 
 // scholarships : 2014-04-24 Scholarships.com, LLC
 scholarships
 
@@ -8778,16 +8868,19 @@ sew
 sex
 
 // sexy : 2013-09-11 Uniregistry, Corp.
 sexy
 
 // sharp : 2014-05-01 Sharp Corporation
 sharp
 
+// shaw : 2015-04-23 Shaw Cablesystems G.P.
+shaw
+
 // shia : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
 shia
 
 // shiksha : 2013-11-14 Afilias Limited
 shiksha
 
 // shoes : 2013-10-02 Binky Galley, LLC
 shoes
@@ -8805,16 +8898,19 @@ shriram
 sina
 
 // singles : 2013-08-27 Fern Madison, LLC
 singles
 
 // site : 2015-01-15 DotSite Inc.
 site
 
+// ski : 2015-04-09 STARTING DOT LIMITED
+ski
+
 // skin : 2015-01-15 L'Oréal
 skin
 
 // sky : 2014-06-19 Sky IP International Ltd, a company incorporated in England and Wales, operating via its registered Swiss branch
 sky
 
 // skype : 2014-12-18 Microsoft Corporation
 skype
@@ -8886,16 +8982,19 @@ stc
 stcgroup
 
 // stockholm : 2014-12-18 Stockholms kommun
 stockholm
 
 // storage : 2014-12-22 Self Storage Company LLC
 storage
 
+// store : 2015-04-09 DotStore Inc.
+store
+
 // studio : 2015-02-11 Spring Goodbye, LLC
 studio
 
 // study : 2014-12-11 OPEN UNIVERSITIES AUSTRALIA PTY LTD
 study
 
 // style : 2014-12-04 Binky Moon, LLC
 style
@@ -8940,23 +9039,26 @@ symantec
 systems
 
 // tab : 2014-12-04 Tabcorp Holdings Limited
 tab
 
 // taipei : 2014-07-10 Taipei City Government
 taipei
 
+// talk : 2015-04-09 Amazon EU S.à r.l.
+talk
+
 // taobao : 2015-01-15 Alibaba Group Holding Limited
 taobao
 
 // tatamotors : 2015-03-12 Tata Motors Ltd
 tatamotors
 
-// tatar : 2014-04-24 Limited Liability Company \
+// tatar : 2014-04-24 Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic"
 tatar
 
 // tattoo : 2013-08-30 Uniregistry, Corp.
 tattoo
 
 // tax : 2014-03-20 Storm Orchard, LLC
 tax
 
@@ -8988,16 +9090,19 @@ temasek
 tennis
 
 // thd : 2015-04-02 Homer TLC, Inc.
 thd
 
 // theater : 2015-03-19 Blue Tigers, LLC
 theater
 
+// theguardian : 2015-04-30 Guardian News and Media Limited
+theguardian
+
 // tickets : 2015-02-05 Accent Media Limited
 tickets
 
 // tienda : 2013-11-14 Victor Manor, LLC
 tienda
 
 // tiffany : 2015-01-30 Tiffany and Company
 tiffany
@@ -9033,16 +9138,19 @@ toray
 toshiba
 
 // tours : 2015-01-22 Sugar Station, LLC
 tours
 
 // town : 2014-03-06 Koko Moon, LLC
 town
 
+// toyota : 2015-04-23 TOYOTA MOTOR CORPORATION
+toyota
+
 // toys : 2014-03-06 Pioneer Orchard, LLC
 toys
 
 // trade : 2014-01-23 Elite Registry Limited
 trade
 
 // trading : 2014-12-11 IG Group Holdings PLC
 trading
@@ -9345,16 +9453,19 @@ xin
 ポイント
 
 // xn--efvy88h : 2014-08-22 Xinhua News Agency Guangdong Branch 新华通讯社广东分社
 新闻
 
 // xn--estv75g : 2015-02-19 Industrial and Commercial Bank of China Limited
 工行
 
+// xn--fct429k : 2015-04-09 Amazon EU S.à r.l.
+家電
+
 // xn--fhbei : 2015-01-15 VeriSign Sarl
 كوم
 
 // xn--fiq228c5hs : 2013-09-08 TLD REGISTRY LIMITED
 中文网
 
 // xn--fiq64b : 2013-10-14 CITIC Group Corporation
 中信
@@ -9474,16 +9585,19 @@ vermögensberater
 vermögensberatung
 
 // xn--vhquv : 2013-08-27 Dash McCook, LLC
 企业
 
 // xn--vuq861b : 2014-10-16 Beijing Tele-info Network Technology Co., Ltd.
 信息
 
+// xn--w4r85el8fhu5dnra : 2015-04-30 Kerry Trading Co. Limited
+嘉里大酒店
+
 // xn--xhq521b : 2013-11-14 Guangzhou YU Wei Information Technology Co., Ltd.
 广东
 
 // xn--zfr164b : 2013-11-08 China Organizational Name Administration Center
 政务
 
 // xyz : 2013-12-05 XYZ.COM LLC
 xyz
@@ -9504,16 +9618,19 @@ yandex
 yodobashi
 
 // yoga : 2014-05-29 Top Level Domain Holdings Limited
 yoga
 
 // yokohama : 2013-12-12 GMO Registry, Inc.
 yokohama
 
+// you : 2015-04-09 Amazon EU S.à r.l.
+you
+
 // youtube : 2014-05-01 Charleston Road Registry Inc.
 youtube
 
 // yun : 2015-01-08 QIHOO 360 TECHNOLOGY CO. LTD.
 yun
 
 // zara : 2014-11-07 Industria de Diseño Textil, S.A. (INDITEX, S.A.)
 zara
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
+++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
@@ -93,19 +93,17 @@ SandboxBroker::SetSecurityLevelForConten
   } else if (aSandboxLevel == 1) {
     jobLevel = sandbox::JOB_NONE;
     accessTokenLevel = sandbox::USER_NON_ADMIN;
     initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW;
     delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW;
   } else {
     jobLevel = sandbox::JOB_NONE;
     accessTokenLevel = sandbox::USER_NON_ADMIN;
-    // INTEGRITY_LEVEL_LAST effectively means don't change from the integrity
-    // level of the broker process.
-    initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LAST;
+    initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_MEDIUM;
     delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_MEDIUM;
   }
 
   sandbox::ResultCode result = mPolicy->SetJobLevel(jobLevel,
                                                     0 /* ui_exceptions */);
   bool ret = (sandbox::SBOX_ALL_OK == result);
 
   result = mPolicy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS,
--- a/testing/web-platform/meta/websockets/interfaces/CloseEvent/historical.html.ini
+++ b/testing/web-platform/meta/websockets/interfaces/CloseEvent/historical.html.ini
@@ -1,8 +1,10 @@
 [historical.html]
   type: testharness
   [createEvent("CloseEvent")]
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1161950
     expected: FAIL
 
   [initCloseEvent]
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1161950
     expected: FAIL
 
--- a/toolkit/components/telemetry/TelemetryController.jsm
+++ b/toolkit/components/telemetry/TelemetryController.jsm
@@ -17,16 +17,17 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/PromiseUtils.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 Cu.import("resource://gre/modules/DeferredTask.jsm", this);
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
+Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
 
 const LOGGER_NAME = "Toolkit.Telemetry";
 const LOGGER_PREFIX = "TelemetryController::";
 
 const PREF_BRANCH = "toolkit.telemetry.";
 const PREF_BRANCH_LOG = PREF_BRANCH + "log.";
 const PREF_SERVER = PREF_BRANCH + "server";
 const PREF_ENABLED = PREF_BRANCH + "enabled";
@@ -50,16 +51,18 @@ const TELEMETRY_DELAY = 60000;
 const TELEMETRY_TEST_DELAY = 100;
 // The number of days to keep pings serialised on the disk in case of failures.
 const DEFAULT_RETENTION_DAYS = 14;
 // Timeout after which we consider a ping submission failed.
 const PING_SUBMIT_TIMEOUT_MS = 2 * 60 * 1000;
 
 // We treat pings before midnight as happening "at midnight" with this tolerance.
 const MIDNIGHT_TOLERANCE_MS = 15 * 60 * 1000;
+// For midnight fuzzing we want to affect pings around midnight with this tolerance.
+const MIDNIGHT_TOLERANCE_FUZZ_MS = 5 * 60 * 1000;
 // We try to spread "midnight" pings out over this interval.
 const MIDNIGHT_FUZZING_INTERVAL_MS = 60 * 60 * 1000;
 const MIDNIGHT_FUZZING_DELAY_MS = Math.random() * MIDNIGHT_FUZZING_INTERVAL_MS;
 
 XPCOMUtils.defineLazyModuleGetter(this, "ClientID",
                                   "resource://gre/modules/ClientID.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
                                    "@mozilla.org/base/telemetry;1",
@@ -121,26 +124,16 @@ function generateUUID() {
 /**
  * Determine if the ping has new ping format or a legacy one.
  */
 function isNewPingFormat(aPing) {
   return ("id" in aPing) && ("application" in aPing) &&
          ("version" in aPing) && (aPing.version >= 2);
 }
 
-/**
- * Takes a date and returns it trunctated to a date with daily precision.
- */
-function truncateToDays(date) {
-  return new Date(date.getFullYear(),
-                  date.getMonth(),
-                  date.getDate(),
-                  0, 0, 0, 0);
-}
-
 function tomorrow(date) {
   let d = new Date(date);
   d.setDate(d.getDate() + 1);
   return d;
 }
 
 /**
  * This is a policy object used to override behavior for testing.
@@ -501,27 +494,30 @@ let Impl = {
    * This helper calculates the next time that we can send pings at.
    * Currently this mostly redistributes ping sends around midnight to avoid submission
    * spikes around local midnight for daily pings.
    *
    * @param now Date The current time.
    * @return Number The next time (ms from UNIX epoch) when we can send pings.
    */
   _getNextPingSendTime: function(now) {
-    const todayDate = truncateToDays(now);
-    const tomorrowDate = tomorrow(todayDate);
-    const nextMidnightRangeStart = tomorrowDate.getTime() - MIDNIGHT_TOLERANCE_MS;
-    const currentMidnightRangeEnd = todayDate.getTime() - MIDNIGHT_TOLERANCE_MS + Policy.midnightPingFuzzingDelay();
+    const midnight = TelemetryUtils.getNearestMidnight(now, MIDNIGHT_FUZZING_INTERVAL_MS);
 
-    if (now.getTime() < currentMidnightRangeEnd) {
-      return currentMidnightRangeEnd;
+    // Don't delay ping if we are not close to midnight.
+    if (!midnight) {
+      return now.getTime();
     }
 
-    if (now.getTime() >= nextMidnightRangeStart) {
-      return nextMidnightRangeStart + Policy.midnightPingFuzzingDelay();
+    // Delay ping send if we are within the midnight fuzzing range.
+    // This is from: |midnight - MIDNIGHT_TOLERANCE_FUZZ_MS|
+    // to: |midnight + MIDNIGHT_FUZZING_INTERVAL_MS|
+    const midnightRangeStart = midnight.getTime() - MIDNIGHT_TOLERANCE_FUZZ_MS;
+    if (now.getTime() >= midnightRangeStart) {
+      // We spread those ping sends out between |midnight| and |midnight + midnightPingFuzzingDelay|.
+      return midnight.getTime() + Policy.midnightPingFuzzingDelay();
     }
 
     return now.getTime();
   },
 
   /**
    * Submit ping payloads to Telemetry. This will assemble a complete ping, adding
    * environment data, client id and some general info.
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -15,16 +15,17 @@ Cu.import("resource://gre/modules/Log.js
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/DeferredTask.jsm", this);
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
+Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
 
 const myScope = this;
 
 const IS_CONTENT_PROCESS = (function() {
   // We cannot use Services.appinfo here because in telemetry xpcshell tests,
   // appinfo is initially unavailable, and becomes available only later on.
   let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
   return runtime.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
@@ -170,68 +171,16 @@ let Policy = {
   now: () => new Date(),
   generateSessionUUID: () => generateUUID(),
   generateSubsessionUUID: () => generateUUID(),
   setSchedulerTickTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
   clearSchedulerTickTimeout: id => clearTimeout(id),
 };
 
 /**
- * Takes a date and returns it trunctated to a date with daily precision.
- */
-function truncateToDays(date) {
-  return new Date(date.getFullYear(),
-                  date.getMonth(),
-                  date.getDate(),
-                  0, 0, 0, 0);
-}
-
-/**
- * Check if the difference between the times is within the provided tolerance.
- * @param {Number} t1 A time in milliseconds.
- * @param {Number} t2 A time in milliseconds.
- * @param {Number} tolerance The tolerance, in milliseconds.
- * @return {Boolean} True if the absolute time difference is within the tolerance, false
- *                   otherwise.
- */
-function areTimesClose(t1, t2, tolerance) {
-  return Math.abs(t1 - t2) <= tolerance;
-}
-
-/**
- * Get the next midnight for a date.
- * @param {Object} date The date object to check.
- * @return {Object} The Date object representing the next midnight.
- */
-function getNextMidnight(date) {
-  let nextMidnight = new Date(truncateToDays(date));
-  nextMidnight.setDate(nextMidnight.getDate() + 1);
-  return nextMidnight;
-}
-
-/**
- * Get the midnight which is closer to the provided date.
- * @param {Object} date The date object to check.
- * @return {Object} The Date object representing the closes midnight, or null if midnight
- *                  is not within the midnight tolerance.
- */
-function getNearestMidnight(date) {
-  let lastMidnight = truncateToDays(date);
-  if (areTimesClose(date.getTime(), lastMidnight.getTime(), SCHEDULER_MIDNIGHT_TOLERANCE_MS)) {
-    return lastMidnight;
-  }
-
-  const nextMidnightDate = getNextMidnight(date);
-  if (areTimesClose(date.getTime(), nextMidnightDate.getTime(), SCHEDULER_MIDNIGHT_TOLERANCE_MS)) {
-    return nextMidnightDate;
-  }
-  return null;
-}
-
-/**
  * Get the ping type based on the payload.
  * @param {Object} aPayload The ping payload.
  * @return {String} A string representing the ping type.
  */
 function getPingType(aPayload) {
   // To remain consistent with server-side ping handling, set "saved-session" as the ping
   // type for "saved-session" payload reasons.
   if (aPayload.info.reason == REASON_SAVED_SESSION) {
@@ -468,29 +417,29 @@ let TelemetryScheduler = {
     const now = Policy.now();
     let timeout = SCHEDULER_TICK_INTERVAL_MS;
 
     // When the user is idle we want to fire the timer less often.
     if (this._isUserIdle) {
       timeout = SCHEDULER_TICK_IDLE_INTERVAL_MS;
       // We need to make sure though that we don't miss sending pings around
       // midnight when we use the longer idle intervals.
-      const nextMidnight = getNextMidnight(now);
+      const nextMidnight = TelemetryUtils.getNextMidnight(now);
       timeout = Math.min(timeout, nextMidnight.getTime() - now.getTime());
     }
 
     this._log.trace("_rescheduleTimeout - scheduling next tick for " + new Date(now.getTime() + timeout));
     this._schedulerTimer =
       Policy.setSchedulerTickTimeout(() => this._onSchedulerTick(), timeout);
   },
 
   _sentDailyPingToday: function(nowDate) {
     // This is today's date and also the previous midnight (0:00).
-    const todayDate = truncateToDays(nowDate);
-    const nearestMidnight = getNearestMidnight(nowDate);
+    const todayDate = TelemetryUtils.truncateToDays(nowDate);
+    const nearestMidnight = TelemetryUtils.getNearestMidnight(nowDate, SCHEDULER_MIDNIGHT_TOLERANCE_MS);
     // If we are close to midnight, we check against that, otherwise against the last midnight.
     const checkDate = nearestMidnight || todayDate;
     // We consider a ping sent for today if it occured after midnight, or prior within the tolerance.
     return (this._lastDailyPingTime >= (checkDate.getTime() - SCHEDULER_MIDNIGHT_TOLERANCE_MS));
   },
 
   /**
    * Checks if we can send a daily ping or not.
@@ -501,17 +450,17 @@ let TelemetryScheduler = {
     const sentPingToday = this._sentDailyPingToday(nowDate);
 
     // The daily ping is not due if we already sent one today.
     if (sentPingToday) {
       this._log.trace("_isDailyPingDue - already sent one today");
       return false;
     }
 
-    const nearestMidnight = getNearestMidnight(nowDate);
+    const nearestMidnight = TelemetryUtils.getNearestMidnight(nowDate, SCHEDULER_MIDNIGHT_TOLERANCE_MS);
     if (!sentPingToday && !nearestMidnight) {
       // Computer must have gone to sleep, the daily ping is overdue.
       this._log.trace("_isDailyPingDue - daily ping is overdue... computer went to sleep?");
       return true;
     }
 
     // Avoid overly short sessions.
     const timeSinceLastDaily = nowDate.getTime() - this._lastDailyPingTime;
@@ -611,17 +560,18 @@ let TelemetryScheduler = {
     // We can combine the daily-ping and the aborted-session ping in the following cases:
     // - If both the daily and the aborted session pings are due (a laptop that wakes
     //   up after a few hours).
     // - If either the daily ping is due and the other one would follow up shortly
     //   (whithin the coalescence threshold).
     let nextSessionCheckpoint =
       this._lastSessionCheckpointTime + ABORTED_SESSION_UPDATE_INTERVAL_MS;
     let combineActions = (shouldSendDaily && isAbortedPingDue) || (shouldSendDaily &&
-                          areTimesClose(now, nextSessionCheckpoint, SCHEDULER_COALESCE_THRESHOLD_MS));
+                          TelemetryUtils.areTimesClose(now, nextSessionCheckpoint,
+                                                       SCHEDULER_COALESCE_THRESHOLD_MS));
 
     if (combineActions) {
       this._log.trace("_schedulerTickLogic - Combining pings.");
       // Send the daily ping and also save its payload as an aborted-session ping.
       return Impl._sendDailyPing(true).then(() => this._dailyPingSucceeded(now),
                                             () => this._dailyPingFailed(now));
     } else if (shouldSendDaily) {
       this._log.trace("_schedulerTickLogic - Daily ping due.");
@@ -656,17 +606,17 @@ let TelemetryScheduler = {
     this._log.trace("reschedulePings - reason: " + reason);
     let now = Policy.now();
     this._lastAdhocPingTime = now.getTime();
     if (reason == REASON_ENVIRONMENT_CHANGE) {
       // We just generated an environment-changed ping, save it as an aborted session and
       // update the schedules.
       this._saveAbortedPing(now.getTime(), competingPayload);
       // If we're close to midnight, skip today's daily ping and reschedule it for tomorrow.
-      let nearestMidnight = getNearestMidnight(now);
+      let nearestMidnight = TelemetryUtils.getNearestMidnight(now, SCHEDULER_MIDNIGHT_TOLERANCE_MS);
       if (nearestMidnight) {
         this._lastDailyPingTime = now.getTime();
       }
     }
 
     this._rescheduleTimeout();
   },
 
@@ -1147,18 +1097,18 @@ let Impl = {
    * @param  reason
    *         The reason for the telemetry ping, this will be included in the
    *         returned metadata,
    * @return The metadata as a JS object
    */
   getMetadata: function getMetadata(reason) {
     this._log.trace("getMetadata - Reason " + reason);
 
-    let sessionStartDate = toLocalTimeISOString(truncateToDays(this._sessionStartDate));
-    let subsessionStartDate = toLocalTimeISOString(truncateToDays(this._subsessionStartDate));
+    let sessionStartDate = toLocalTimeISOString(TelemetryUtils.truncateToDays(this._sessionStartDate));
+    let subsessionStartDate = toLocalTimeISOString(TelemetryUtils.truncateToDays(this._subsessionStartDate));
     // Compute the subsession length in milliseconds, then convert to seconds.
     let subsessionLength =
       Math.floor((Policy.now() - this._subsessionStartDate.getTime()) / 1000);
 
     let ret = {
       reason: reason,
       revision: HISTOGRAMS_FILE_VERSION,
       asyncPluginInit: Preferences.get(PREF_ASYNC_PLUGIN_INIT, false),
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/TelemetryUtils.jsm
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "TelemetryUtils"
+];
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+this.TelemetryUtils = {
+  /**
+   * Takes a date and returns it trunctated to a date with daily precision.
+   */
+  truncateToDays: function(date) {
+    return new Date(date.getFullYear(),
+                    date.getMonth(),
+                    date.getDate(),
+                    0, 0, 0, 0);
+  },
+
+  /**
+   * Check if the difference between the times is within the provided tolerance.
+   * @param {Number} t1 A time in milliseconds.
+   * @param {Number} t2 A time in milliseconds.
+   * @param {Number} tolerance The tolerance, in milliseconds.
+   * @return {Boolean} True if the absolute time difference is within the tolerance, false
+   *                   otherwise.
+   */
+  areTimesClose: function(t1, t2, tolerance) {
+    return Math.abs(t1 - t2) <= tolerance;
+  },
+
+  /**
+   * Get the next midnight for a date.
+   * @param {Object} date The date object to check.
+   * @return {Object} The Date object representing the next midnight.
+   */
+  getNextMidnight: function(date) {
+    let nextMidnight = new Date(this.truncateToDays(date));
+    nextMidnight.setDate(nextMidnight.getDate() + 1);
+    return nextMidnight;
+  },
+
+  /**
+   * Get the midnight which is closer to the provided date.
+   * @param {Object} date The date object to check.
+   * @param {Number} tolerance The tolerance within we find the closest midnight.
+   * @return {Object} The Date object representing the closes midnight, or null if midnight
+   *                  is not within the midnight tolerance.
+   */
+  getNearestMidnight: function(date, tolerance) {
+    let lastMidnight = this.truncateToDays(date);
+    if (this.areTimesClose(date.getTime(), lastMidnight.getTime(), tolerance)) {
+      return lastMidnight;
+    }
+
+    const nextMidnightDate = this.getNextMidnight(date);
+    if (this.areTimesClose(date.getTime(), nextMidnightDate.getTime(), tolerance)) {
+      return nextMidnightDate;
+    }
+    return null;
+  },
+};
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -29,16 +29,17 @@ EXTRA_COMPONENTS += [
     'TelemetryStartup.manifest'
 ]
 
 EXTRA_JS_MODULES += [
     'TelemetryArchive.jsm',
     'TelemetryLog.jsm',
     'TelemetryStopwatch.jsm',
     'TelemetryStorage.jsm',
+    'TelemetryUtils.jsm',
     'ThirdPartyCookieProbe.jsm',
     'UITelemetry.jsm',
 ]
 
 EXTRA_PP_JS_MODULES += [
     'TelemetryController.jsm',
     'TelemetryEnvironment.jsm',
     'TelemetrySession.jsm',
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js
@@ -282,24 +282,24 @@ add_task(function* test_midnightPingSend
   now = new Date(2030, 5, 1, 23, 55, 0);
   fakeNow(now);
   registerPingHandler((req, res) => {
     Assert.ok(false, "No ping should be received yet.");
   });
   yield sendPing(true, true);
 
   Assert.ok(!!pingSendTimerCallback);
-  Assert.deepEqual(futureDate(now, pingSendTimeout), new Date(2030, 5, 2, 0, 45, 0));
+  Assert.deepEqual(futureDate(now, pingSendTimeout), new Date(2030, 5, 2, 1, 0, 0));
 
   // A ping after midnight within the fuzzing delay should also not get sent.
   now = new Date(2030, 5, 2, 0, 40, 0);
   fakeNow(now);
   pingSendTimeout = null;
   yield sendPing(true, true);
-  Assert.deepEqual(futureDate(now, pingSendTimeout), new Date(2030, 5, 2, 0, 45, 0));
+  Assert.deepEqual(futureDate(now, pingSendTimeout), new Date(2030, 5, 2, 1, 0, 0));
 
   // The Request constructor restores the previous ping handler.
   gRequestIterator = Iterator(new Request());
 
   // Setting the clock to after the fuzzing delay, we should trigger the two ping sends
   // with the timer callback.
   now = futureDate(now, pingSendTimeout);
   fakeNow(now);
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -1667,17 +1667,17 @@ add_task(function* test_sendDailyOnIdle(
   let request = yield gRequestIterator.next();
   Assert.ok(!!request);
   let ping = decodeRequestPayload(request);
 
   Assert.equal(ping.type, PING_TYPE_MAIN);
   Assert.equal(ping.payload.info.reason, REASON_DAILY);
 
   // We should also trigger a ping when going idle shortly before next midnight.
-  now = new Date(2040, 1, 2, 23, 55, 0);
+  now = new Date(2040, 1, 2, 23, 54, 0);
   fakeNow(now);
   yield fakeIdleNotification("idle");
 
   request = yield gRequestIterator.next();
   Assert.ok(!!request);
   ping = decodeRequestPayload(request);
 
   Assert.equal(ping.type, PING_TYPE_MAIN);
--- a/toolkit/crashreporter/content/crashes.xhtml
+++ b/toolkit/crashreporter/content/crashes.xhtml
@@ -12,43 +12,56 @@
   %crashesDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <style type="text/css">
 :root {
   font-family: sans-serif;
+  margin: 40px auto;
+  min-width: 30em;
+  max-width: 60em;
 }
 table {
+  clear: both;
+  width: 90%;
+  margin: 0 auto;
   padding-bottom: 2em;
 }
 th {
+  font-size: 130%;
   text-align: left;
   white-space: nowrap;
 }
 th[chromedir="rtl"] {
   text-align: right;
 }
 /* name */
 th:first-child {
   -moz-padding-end: 2em;
 }
+/* submitted */
+th:last-child {
+  text-align: center;
+}
 :link, :visited {
   display: block;
   min-height: 17px;
 }
 /* date */
 td:first-child + td {
+  width: 0;
   -moz-padding-start: 1em;
   -moz-padding-end: .5em;
   white-space: nowrap;
 }
 /* time */
 td:last-child {
+  width: 0;
   -moz-padding-start: .5em;
   white-space: nowrap;
 }
 
 #clear-reports {
   float: right;
 }
 #clear-reports[chromedir="rtl"] {
@@ -57,17 +70,17 @@ td:last-child {
 
 .submitting {
   background-image: url(chrome://global/skin/icons/loading_16.png);
   background-repeat: no-repeat;
   background-position: right;
 }
 </style>
 <link rel="stylesheet" media="screen, projection" type="text/css"
-      href="chrome://global/skin/dirListing/dirListing.css"/>
+      href="chrome://global/skin/in-content/common.css"/>
 <script type="application/javascript;version=1.8" src="chrome://global/content/crashes.js"/>
 
 <title>&crashes.title;</title>
 </head><body onload="populateReportList()" dir="&locale.dir;">
 <button chromedir="&locale.dir;" id="clear-reports"
         onclick="clearReports().then(null, Cu.reportError)">&clearAllReports.label;</button>
 <h1>&crashes.title;</h1>
 <div id="reportList">
--- a/toolkit/devtools/server/actors/profiler.js
+++ b/toolkit/devtools/server/actors/profiler.js
@@ -54,17 +54,22 @@ ProfilerActor.prototype = {
    * Returns an array of feature strings, describing the profiler features
    * that are available on this platform. Can be called while the profiler
    * is stopped.
    */
   onGetFeatures: function() {
     return { features: nsIProfilerModule.GetFeatures([]) };
   },
 
-  onGetBufferInfo: function(request) {
+  /**
+   * Returns an object with the values of the current status of the
+   * circular buffer in the profiler, returning `position`, `totalSize`,
+   * and the current `generation` of the buffer.
+   */
+  onGetBufferInfo: function() {
     let position = {}, totalSize = {}, generation = {};
     nsIProfilerModule.GetBufferInfo(position, totalSize, generation);
     return {
       position: position.value,
       totalSize: totalSize.value,
       generation: generation.value
     }
   },
@@ -97,18 +102,19 @@ ProfilerActor.prototype = {
     nsIProfilerModule.StartProfiler(
       options.entries,
       options.interval,
       options.features,
       options.features.length,
       options.threadFilters,
       options.threadFilters.length
     );
+    let { position, totalSize, generation } = this.onGetBufferInfo();
 
-    return { started: true };
+    return { started: true, position, totalSize, generation };
   },
 
   /**
    * Stops the nsIProfiler module, if no other client is using it.
    */
   onStopProfiler: function() {
     // Actually stop the profiler only if the last client has stopped profiling.
     // Since this is a root actor, and the profiler module interacts with the
@@ -122,17 +128,18 @@ ProfilerActor.prototype = {
 
   /**
    * Verifies whether or not the nsIProfiler module has started.
    * If already active, the current time is also returned.
    */
   onIsActive: function() {
     let isActive = nsIProfilerModule.IsActive();
     let elapsedTime = isActive ? nsIProfilerModule.getElapsedTime() : undefined;
-    return { isActive: isActive, currentTime: elapsedTime };
+    let { position, totalSize, generation } = this.onGetBufferInfo();
+    return { isActive: isActive, currentTime: elapsedTime, position, totalSize, generation };
   },
 
   /**
    * Returns a stringified JSON object that describes the shared libraries
    * which are currently loaded into our process. Can be called while the
    * profiler is stopped.
    */
   onGetSharedLibraryInformation: function() {
--- a/toolkit/devtools/server/actors/root.js
+++ b/toolkit/devtools/server/actors/root.js
@@ -166,20 +166,17 @@ RootActor.prototype = {
     // if allocChromeProcess is defined, but not true, it means that root actor
     // no longer expose tab actors, but also that getProcess forbids
     // exposing actors for security reasons
     get allowChromeProcess() {
       return DebuggerServer.allowChromeProcess;
     },
     // Whether or not `getProfile()` supports specifying a `startTime`
     // and `endTime` to filter out samples. Fx40+
-    profilerDataFilterable: true,
-    // Whether or not the profiler has a `getBufferInfo` method
-    // necessary as the profiler does not use the ActorFront class.
-    profilerBufferStatus: true,
+    profilerDataFilterable: true
   },
 
   /**
    * Return a 'hello' packet as specified by the Remote Debugging Protocol.
    */
   sayHello: function() {
     return {
       from: this.actorID,
--- a/toolkit/devtools/server/tests/unit/test_profiler_activation-01.js
+++ b/toolkit/devtools/server/tests/unit/test_profiler_activation-01.js
@@ -4,16 +4,17 @@
 "use strict";
 
 /**
  * Tests whether the profiler module and actor have the correct state on
  * initialization, activation, and when a clients' connection closes.
  */
 
 const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+const MAX_PROFILER_ENTRIES = 10000000;
 
 function run_test()
 {
   // Ensure the profiler is not running when the test starts (it could
   // happen if the MOZ_PROFILER_STARTUP environment variable is set).
   Profiler.StopProfiler();
 
   get_chrome_actors((client1, form1) => {
@@ -30,27 +31,40 @@ function run_test()
 }
 
 function test_activate(client1, actor1, client2, actor2, callback) {
   // Profiler should be inactive at this point.
   client1.request({ to: actor1, type: "isActive" }, response => {
     do_check_false(Profiler.IsActive());
     do_check_false(response.isActive);
     do_check_eq(response.currentTime, undefined);
+    do_check_true(typeof response.position === "number");
+    do_check_true(typeof response.totalSize === "number");
+    do_check_true(typeof response.generation === "number");
 
     // Start the profiler on the first connection....
-    client1.request({ to: actor1, type: "startProfiler" }, response => {
+    client1.request({ to: actor1, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => {
       do_check_true(Profiler.IsActive());
       do_check_true(response.started);
+      do_check_true(typeof response.position === "number");
+      do_check_true(typeof response.totalSize === "number");
+      do_check_true(typeof response.generation === "number");
+      do_check_true(response.position >= 0 && response.position < response.totalSize);
+      do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
 
       // On the next connection just make sure the actor has been instantiated.
       client2.request({ to: actor2, type: "isActive" }, response => {