Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 13 Feb 2017 17:04:20 -0800
changeset 342664 195049fabb7ac5709e5f75614ba630ba3d1b5a9b
parent 342663 bca0824417677907d47771bf12f293ae7909d9c3 (current diff)
parent 342543 7a0bdc9f6c1d118c3274157dc6359d50b7aca4e3 (diff)
child 342665 5a7f8a39068aa5913543b61c0220146cdb1cfc5f
child 342724 5264dfa9e8f19d120f9b78aae534e3fa5f96c52e
push id86923
push userkwierso@gmail.com
push dateTue, 14 Feb 2017 01:07:39 +0000
treeherdermozilla-inbound@5a7f8a39068a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone54.0a1
first release with
nightly linux32
195049fabb7a / 54.0a1 / 20170214110212 / files
nightly linux64
195049fabb7a / 54.0a1 / 20170214110212 / files
nightly mac
195049fabb7a / 54.0a1 / 20170214030231 / files
nightly win32
195049fabb7a / 54.0a1 / 20170214030231 / files
nightly win64
195049fabb7a / 54.0a1 / 20170214030231 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge MozReview-Commit-ID: Gx7IszkCDZ4
toolkit/components/telemetry/Histograms.json
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -6,16 +6,17 @@ module.exports = {
     "mozilla"
   ],
   "rules": {
     "mozilla/avoid-removeChild": "error",
     "mozilla/import-globals": "warn",
     "mozilla/no-import-into-var-and-global": "error",
     "mozilla/no-useless-parameters": "error",
     "mozilla/no-useless-removeEventListener": "error",
+    "mozilla/use-ownerGlobal": "error",
 
     // No (!foo in bar) or (!object instanceof Class)
     "no-unsafe-negation": "error",
   },
   "env": {
     "es6": true
   },
   "parserOptions": {
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -420,17 +420,17 @@ DocAccessibleParent::AddChildDoc(DocAcce
   // OuterDocAccessibles are expected to only have a document as a child.
   // However for compatibility we tolerate replacing one document with another
   // here.
   if (outerDoc->ChildrenCount() > 1 ||
       (outerDoc->ChildrenCount() == 1 && !outerDoc->ChildAt(0)->IsDoc())) {
     return IPC_FAIL(this, "binding to proxy that can't be a outerDoc!");
   }
 
-  aChildDoc->mParent = outerDoc;
+  aChildDoc->SetParent(outerDoc);
   outerDoc->SetChildDoc(aChildDoc);
   mChildDocs.AppendElement(aChildDoc);
   aChildDoc->mParentDoc = this;
 
   if (aCreating) {
     ProxyCreated(aChildDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
   }
 
--- a/accessible/ipc/DocAccessibleParent.h
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -90,17 +90,17 @@ public:
   virtual mozilla::ipc::IPCResult RecvBindChildDoc(PDocAccessibleParent* aChildDoc, const uint64_t& aID) override;
 
   void Unbind()
   {
     if (DocAccessibleParent* parent = ParentDoc()) {
       parent->RemoveChildDoc(this);
     }
 
-    mParent = nullptr;
+    SetParent(nullptr);
   }
 
   virtual mozilla::ipc::IPCResult RecvShutdown() override;
   void Destroy();
   virtual void ActorDestroy(ActorDestroyReason aWhy) override
   {
     MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
     if (!mShutdown)
--- a/accessible/ipc/ProxyAccessibleBase.cpp
+++ b/accessible/ipc/ProxyAccessibleBase.cpp
@@ -162,12 +162,53 @@ ProxyAccessibleBase<Derived>::OuterDocOf
   if (!frame)
     return nullptr;
 
   DocAccessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc());
 
   return chromeDoc ? chromeDoc->GetAccessible(frame) : nullptr;
 }
 
+template<class Derived>
+void
+ProxyAccessibleBase<Derived>::SetParent(Derived* aParent)
+{
+  MOZ_ASSERT(IsDoc(), "we should only reparent documents");
+  if (!aParent) {
+    mParent = kNoParent;
+  } else {
+    MOZ_ASSERT(!aParent->IsDoc());
+    mParent = aParent->ID();
+  }
+}
+
+template<class Derived>
+Derived*
+ProxyAccessibleBase<Derived>::Parent() const
+{
+  if (mParent == kNoParent) {
+    return nullptr;
+  }
+
+  // if we are not a document then are parent is another proxy in the same
+  // document.  That means we can just ask our document for the proxy with our
+  // parent id.
+  if (!IsDoc()) {
+    return Document()->GetAccessible(mParent);
+  }
+
+  // If we are a top level document then our parent is not a proxy.
+  if (AsDoc()->IsTopLevel()) {
+    return nullptr;
+  }
+
+  // Finally if we are a non top level document then our parent id is for a
+  // proxy in our parent document so get the proxy from there.
+  DocAccessibleParent* parentDoc = AsDoc()->ParentDoc();
+  MOZ_ASSERT(parentDoc);
+  MOZ_ASSERT(mParent);
+  return parentDoc->GetAccessible(mParent);
+}
+
 template class ProxyAccessibleBase<ProxyAccessible>;
 
 } // namespace a11y
 } // namespace mozilla
--- a/accessible/ipc/ProxyAccessibleBase.h
+++ b/accessible/ipc/ProxyAccessibleBase.h
@@ -85,17 +85,17 @@ public:
    * Remove The given child.
    */
   void RemoveChild(Derived* aChild)
     { mChildren.RemoveElement(aChild); }
 
   /**
    * Return the proxy for the parent of the wrapped accessible.
    */
-  Derived* Parent() const { return mParent; }
+  Derived* Parent() const;
 
   Accessible* OuterDocOfRemoteBrowser() const;
 
   /**
    * Get the role of the accessible we're proxying.
    */
   role Role() const { return mRole; }
 
@@ -153,39 +153,42 @@ public:
         mRole == roles::GRID_CELL ||
         mRole == roles::MATHML_CELL);
   }
 
 protected:
   ProxyAccessibleBase(uint64_t aID, Derived* aParent,
                       DocAccessibleParent* aDoc, role aRole,
                       uint32_t aInterfaces)
-    : mParent(aParent)
+    : mParent(aParent->ID())
     , mDoc(aDoc)
     , mWrapper(0)
     , mID(aID)
     , mRole(aRole)
     , mOuterDoc(false)
     , mIsDoc(false)
     , mHasValue(aInterfaces & Interfaces::VALUE)
     , mIsHyperLink(aInterfaces & Interfaces::HYPERLINK)
     , mIsHyperText(aInterfaces & Interfaces::HYPERTEXT)
   {
   }
 
   explicit ProxyAccessibleBase(DocAccessibleParent* aThisAsDoc) :
-    mParent(nullptr), mDoc(aThisAsDoc), mWrapper(0), mID(0),
+    mParent(kNoParent), mDoc(aThisAsDoc), mWrapper(0), mID(0),
     mRole(roles::DOCUMENT), mOuterDoc(false), mIsDoc(true), mHasValue(false),
     mIsHyperLink(false), mIsHyperText(false)
   {}
 
 protected:
-  Derived* mParent;
+  void SetParent(Derived* aParent);
 
 private:
+  uintptr_t mParent;
+  static const uintptr_t kNoParent = UINTPTR_MAX;
+
   friend Derived;
 
   nsTArray<Derived*> mChildren;
   DocAccessibleParent* mDoc;
   uintptr_t mWrapper;
   uint64_t mID;
 
 protected:
--- a/browser/base/content/test/general/browser_temporary_permissions_navigation.js
+++ b/browser/base/content/test/general/browser_temporary_permissions_navigation.js
@@ -69,16 +69,46 @@ add_task(function* testTempPermissionOnR
       state: SitePermissions.UNKNOWN,
       scope: SitePermissions.SCOPE_PERSISTENT,
     });
 
     SitePermissions.remove(uri, id, browser);
   });
 });
 
+// Test that temporary permissions are not removed when reloading all tabs.
+add_task(function* testTempPermissionOnReloadAllTabs() {
+  let uri = NetUtil.newURI("https://example.com");
+  let id = "geo";
+
+  yield BrowserTestUtils.withNewTab(uri.spec, function*(browser) {
+    SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
+
+    // Open the tab context menu.
+    let contextMenu = document.getElementById("tabContextMenu");
+    let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+    EventUtils.synthesizeMouseAtCenter(gBrowser.selectedTab, {type: "contextmenu", button: 2});
+    yield popupShownPromise;
+
+    let reloadMenuItem = document.getElementById("context_reloadAllTabs");
+
+    let reloaded = Promise.all(gBrowser.visibleTabs.map(
+      tab => BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab))));
+    EventUtils.synthesizeMouseAtCenter(reloadMenuItem, {});
+    yield reloaded;
+
+    Assert.deepEqual(SitePermissions.get(uri, id, browser), {
+      state: SitePermissions.BLOCK,
+      scope: SitePermissions.SCOPE_TEMPORARY,
+    });
+
+    SitePermissions.remove(uri, id, browser);
+  });
+});
+
 // Test that temporary permissions are persisted through navigation in a tab.
 add_task(function* testTempPermissionOnNavigation() {
   let uri = NetUtil.newURI("https://example.com/");
   let id = "geo";
 
   yield BrowserTestUtils.withNewTab(uri.spec, function*(browser) {
     SitePermissions.set(uri, id, SitePermissions.BLOCK, SitePermissions.SCOPE_TEMPORARY, browser);
 
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_5.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_5.js
@@ -66,19 +66,20 @@ var tests = [
   { id: "Test#3",
     *run() {
       let notifyObj = new BasicNotification(this.id);
       notifyObj.options.dismissed = true;
 
       let win = yield BrowserTestUtils.openNewBrowserWindow();
 
       // Open the notification in the original window, now in the background.
-      showNotification(notifyObj);
+      let notification = showNotification(notifyObj);
       let anchor = document.getElementById("default-notification-icon");
       is(anchor.getAttribute("showing"), "true", "the anchor is shown");
+      notification.remove();
 
       yield BrowserTestUtils.closeWindow(win);
       yield waitForWindowReadyForPopupNotifications(window);
 
       goNext();
     }
   },
   // Test that persistent doesn't allow the notification to persist after
@@ -325,9 +326,78 @@ var tests = [
       ok(!this.notification2.options.persistent, "notification 2 is not persistent");
       ok(this.notification3.options.persistent, "notification 3 is persistent");
 
       this.notification1.remove();
       this.notification2.remove();
       this.notification3.remove();
     }
   },
+  // Test clicking the anchor icon.
+  // Clicking the anchor of an already visible persistent notification should
+  // focus the main action button, but not cause additional showing/shown event
+  // callback calls.
+  // Clicking the anchor of a dismissed notification should show it, even when
+  // the currently displayed notification is a persistent one.
+  { id: "Test#11",
+    *run() {
+      function clickAnchor(notifyObj) {
+        let anchor = document.getElementById(notifyObj.anchorID);
+        EventUtils.synthesizeMouseAtCenter(anchor, {});
+      }
+
+      let popup = PopupNotifications.panel;
+
+      let notifyObj1 = new BasicNotification(this.id);
+      notifyObj1.id += "_1";
+      notifyObj1.anchorID = "default-notification-icon";
+      notifyObj1.options.persistent = true;
+      let shown = waitForNotificationPanel();
+      let notification1 = showNotification(notifyObj1);
+      yield shown;
+      checkPopup(popup, notifyObj1);
+      ok(!notifyObj1.dismissalCallbackTriggered,
+         "Should not have dismissed the notification");
+      notifyObj1.shownCallbackTriggered = false;
+      notifyObj1.showingCallbackTriggered = false;
+
+      // Click the anchor. This should focus the primary button, but
+      // not call event callbacks on the notification object.
+      clickAnchor(notifyObj1);
+      is(document.activeElement, popup.childNodes[0].button);
+      ok(!notifyObj1.dismissalCallbackTriggered,
+         "Should not have dismissed the notification");
+      ok(!notifyObj1.shownCallbackTriggered,
+         "Should have triggered the shown event again");
+      ok(!notifyObj1.showingCallbackTriggered,
+         "Should have triggered the showing event again");
+
+      // Add another notification.
+      let notifyObj2 = new BasicNotification(this.id);
+      notifyObj2.id += "_2";
+      notifyObj2.anchorID = "geo-notification-icon";
+      notifyObj2.options.dismissed = true;
+      let notification2 = showNotification(notifyObj2);
+
+      // Click the anchor of the second notification, this should dismiss the
+      // first notification.
+      shown = waitForNotificationPanel();
+      clickAnchor(notifyObj2);
+      yield shown;
+      checkPopup(popup, notifyObj2);
+      ok(notifyObj1.dismissalCallbackTriggered,
+         "Should have dismissed the first notification");
+
+      // Click the anchor of the first notification, it should be shown again.
+      shown = waitForNotificationPanel();
+      clickAnchor(notifyObj1);
+      yield shown;
+      checkPopup(popup, notifyObj1);
+      ok(notifyObj2.dismissalCallbackTriggered,
+         "Should have dismissed the second notification");
+
+      // Cleanup.
+      notification1.remove();
+      notification2.remove();
+      goNext();
+    }
+  },
 ];
--- a/build/moz.configure/rust.configure
+++ b/build/moz.configure/rust.configure
@@ -7,22 +7,40 @@
 # Rust is required by `rust_compiler` below. We allow_missing here
 # to propagate failures to the better error message there.
 rustc = check_prog('RUSTC', ['rustc'], allow_missing=True)
 cargo = check_prog('CARGO', ['cargo'], allow_missing=True)
 
 @depends_if(rustc)
 @checking('rustc version', lambda info: info.version)
 def rustc_info(rustc):
-        out = check_cmd_output(rustc, '--version', '--verbose').splitlines()
-        info = dict((s.strip() for s in line.split(':', 1)) for line in out[1:])
-        return namespace(
-            version=Version(info.get('release', '0')),
-            commit=info.get('commit-hash', 'unknown'),
-        )
+    out = check_cmd_output(rustc, '--version', '--verbose').splitlines()
+    info = dict((s.strip() for s in line.split(':', 1)) for line in out[1:])
+    return namespace(
+        version=Version(info.get('release', '0')),
+        commit=info.get('commit-hash', 'unknown'),
+    )
+
+@depends_if(cargo)
+@checking('cargo version', lambda info: info.version)
+@imports('re')
+def cargo_info(cargo):
+    # |cargo --version| doesn't have pleasant-to-parse output like rustc
+    # does. So we have to resort to regexes.
+    out = check_cmd_output(cargo, '--version').splitlines()[0]
+    VERSION_FORMAT = r'^cargo (\d\.\d+\.\d+).*'
+
+    m = re.search(VERSION_FORMAT, out)
+    # Fail fast if cargo changes its output on us.
+    if not m:
+        die('Could not determine cargo version from output: %s', out)
+
+    return namespace(
+        version=Version(m.group(1)),
+    )
 
 @depends_if(cargo)
 @checking('cargo support for --frozen')
 @imports('subprocess')
 @imports('os')
 def cargo_supports_frozen(cargo):
     try:
         lines = subprocess.check_output(
@@ -32,44 +50,66 @@ def cargo_supports_frozen(cargo):
         if 'MOZ_AUTOMATION' in os.environ and not supported:
             die('cargo in automation must support --frozen')
         return supported
     except subprocess.CalledProcessError as e:
         die('Failed to call cargo: %s', e.message)
 
 set_config('MOZ_CARGO_SUPPORTS_FROZEN', cargo_supports_frozen)
 
-@depends(rustc, rustc_info)
+@depends(rustc, cargo, rustc_info)
 @imports(_from='textwrap', _import='dedent')
-def rust_compiler(rustc, rustc_info):
+def rust_compiler(rustc, cargo, rustc_info):
     if not rustc:
         die(dedent('''\
         Rust compiler not found.
         To compile rust language sources, you must have 'rustc' in your path.
         See https//www.rust-lang.org/ for more information.
 
         You can install rust by running './mach bootstrap'
         or by directly running the installer from https://rustup.rs/
         '''))
+    rustc_min_version = Version('1.15.1')
+    cargo_min_version = Version('0.{}'.format(rustc_min_version.minor + 1))
+
     version = rustc_info.version
-    min_version = Version('1.15.1')
-    if version < min_version:
+    if version < rustc_min_version:
         die(dedent('''\
         Rust compiler {} is too old.
 
         To compile Rust language sources please install at least
         version {} of the 'rustc' toolchain and make sure it is
         first in your path.
 
         You can verify this by typing 'rustc --version'.
 
         If you have the 'rustup' tool installed you can upgrade
         to the latest release by typing 'rustup update'. The
         installer is available from https://rustup.rs/
-        '''.format(version, min_version)))
+        '''.format(version, rustc_min_version)))
+
+        if not cargo:
+            die(dedent('''\
+            Cargo package manager not found.
+            To compile Rust language sources, you must have 'cargo' in your path.
+            See https://www.rust-lang.org/ for more information.
+
+            You can install cargo by running './mach bootstrap'
+            or by directly running the installer from https://rustup.rs/
+            '''))
+        version = cargo_info.version
+        if version < cargo_min_version:
+            die(dedent('''\
+            Cargo package manager {} is too old.
+
+            To compile Rust language sources please install at least
+            version {} of 'cargo' and make sure it is first in your path.
+
+            You can verify this by typing 'cargo --version'.
+            ''').format(version, cargo_min_version))
     return True
 
 set_config('MOZ_RUST', rust_compiler)
 
 @template
 def rust_triple_alias(host_or_target):
     """Template defining the alias used for rustc's --target flag.
     `host_or_target` is either `host` or `target` (the @depends functions
new file mode 100644
--- /dev/null
+++ b/devtools/client/.eslintrc.js
@@ -0,0 +1,9 @@
+"use strict";
+
+module.exports = {
+  "rules": {
+    // See bug 1288147, the devtools front-end wants to be able to run in
+    // content privileged windows, where ownerGlobal doesn't exist.
+    "mozilla/use-ownerGlobal": "off",
+  }
+};
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -117,16 +117,18 @@ var AnimationPlayerActor = protocol.Acto
       // The target is a DOM node.
       this._node = node;
     }
 
     return this._node;
   },
 
   get window() {
+    // ownerGlobal doesn't exist in content privileged windows.
+    // eslint-disable-next-line mozilla/use-ownerGlobal
     return this.node.ownerDocument.defaultView;
   },
 
   /**
    * Release the actor, when it isn't needed anymore.
    * Protocol.js uses this release method to call the destroy method.
    */
   release: function () {},
@@ -532,16 +534,18 @@ exports.AnimationsActor = protocol.Actor
       this.actors.push(actor);
     }
 
     // When a front requests the list of players for a node, start listening
     // for animation mutations on this node to send updates to the front, until
     // either getAnimationPlayersForNode is called again or
     // stopAnimationPlayerUpdates is called.
     this.stopAnimationPlayerUpdates();
+    // ownerGlobal doesn't exist in content privileged windows.
+    // eslint-disable-next-line mozilla/use-ownerGlobal
     let win = nodeActor.rawNode.ownerDocument.defaultView;
     this.observer = new win.MutationObserver(this.onAnimationMutation);
     this.observer.observe(nodeActor.rawNode, {
       animations: true,
       subtree: true
     });
 
     return this.actors;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/.eslintrc.js
@@ -0,0 +1,9 @@
+"use strict";
+
+module.exports = {
+  "rules": {
+    // See bug 1288147, the devtools front-end wants to be able to run in
+    // content privileged windows, where ownerGlobal doesn't exist.
+    "mozilla/use-ownerGlobal": "off",
+  }
+};
--- a/dom/base/EventSource.cpp
+++ b/dom/base/EventSource.cpp
@@ -124,17 +124,17 @@ public:
   static nsresult StreamReaderFunc(nsIInputStream* aInputStream,
                                    void* aClosure,
                                    const char* aFromRawSegment,
                                    uint32_t aToOffset,
                                    uint32_t aCount,
                                    uint32_t* aWriteCount);
   void ParseSegment(const char* aBuffer, uint32_t aLength);
   nsresult SetFieldAndClear();
-  nsresult ClearFields();
+  void ClearFields();
   nsresult ResetEvent();
   nsresult DispatchCurrentMessageEvent();
   nsresult ParseCharacter(char16_t aChr);
   nsresult CheckHealthOfRequestCallback(nsIRequest* aRequestCallback);
   nsresult OnRedirectVerifyCallback(nsresult result);
   nsresult ParseURL(const nsAString& aURL);
   nsresult AddWindowObservers();
   void RemoveWindowObservers();
@@ -263,17 +263,17 @@ public:
     nsString mEventName;
     nsString mLastEventID;
     nsString mData;
   };
 
   // Message related data members. May be set / initialized when initializing
   // EventSourceImpl on target thread but should only be used on target thread.
   nsString mLastEventID;
-  Message mCurrentMessage;
+  UniquePtr<Message> mCurrentMessage;
   nsDeque mMessagesToDispatch;
   ParserStatus mStatus;
   nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
   nsresult mLastConvertionResult;
   nsString mLastFieldName;
   nsString mLastFieldValue;
 
   // EventSourceImpl internal states.
@@ -1396,22 +1396,20 @@ EventSourceImpl::Freeze()
   SetFrozen(true);
   return NS_OK;
 }
 
 nsresult
 EventSourceImpl::DispatchCurrentMessageEvent()
 {
   AssertIsOnTargetThread();
-  nsAutoPtr<Message> message(new Message());
-  *message = mCurrentMessage;
-
+  UniquePtr<Message> message(Move(mCurrentMessage));
   ClearFields();
 
-  if (message->mData.IsEmpty()) {
+  if (!message || message->mData.IsEmpty()) {
     return NS_OK;
   }
 
   // removes the trailing LF from mData
   MOZ_ASSERT(message->mData.CharAt(message->mData.Length() - 1) == LF_CHAR,
              "Invalid trailing character! LF was expected instead.");
   message->mData.SetLength(message->mData.Length() - 1);
 
@@ -1419,17 +1417,17 @@ EventSourceImpl::DispatchCurrentMessageE
     message->mEventName.AssignLiteral("message");
   }
 
   if (message->mLastEventID.IsEmpty() && !mLastEventID.IsEmpty()) {
     message->mLastEventID.Assign(mLastEventID);
   }
 
   size_t sizeBefore = mMessagesToDispatch.GetSize();
-  mMessagesToDispatch.Push(message.forget());
+  mMessagesToDispatch.Push(message.release());
   NS_ENSURE_TRUE(mMessagesToDispatch.GetSize() == sizeBefore + 1,
                  NS_ERROR_OUT_OF_MEMORY);
 
 
   if (!mGoingToDispatchAllMessages) {
     nsCOMPtr<nsIRunnable> event =
       NewRunnableMethod(this, &EventSourceImpl::DispatchAllMessageEvents);
     NS_ENSURE_STATE(event);
@@ -1441,22 +1439,22 @@ EventSourceImpl::DispatchCurrentMessageE
 
   return NS_OK;
 }
 
 void
 EventSourceImpl::DispatchAllMessageEvents()
 {
   AssertIsOnTargetThread();
+  mGoingToDispatchAllMessages = false;
+
   if (IsClosed() || IsFrozen()) {
     return;
   }
 
-  mGoingToDispatchAllMessages = false;
-
   nsresult rv = mEventSource->CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     return;
   }
 
   AutoJSAPI jsapi;
   if (mIsMainThread) {
     if (NS_WARN_IF(!jsapi.Init(mEventSource->GetOwner()))) {
@@ -1466,19 +1464,17 @@ EventSourceImpl::DispatchAllMessageEvent
     MOZ_ASSERT(mWorkerPrivate);
     if (NS_WARN_IF(!jsapi.Init(mWorkerPrivate->GlobalScope()))) {
       return;
     }
   }
   JSContext* cx = jsapi.cx();
 
   while (mMessagesToDispatch.GetSize() > 0) {
-    nsAutoPtr<Message>
-      message(static_cast<Message*>(mMessagesToDispatch.PopFront()));
-
+    UniquePtr<Message> message(static_cast<Message*>(mMessagesToDispatch.PopFront()));
     // Now we can turn our string into a jsval
     JS::Rooted<JS::Value> jsData(cx);
     {
       JSString* jsString;
       jsString = JS_NewUCStringCopyN(cx,
                                      message->mData.get(),
                                      message->mData.Length());
       NS_ENSURE_TRUE_VOID(jsString);
@@ -1506,64 +1502,60 @@ EventSourceImpl::DispatchAllMessageEvent
 
     mLastEventID.Assign(message->mLastEventID);
     if (IsClosed() || IsFrozen()) {
       return;
     }
   }
 }
 
-nsresult
+void
 EventSourceImpl::ClearFields()
 {
   AssertIsOnTargetThread();
-  // mLastEventID and mReconnectionTime must be cached
-  mCurrentMessage.mEventName.Truncate();
-  mCurrentMessage.mLastEventID.Truncate();
-  mCurrentMessage.mData.Truncate();
-
+  mCurrentMessage = nullptr;
   mLastFieldName.Truncate();
   mLastFieldValue.Truncate();
-
-  return NS_OK;
 }
 
 nsresult
 EventSourceImpl::SetFieldAndClear()
 {
   AssertIsOnTargetThread();
   if (mLastFieldName.IsEmpty()) {
     mLastFieldValue.Truncate();
     return NS_OK;
   }
-
+  if (!mCurrentMessage) {
+    mCurrentMessage = MakeUnique<Message>();
+  }
   char16_t first_char;
   first_char = mLastFieldName.CharAt(0);
 
   // with no case folding performed
   switch (first_char) {
     case char16_t('d'):
       if (mLastFieldName.EqualsLiteral("data")) {
         // If the field name is "data" append the field value to the data
         // buffer, then append a single U+000A LINE FEED (LF) character
         // to the data buffer.
-        mCurrentMessage.mData.Append(mLastFieldValue);
-        mCurrentMessage.mData.Append(LF_CHAR);
+        mCurrentMessage->mData.Append(mLastFieldValue);
+        mCurrentMessage->mData.Append(LF_CHAR);
       }
       break;
 
     case char16_t('e'):
       if (mLastFieldName.EqualsLiteral("event")) {
-        mCurrentMessage.mEventName.Assign(mLastFieldValue);
+        mCurrentMessage->mEventName.Assign(mLastFieldValue);
       }
       break;
 
     case char16_t('i'):
       if (mLastFieldName.EqualsLiteral("id")) {
-        mCurrentMessage.mLastEventID.Assign(mLastFieldValue);
+        mCurrentMessage->mLastEventID.Assign(mLastFieldValue);
       }
       break;
 
     case char16_t('r'):
       if (mLastFieldName.EqualsLiteral("retry")) {
         uint32_t newValue = 0;
         uint32_t i = 0;  // we must ensure that there are only digits
         bool assign = true;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -7771,17 +7771,29 @@ nsGlobalWindow::PrintOuter(ErrorResult& 
       bool printSettingsAreGlobal =
         Preferences::GetBool("print.use_global_printsettings", false);
 
       if (printSettingsAreGlobal) {
         printSettingsService->GetGlobalPrintSettings(getter_AddRefs(printSettings));
 
         nsXPIDLString printerName;
         printSettings->GetPrinterName(getter_Copies(printerName));
-        if (printerName.IsEmpty()) {
+
+        bool shouldGetDefaultPrinterName = printerName.IsEmpty();
+#ifdef MOZ_X11
+        // In Linux, GTK backend does not support per printer settings.
+        // Calling GetDefaultPrinterName causes a sandbox violation (see Bug 1329216).
+        // The printer name is not needed anywhere else on Linux before it gets to the parent.
+        // In the parent, we will then query the default printer name if no name is set.
+        // Unless we are in the parent, we will skip this part.
+        if (!XRE_IsParentProcess()) {
+          shouldGetDefaultPrinterName = false;
+        }
+#endif
+        if (shouldGetDefaultPrinterName) {
           printSettingsService->GetDefaultPrinterName(getter_Copies(printerName));
           printSettings->SetPrinterName(printerName);
         }
         printSettingsService->InitPrintSettingsFromPrinter(printerName, printSettings);
         printSettingsService->InitPrintSettingsFromPrefs(printSettings,
                                                          true,
                                                          nsIPrintSettings::kInitSaveAll);
       } else {
--- a/dom/browser-element/mochitest/chrome.ini
+++ b/dom/browser-element/mochitest/chrome.ini
@@ -57,16 +57,17 @@ tags = audiochannel
 tags = audiochannel
 skip-if = toolkit == 'android'
 [test_browserElement_inproc_AudioChannelSeeking.html]
 tags = audiochannel
 [test_browserElement_inproc_AudioPlayback.html]
 skip-if = true # bug 1332850, bug 1332862
 [test_browserElement_inproc_AudioChannel.html]
 tags = audiochannel
+skip-if = true # bug 1332862
 [test_browserElement_inproc_AudioChannel_nested.html]
 tags = audiochannel
 skip-if = toolkit == 'android' # bug 1332850
 [test_browserElement_inproc_BackForward.html]
 [test_browserElement_inproc_BadScreenshot.html]
 [test_browserElement_inproc_DocumentFirstPaint.html]
 [test_browserElement_inproc_DOMRequestError.html]
 [test_browserElement_inproc_ExecuteScript.html]
--- a/dom/canvas/test/reftest/reftest.list
+++ b/dom/canvas/test/reftest/reftest.list
@@ -158,16 +158,16 @@ skip-if(!winWidget) pref(webgl.disable-a
 
 # focus rings
 pref(canvas.focusring.enabled,true) skip-if(cocoaWidget) skip-if(winWidget) needs-focus fails-if(webrender) == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html
 pref(canvas.customfocusring.enabled,true) skip-if(Android||cocoaWidget||winWidget) fuzzy-if(gtkWidget,64,410) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html
 
 # Check that captureStream() displays in a local video element
 == capturestream.html wrapper.html?green.png
 
-fuzzy-if(azureSkia,16,2) fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),1,1) fails-if(webrender) == 1177726-text-stroke-bounds.html 1177726-text-stroke-bounds-ref.html
+fuzzy-if(azureSkia,16,2) fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),1,1) == 1177726-text-stroke-bounds.html 1177726-text-stroke-bounds-ref.html
 
 # Canvas Filter Reftests
 include filters/reftest.list
 
 # Bug 1305963
 == mozCurrentTransform.html mozCurrentTransform-ref.html
 == mozCurrentTransformInverse.html mozCurrentTransform-ref.html
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -850,21 +850,16 @@ private:
     return true;
   }
 
   void
   NotifyAudioChannelAgent(bool aPlaying)
   {
     MOZ_ASSERT(mAudioChannelAgent);
 
-    // This is needed to pass nsContentUtils::IsCallerChrome().
-    // AudioChannel API should not called from content but it can happen that
-    // this method has some content JS in its stack.
-    AutoNoJSAPI nojsapi;
-
     if (aPlaying) {
       AudioPlaybackConfig config;
       nsresult rv = mAudioChannelAgent->NotifyStartedPlaying(&config,
                                                              IsOwnerAudible());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return;
       }
 
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -14,31 +14,20 @@
 #include "webrtc/MediaEngineWebRTC.h"
 #endif
 
 #ifdef XP_MACOSX
 #include <sys/sysctl.h>
 #endif
 
 extern mozilla::LazyLogModule gMediaStreamGraphLog;
-#define STREAM_LOG(type, msg) MOZ_LOG(gMediaStreamGraphLog, type, msg)
-
-// We don't use NSPR log here because we want this interleaved with adb logcat
-// on Android/B2G
-// #define ENABLE_LIFECYCLE_LOG
-#ifdef ENABLE_LIFECYCLE_LOG
-#ifdef ANDROID
-#include "android/log.h"
-#define LIFECYCLE_LOG(...)  __android_log_print(ANDROID_LOG_INFO, "Gecko - MSG" , __VA_ARGS__);
-#else
-#define LIFECYCLE_LOG(...) printf(__VA_ARGS__);printf("\n");
-#endif
-#else
-#define LIFECYCLE_LOG(...)
-#endif
+#ifdef LOG
+#undef LOG
+#endif // LOG
+#define LOG(type, msg) MOZ_LOG(gMediaStreamGraphLog, type, msg)
 
 namespace mozilla {
 
 StaticRefPtr<nsIThreadPool> AsyncCubebTask::sThreadPool;
 
 GraphDriver::GraphDriver(MediaStreamGraphImpl* aGraphImpl)
   : mIterationStart(0),
     mIterationEnd(0),
@@ -59,35 +48,40 @@ void GraphDriver::SetGraphTime(GraphDriv
   // mIterationStart of the next iteration by setting the end of the previous
   // iteration.
   mIterationStart = aLastSwitchNextIterationStart;
   mIterationEnd = aLastSwitchNextIterationEnd;
 
   MOZ_ASSERT(!PreviousDriver());
   MOZ_ASSERT(aPreviousDriver);
 
-  STREAM_LOG(LogLevel::Debug, ("Setting previous driver: %p (%s)",
-                               aPreviousDriver,
-                               aPreviousDriver->AsAudioCallbackDriver()
-                               ? "AudioCallbackDriver"
-                               : "SystemClockDriver"));
+  LOG(LogLevel::Debug,
+      ("Setting previous driver: %p (%s)",
+       aPreviousDriver,
+       aPreviousDriver->AsAudioCallbackDriver() ? "AudioCallbackDriver"
+                                                : "SystemClockDriver"));
+
   SetPreviousDriver(aPreviousDriver);
 }
 
 void GraphDriver::SwitchAtNextIteration(GraphDriver* aNextDriver)
 {
   GraphImpl()->GetMonitor().AssertCurrentThreadOwns();
-  LIFECYCLE_LOG("Switching to new driver: %p (%s)",
-      aNextDriver, aNextDriver->AsAudioCallbackDriver() ?
-      "AudioCallbackDriver" : "SystemClockDriver");
+  LOG(LogLevel::Debug,
+      ("Switching to new driver: %p (%s)",
+       aNextDriver,
+       aNextDriver->AsAudioCallbackDriver() ? "AudioCallbackDriver"
+                                            : "SystemClockDriver"));
   if (mNextDriver &&
       mNextDriver != GraphImpl()->CurrentDriver()) {
-    LIFECYCLE_LOG("Discarding previous next driver: %p (%s)",
-                  mNextDriver.get(), mNextDriver->AsAudioCallbackDriver() ?
-                  "AudioCallbackDriver" : "SystemClockDriver");
+    LOG(LogLevel::Debug,
+        ("Discarding previous next driver: %p (%s)",
+         mNextDriver.get(),
+         mNextDriver->AsAudioCallbackDriver() ? "AudioCallbackDriver"
+                                              : "SystemClockDriver"));
   }
   SetNextDriver(aNextDriver);
 }
 
 GraphTime
 GraphDriver::StateComputedTime() const
 {
   return mGraphImpl->mStateComputedTime;
@@ -96,17 +90,18 @@ GraphDriver::StateComputedTime() const
 void GraphDriver::EnsureNextIteration()
 {
   mGraphImpl->EnsureNextIteration();
 }
 
 void GraphDriver::Shutdown()
 {
   if (AsAudioCallbackDriver()) {
-    LIFECYCLE_LOG("Releasing audio driver off main thread (GraphDriver::Shutdown).\n");
+    LOG(LogLevel::Debug,
+        ("Releasing audio driver off main thread (GraphDriver::Shutdown)."));
     RefPtr<AsyncCubebTask> releaseEvent =
       new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN);
     releaseEvent->Dispatch(NS_DISPATCH_SYNC);
   } else {
     Stop();
   }
 }
 
@@ -178,30 +173,30 @@ ThreadedDriver::~ThreadedDriver()
 class MediaStreamGraphInitThreadRunnable : public Runnable {
 public:
   explicit MediaStreamGraphInitThreadRunnable(ThreadedDriver* aDriver)
     : mDriver(aDriver)
   {
   }
   NS_IMETHOD Run() override
   {
-    STREAM_LOG(LogLevel::Debug, ("Starting system thread"));
-    LIFECYCLE_LOG("Starting a new system driver for graph %p\n",
-                  mDriver->mGraphImpl);
+    LOG(LogLevel::Debug,
+        ("Starting a new system driver for graph %p", mDriver->mGraphImpl));
 
     GraphDriver* previousDriver = nullptr;
     {
       MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor());
       previousDriver = mDriver->PreviousDriver();
     }
     if (previousDriver) {
-      LIFECYCLE_LOG("%p releasing an AudioCallbackDriver(%p), for graph %p\n",
-                    mDriver.get(),
-                    previousDriver,
-                    mDriver->GraphImpl());
+      LOG(LogLevel::Debug,
+          ("%p releasing an AudioCallbackDriver(%p), for graph %p",
+           mDriver.get(),
+           previousDriver,
+           mDriver->GraphImpl()));
       MOZ_ASSERT(!mDriver->AsAudioCallbackDriver());
       RefPtr<AsyncCubebTask> releaseEvent =
         new AsyncCubebTask(previousDriver->AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN);
       releaseEvent->Dispatch();
 
       MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor());
       mDriver->SetPreviousDriver(nullptr);
     } else {
@@ -216,17 +211,18 @@ public:
   }
 private:
   RefPtr<ThreadedDriver> mDriver;
 };
 
 void
 ThreadedDriver::Start()
 {
-  LIFECYCLE_LOG("Starting thread for a SystemClockDriver  %p\n", mGraphImpl);
+  LOG(LogLevel::Debug,
+      ("Starting thread for a SystemClockDriver  %p", mGraphImpl));
   Unused << NS_WARN_IF(mThread);
   if (!mThread) { // Ensure we haven't already started it
     nsCOMPtr<nsIRunnable> event = new MediaStreamGraphInitThreadRunnable(this);
     // Note: mThread may be null during event->Run() if we pass to NewNamedThread!  See AudioInitTask
     nsresult rv = NS_NewNamedThread("MediaStreamGrph", getter_AddRefs(mThread));
     if (NS_SUCCEEDED(rv)) {
       mThread->Dispatch(event, NS_DISPATCH_NORMAL);
     }
@@ -239,17 +235,17 @@ ThreadedDriver::Resume()
   Start();
 }
 
 void
 ThreadedDriver::Revive()
 {
   // Note: only called on MainThread, without monitor
   // We know were weren't in a running state
-  STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver reviving."));
+  LOG(LogLevel::Debug, ("AudioCallbackDriver reviving."));
   // If we were switching, switch now. Otherwise, tell thread to run the main
   // loop again.
   MonitorAutoLock mon(mGraphImpl->GetMonitor());
   if (NextDriver()) {
     NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd);
     mGraphImpl->SetCurrentDriver(NextDriver());
     NextDriver()->Start();
   } else {
@@ -263,17 +259,17 @@ ThreadedDriver::RemoveCallback()
 {
 }
 
 void
 ThreadedDriver::Stop()
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread");
   // mGraph's thread is not running so it's OK to do whatever here
-  STREAM_LOG(LogLevel::Debug, ("Stopping threads for MediaStreamGraph %p", this));
+  LOG(LogLevel::Debug, ("Stopping threads for MediaStreamGraph %p", this));
 
   if (mThread) {
     mThread->Shutdown();
     mThread = nullptr;
   }
 }
 
 SystemClockDriver::SystemClockDriver(MediaStreamGraphImpl* aGraphImpl)
@@ -303,49 +299,54 @@ ThreadedDriver::RunThread()
 {
   bool stillProcessing = true;
   while (stillProcessing) {
     mIterationStart = IterationEnd();
     mIterationEnd += GetIntervalForIteration();
 
     GraphTime stateComputedTime = StateComputedTime();
     if (stateComputedTime < mIterationEnd) {
-      STREAM_LOG(LogLevel::Warning, ("Media graph global underrun detected"));
+      LOG(LogLevel::Warning, ("Media graph global underrun detected"));
       mIterationEnd = stateComputedTime;
     }
 
     if (mIterationStart >= mIterationEnd) {
       NS_ASSERTION(mIterationStart == mIterationEnd ,
                    "Time can't go backwards!");
       // This could happen due to low clock resolution, maybe?
-      STREAM_LOG(LogLevel::Debug, ("Time did not advance"));
+      LOG(LogLevel::Debug, ("Time did not advance"));
     }
 
     GraphTime nextStateComputedTime =
       mGraphImpl->RoundUpToNextAudioBlock(
         mIterationEnd + mGraphImpl->MillisecondsToMediaTime(AUDIO_TARGET_MS));
     if (nextStateComputedTime < stateComputedTime) {
       // A previous driver may have been processing further ahead of
       // iterationEnd.
-      STREAM_LOG(LogLevel::Warning,
-                 ("Prevent state from going backwards. interval[%ld; %ld] state[%ld; %ld]",
-                  (long)mIterationStart, (long)mIterationEnd,
-                  (long)stateComputedTime, (long)nextStateComputedTime));
+      LOG(LogLevel::Warning,
+          ("Prevent state from going backwards. interval[%ld; %ld] state[%ld; "
+           "%ld]",
+           (long)mIterationStart,
+           (long)mIterationEnd,
+           (long)stateComputedTime,
+           (long)nextStateComputedTime));
       nextStateComputedTime = stateComputedTime;
     }
-    STREAM_LOG(LogLevel::Verbose,
-               ("interval[%ld; %ld] state[%ld; %ld]",
-               (long)mIterationStart, (long)mIterationEnd,
-               (long)stateComputedTime, (long)nextStateComputedTime));
+    LOG(LogLevel::Verbose,
+        ("interval[%ld; %ld] state[%ld; %ld]",
+         (long)mIterationStart,
+         (long)mIterationEnd,
+         (long)stateComputedTime,
+         (long)nextStateComputedTime));
 
     stillProcessing = mGraphImpl->OneIteration(nextStateComputedTime);
 
     MonitorAutoLock lock(GraphImpl()->GetMonitor());
     if (NextDriver() && stillProcessing) {
-      STREAM_LOG(LogLevel::Debug, ("Switching to AudioCallbackDriver"));
+      LOG(LogLevel::Debug, ("Switching to AudioCallbackDriver"));
       RemoveCallback();
       NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd);
       mGraphImpl->SetCurrentDriver(NextDriver());
       NextDriver()->Start();
       return;
     }
   }
 }
@@ -394,29 +395,31 @@ SystemClockDriver::WaitForNextIteration(
   // mGraphDriverAsleep
   if (another || mGraphImpl->mNeedAnotherIteration) { // atomic
     int64_t timeoutMS = MEDIA_GRAPH_TARGET_PERIOD_MS -
       int64_t((now - mCurrentTimeStamp).ToMilliseconds());
     // Make sure timeoutMS doesn't overflow 32 bits by waking up at
     // least once a minute, if we need to wake up at all
     timeoutMS = std::max<int64_t>(0, std::min<int64_t>(timeoutMS, 60*1000));
     timeout = PR_MillisecondsToInterval(uint32_t(timeoutMS));
-    STREAM_LOG(LogLevel::Verbose,
-               ("Waiting for next iteration; at %f, timeout=%f",
-                (now - mInitialTimeStamp).ToSeconds(), timeoutMS/1000.0));
+    LOG(LogLevel::Verbose,
+        ("Waiting for next iteration; at %f, timeout=%f",
+         (now - mInitialTimeStamp).ToSeconds(),
+         timeoutMS / 1000.0));
     if (mWaitState == WAITSTATE_WAITING_INDEFINITELY) {
       mGraphImpl->mGraphDriverAsleep = false; // atomic
     }
     mWaitState = WAITSTATE_WAITING_FOR_NEXT_ITERATION;
   }
   if (timeout > 0) {
     mGraphImpl->GetMonitor().Wait(timeout);
-    STREAM_LOG(LogLevel::Verbose, ("Resuming after timeout; at %f, elapsed=%f",
-          (TimeStamp::Now() - mInitialTimeStamp).ToSeconds(),
-          (TimeStamp::Now() - now).ToSeconds()));
+    LOG(LogLevel::Verbose,
+        ("Resuming after timeout; at %f, elapsed=%f",
+         (TimeStamp::Now() - mInitialTimeStamp).ToSeconds(),
+         (TimeStamp::Now() - now).ToSeconds()));
   }
 
   if (mWaitState == WAITSTATE_WAITING_INDEFINITELY) {
     mGraphImpl->mGraphDriverAsleep = false; // atomic
   }
   // Note: this can race against the EnsureNextIteration setting
   // WAITSTATE_RUNNING and setting mGraphDriverAsleep to false, so you can
   // have an iteration with WAITSTATE_WAKING_UP instead of RUNNING.
@@ -509,25 +512,27 @@ AsyncCubebTask::EnsureThread()
 
 NS_IMETHODIMP
 AsyncCubebTask::Run()
 {
   MOZ_ASSERT(mDriver);
 
   switch(mOperation) {
     case AsyncCubebOperation::INIT: {
-      LIFECYCLE_LOG("AsyncCubebOperation::INIT driver=%p\n", mDriver.get());
+      LOG(LogLevel::Debug,
+          ("AsyncCubebOperation::INIT driver=%p", mDriver.get()));
       if (!mDriver->Init()) {
         return NS_ERROR_FAILURE;
       }
       mDriver->CompleteAudioContextOperations(mOperation);
       break;
     }
     case AsyncCubebOperation::SHUTDOWN: {
-      LIFECYCLE_LOG("AsyncCubebOperation::SHUTDOWN driver=%p\n", mDriver.get());
+      LOG(LogLevel::Debug,
+          ("AsyncCubebOperation::SHUTDOWN driver=%p", mDriver.get()));
       mDriver->Stop();
 
       mDriver->CompleteAudioContextOperations(mOperation);
 
       mDriver = nullptr;
       mShutdownGrip = nullptr;
       break;
     }
@@ -557,17 +562,17 @@ AudioCallbackDriver::AudioCallbackDriver
   , mStarted(false)
   , mAudioInput(nullptr)
   , mAudioChannel(aGraphImpl->AudioChannel())
   , mAddedMixer(false)
   , mInCallback(false)
   , mMicrophoneActive(false)
   , mFromFallback(false)
 {
-  STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver ctor for graph %p", aGraphImpl));
+  LOG(LogLevel::Debug, ("AudioCallbackDriver ctor for graph %p", aGraphImpl));
 }
 
 AudioCallbackDriver::~AudioCallbackDriver()
 {
   MOZ_ASSERT(mPromisesForOperation.IsEmpty());
 }
 
 bool IsMacbookOrMacbookAir()
@@ -724,63 +729,66 @@ AudioCallbackDriver::Init()
   bool aec;
   Unused << mGraphImpl->AudioTrackPresent(aec);
   SetMicrophoneActive(aec);
 
   cubeb_stream_register_device_changed_callback(mAudioStream,
                                                 AudioCallbackDriver::DeviceChangedCallback_s);
 
   if (!StartStream()) {
-    STREAM_LOG(LogLevel::Warning, ("AudioCallbackDriver couldn't start stream."));
+    LOG(LogLevel::Warning, ("AudioCallbackDriver couldn't start stream."));
     return false;
   }
 
-  STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver started."));
+  LOG(LogLevel::Debug, ("AudioCallbackDriver started."));
   return true;
 }
 
 
 void
 AudioCallbackDriver::Destroy()
 {
-  STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver destroyed."));
+  LOG(LogLevel::Debug, ("AudioCallbackDriver destroyed."));
   mAudioInput = nullptr;
   mAudioStream.reset();
 }
 
 void
 AudioCallbackDriver::Resume()
 {
-  STREAM_LOG(LogLevel::Debug, ("Resuming audio threads for MediaStreamGraph %p", mGraphImpl));
+  LOG(LogLevel::Debug,
+      ("Resuming audio threads for MediaStreamGraph %p", mGraphImpl));
   if (cubeb_stream_start(mAudioStream) != CUBEB_OK) {
     NS_WARNING("Could not start cubeb stream for MSG.");
   }
 }
 
 void
 AudioCallbackDriver::Start()
 {
   if (mPreviousDriver) {
     if (mPreviousDriver->AsAudioCallbackDriver()) {
-      LIFECYCLE_LOG("Releasing audio driver off main thread.");
+      LOG(LogLevel::Debug, ("Releasing audio driver off main thread."));
       RefPtr<AsyncCubebTask> releaseEvent =
         new AsyncCubebTask(mPreviousDriver->AsAudioCallbackDriver(),
                            AsyncCubebOperation::SHUTDOWN);
       releaseEvent->Dispatch();
       mPreviousDriver = nullptr;
     } else {
-      LIFECYCLE_LOG("Dropping driver reference for SystemClockDriver.");
+      LOG(LogLevel::Debug,
+          ("Dropping driver reference for SystemClockDriver."));
       MOZ_ASSERT(mPreviousDriver->AsSystemClockDriver());
       mFromFallback = mPreviousDriver->AsSystemClockDriver()->IsFallback();
       mPreviousDriver = nullptr;
     }
   }
 
-  LIFECYCLE_LOG("Starting new audio driver off main thread, "
-                "to ensure it runs after previous shutdown.");
+  LOG(LogLevel::Debug,
+      ("Starting new audio driver off main thread, "
+       "to ensure it runs after previous shutdown."));
   RefPtr<AsyncCubebTask> initEvent =
     new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebOperation::INIT);
   initEvent->Dispatch();
 }
 
 bool
 AudioCallbackDriver::StartStream()
 {
@@ -805,26 +813,28 @@ AudioCallbackDriver::Stop()
   }
 }
 
 void
 AudioCallbackDriver::Revive()
 {
   // Note: only called on MainThread, without monitor
   // We know were weren't in a running state
-  STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver reviving."));
+  LOG(LogLevel::Debug, ("AudioCallbackDriver reviving."));
   // If we were switching, switch now. Otherwise, start the audio thread again.
   MonitorAutoLock mon(mGraphImpl->GetMonitor());
   if (NextDriver()) {
     RemoveCallback();
     NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd);
     mGraphImpl->SetCurrentDriver(NextDriver());
     NextDriver()->Start();
   } else {
-    STREAM_LOG(LogLevel::Debug, ("Starting audio threads for MediaStreamGraph %p from a new thread.", mGraphImpl));
+    LOG(LogLevel::Debug,
+        ("Starting audio threads for MediaStreamGraph %p from a new thread.",
+         mGraphImpl));
     RefPtr<AsyncCubebTask> initEvent =
       new AsyncCubebTask(this, AsyncCubebOperation::INIT);
     initEvent->Dispatch();
   }
 }
 
 void
 AudioCallbackDriver::RemoveCallback()
@@ -963,32 +973,39 @@ AudioCallbackDriver::DataCallback(const 
     // We want the interval [mIterationStart; mIterationEnd] to be before the
     // interval [stateComputedTime; nextStateComputedTime]. We also want
     // the distance between these intervals to be roughly equivalent each time, to
     // ensure there is no clock drift between current time and state time. Since
     // we can't act on the state time because we have to fill the audio buffer, we
     // reclock the current time against the state time, here.
     mIterationEnd = mIterationStart + 0.8 * inGraph;
 
-    STREAM_LOG(LogLevel::Verbose, ("interval[%ld; %ld] state[%ld; %ld] (frames: %ld) (durationMS: %u) (duration ticks: %ld)\n",
-                                   (long)mIterationStart, (long)mIterationEnd,
-                                   (long)stateComputedTime, (long)nextStateComputedTime,
-                                   (long)aFrames, (uint32_t)durationMS,
-                                   (long)(nextStateComputedTime - stateComputedTime)));
+    LOG(LogLevel::Verbose,
+        ("interval[%ld; %ld] state[%ld; %ld] (frames: %ld) (durationMS: %u) "
+         "(duration ticks: %ld)",
+         (long)mIterationStart,
+         (long)mIterationEnd,
+         (long)stateComputedTime,
+         (long)nextStateComputedTime,
+         (long)aFrames,
+         (uint32_t)durationMS,
+         (long)(nextStateComputedTime - stateComputedTime)));
 
     mCurrentTimeStamp = TimeStamp::Now();
 
     if (stateComputedTime < mIterationEnd) {
-      STREAM_LOG(LogLevel::Warning, ("Media graph global underrun detected"));
+      LOG(LogLevel::Warning, ("Media graph global underrun detected"));
       mIterationEnd = stateComputedTime;
     }
 
     stillProcessing = mGraphImpl->OneIteration(nextStateComputedTime);
   } else {
-    STREAM_LOG(LogLevel::Verbose, ("DataCallback buffer filled entirely from scratch buffer, skipping iteration."));
+    LOG(LogLevel::Verbose,
+        ("DataCallback buffer filled entirely from scratch "
+         "buffer, skipping iteration."));
     stillProcessing = true;
   }
 
   mBuffer.BufferFilled();
 
   // Callback any observers for the AEC speaker data.  Note that one
   // (maybe) of these will be full-duplex, the others will get their input
   // data off separate cubeb callbacks.  Take care with how stuff is
@@ -1005,37 +1022,38 @@ AudioCallbackDriver::DataCallback(const 
 
   if (switching && stillProcessing) {
     // If the audio stream has not been started by the previous driver or
     // the graph itself, keep it alive.
     MonitorAutoLock mon(mGraphImpl->GetMonitor());
     if (!IsStarted()) {
       return aFrames;
     }
-    STREAM_LOG(LogLevel::Debug, ("Switching to system driver."));
+    LOG(LogLevel::Debug, ("Switching to system driver."));
     RemoveCallback();
     NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd);
     mGraphImpl->SetCurrentDriver(NextDriver());
     NextDriver()->Start();
     // Returning less than aFrames starts the draining and eventually stops the
     // audio thread. This function will never get called again.
     return aFrames - 1;
   }
 
   if (!stillProcessing) {
-    LIFECYCLE_LOG("Stopping audio thread for MediaStreamGraph %p", this);
+    LOG(LogLevel::Debug,
+        ("Stopping audio thread for MediaStreamGraph %p", this));
     return aFrames - 1;
   }
   return aFrames;
 }
 
 void
 AudioCallbackDriver::StateCallback(cubeb_state aState)
 {
-  STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver State: %d", aState));
+  LOG(LogLevel::Debug, ("AudioCallbackDriver State: %d", aState));
 }
 
 void
 AudioCallbackDriver::MixerCallback(AudioDataValue* aMixedBuffer,
                                    AudioSampleFormat aFormat,
                                    uint32_t aChannels,
                                    uint32_t aFrames,
                                    uint32_t aSampleRate)
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -42,65 +42,54 @@
 using namespace mozilla::layers;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::media;
 
 namespace mozilla {
 
 LazyLogModule gMediaStreamGraphLog("MediaStreamGraph");
-#define STREAM_LOG(type, msg) MOZ_LOG(gMediaStreamGraphLog, type, msg)
-
-// #define ENABLE_LIFECYCLE_LOG
-
-// We don't use NSPR log here because we want this interleaved with adb logcat
-// on Android/B2G
-#ifdef ENABLE_LIFECYCLE_LOG
-#  ifdef ANDROID
-#    include "android/log.h"
-#    define LIFECYCLE_LOG(...)  __android_log_print(ANDROID_LOG_INFO, "Gecko - MSG", ## __VA_ARGS__);
-#  else
-#    define LIFECYCLE_LOG(...) printf(__VA_ARGS__);printf("\n");
-#  endif
-#else
-#  define LIFECYCLE_LOG(...)
-#endif
+#ifdef LOG
+#undef LOG
+#endif // LOG
+#define LOG(type, msg) MOZ_LOG(gMediaStreamGraphLog, type, msg)
 
 enum SourceMediaStream::TrackCommands : uint32_t {
   TRACK_CREATE = TrackEventCommand::TRACK_EVENT_CREATED,
   TRACK_END = TrackEventCommand::TRACK_EVENT_ENDED,
   TRACK_UNUSED = TrackEventCommand::TRACK_EVENT_UNUSED,
 };
 
 /**
  * A hash table containing the graph instances, one per AudioChannel.
  */
 static nsDataHashtable<nsUint32HashKey, MediaStreamGraphImpl*> gGraphs;
 
 MediaStreamGraphImpl::~MediaStreamGraphImpl()
 {
   NS_ASSERTION(IsEmpty(),
                "All streams should have been destroyed by messages from the main thread");
-  STREAM_LOG(LogLevel::Debug, ("MediaStreamGraph %p destroyed", this));
-  LIFECYCLE_LOG("MediaStreamGraphImpl::~MediaStreamGraphImpl\n");
+  LOG(LogLevel::Debug, ("MediaStreamGraph %p destroyed", this));
+  LOG(LogLevel::Debug, ("MediaStreamGraphImpl::~MediaStreamGraphImpl"));
 }
 
 void
 MediaStreamGraphImpl::FinishStream(MediaStream* aStream)
 {
   if (aStream->mFinished)
     return;
-  STREAM_LOG(LogLevel::Debug, ("MediaStream %p will finish", aStream));
+  LOG(LogLevel::Debug, ("MediaStream %p will finish", aStream));
 #ifdef DEBUG
   for (StreamTracks::TrackIter track(aStream->mTracks);
          !track.IsEnded(); track.Next()) {
     if (!track->IsEnded()) {
-      STREAM_LOG(LogLevel::Error,
-                 ("MediaStream %p will finish, but track %d has not ended.",
-                  aStream, track->GetID()));
+      LOG(LogLevel::Error,
+          ("MediaStream %p will finish, but track %d has not ended.",
+           aStream,
+           track->GetID()));
       NS_ASSERTION(false, "Finished stream cannot contain live track");
     }
   }
 #endif
   aStream->mFinished = true;
   aStream->mTracks.AdvanceKnownTracksTime(STREAM_TIME_MAX);
 
   SetStreamOrderDirty();
@@ -116,21 +105,31 @@ MediaStreamGraphImpl::AddStreamGraphThre
     TimeStamp currentTimeStamp = CurrentDriver()->GetCurrentTimeStamp();
     TimeStamp processedTimeStamp = currentTimeStamp +
       TimeDuration::FromSeconds(MediaTimeToSeconds(mProcessedTime - IterationEnd()));
     source->SetStreamTracksStartTimeStamp(processedTimeStamp);
   }
 
   if (aStream->IsSuspended()) {
     mSuspendedStreams.AppendElement(aStream);
-    STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to the graph, in the suspended stream array", aStream));
+    LOG(LogLevel::Debug,
+        ("Adding media stream %p to the graph, in the suspended stream array",
+         aStream));
   } else {
     mStreams.AppendElement(aStream);
-    STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to graph %p, count %lu", aStream, this, mStreams.Length()));
-    LIFECYCLE_LOG("Adding media stream %p to graph %p, count %lu", aStream, this, (long)mStreams.Length());
+    LOG(LogLevel::Debug,
+        ("Adding media stream %p to graph %p, count %lu",
+         aStream,
+         this,
+         mStreams.Length()));
+    LOG(LogLevel::Debug,
+        ("Adding media stream %p to graph %p, count %lu",
+         aStream,
+         this,
+         (long)mStreams.Length()));
   }
 
   SetStreamOrderDirty();
 }
 
 void
 MediaStreamGraphImpl::RemoveStreamGraphThread(MediaStream* aStream)
 {
@@ -150,20 +149,26 @@ MediaStreamGraphImpl::RemoveStreamGraphT
   SetStreamOrderDirty();
 
   if (aStream->IsSuspended()) {
     mSuspendedStreams.RemoveElement(aStream);
   } else {
     mStreams.RemoveElement(aStream);
   }
 
-  STREAM_LOG(LogLevel::Debug, ("Removed media stream %p from graph %p, count %lu",
-                               aStream, this, mStreams.Length()));
-  LIFECYCLE_LOG("Removed media stream %p from graph %p, count %lu",
-                aStream, this, (long)mStreams.Length());
+  LOG(LogLevel::Debug,
+      ("Removed media stream %p from graph %p, count %lu",
+       aStream,
+       this,
+       mStreams.Length()));
+  LOG(LogLevel::Debug,
+      ("Removed media stream %p from graph %p, count %lu",
+       aStream,
+       this,
+       (long)mStreams.Length()));
 
   NS_RELEASE(aStream); // probably destroying it
 }
 
 void
 MediaStreamGraphImpl::ExtractPendingInput(SourceMediaStream* aStream,
                                           GraphTime aDesiredUpToTime,
                                           bool* aEnsureNextIteration)
@@ -171,26 +176,31 @@ MediaStreamGraphImpl::ExtractPendingInpu
   bool finished;
   {
     MutexAutoLock lock(aStream->mMutex);
     if (aStream->mPullEnabled && !aStream->mFinished &&
         !aStream->mListeners.IsEmpty()) {
       // Compute how much stream time we'll need assuming we don't block
       // the stream at all.
       StreamTime t = aStream->GraphTimeToStreamTime(aDesiredUpToTime);
-      STREAM_LOG(LogLevel::Verbose, ("Calling NotifyPull aStream=%p t=%f current end=%f", aStream,
-                                  MediaTimeToSeconds(t),
-                                  MediaTimeToSeconds(aStream->mTracks.GetEnd())));
+      LOG(LogLevel::Verbose,
+          ("Calling NotifyPull aStream=%p t=%f current end=%f",
+           aStream,
+           MediaTimeToSeconds(t),
+           MediaTimeToSeconds(aStream->mTracks.GetEnd())));
       if (t > aStream->mTracks.GetEnd()) {
         *aEnsureNextIteration = true;
 #ifdef DEBUG
         if (aStream->mListeners.Length() == 0) {
-          STREAM_LOG(LogLevel::Error, ("No listeners in NotifyPull aStream=%p desired=%f current end=%f",
-                                    aStream, MediaTimeToSeconds(t),
-                                    MediaTimeToSeconds(aStream->mTracks.GetEnd())));
+          LOG(
+            LogLevel::Error,
+            ("No listeners in NotifyPull aStream=%p desired=%f current end=%f",
+             aStream,
+             MediaTimeToSeconds(t),
+             MediaTimeToSeconds(aStream->mTracks.GetEnd())));
           aStream->DumpTrackInfo();
         }
 #endif
         for (uint32_t j = 0; j < aStream->mListeners.Length(); ++j) {
           MediaStreamListener* l = aStream->mListeners[j];
           {
             MutexAutoUnlock unlock(aStream->mMutex);
             l->NotifyPull(this, t);
@@ -253,33 +263,39 @@ MediaStreamGraphImpl::ExtractPendingInpu
         }
         b.mListener->NotifyQueuedChanges(this, offset, *data->mData);
         if (data->mCommands & SourceMediaStream::TRACK_END) {
           b.mListener->NotifyEnded();
         }
       }
       if (data->mCommands & SourceMediaStream::TRACK_CREATE) {
         MediaSegment* segment = data->mData.forget();
-        STREAM_LOG(LogLevel::Debug, ("SourceMediaStream %p creating track %d, start %lld, initial end %lld",
-                                  aStream, data->mID, int64_t(data->mStart),
-                                  int64_t(segment->GetDuration())));
+        LOG(LogLevel::Debug,
+            ("SourceMediaStream %p creating track %d, start %lld, initial end "
+             "%lld",
+             aStream,
+             data->mID,
+             int64_t(data->mStart),
+             int64_t(segment->GetDuration())));
 
         data->mEndOfFlushedData += segment->GetDuration();
         aStream->mTracks.AddTrack(data->mID, data->mStart, segment);
         // The track has taken ownership of data->mData, so let's replace
         // data->mData with an empty clone.
         data->mData = segment->CreateEmptyClone();
         data->mCommands &= ~SourceMediaStream::TRACK_CREATE;
         shouldNotifyTrackCreated = true;
       } else if (data->mData->GetDuration() > 0) {
         MediaSegment* dest = aStream->mTracks.FindTrack(data->mID)->GetSegment();
-        STREAM_LOG(LogLevel::Verbose, ("SourceMediaStream %p track %d, advancing end from %lld to %lld",
-                                    aStream, data->mID,
-                                    int64_t(dest->GetDuration()),
-                                    int64_t(dest->GetDuration() + data->mData->GetDuration())));
+        LOG(LogLevel::Verbose,
+            ("SourceMediaStream %p track %d, advancing end from %lld to %lld",
+             aStream,
+             data->mID,
+             int64_t(dest->GetDuration()),
+             int64_t(dest->GetDuration() + data->mData->GetDuration())));
         data->mEndOfFlushedData += data->mData->GetDuration();
         dest->AppendFrom(data->mData);
       }
       if (data->mCommands & SourceMediaStream::TRACK_END) {
         aStream->mTracks.FindTrack(data->mID)->SetEnded();
         aStream->mUpdateTracks.RemoveElementAt(i);
       }
     }
@@ -323,20 +339,21 @@ MediaStreamGraphImpl::UpdateCurrentTimeF
     bool isAnyBlocked = stream->mStartBlocking < mStateComputedTime;
     bool isAnyUnblocked = stream->mStartBlocking > aPrevCurrentTime;
 
     // Calculate blocked time and fire Blocked/Unblocked events
     GraphTime blockedTime = mStateComputedTime - stream->mStartBlocking;
     NS_ASSERTION(blockedTime >= 0, "Error in blocking time");
     stream->AdvanceTimeVaryingValuesToCurrentTime(mStateComputedTime,
                                                   blockedTime);
-    STREAM_LOG(LogLevel::Verbose,
-               ("MediaStream %p bufferStartTime=%f blockedTime=%f", stream,
-                MediaTimeToSeconds(stream->mTracksStartTime),
-                MediaTimeToSeconds(blockedTime)));
+    LOG(LogLevel::Verbose,
+        ("MediaStream %p bufferStartTime=%f blockedTime=%f",
+         stream,
+         MediaTimeToSeconds(stream->mTracksStartTime),
+         MediaTimeToSeconds(blockedTime)));
     stream->mStartBlocking = mStateComputedTime;
 
     if (isAnyUnblocked && stream->mNotifiedBlocked) {
       for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
         MediaStreamListener* l = stream->mListeners[j];
         l->NotifyBlockingChanged(this, MediaStreamListener::UNBLOCKED);
       }
       stream->mNotifiedBlocked = false;
@@ -392,22 +409,23 @@ MediaStreamGraphImpl::ProcessChunkMetada
     }
     offset += chunk->GetDuration();
     if (chunk->IsNull() || offset < aStart) {
       continue;
     }
     PrincipalHandle principalHandle = chunk->GetPrincipalHandle();
     if (principalHandle != aSegment.GetLastPrincipalHandle()) {
       aSegment.SetLastPrincipalHandle(principalHandle);
-      STREAM_LOG(LogLevel::Debug,
-                 ("MediaStream %p track %d, principalHandle "
-                  "changed in %sChunk with duration %lld",
-                  aStream, aTrackID,
-                  aSegment.GetType() == MediaSegment::AUDIO ? "Audio" : "Video",
-                  (long long)chunk->GetDuration()));
+      LOG(LogLevel::Debug,
+          ("MediaStream %p track %d, principalHandle "
+           "changed in %sChunk with duration %lld",
+           aStream,
+           aTrackID,
+           aSegment.GetType() == MediaSegment::AUDIO ? "Audio" : "Video",
+           (long long)chunk->GetDuration()));
       for (const TrackBound<MediaStreamTrackListener>& listener :
            aStream->mTrackListeners) {
         if (listener.mTrackID == aTrackID) {
           listener.mListener->NotifyPrincipalHandleChanged(this, principalHandle);
         }
       }
     }
   }
@@ -451,20 +469,25 @@ MediaStreamGraphImpl::WillUnderrun(Media
     return aEndBlockingDecisions;
   }
   // This stream isn't finished or suspended. We don't need to call
   // StreamTimeToGraphTime since an underrun is the only thing that can block
   // it.
   GraphTime bufferEnd = aStream->GetTracksEnd() + aStream->mTracksStartTime;
 #ifdef DEBUG
   if (bufferEnd < mProcessedTime) {
-    STREAM_LOG(LogLevel::Error, ("MediaStream %p underrun, "
-                              "bufferEnd %f < mProcessedTime %f (%lld < %lld), Streamtime %lld",
-                              aStream, MediaTimeToSeconds(bufferEnd), MediaTimeToSeconds(mProcessedTime),
-                              bufferEnd, mProcessedTime, aStream->GetTracksEnd()));
+    LOG(LogLevel::Error,
+        ("MediaStream %p underrun, "
+         "bufferEnd %f < mProcessedTime %f (%lld < %lld), Streamtime %lld",
+         aStream,
+         MediaTimeToSeconds(bufferEnd),
+         MediaTimeToSeconds(mProcessedTime),
+         bufferEnd,
+         mProcessedTime,
+         aStream->GetTracksEnd()));
     aStream->DumpTrackInfo();
     NS_ASSERTION(bufferEnd >= mProcessedTime, "Buffer underran");
   }
 #endif
   return std::min(bufferEnd, aEndBlockingDecisions);
 }
 
 namespace {
@@ -781,17 +804,18 @@ MediaStreamGraphImpl::CreateOrDestroyAud
     return;
   }
 
   if (!aStream->GetStreamTracks().GetAndResetTracksDirty() &&
       !aStream->mAudioOutputStreams.IsEmpty()) {
     return;
   }
 
-  STREAM_LOG(LogLevel::Debug, ("Updating AudioOutputStreams for MediaStream %p", aStream));
+  LOG(LogLevel::Debug,
+      ("Updating AudioOutputStreams for MediaStream %p", aStream));
 
   AutoTArray<bool,2> audioOutputStreamsFound;
   for (uint32_t i = 0; i < aStream->mAudioOutputStreams.Length(); ++i) {
     audioOutputStreamsFound.AppendElement(false);
   }
 
   for (StreamTracks::TrackIter tracks(aStream->GetStreamTracks(), MediaSegment::AUDIO);
        !tracks.IsEnded(); tracks.Next()) {
@@ -871,55 +895,73 @@ MediaStreamGraphImpl::PlayAudio(MediaStr
 
       // Check how many ticks of sound we can provide if we are blocked some
       // time in the middle of this cycle.
       StreamTime toWrite = end - t;
 
       if (blocked) {
         output.InsertNullDataAtStart(toWrite);
         ticksWritten += toWrite;
-        STREAM_LOG(LogLevel::Verbose, ("MediaStream %p writing %ld blocking-silence samples for %f to %f (%ld to %ld)\n",
-                                    aStream, toWrite, MediaTimeToSeconds(t), MediaTimeToSeconds(end),
-                                    offset, offset + toWrite));
+        LOG(LogLevel::Verbose,
+            ("MediaStream %p writing %ld blocking-silence samples for "
+             "%f to %f (%ld to %ld)",
+             aStream,
+             toWrite,
+             MediaTimeToSeconds(t),
+             MediaTimeToSeconds(end),
+             offset,
+             offset + toWrite));
       } else {
         StreamTime endTicksNeeded = offset + toWrite;
         StreamTime endTicksAvailable = audio->GetDuration();
 
         if (endTicksNeeded <= endTicksAvailable) {
-          STREAM_LOG(LogLevel::Verbose,
-                     ("MediaStream %p writing %ld samples for %f to %f "
-                      "(samples %ld to %ld)\n",
-                      aStream, toWrite, MediaTimeToSeconds(t),
-                      MediaTimeToSeconds(end), offset, endTicksNeeded));
+          LOG(LogLevel::Verbose,
+              ("MediaStream %p writing %ld samples for %f to %f "
+               "(samples %ld to %ld)",
+               aStream,
+               toWrite,
+               MediaTimeToSeconds(t),
+               MediaTimeToSeconds(end),
+               offset,
+               endTicksNeeded));
           output.AppendSlice(*audio, offset, endTicksNeeded);
           ticksWritten += toWrite;
           offset = endTicksNeeded;
         } else {
           // MOZ_ASSERT(track->IsEnded(), "Not enough data, and track not ended.");
           // If we are at the end of the track, maybe write the remaining
           // samples, and pad with/output silence.
           if (endTicksNeeded > endTicksAvailable &&
               offset < endTicksAvailable) {
             output.AppendSlice(*audio, offset, endTicksAvailable);
-            STREAM_LOG(LogLevel::Verbose,
-                       ("MediaStream %p writing %ld samples for %f to %f "
-                        "(samples %ld to %ld)\n",
-                        aStream, toWrite, MediaTimeToSeconds(t),
-                        MediaTimeToSeconds(end), offset, endTicksNeeded));
+            LOG(LogLevel::Verbose,
+                ("MediaStream %p writing %ld samples for %f to %f "
+                 "(samples %ld to %ld)",
+                 aStream,
+                 toWrite,
+                 MediaTimeToSeconds(t),
+                 MediaTimeToSeconds(end),
+                 offset,
+                 endTicksNeeded));
             uint32_t available = endTicksAvailable - offset;
             ticksWritten += available;
             toWrite -= available;
             offset = endTicksAvailable;
           }
           output.AppendNullData(toWrite);
-          STREAM_LOG(LogLevel::Verbose,
-                     ("MediaStream %p writing %ld padding slsamples for %f to "
-                      "%f (samples %ld to %ld)\n",
-                      aStream, toWrite, MediaTimeToSeconds(t),
-                      MediaTimeToSeconds(end), offset, endTicksNeeded));
+          LOG(LogLevel::Verbose,
+              ("MediaStream %p writing %ld padding slsamples for %f to "
+               "%f (samples %ld to %ld)",
+               aStream,
+               toWrite,
+               MediaTimeToSeconds(t),
+               MediaTimeToSeconds(end),
+               offset,
+               endTicksNeeded));
           ticksWritten += toWrite;
         }
         output.ApplyVolume(volume);
       }
       t = end;
     }
     audioOutput.mLastTickWritten = offset;
 
@@ -959,24 +1001,28 @@ MediaStreamGraphImpl::OpenAudioInputImpl
     // we close cubeb.
     mInputDeviceID = aID;
     mAudioInputs.AppendElement(aListener); // always monitor speaker data
 
     // Switch Drivers since we're adding input (to input-only or full-duplex)
     MonitorAutoLock mon(mMonitor);
     if (mLifecycleState == LIFECYCLE_RUNNING) {
       AudioCallbackDriver* driver = new AudioCallbackDriver(this);
-      STREAM_LOG(LogLevel::Debug, ("OpenAudioInput: starting new AudioCallbackDriver(input) %p", driver));
-      LIFECYCLE_LOG("OpenAudioInput: starting new AudioCallbackDriver(input) %p", driver);
+      LOG(
+        LogLevel::Debug,
+        ("OpenAudioInput: starting new AudioCallbackDriver(input) %p", driver));
+      LOG(
+        LogLevel::Debug,
+        ("OpenAudioInput: starting new AudioCallbackDriver(input) %p", driver));
       driver->SetInputListener(aListener);
       CurrentDriver()->SwitchAtNextIteration(driver);
    } else {
-      STREAM_LOG(LogLevel::Error, ("OpenAudioInput in shutdown!"));
-      LIFECYCLE_LOG("OpenAudioInput in shutdown!");
-      NS_ASSERTION(false, "Can't open cubeb inputs in shutdown");
+     LOG(LogLevel::Error, ("OpenAudioInput in shutdown!"));
+     LOG(LogLevel::Debug, ("OpenAudioInput in shutdown!"));
+     NS_ASSERTION(false, "Can't open cubeb inputs in shutdown");
     }
   }
 }
 
 nsresult
 MediaStreamGraphImpl::OpenAudioInput(int aID,
                                      AudioDataListener *aListener)
 {
@@ -1028,22 +1074,23 @@ MediaStreamGraphImpl::CloseAudioInputImp
   bool shouldAEC = false;
   bool audioTrackPresent = AudioTrackPresent(shouldAEC);
 
   MonitorAutoLock mon(mMonitor);
   if (mLifecycleState == LIFECYCLE_RUNNING) {
     GraphDriver* driver;
     if (audioTrackPresent) {
       // We still have audio output
-      STREAM_LOG(LogLevel::Debug, ("CloseInput: output present (AudioCallback)"));
+      LOG(LogLevel::Debug, ("CloseInput: output present (AudioCallback)"));
 
       driver = new AudioCallbackDriver(this);
       CurrentDriver()->SwitchAtNextIteration(driver);
     } else if (CurrentDriver()->AsAudioCallbackDriver()) {
-      STREAM_LOG(LogLevel::Debug, ("CloseInput: no output present (SystemClockCallback)"));
+      LOG(LogLevel::Debug,
+          ("CloseInput: no output present (SystemClockCallback)"));
 
       driver = new SystemClockDriver(this);
       CurrentDriver()->SwitchAtNextIteration(driver);
     } // else SystemClockDriver->SystemClockDriver, no switch
   }
 }
 
 void
@@ -1271,22 +1318,26 @@ MediaStreamGraphImpl::UpdateGraph(GraphT
 
     if (stream->mFinished) {
       // The stream's not suspended, and since it's finished, underruns won't
       // stop it playing out. So there's no blocking other than what we impose
       // here.
       GraphTime endTime = stream->GetStreamTracks().GetAllTracksEnd() +
           stream->mTracksStartTime;
       if (endTime <= mStateComputedTime) {
-        STREAM_LOG(LogLevel::Verbose, ("MediaStream %p is blocked due to being finished", stream));
+        LOG(LogLevel::Verbose,
+            ("MediaStream %p is blocked due to being finished", stream));
         stream->mStartBlocking = mStateComputedTime;
       } else {
-        STREAM_LOG(LogLevel::Verbose, ("MediaStream %p is finished, but not blocked yet (end at %f, with blocking at %f)",
-            stream, MediaTimeToSeconds(stream->GetTracksEnd()),
-            MediaTimeToSeconds(endTime)));
+        LOG(LogLevel::Verbose,
+            ("MediaStream %p is finished, but not blocked yet (end at %f, with "
+             "blocking at %f)",
+             stream,
+             MediaTimeToSeconds(stream->GetTracksEnd()),
+             MediaTimeToSeconds(endTime)));
         // Data can't be added to a finished stream, so underruns are irrelevant.
         stream->mStartBlocking = std::min(endTime, aEndBlockingDecisions);
       }
     } else {
       stream->mStartBlocking = WillUnderrun(stream, aEndBlockingDecisions);
     }
   }
 
@@ -1384,17 +1435,18 @@ MediaStreamGraphImpl::UpdateMainThreadSt
   MonitorAutoLock lock(mMonitor);
   bool finalUpdate = mForceShutDown ||
     (mProcessedTime >= mEndTime && AllFinishedStreamsNotified()) ||
     (IsEmpty() && mBackMessageQueue.IsEmpty());
   PrepareUpdatesToMainThreadState(finalUpdate);
   if (finalUpdate) {
     // Enter shutdown mode. The stable-state handler will detect this
     // and complete shutdown. Destroy any streams immediately.
-    STREAM_LOG(LogLevel::Debug, ("MediaStreamGraph %p waiting for main thread cleanup", this));
+    LOG(LogLevel::Debug,
+        ("MediaStreamGraph %p waiting for main thread cleanup", this));
     // We'll shut down this graph object if it does not get restarted.
     mLifecycleState = LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP;
     // No need to Destroy streams here. The main-thread owner of each
     // stream is responsible for calling Destroy on them.
     return false;
   }
 
   CurrentDriver()->WaitForNextIteration();
@@ -1447,17 +1499,17 @@ MediaStreamGraphImpl::ApplyStreamUpdate(
     stream->NotifyMainThreadListeners();
   }
 }
 
 void
 MediaStreamGraphImpl::ForceShutDown(ShutdownTicket* aShutdownTicket)
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread");
-  STREAM_LOG(LogLevel::Debug, ("MediaStreamGraph %p ForceShutdown", this));
+  LOG(LogLevel::Debug, ("MediaStreamGraph %p ForceShutdown", this));
 
   MonitorAutoLock lock(mMonitor);
   if (aShutdownTicket) {
     MOZ_ASSERT(!mForceShutdownTicket);
     // Avoid waiting forever for a graph to shut down
     // synchronously.  Reports are that some 3rd-party audio drivers
     // occasionally hang in shutdown (both for us and Chrome).
     mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
@@ -1502,17 +1554,17 @@ public:
   explicit MediaStreamGraphShutDownRunnable(MediaStreamGraphImpl* aGraph)
     : mGraph(aGraph)
   {}
   NS_IMETHOD Run()
   {
     NS_ASSERTION(mGraph->mDetectedNotRunning,
                  "We should know the graph thread control loop isn't running!");
 
-    LIFECYCLE_LOG("Shutting down graph %p", mGraph.get());
+    LOG(LogLevel::Debug, ("Shutting down graph %p", mGraph.get()));
 
     // We've asserted the graph isn't running.  Use mDriver instead of CurrentDriver
     // to avoid thread-safety checks
 #if 0 // AudioCallbackDrivers are released asynchronously anyways
     // XXX a better test would be have setting mDetectedNotRunning make sure
     // any current callback has finished and block future ones -- or just
     // handle it all in Shutdown()!
     if (mGraph->mDriver->AsAudioCallbackDriver()) {
@@ -1624,32 +1676,32 @@ MediaStreamGraphImpl::RunInStableState(b
 
   {
     MonitorAutoLock lock(mMonitor);
     if (aSourceIsMSG) {
       MOZ_ASSERT(mPostedRunInStableStateEvent);
       mPostedRunInStableStateEvent = false;
     }
 
-#ifdef ENABLE_LIFECYCLE_LOG
-  // This should be kept in sync with the LifecycleState enum in
-  // MediaStreamGraphImpl.h
-  const char * LifecycleState_str[] = {
-    "LIFECYCLE_THREAD_NOT_STARTED",
-    "LIFECYCLE_RUNNING",
-    "LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP",
-    "LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN",
-    "LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION"
-  };
-
-  if (mLifecycleState != LIFECYCLE_RUNNING) {
-    LIFECYCLE_LOG("Running %p in stable state. Current state: %s\n",
-        this, LifecycleState_str[mLifecycleState]);
-  }
-#endif
+    // This should be kept in sync with the LifecycleState enum in
+    // MediaStreamGraphImpl.h
+    const char* LifecycleState_str[] = {
+      "LIFECYCLE_THREAD_NOT_STARTED",
+      "LIFECYCLE_RUNNING",
+      "LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP",
+      "LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN",
+      "LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION"
+    };
+
+    if (mLifecycleState != LIFECYCLE_RUNNING) {
+      LOG(LogLevel::Debug,
+          ("Running %p in stable state. Current state: %s",
+           this,
+           LifecycleState_str[mLifecycleState]));
+    }
 
     runnables.SwapElements(mUpdateRunnables);
     for (uint32_t i = 0; i < mStreamUpdates.Length(); ++i) {
       StreamUpdate* update = &mStreamUpdates[i];
       if (update->mStream) {
         ApplyStreamUpdate(update);
       }
     }
@@ -1658,21 +1710,22 @@ MediaStreamGraphImpl::RunInStableState(b
     if (mCurrentTaskMessageQueue.IsEmpty()) {
       if (mLifecycleState == LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP && IsEmpty()) {
         // Complete shutdown. First, ensure that this graph is no longer used.
         // A new graph graph will be created if one is needed.
         // Asynchronously clean up old graph. We don't want to do this
         // synchronously because it spins the event loop waiting for threads
         // to shut down, and we don't want to do that in a stable state handler.
         mLifecycleState = LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN;
-        LIFECYCLE_LOG("Sending MediaStreamGraphShutDownRunnable %p", this);
+        LOG(LogLevel::Debug,
+            ("Sending MediaStreamGraphShutDownRunnable %p", this));
         nsCOMPtr<nsIRunnable> event = new MediaStreamGraphShutDownRunnable(this );
         NS_DispatchToMainThread(event.forget());
 
-        LIFECYCLE_LOG("Disconnecting MediaStreamGraph %p", this);
+        LOG(LogLevel::Debug, ("Disconnecting MediaStreamGraph %p", this));
         MediaStreamGraphImpl* graph;
         if (gGraphs.Get(uint32_t(mAudioChannel), &graph) && graph == this) {
           // null out gGraph if that's the graph being shut down
           gGraphs.Remove(uint32_t(mAudioChannel));
         }
       }
     } else {
       if (mLifecycleState <= LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP) {
@@ -1687,19 +1740,21 @@ MediaStreamGraphImpl::RunInStableState(b
       // processing.
       if (mLifecycleState == LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP &&
           mRealtime && !mForceShutDown) {
         mLifecycleState = LIFECYCLE_RUNNING;
         // Revive the MediaStreamGraph since we have more messages going to it.
         // Note that we need to put messages into its queue before reviving it,
         // or it might exit immediately.
         {
-          LIFECYCLE_LOG("Reviving a graph (%p) ! %s\n",
-              this, CurrentDriver()->AsAudioCallbackDriver() ? "AudioDriver" :
-                                                               "SystemDriver");
+          LOG(LogLevel::Debug,
+              ("Reviving a graph (%p) ! %s",
+               this,
+               CurrentDriver()->AsAudioCallbackDriver() ? "AudioDriver"
+                                                        : "SystemDriver"));
           RefPtr<GraphDriver> driver = CurrentDriver();
           MonitorAutoUnlock unlock(mMonitor);
           driver->Revive();
         }
       }
     }
 
     // Don't start the thread for a non-realtime graph until it has been
@@ -1708,20 +1763,21 @@ MediaStreamGraphImpl::RunInStableState(b
         (mRealtime || mNonRealtimeProcessing)) {
       mLifecycleState = LIFECYCLE_RUNNING;
       // Start the thread now. We couldn't start it earlier because
       // the graph might exit immediately on finding it has no streams. The
       // first message for a new graph must create a stream.
       {
         // We should exit the monitor for now, because starting a stream might
         // take locks, and we don't want to deadlock.
-        LIFECYCLE_LOG("Starting a graph (%p) ! %s\n",
-                      this,
-                      CurrentDriver()->AsAudioCallbackDriver() ? "AudioDriver" :
-                                                                 "SystemDriver");
+        LOG(LogLevel::Debug,
+            ("Starting a graph (%p) ! %s",
+             this,
+             CurrentDriver()->AsAudioCallbackDriver() ? "AudioDriver"
+                                                      : "SystemDriver"));
         RefPtr<GraphDriver> driver = CurrentDriver();
         MonitorAutoUnlock unlock(mMonitor);
         driver->Start();
         // It's not safe to Shutdown() a thread from StableState, and
         // releasing this may shutdown a SystemClockDriver thread.
         // Proxy the release to outside of StableState.
         NS_ReleaseOnMainThread(driver.forget(), true); // always proxy
       }
@@ -2109,26 +2165,26 @@ MediaStream::SetAudioOutputVolume(void* 
     float mVolume;
   };
   GraphImpl()->AppendMessage(MakeUnique<Message>(this, aKey, aVolume));
 }
 
 void
 MediaStream::AddAudioOutputImpl(void* aKey)
 {
-  STREAM_LOG(LogLevel::Info, ("MediaStream %p Adding AudioOutput for key %p",
-                              this, aKey));
+  LOG(LogLevel::Info,
+      ("MediaStream %p Adding AudioOutput for key %p", this, aKey));
   mAudioOutputs.AppendElement(AudioOutput(aKey));
 }
 
 void
 MediaStream::RemoveAudioOutputImpl(void* aKey)
 {
-  STREAM_LOG(LogLevel::Info, ("MediaStream %p Removing AudioOutput for key %p",
-                              this, aKey));
+  LOG(LogLevel::Info,
+      ("MediaStream %p Removing AudioOutput for key %p", this, aKey));
   for (uint32_t i = 0; i < mAudioOutputs.Length(); ++i) {
     if (mAudioOutputs[i].mKey == aKey) {
       mAudioOutputs.RemoveElementAt(i);
       return;
     }
   }
   NS_ERROR("Audio output key not found");
 }
@@ -2149,18 +2205,20 @@ MediaStream::RemoveAudioOutput(void* aKe
   GraphImpl()->AppendMessage(MakeUnique<Message>(this, aKey));
 }
 
 void
 MediaStream::AddVideoOutputImpl(already_AddRefed<MediaStreamVideoSink> aSink,
                                 TrackID aID)
 {
   RefPtr<MediaStreamVideoSink> sink = aSink;
-  STREAM_LOG(LogLevel::Info, ("MediaStream %p Adding MediaStreamVideoSink %p as output",
-                              this, sink.get()));
+  LOG(LogLevel::Info,
+      ("MediaStream %p Adding MediaStreamVideoSink %p as output",
+       this,
+       sink.get()));
   MOZ_ASSERT(aID != TRACK_NONE);
    for (auto entry : mVideoOutputs) {
      if (entry.mListener == sink &&
          (entry.mTrackID == TRACK_ANY || entry.mTrackID == aID)) {
        return;
      }
    }
    TrackBound<MediaStreamVideoSink>* l = mVideoOutputs.AppendElement();
@@ -2169,18 +2227,19 @@ MediaStream::AddVideoOutputImpl(already_
 
    AddDirectTrackListenerImpl(sink.forget(), aID);
 }
 
 void
 MediaStream::RemoveVideoOutputImpl(MediaStreamVideoSink* aSink,
                                    TrackID aID)
 {
-  STREAM_LOG(LogLevel::Info, ("MediaStream %p Removing MediaStreamVideoSink %p as output",
-                              this, aSink));
+  LOG(
+    LogLevel::Info,
+    ("MediaStream %p Removing MediaStreamVideoSink %p as output", this, aSink));
   MOZ_ASSERT(aID != TRACK_NONE);
 
   // Ensure that any frames currently queued for playback by the compositor
   // are removed.
   aSink->ClearFrames();
   for (size_t i = 0; i < mVideoOutputs.Length(); ++i) {
     if (mVideoOutputs[i].mListener == aSink &&
         (mVideoOutputs[i].mTrackID == TRACK_ANY ||
@@ -2695,17 +2754,20 @@ SourceMediaStream::SetPullEnabled(bool a
 void
 SourceMediaStream::AddTrackInternal(TrackID aID, TrackRate aRate, StreamTime aStart,
                                     MediaSegment* aSegment, uint32_t aFlags)
 {
   MutexAutoLock lock(mMutex);
   nsTArray<TrackData> *track_data = (aFlags & ADDTRACK_QUEUED) ?
                                     &mPendingTracks : &mUpdateTracks;
   TrackData* data = track_data->AppendElement();
-  LIFECYCLE_LOG("AddTrackInternal: %lu/%lu", (long)mPendingTracks.Length(), (long)mUpdateTracks.Length());
+  LOG(LogLevel::Debug,
+      ("AddTrackInternal: %lu/%lu",
+       (long)mPendingTracks.Length(),
+       (long)mUpdateTracks.Length()));
   data->mID = aID;
   data->mInputRate = aRate;
   data->mResamplerChannelCount = 0;
   data->mStart = aStart;
   data->mEndOfFlushedData = aStart;
   data->mCommands = TRACK_CREATE;
   data->mData = aSegment;
   ResampleAudioToGraphSampleRate(data, aSegment);
@@ -2721,17 +2783,20 @@ SourceMediaStream::AddAudioTrack(TrackID
   AddTrackInternal(aID, aRate, aStart, aSegment, aFlags);
 }
 
 void
 SourceMediaStream::FinishAddTracks()
 {
   MutexAutoLock lock(mMutex);
   mUpdateTracks.AppendElements(Move(mPendingTracks));
-  LIFECYCLE_LOG("FinishAddTracks: %lu/%lu", (long)mPendingTracks.Length(), (long)mUpdateTracks.Length());
+  LOG(LogLevel::Debug,
+      ("FinishAddTracks: %lu/%lu",
+       (long)mPendingTracks.Length(),
+       (long)mUpdateTracks.Length()));
   if (GraphImpl()) {
     GraphImpl()->EnsureNextIteration();
   }
 }
 
 void
 SourceMediaStream::ResampleAudioToGraphSampleRate(TrackData* aTrackData, MediaSegment* aSegment)
 {
@@ -2890,18 +2955,21 @@ SourceMediaStream::AddDirectTrackListene
                                               TrackID aTrackID)
 {
   MOZ_ASSERT(IsTrackIDExplicit(aTrackID));
   TrackData* updateData = nullptr;
   StreamTracks::Track* track = nullptr;
   bool isAudio = false;
   bool isVideo = false;
   RefPtr<DirectMediaStreamTrackListener> listener = aListener;
-  STREAM_LOG(LogLevel::Debug, ("Adding direct track listener %p bound to track %d to source stream %p",
-             listener.get(), aTrackID, this));
+  LOG(LogLevel::Debug,
+      ("Adding direct track listener %p bound to track %d to source stream %p",
+       listener.get(),
+       aTrackID,
+       this));
 
   {
     MutexAutoLock lock(mMutex);
     updateData = FindDataForTrack(aTrackID);
     track = FindTrack(aTrackID);
     if (track) {
       isAudio = track->GetType() == MediaSegment::AUDIO;
       isVideo = track->GetType() == MediaSegment::VIDEO;
@@ -2934,31 +3002,34 @@ SourceMediaStream::AddDirectTrackListene
 
       TrackBound<DirectMediaStreamTrackListener>* sourceListener =
         mDirectTrackListeners.AppendElement();
       sourceListener->mListener = listener;
       sourceListener->mTrackID = aTrackID;
     }
   }
   if (!track) {
-    STREAM_LOG(LogLevel::Warning, ("Couldn't find source track for direct track listener %p",
-                                   listener.get()));
+    LOG(LogLevel::Warning,
+        ("Couldn't find source track for direct track listener %p",
+         listener.get()));
     listener->NotifyDirectListenerInstalled(
       DirectMediaStreamTrackListener::InstallationResult::TRACK_NOT_FOUND_AT_SOURCE);
     return;
   }
   if (!isAudio && !isVideo) {
-    STREAM_LOG(LogLevel::Warning, ("Source track for direct track listener %p is unknown",
-                                   listener.get()));
+    LOG(
+      LogLevel::Warning,
+      ("Source track for direct track listener %p is unknown", listener.get()));
     // It is not a video or audio track.
     MOZ_ASSERT(true);
     return;
   }
-  STREAM_LOG(LogLevel::Debug, ("Added direct track listener %p. ended=%d",
-                               listener.get(), !updateData));
+  LOG(
+    LogLevel::Debug,
+    ("Added direct track listener %p. ended=%d", listener.get(), !updateData));
   listener->NotifyDirectListenerInstalled(
     DirectMediaStreamTrackListener::InstallationResult::SUCCESS);
   if (!updateData) {
     // The track exists but the mUpdateTracks entry was removed.
     // This means that the track has ended.
     listener->NotifyEnded();
   }
 }
@@ -3031,24 +3102,28 @@ SourceMediaStream::SetTrackEnabledImpl(T
     MutexAutoLock lock(mMutex);
     for (TrackBound<DirectMediaStreamTrackListener>& l: mDirectTrackListeners) {
       if (l.mTrackID != aTrackID) {
         continue;
       }
       DisabledTrackMode oldMode = GetDisabledTrackMode(aTrackID);
       bool oldEnabled = oldMode == DisabledTrackMode::ENABLED;
       if (!oldEnabled && aMode == DisabledTrackMode::ENABLED) {
-        STREAM_LOG(LogLevel::Debug, ("SourceMediaStream %p track %d setting "
-                                     "direct listener enabled",
-                                     this, aTrackID));
+        LOG(LogLevel::Debug,
+            ("SourceMediaStream %p track %d setting "
+             "direct listener enabled",
+             this,
+             aTrackID));
         l.mListener->DecreaseDisabled(oldMode);
       } else if (oldEnabled && aMode != DisabledTrackMode::ENABLED) {
-        STREAM_LOG(LogLevel::Debug, ("SourceMediaStream %p track %d setting "
-                                     "direct listener disabled",
-                                     this, aTrackID));
+        LOG(LogLevel::Debug,
+            ("SourceMediaStream %p track %d setting "
+             "direct listener disabled",
+             this,
+             aTrackID));
         l.mListener->IncreaseDisabled(aMode);
       }
     }
   }
   MediaStream::SetTrackEnabledImpl(aTrackID, aMode);
 }
 
 void
@@ -3096,18 +3171,21 @@ SourceMediaStream::HasPendingAudioTrack(
   }
 
   return audioTrackPresent;
 }
 
 void
 MediaInputPort::Init()
 {
-  STREAM_LOG(LogLevel::Debug, ("Adding MediaInputPort %p (from %p to %p) to the graph",
-             this, mSource, mDest));
+  LOG(LogLevel::Debug,
+      ("Adding MediaInputPort %p (from %p to %p) to the graph",
+       this,
+       mSource,
+       mDest));
   mSource->AddConsumer(this);
   mDest->AddInput(this);
   // mPortCount decremented via MediaInputPort::Destroy's message
   ++mDest->GraphImpl()->mPortCount;
 }
 
 void
 MediaInputPort::Disconnect()
@@ -3449,35 +3527,36 @@ MediaStreamGraph::GetInstance(MediaStrea
     }
 
     graph = new MediaStreamGraphImpl(aGraphDriverRequested,
                                      CubebUtils::PreferredSampleRate(),
                                      aChannel);
 
     gGraphs.Put(channel, graph);
 
-    STREAM_LOG(LogLevel::Debug,
+    LOG(LogLevel::Debug,
         ("Starting up MediaStreamGraph %p for channel %s",
-         graph, AudioChannelValues::strings[channel].value));
+         graph,
+         AudioChannelValues::strings[channel].value));
   }
 
   return graph;
 }
 
 MediaStreamGraph*
 MediaStreamGraph::CreateNonRealtimeInstance(TrackRate aSampleRate)
 {
   NS_ASSERTION(NS_IsMainThread(), "Main thread only");
 
   MediaStreamGraphImpl* graph =
     new MediaStreamGraphImpl(OFFLINE_THREAD_DRIVER,
                              aSampleRate,
                              AudioChannel::Normal);
 
-  STREAM_LOG(LogLevel::Debug, ("Starting up Offline MediaStreamGraph %p", graph));
+  LOG(LogLevel::Debug, ("Starting up Offline MediaStreamGraph %p", graph));
 
   return graph;
 }
 
 void
 MediaStreamGraph::DestroyNonRealtimeInstance(MediaStreamGraph* aGraph)
 {
   NS_ASSERTION(NS_IsMainThread(), "Main thread only");
@@ -3767,19 +3846,21 @@ MediaStreamGraphImpl::SuspendOrResumeStr
   // streams from the set of streams that are going to be processed.
   for (MediaStream* stream : aStreamSet) {
     if (aAudioContextOperation == AudioContextOperation::Resume) {
       DecrementSuspendCount(stream);
     } else {
       IncrementSuspendCount(stream);
     }
   }
-  STREAM_LOG(LogLevel::Debug, ("Moving streams between suspended and running"
-      "state: mStreams: %d, mSuspendedStreams: %d\n", mStreams.Length(),
-      mSuspendedStreams.Length()));
+  LOG(LogLevel::Debug,
+      ("Moving streams between suspended and running"
+       "state: mStreams: %d, mSuspendedStreams: %d",
+       mStreams.Length(),
+       mSuspendedStreams.Length()));
 #ifdef DEBUG
   // The intersection of the two arrays should be null.
   for (uint32_t i = 0; i < mStreams.Length(); i++) {
     for (uint32_t j = 0; j < mSuspendedStreams.Length(); j++) {
       MOZ_ASSERT(
         mStreams[i] != mSuspendedStreams[j],
         "The suspended stream set and running stream set are not disjoint.");
     }
--- a/dom/tests/browser/helper_largeAllocation.js
+++ b/dom/tests/browser/helper_largeAllocation.js
@@ -386,18 +386,44 @@ function* largeAllocSuccessTests() {
         content.document.location = TEST_URI;
       });
       yield ready;
 
       is(false, yield getInLAProc(aBrowser));
     });
   });
 
-  // XXX: Make sure to reset dom.ipc.processCount.webLargeAllocation if adding a
-  // test after the above test.
+  // XXX: Important - reset the process count, as it was set to 1 by the
+  // previous test.
+  yield SpecialPowers.pushPrefEnv({
+    set: [["dom.ipc.processCount.webLargeAllocation", 20]],
+  });
+
+  // view-source tabs should not be considered to be in a large-allocation process.
+  yield BrowserTestUtils.withNewTab("about:blank", function*(aBrowser) {
+    info("Starting test 8");
+    let pid1 = yield getPID(aBrowser);
+    is(false, yield getInLAProc(aBrowser));
+
+    // Fail the test if we create a process
+    let stopExpectNoProcess = expectNoProcess();
+
+    yield ContentTask.spawn(aBrowser, null, () => {
+      content.document.location = "view-source:http://example.com";
+    });
+
+    yield BrowserTestUtils.browserLoaded(aBrowser);
+
+    let pid2 = yield getPID(aBrowser);
+
+    is(pid1, pid2, "The PID should not have changed");
+    is(false, yield getInLAProc(aBrowser));
+
+    stopExpectNoProcess();
+  });
 }
 
 function* largeAllocFailTests() {
   yield BrowserTestUtils.withNewTab("http://example.com", function*(aBrowser) {
     info("Starting test 1");
     let pid1 = yield getPID(aBrowser);
     is(false, yield getInLAProc(aBrowser));
 
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -375,17 +375,22 @@ Decoder::AllocateFrameInternal(uint32_t 
 
 /*
  * Hook stubs. Override these as necessary in decoder implementations.
  */
 
 nsresult Decoder::InitInternal() { return NS_OK; }
 nsresult Decoder::BeforeFinishInternal() { return NS_OK; }
 nsresult Decoder::FinishInternal() { return NS_OK; }
-nsresult Decoder::FinishWithErrorInternal() { return NS_OK; }
+
+nsresult Decoder::FinishWithErrorInternal()
+{
+  MOZ_ASSERT(!mInFrame);
+  return NS_OK;
+}
 
 /*
  * Progress Notifications
  */
 
 void
 Decoder::PostSize(int32_t aWidth,
                   int32_t aHeight,
--- a/image/SurfacePipe.cpp
+++ b/image/SurfacePipe.cpp
@@ -142,16 +142,18 @@ SurfaceSink::GetRowPointer() const
 
   return rowPtr;
 }
 
 
 nsresult
 PalettedSurfaceSink::Configure(const PalettedSurfaceConfig& aConfig)
 {
+  MOZ_ASSERT(aConfig.mFormat == SurfaceFormat::B8G8R8A8);
+
   // For paletted surfaces, the surface size is the size of the frame rect.
   IntSize surfaceSize = aConfig.mFrameRect.Size();
 
   // Allocate the frame.
   // XXX(seth): Once every Decoder subclass uses SurfacePipe, we probably want
   // to allocate the frame directly here and get rid of Decoder::AllocateFrame
   // altogether.
   nsresult rv = aConfig.mDecoder->AllocateFrame(aConfig.mFrameNum,
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -176,43 +176,51 @@ nsGIFDecoder2::CheckForTransparency(cons
 nsresult
 nsGIFDecoder2::BeginImageFrame(const IntRect& aFrameRect,
                                uint16_t aDepth,
                                bool aIsInterlaced)
 {
   MOZ_ASSERT(HasSize());
 
   bool hasTransparency = CheckForTransparency(aFrameRect);
-  gfx::SurfaceFormat format = hasTransparency ? SurfaceFormat::B8G8R8A8
-                                              : SurfaceFormat::B8G8R8X8;
 
   // Make sure there's no animation if we're downscaling.
   MOZ_ASSERT_IF(Size() != OutputSize(), !GetImageMetadata().HasAnimation());
 
   SurfacePipeFlags pipeFlags = aIsInterlaced
                              ? SurfacePipeFlags::DEINTERLACE
                              : SurfacePipeFlags();
 
   Maybe<SurfacePipe> pipe;
   if (mGIFStruct.images_decoded == 0) {
+    gfx::SurfaceFormat format = hasTransparency ? SurfaceFormat::B8G8R8A8
+                                                : SurfaceFormat::B8G8R8X8;
+
     // The first frame may be displayed progressively.
     pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;
 
     // The first frame is always decoded into an RGB surface.
     pipe =
       SurfacePipeFactory::CreateSurfacePipe(this, mGIFStruct.images_decoded,
                                             Size(), OutputSize(),
                                             aFrameRect, format, pipeFlags);
   } else {
     // This is an animation frame (and not the first). To minimize the memory
     // usage of animations, the image data is stored in paletted form.
+    //
+    // We should never use paletted surfaces with a draw target directly, so
+    // the only practical difference between B8G8R8A8 and B8G8R8X8 is the
+    // cleared pixel value if we get truncated. We want 0 in that case to
+    // ensure it is an acceptable value for the color map as was the case
+    // historically.
     MOZ_ASSERT(Size() == OutputSize());
     pipe =
       SurfacePipeFactory::CreatePalettedSurfacePipe(this, mGIFStruct.images_decoded,
-                                                    Size(), aFrameRect, format,
+                                                    Size(), aFrameRect,
+                                                    SurfaceFormat::B8G8R8A8,
                                                     aDepth, pipeFlags);
   }
 
   mCurrentFrameIndex = mGIFStruct.images_decoded;
 
   if (!pipe) {
     mPipe = SurfacePipe();
     return NS_ERROR_FAILURE;
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -74,16 +74,18 @@ nsICODecoder::FinishInternal()
   MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
 
   return GetFinalStateFromContainedDecoder();
 }
 
 nsresult
 nsICODecoder::FinishWithErrorInternal()
 {
+  // No need to assert !mInFrame here because this condition is enforced by
+  // mContainedDecoder.
   return GetFinalStateFromContainedDecoder();
 }
 
 nsresult
 nsICODecoder::GetFinalStateFromContainedDecoder()
 {
   if (!mContainedDecoder) {
     return NS_OK;
--- a/intl/locale/mac/OSPreferences_mac.cpp
+++ b/intl/locale/mac/OSPreferences_mac.cpp
@@ -15,16 +15,17 @@ OSPreferences::ReadSystemLocales(nsTArra
   MOZ_ASSERT(aLocaleList.IsEmpty());
 
   // Get string representation of user's current locale
   CFLocaleRef userLocaleRef = ::CFLocaleCopyCurrent();
   CFStringRef userLocaleStr = ::CFLocaleGetIdentifier(userLocaleRef);
 
   AutoTArray<UniChar, 32> buffer;
   int size = ::CFStringGetLength(userLocaleStr);
+  buffer.SetLength(size);
 
   CFRange range = ::CFRangeMake(0, size);
   ::CFStringGetCharacters(userLocaleStr, range, buffer.Elements());
 
   // Convert the locale string to the format that Mozilla expects
   NS_LossyConvertUTF16toASCII locale(
       reinterpret_cast<const char16_t*>(buffer.Elements()), buffer.Length());
 
--- a/js/ipc/JavaScriptShared.cpp
+++ b/js/ipc/JavaScriptShared.cpp
@@ -231,17 +231,17 @@ JavaScriptShared::convertGeckoStringToId
 
     return JS_StringToId(cx, str, to);
 }
 
 bool
 JavaScriptShared::toVariant(JSContext* cx, JS::HandleValue from, JSVariant* to)
 {
     switch (JS_TypeOfValue(cx, from)) {
-      case JSTYPE_VOID:
+      case JSTYPE_UNDEFINED:
         *to = UndefinedVariant();
         return true;
 
       case JSTYPE_OBJECT:
       case JSTYPE_FUNCTION:
       {
         RootedObject obj(cx, from.toObjectOrNull());
         if (!obj) {
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -420,35 +420,37 @@ IsIncrementalGCEnabled(JSContext* cx);
 /**
  * Returns true while an incremental GC is ongoing, both when actively
  * collecting and between slices.
  */
 extern JS_PUBLIC_API(bool)
 IsIncrementalGCInProgress(JSContext* cx);
 
 /*
- * Returns true when writes to GC things must call an incremental (pre) barrier.
- * This is generally only true when running mutator code in-between GC slices.
- * At other times, the barrier may be elided for performance.
+ * Returns true when writes to GC thing pointers (and reads from weak pointers)
+ * must call an incremental barrier. This is generally only true when running
+ * mutator code in-between GC slices. At other times, the barrier may be elided
+ * for performance.
  */
 extern JS_PUBLIC_API(bool)
 IsIncrementalBarrierNeeded(JSContext* cx);
 
 /*
- * Notify the GC that a reference to a GC thing is about to be overwritten.
- * These methods must be called if IsIncrementalBarrierNeeded.
+ * Notify the GC that a reference to a JSObject is about to be overwritten.
+ * This method must be called if IsIncrementalBarrierNeeded.
  */
 extern JS_PUBLIC_API(void)
-IncrementalReferenceBarrier(GCCellPtr thing);
+IncrementalPreWriteBarrier(JSObject* obj);
 
+/*
+ * Notify the GC that a weak reference to a GC thing has been read.
+ * This method must be called if IsIncrementalBarrierNeeded.
+ */
 extern JS_PUBLIC_API(void)
-IncrementalValueBarrier(const Value& v);
-
-extern JS_PUBLIC_API(void)
-IncrementalObjectBarrier(JSObject* obj);
+IncrementalReadBarrier(GCCellPtr thing);
 
 /**
  * Returns true if the most recent GC ran incrementally.
  */
 extern JS_PUBLIC_API(bool)
 WasIncrementalGC(JSContext* cx);
 
 /*
@@ -638,38 +640,40 @@ ExposeGCThingToActiveJS(JS::GCCellPtr th
     // There's nothing to do for permanent GC things that might be owned by
     // another runtime.
     if (thing.mayBeOwnedByOtherRuntime())
         return;
 
     MOZ_DIAGNOSTIC_ASSERT(BarriersAreAllowedOnCurrentThread());
 
     if (IsIncrementalBarrierNeededOnTenuredGCThing(thing))
-        JS::IncrementalReferenceBarrier(thing);
-    else if (!thing.mayBeOwnedByOtherRuntime() && js::gc::detail::CellIsMarkedGray(thing.asCell()))
+        JS::IncrementalReadBarrier(thing);
+    else if (js::gc::detail::CellIsMarkedGray(thing.asCell()))
         JS::UnmarkGrayGCThingRecursively(thing);
+
+    MOZ_ASSERT(!js::gc::detail::CellIsMarkedGray(thing.asCell()));
 }
 
 static MOZ_ALWAYS_INLINE void
-MarkGCThingAsLive(JSRuntime* aRt, JS::GCCellPtr thing)
+GCThingReadBarrier(JS::GCCellPtr thing)
 {
     // Any object in the nursery will not be freed during any GC running at that
     // time.
     if (IsInsideNursery(thing.asCell()))
         return;
 
     // There's nothing to do for permanent GC things that might be owned by
     // another runtime.
     if (thing.mayBeOwnedByOtherRuntime())
         return;
 
     MOZ_DIAGNOSTIC_ASSERT(BarriersAreAllowedOnCurrentThread());
 
     if (IsIncrementalBarrierNeededOnTenuredGCThing(thing))
-        JS::IncrementalReferenceBarrier(thing);
+        JS::IncrementalReadBarrier(thing);
 }
 
 } /* namespace gc */
 } /* namespace js */
 
 namespace JS {
 
 /*
@@ -690,20 +694,20 @@ ExposeScriptToActiveJS(JSScript* script)
 {
     js::gc::ExposeGCThingToActiveJS(GCCellPtr(script));
 }
 
 /*
  * If a GC is currently marking, mark the string black.
  */
 static MOZ_ALWAYS_INLINE void
-MarkStringAsLive(Zone* zone, JSString* string)
+StringReadBarrier(JSString* string)
 {
-    JSRuntime* rt = JS::shadow::Zone::asShadowZone(zone)->runtimeFromActiveCooperatingThread();
-    js::gc::MarkGCThingAsLive(rt, GCCellPtr(string));
+    MOZ_ASSERT(js::CurrentThreadCanAccessZone(GetStringZone(string)));
+    js::gc::GCThingReadBarrier(GCCellPtr(string));
 }
 
 /*
  * Internal to Firefox.
  *
  * Note: this is not related to the PokeGC in nsJSEnvironment.
  */
 extern JS_FRIEND_API(void)
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -1202,23 +1202,23 @@ class JS_PUBLIC_API(ObjectPtr)
     void finalize(JSContext* cx);
 
     void init(JSObject* obj) { value = obj; }
 
     JSObject* get() const { return value; }
     JSObject* unbarrieredGet() const { return value.unbarrieredGet(); }
 
     void writeBarrierPre(JSContext* cx) {
-        IncrementalObjectBarrier(value);
+        IncrementalPreWriteBarrier(value);
     }
 
     void updateWeakPointerAfterGC();
 
     ObjectPtr& operator=(JSObject* obj) {
-        IncrementalObjectBarrier(value);
+        IncrementalPreWriteBarrier(value);
         value = obj;
         return *this;
     }
 
     void trace(JSTracer* trc, const char* name);
 
     JSObject& operator*() const { return *value; }
     JSObject* operator->() const { return value; }
--- a/js/public/StructuredClone.h
+++ b/js/public/StructuredClone.h
@@ -322,16 +322,17 @@ class JS_PUBLIC_API(JSAutoStructuredClon
 // The range of tag values the application may use for its own custom object types.
 #define JS_SCTAG_USER_MIN  ((uint32_t) 0xFFFF8000)
 #define JS_SCTAG_USER_MAX  ((uint32_t) 0xFFFFFFFF)
 
 #define JS_SCERR_RECURSION 0
 #define JS_SCERR_TRANSFERABLE 1
 #define JS_SCERR_DUP_TRANSFERABLE 2
 #define JS_SCERR_UNSUPPORTED_TYPE 3
+#define JS_SCERR_SAB_TRANSFERABLE 4
 
 JS_PUBLIC_API(bool)
 JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1, uint32_t* p2);
 
 JS_PUBLIC_API(bool)
 JS_ReadBytes(JSStructuredCloneReader* r, void* p, size_t len);
 
 JS_PUBLIC_API(bool)
--- a/js/src/builtin/DataViewObject.cpp
+++ b/js/src/builtin/DataViewObject.cpp
@@ -426,18 +426,18 @@ struct DataViewIO
         if (wantSwap)
             temp = swapBytes(temp);
         Memcpy(unalignedBuffer, (uint8_t*) &temp, sizeof(ReadWriteType));
     }
 };
 
 template<typename NativeType>
 /* static */ bool
-DataViewObject::read(JSContext* cx, Handle<DataViewObject*> obj,
-                     const CallArgs& args, NativeType* val, const char* method)
+DataViewObject::read(JSContext* cx, Handle<DataViewObject*> obj, const CallArgs& args,
+                     NativeType* val)
 {
     // Steps 1-2. done by the caller
     // Step 3. unnecessary assert
 
     // Step 4.
     uint64_t getIndex;
     if (!ToIndex(cx, args.get(0), &getIndex))
         return false;
@@ -498,18 +498,17 @@ template <>
 inline bool
 WebIDLCast<double>(JSContext* cx, HandleValue value, double* out)
 {
     return ToNumber(cx, value, out);
 }
 
 template<typename NativeType>
 /* static */ bool
-DataViewObject::write(JSContext* cx, Handle<DataViewObject*> obj,
-                      const CallArgs& args, const char* method)
+DataViewObject::write(JSContext* cx, Handle<DataViewObject*> obj, const CallArgs& args)
 {
     // Steps 1-2. done by the caller
     // Step 3. unnecessary assert
 
     // Step 4.
     uint64_t getIndex;
     if (!ToIndex(cx, args.get(0), &getIndex))
         return false;
@@ -555,17 +554,17 @@ DataViewObject::write(JSContext* cx, Han
 bool
 DataViewObject::getInt8Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
     int8_t val;
-    if (!read(cx, thisView, args, &val, "getInt8"))
+    if (!read(cx, thisView, args, &val))
         return false;
     args.rval().setInt32(val);
     return true;
 }
 
 bool
 DataViewObject::fun_getInt8(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -576,17 +575,17 @@ DataViewObject::fun_getInt8(JSContext* c
 bool
 DataViewObject::getUint8Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
     uint8_t val;
-    if (!read(cx, thisView, args, &val, "getUint8"))
+    if (!read(cx, thisView, args, &val))
         return false;
     args.rval().setInt32(val);
     return true;
 }
 
 bool
 DataViewObject::fun_getUint8(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -597,17 +596,17 @@ DataViewObject::fun_getUint8(JSContext* 
 bool
 DataViewObject::getInt16Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
     int16_t val;
-    if (!read(cx, thisView, args, &val, "getInt16"))
+    if (!read(cx, thisView, args, &val))
         return false;
     args.rval().setInt32(val);
     return true;
 }
 
 bool
 DataViewObject::fun_getInt16(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -618,17 +617,17 @@ DataViewObject::fun_getInt16(JSContext* 
 bool
 DataViewObject::getUint16Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
     uint16_t val;
-    if (!read(cx, thisView, args, &val, "getUint16"))
+    if (!read(cx, thisView, args, &val))
         return false;
     args.rval().setInt32(val);
     return true;
 }
 
 bool
 DataViewObject::fun_getUint16(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -639,17 +638,17 @@ DataViewObject::fun_getUint16(JSContext*
 bool
 DataViewObject::getInt32Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
     int32_t val;
-    if (!read(cx, thisView, args, &val, "getInt32"))
+    if (!read(cx, thisView, args, &val))
         return false;
     args.rval().setInt32(val);
     return true;
 }
 
 bool
 DataViewObject::fun_getInt32(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -660,17 +659,17 @@ DataViewObject::fun_getInt32(JSContext* 
 bool
 DataViewObject::getUint32Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
     uint32_t val;
-    if (!read(cx, thisView, args, &val, "getUint32"))
+    if (!read(cx, thisView, args, &val))
         return false;
     args.rval().setNumber(val);
     return true;
 }
 
 bool
 DataViewObject::fun_getUint32(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -681,17 +680,17 @@ DataViewObject::fun_getUint32(JSContext*
 bool
 DataViewObject::getFloat32Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
     float val;
-    if (!read(cx, thisView, args, &val, "getFloat32"))
+    if (!read(cx, thisView, args, &val))
         return false;
 
     args.rval().setDouble(CanonicalizeNaN(val));
     return true;
 }
 
 bool
 DataViewObject::fun_getFloat32(JSContext* cx, unsigned argc, Value* vp)
@@ -703,17 +702,17 @@ DataViewObject::fun_getFloat32(JSContext
 bool
 DataViewObject::getFloat64Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
     double val;
-    if (!read(cx, thisView, args, &val, "getFloat64"))
+    if (!read(cx, thisView, args, &val))
         return false;
 
     args.rval().setDouble(CanonicalizeNaN(val));
     return true;
 }
 
 bool
 DataViewObject::fun_getFloat64(JSContext* cx, unsigned argc, Value* vp)
@@ -724,17 +723,17 @@ DataViewObject::fun_getFloat64(JSContext
 
 bool
 DataViewObject::setInt8Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
-    if (!write<int8_t>(cx, thisView, args, "setInt8"))
+    if (!write<int8_t>(cx, thisView, args))
         return false;
     args.rval().setUndefined();
     return true;
 }
 
 bool
 DataViewObject::fun_setInt8(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -744,17 +743,17 @@ DataViewObject::fun_setInt8(JSContext* c
 
 bool
 DataViewObject::setUint8Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
-    if (!write<uint8_t>(cx, thisView, args, "setUint8"))
+    if (!write<uint8_t>(cx, thisView, args))
         return false;
     args.rval().setUndefined();
     return true;
 }
 
 bool
 DataViewObject::fun_setUint8(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -764,17 +763,17 @@ DataViewObject::fun_setUint8(JSContext* 
 
 bool
 DataViewObject::setInt16Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
-    if (!write<int16_t>(cx, thisView, args, "setInt16"))
+    if (!write<int16_t>(cx, thisView, args))
         return false;
     args.rval().setUndefined();
     return true;
 }
 
 bool
 DataViewObject::fun_setInt16(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -784,17 +783,17 @@ DataViewObject::fun_setInt16(JSContext* 
 
 bool
 DataViewObject::setUint16Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
-    if (!write<uint16_t>(cx, thisView, args, "setUint16"))
+    if (!write<uint16_t>(cx, thisView, args))
         return false;
     args.rval().setUndefined();
     return true;
 }
 
 bool
 DataViewObject::fun_setUint16(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -804,17 +803,17 @@ DataViewObject::fun_setUint16(JSContext*
 
 bool
 DataViewObject::setInt32Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
-    if (!write<int32_t>(cx, thisView, args, "setInt32"))
+    if (!write<int32_t>(cx, thisView, args))
         return false;
     args.rval().setUndefined();
     return true;
 }
 
 bool
 DataViewObject::fun_setInt32(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -824,17 +823,17 @@ DataViewObject::fun_setInt32(JSContext* 
 
 bool
 DataViewObject::setUint32Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
-    if (!write<uint32_t>(cx, thisView, args, "setUint32"))
+    if (!write<uint32_t>(cx, thisView, args))
         return false;
     args.rval().setUndefined();
     return true;
 }
 
 bool
 DataViewObject::fun_setUint32(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -844,17 +843,17 @@ DataViewObject::fun_setUint32(JSContext*
 
 bool
 DataViewObject::setFloat32Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
-    if (!write<float>(cx, thisView, args, "setFloat32"))
+    if (!write<float>(cx, thisView, args))
         return false;
     args.rval().setUndefined();
     return true;
 }
 
 bool
 DataViewObject::fun_setFloat32(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -864,17 +863,17 @@ DataViewObject::fun_setFloat32(JSContext
 
 bool
 DataViewObject::setFloat64Impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(is(args.thisv()));
 
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
-    if (!write<double>(cx, thisView, args, "setFloat64"))
+    if (!write<double>(cx, thisView, args))
         return false;
     args.rval().setUndefined();
     return true;
 }
 
 bool
 DataViewObject::fun_setFloat64(JSContext* cx, unsigned argc, Value* vp)
 {
--- a/js/src/builtin/DataViewObject.h
+++ b/js/src/builtin/DataViewObject.h
@@ -156,21 +156,20 @@ class DataViewObject : public NativeObje
     static bool fun_setFloat32(JSContext* cx, unsigned argc, Value* vp);
 
     static bool setFloat64Impl(JSContext* cx, const CallArgs& args);
     static bool fun_setFloat64(JSContext* cx, unsigned argc, Value* vp);
 
     static bool initClass(JSContext* cx);
     static void notifyBufferDetached(JSObject* view);
     template<typename NativeType>
-    static bool read(JSContext* cx, Handle<DataViewObject*> obj,
-                     const CallArgs& args, NativeType* val, const char* method);
+    static bool read(JSContext* cx, Handle<DataViewObject*> obj, const CallArgs& args,
+                     NativeType* val);
     template<typename NativeType>
-    static bool write(JSContext* cx, Handle<DataViewObject*> obj,
-                      const CallArgs& args, const char* method);
+    static bool write(JSContext* cx, Handle<DataViewObject*> obj, const CallArgs& args);
 
     void notifyBufferDetached(void* newData);
 
   private:
     static const JSFunctionSpec methods[];
     static const JSPropertySpec properties[];
 };
 
--- a/js/src/gc/NurseryAwareHashMap.h
+++ b/js/src/gc/NurseryAwareHashMap.h
@@ -65,101 +65,171 @@ class UnsafeBareReadBarriered : public R
 template <typename Key,
           typename Value,
           typename HashPolicy = DefaultHasher<Key>,
           typename AllocPolicy = TempAllocPolicy>
 class NurseryAwareHashMap
 {
     using BarrieredValue = detail::UnsafeBareReadBarriered<Value>;
     using MapType = GCRekeyableHashMap<Key, BarrieredValue, HashPolicy, AllocPolicy>;
-    MapType map;
+
+    // Contains entries that have a nursery-allocated key or value (or both).
+    MapType nurseryMap_;
 
-    // Keep a list of all keys for which JS::GCPolicy<Key>::isTenured is false.
-    // This lets us avoid a full traveral of the map on each minor GC, keeping
-    // the minor GC times proportional to the nursery heap size.
-    Vector<Key, 0, AllocPolicy> nurseryEntries;
+    // All entries in this map have a tenured key and value.
+    MapType tenuredMap_;
+
+    // Keys and values usually have the same lifetime (for the WrapperMap we
+    // ensure this when we allocate the wrapper object). If this flag is set,
+    // it means nurseryMap_ contains a tenured key with a nursery allocated
+    // value.
+    bool nurseryMapContainsTenuredKeys_;
 
   public:
     using Lookup = typename MapType::Lookup;
-    using Ptr = typename MapType::Ptr;
-    using Range = typename MapType::Range;
+
+    class Ptr {
+        friend class NurseryAwareHashMap;
+
+        typename MapType::Ptr ptr_;
+        bool isNurseryMap_;
+
+      public:
+        Ptr(typename MapType::Ptr ptr, bool isNurseryMap)
+          : ptr_(ptr), isNurseryMap_(isNurseryMap)
+        {}
 
-    explicit NurseryAwareHashMap(AllocPolicy a = AllocPolicy()) : map(a) {}
+        const typename MapType::Entry& operator*() const { return *ptr_; }
+        const typename MapType::Entry* operator->() const { return &*ptr_; }
+
+        bool found() const { return ptr_.found(); }
+        explicit operator bool() const { return bool(ptr_); }
+    };
 
-    MOZ_MUST_USE bool init(uint32_t len = 16) { return map.init(len); }
+    explicit NurseryAwareHashMap(AllocPolicy a = AllocPolicy())
+      : nurseryMap_(a), tenuredMap_(a), nurseryMapContainsTenuredKeys_(false)
+    {}
+
+    MOZ_MUST_USE bool init(uint32_t len = 16) {
+        return nurseryMap_.init(len) && tenuredMap_.init(len);
+    }
+
+    bool empty() const { return nurseryMap_.empty() && tenuredMap_.empty(); }
 
-    bool empty() const { return map.empty(); }
-    Ptr lookup(const Lookup& l) const { return map.lookup(l); }
-    void remove(Ptr p) { map.remove(p); }
-    Range all() const { return map.all(); }
-    struct Enum : public MapType::Enum {
-        explicit Enum(NurseryAwareHashMap& namap) : MapType::Enum(namap.map) {}
+    Ptr lookup(const Lookup& l) const {
+        if (JS::GCPolicy<Key>::isTenured(l)) {
+            // If we find the key in the tenuredMap_, we're done. If we don't
+            // find it there and we know nurseryMap_ contains tenured keys
+            // (hopefully uncommon), we have to try nurseryMap_ as well.
+            typename MapType::Ptr p = tenuredMap_.lookup(l);
+            if (p || !nurseryMapContainsTenuredKeys_)
+                return Ptr(p, /* isNurseryMap = */ false);
+        }
+        return Ptr(nurseryMap_.lookup(l), /* isNurseryMap = */ true);
+    }
+
+    void remove(Ptr p) {
+        if (p.isNurseryMap_)
+            nurseryMap_.remove(p.ptr_);
+        else
+            tenuredMap_.remove(p.ptr_);
+    }
+
+    class Enum {
+        // First iterate over the nursery map. When nurseryEnum_ becomes
+        // empty() we switch to tenuredEnum_.
+        typename MapType::Enum nurseryEnum_;
+        typename MapType::Enum tenuredEnum_;
+
+        const typename MapType::Enum& currentEnum() const {
+            return nurseryEnum_.empty() ? tenuredEnum_ : nurseryEnum_;
+        }
+        typename MapType::Enum& currentEnum() {
+            return nurseryEnum_.empty() ? tenuredEnum_ : nurseryEnum_;
+        }
+
+      public:
+        explicit Enum(NurseryAwareHashMap& namap)
+          : nurseryEnum_(namap.nurseryMap_), tenuredEnum_(namap.tenuredMap_)
+        {}
+
+        typename MapType::Entry& front() const { return currentEnum().front(); }
+        void popFront() { currentEnum().popFront(); }
+        void removeFront() { currentEnum().removeFront(); }
+
+        bool empty() const { return nurseryEnum_.empty() && tenuredEnum_.empty(); }
     };
+
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
-        return map.sizeOfExcludingThis(mallocSizeOf);
+        size_t size = nurseryMap_.sizeOfExcludingThis(mallocSizeOf);
+        size += tenuredMap_.sizeOfExcludingThis(mallocSizeOf);
+        return size;
     }
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
-        return map.sizeOfIncludingThis(mallocSizeOf);
+        size_t size = nurseryMap_.sizeOfIncludingThis(mallocSizeOf);
+        size += tenuredMap_.sizeOfIncludingThis(mallocSizeOf);
+        return size;
+    }
+
+    MOZ_MUST_USE bool putNew(const Key& k, const Value& v) {
+        MOZ_ASSERT(!tenuredMap_.has(k));
+        MOZ_ASSERT(!nurseryMap_.has(k));
+
+        bool tenuredKey = JS::GCPolicy<Key>::isTenured(k);
+        if (tenuredKey && JS::GCPolicy<Value>::isTenured(v))
+            return tenuredMap_.putNew(k, v);
+
+        if (tenuredKey)
+            nurseryMapContainsTenuredKeys_ = true;
+
+        return nurseryMap_.putNew(k, v);
     }
 
     MOZ_MUST_USE bool put(const Key& k, const Value& v) {
-        auto p = map.lookupForAdd(k);
-        if (p) {
-            if (!JS::GCPolicy<Key>::isTenured(k) || !JS::GCPolicy<Value>::isTenured(v)) {
-                if (!nurseryEntries.append(k))
-                    return false;
-            }
-            p->value() = v;
-            return true;
-        }
-
-        bool ok = map.add(p, k, v);
-        if (!ok)
-            return false;
-
-        if (!JS::GCPolicy<Key>::isTenured(k) || !JS::GCPolicy<Value>::isTenured(v)) {
-            if (!nurseryEntries.append(k)) {
-                map.remove(k);
-                return false;
-            }
-        }
-
-        return true;
+        // For simplicity, just remove the entry and forward to putNew for now.
+        // Performance-sensitive callers should prefer putNew.
+        if (Ptr p = lookup(k))
+            remove(p);
+        return putNew(k, v);
     }
 
     void sweepAfterMinorGC(JSTracer* trc) {
-        for (auto& key : nurseryEntries) {
-            auto p = map.lookup(key);
-            if (!p)
-                continue;
+        for (typename MapType::Enum e(nurseryMap_); !e.empty(); e.popFront()) {
+            auto& key = e.front().key();
+            auto& value = e.front().value();
 
             // Drop the entry if the value is not marked.
-            if (JS::GCPolicy<BarrieredValue>::needsSweep(&p->value())) {
-                map.remove(key);
+            if (JS::GCPolicy<BarrieredValue>::needsSweep(&value))
                 continue;
-            }
 
-            // Update and relocate the key, if the value is still needed.
+            // Insert the key/value in the tenured map, if the value is still
+            // needed.
             //
-            // Note that this currently assumes that all Value will contain a
+            // Note that this currently assumes that each Value will contain a
             // strong reference to Key, as per its use as the
             // CrossCompartmentWrapperMap. We may need to make the following
             // behavior more dynamic if we use this map in other nursery-aware
             // contexts.
-            Key copy(key);
-            mozilla::DebugOnly<bool> sweepKey = JS::GCPolicy<Key>::needsSweep(&copy);
+
+            Key keyCopy(key);
+            mozilla::DebugOnly<bool> sweepKey = JS::GCPolicy<Key>::needsSweep(&keyCopy);
             MOZ_ASSERT(!sweepKey);
-            map.rekeyIfMoved(key, copy);
+
+            AutoEnterOOMUnsafeRegion oomUnsafe;
+            if (!tenuredMap_.putNew(keyCopy, value))
+                oomUnsafe.crash("NurseryAwareHashMap sweepAfterMinorGC");
         }
-        nurseryEntries.clear();
+
+        nurseryMap_.clear();
+        nurseryMapContainsTenuredKeys_ = false;
     }
 
     void sweep() {
-        MOZ_ASSERT(nurseryEntries.empty());
-        map.sweep();
+        MOZ_ASSERT(nurseryMap_.empty());
+        tenuredMap_.sweep();
     }
 };
 
 } // namespace js
 
 namespace JS {
 template <typename T>
 struct GCPolicy<js::detail::UnsafeBareReadBarriered<T>>
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1331350.js
@@ -0,0 +1,56 @@
+// |jit-test| --ion-eager
+function optimize(a, b) {
+    a = a | 0;
+    b = b | 0;
+
+    if ((a & 3) === 0) {
+        a = a + 1 | 0
+    }
+
+    if ((a & 7) !== 0) {
+        a = a + 1 | 0
+    }
+
+    return a + b | 0
+}
+
+for (var i=0; i<20; i++) {
+    assertEq(optimize(4 | 0, 6 | 0), 12);
+    assertEq(optimize(7 | 0, 11 | 0), 19);
+}
+
+function not_optimizable(a, b) {
+    a = a | 0;
+    b = b | 0;
+
+    if ((a & 3) > 0) {
+        a = a + 1 | 0
+    }
+
+    if ((a & 3) >= 0) {
+        a = a + 1 | 0
+    }
+
+    if ((a & 7) < 0) {
+        a = a + 1 | 0
+    }
+
+    if ((a & 7) <= 0) {
+        a = a + 1 | 0
+    }
+
+    if ((b & 3) === 1) {
+        b = b + 1 | 0
+    }
+
+    if ((b & 7) !== 3) {
+        b = b + 1 | 0
+    }
+
+    return a + b | 0
+}
+
+for (var i=0; i<20; i++) {
+    assertEq(not_optimizable(4 | 0, 6 | 0), 12);
+    assertEq(not_optimizable(7 | 0, 11 | 0), 20);
+}
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -1084,16 +1084,126 @@ BaselineCacheIRCompiler::emitStoreDenseE
     EmitPreBarrier(masm, element, MIRType::Value);
     masm.storeValue(val, element);
 
     BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch, LiveGeneralRegisterSet(), cx_);
     return true;
 }
 
 bool
+BaselineCacheIRCompiler::emitStoreDenseElementHole()
+{
+    ObjOperandId objId = reader.objOperandId();
+    Int32OperandId indexId = reader.int32OperandId();
+
+    // Allocate the fixed registers first. These need to be fixed for
+    // callTypeUpdateIC.
+    AutoScratchRegister scratch(allocator, masm, R1.scratchReg());
+    ValueOperand val = allocator.useFixedValueRegister(masm, reader.valOperandId(), R0);
+
+    Register obj = allocator.useRegister(masm, objId);
+    Register index = allocator.useRegister(masm, indexId);
+
+    bool handleAdd = reader.readBool();
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    // Load obj->elements in scratch.
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+
+    BaseObjectElementIndex element(scratch, index);
+    Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
+    Address elementsFlags(scratch, ObjectElements::offsetOfFlags());
+
+    // Check for copy-on-write or frozen elements.
+    masm.branchTest32(Assembler::NonZero, elementsFlags,
+                      Imm32(ObjectElements::COPY_ON_WRITE |
+                            ObjectElements::FROZEN),
+                      failure->label());
+
+    if (handleAdd) {
+        // Fail if index > initLength.
+        masm.branch32(Assembler::Below, initLength, index, failure->label());
+
+        // Check the capacity.
+        Address capacity(scratch, ObjectElements::offsetOfCapacity());
+        masm.branch32(Assembler::BelowOrEqual, capacity, index, failure->label());
+
+        // We increment initLength after the callTypeUpdateIC call, to ensure
+        // the type update code doesn't read uninitialized memory.
+    } else {
+        // Fail if index >= initLength.
+        masm.branch32(Assembler::BelowOrEqual, initLength, index, failure->label());
+    }
+
+    // Check if we have to convert a double element.
+    Label noConversion;
+    masm.branchTest32(Assembler::Zero, elementsFlags,
+                      Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
+                      &noConversion);
+
+    // We need to convert int32 values being stored into doubles. Note that
+    // double arrays are only created by IonMonkey, so if we have no FP support
+    // Ion is disabled and there should be no double arrays.
+    if (cx_->runtime()->jitSupportsFloatingPoint) {
+        // It's fine to convert the value in place in Baseline. We can't do
+        // this in Ion.
+        masm.convertInt32ValueToDouble(val);
+    } else {
+        masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
+    }
+
+    masm.bind(&noConversion);
+
+    // Call the type update IC. After this everything must be infallible as we
+    // don't save all registers here.
+    LiveGeneralRegisterSet saveRegs;
+    saveRegs.add(obj);
+    saveRegs.add(index);
+    saveRegs.add(val);
+    if (!callTypeUpdateIC(obj, val, scratch, saveRegs))
+        return false;
+
+    // Reload obj->elements as callTypeUpdateIC used the scratch register.
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+
+    Label doStore;
+    if (handleAdd) {
+        // If index == initLength, increment initLength.
+        Label inBounds;
+        masm.branch32(Assembler::NotEqual, initLength, index, &inBounds);
+
+        // Increment initLength.
+        masm.add32(Imm32(1), initLength);
+
+        // If length is now <= index, increment length too.
+        Label skipIncrementLength;
+        Address length(scratch, ObjectElements::offsetOfLength());
+        masm.branch32(Assembler::Above, length, index, &skipIncrementLength);
+        masm.add32(Imm32(1), length);
+        masm.bind(&skipIncrementLength);
+
+        // Skip EmitPreBarrier as the memory is uninitialized.
+        masm.jump(&doStore);
+
+        masm.bind(&inBounds);
+    }
+
+    EmitPreBarrier(masm, element, MIRType::Value);
+
+    masm.bind(&doStore);
+    masm.storeValue(val, element);
+
+    BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch, LiveGeneralRegisterSet(), cx_);
+    return true;
+}
+
+bool
 BaselineCacheIRCompiler::emitStoreUnboxedArrayElement()
 {
     ObjOperandId objId = reader.objOperandId();
     Int32OperandId indexId = reader.int32OperandId();
 
     // Allocate the fixed registers first. These need to be fixed for
     // callTypeUpdateIC.
     AutoScratchRegister scratch(allocator, masm, R1.scratchReg());
@@ -1135,16 +1245,93 @@ BaselineCacheIRCompiler::emitStoreUnboxe
                               ConstantOrRegister(TypedOrValueRegister(val)),
                               /* failure = */ nullptr);
 
     if (UnboxedTypeNeedsPostBarrier(elementType))
         BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch, LiveGeneralRegisterSet(), cx_);
     return true;
 }
 
+bool
+BaselineCacheIRCompiler::emitStoreUnboxedArrayElementHole()
+{
+    ObjOperandId objId = reader.objOperandId();
+    Int32OperandId indexId = reader.int32OperandId();
+
+    // Allocate the fixed registers first. These need to be fixed for
+    // callTypeUpdateIC.
+    AutoScratchRegister scratch(allocator, masm, R1.scratchReg());
+    ValueOperand val = allocator.useFixedValueRegister(masm, reader.valOperandId(), R0);
+
+    JSValueType elementType = reader.valueType();
+    Register obj = allocator.useRegister(masm, objId);
+    Register index = allocator.useRegister(masm, indexId);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    // Check index <= initLength.
+    Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+    masm.load32(initLength, scratch);
+    masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratch);
+    masm.branch32(Assembler::Below, scratch, index, failure->label());
+
+    // Check capacity.
+    masm.checkUnboxedArrayCapacity(obj, RegisterOrInt32Constant(index), scratch, failure->label());
+
+    // Call the type update IC. After this everything must be infallible as we
+    // don't save all registers here.
+    if (elementType == JSVAL_TYPE_OBJECT) {
+        LiveGeneralRegisterSet saveRegs;
+        saveRegs.add(obj);
+        saveRegs.add(index);
+        saveRegs.add(val);
+        if (!callTypeUpdateIC(obj, val, scratch, saveRegs))
+            return false;
+    }
+
+    // Load obj->elements.
+    masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratch);
+
+    // If index == initLength, increment initialized length.
+    Label inBounds, doStore;
+    masm.load32(initLength, scratch);
+    masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratch);
+    masm.branch32(Assembler::NotEqual, scratch, index, &inBounds);
+
+    masm.add32(Imm32(1), initLength);
+
+    // If length is now <= index, increment length.
+    Address length(obj, UnboxedArrayObject::offsetOfLength());
+    Label skipIncrementLength;
+    masm.branch32(Assembler::Above, length, index, &skipIncrementLength);
+    masm.add32(Imm32(1), length);
+    masm.bind(&skipIncrementLength);
+
+    // Skip EmitUnboxedPreBarrierForBaseline as the memory is uninitialized.
+    masm.jump(&doStore);
+
+    masm.bind(&inBounds);
+
+    BaseIndex element(scratch, index, ScaleFromElemWidth(UnboxedTypeSize(elementType)));
+    EmitUnboxedPreBarrierForBaseline(masm, element, elementType);
+
+    // Note that the storeUnboxedProperty call here is infallible, as the
+    // IR emitter is responsible for guarding on |val|'s type.
+    masm.bind(&doStore);
+    masm.storeUnboxedProperty(element, elementType,
+                              ConstantOrRegister(TypedOrValueRegister(val)),
+                              /* failure = */ nullptr);
+
+    if (UnboxedTypeNeedsPostBarrier(elementType))
+        BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch, LiveGeneralRegisterSet(), cx_);
+    return true;
+}
+
 typedef bool (*CallNativeSetterFn)(JSContext*, HandleFunction, HandleObject, HandleValue);
 static const VMFunction CallNativeSetterInfo =
     FunctionInfo<CallNativeSetterFn>(CallNativeSetter, "CallNativeSetter");
 
 bool
 BaselineCacheIRCompiler::emitCallNativeSetter()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -320,21 +320,16 @@ DoTypeUpdateFallback(JSContext* cx, Base
                     break;
             }
         }
 
         JSObject* maybeSingleton = obj->isSingleton() ? obj.get() : nullptr;
         AddTypePropertyId(cx, group, maybeSingleton, id, value);
         break;
       }
-      case ICStub::SetElem_DenseOrUnboxedArrayAdd: {
-        id = JSID_VOID;
-        AddTypePropertyId(cx, obj, id, value);
-        break;
-      }
       default:
         MOZ_CRASH("Invalid stub");
     }
 
     return stub->addUpdateStubForValue(cx, script /* = outerScript */, obj, id, value);
 }
 
 typedef bool (*DoTypeUpdateFallbackFn)(JSContext*, BaselineFrame*, ICUpdatedStub*, HandleValue,
@@ -766,26 +761,16 @@ TypedThingRequiresFloatingPoint(JSObject
 {
     Scalar::Type type = TypedThingElementType(obj);
     return type == Scalar::Uint32 ||
            type == Scalar::Float32 ||
            type == Scalar::Float64;
 }
 
 static bool
-IsNativeOrUnboxedDenseElementAccess(HandleObject obj, HandleValue key)
-{
-    if (!obj->isNative() && !obj->is<UnboxedArrayObject>())
-        return false;
-    if (key.isInt32() && key.toInt32() >= 0 && !obj->is<TypedArrayObject>())
-        return true;
-    return false;
-}
-
-static bool
 DoGetElemFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub_, HandleValue lhs,
                   HandleValue rhs, MutableHandleValue res)
 {
     SharedStubInfo info(cx, frame, stub_->icEntry());
 
     // This fallback stub may trigger debug mode toggling.
     DebugModeOSRVolatileStub<ICGetElem_Fallback*> stub(frame, stub_);
 
@@ -913,65 +898,16 @@ LoadTypedThingLength(MacroAssembler& mas
         masm.loadPtr(Address(result, ObjectGroup::offsetOfAddendum()), result);
         masm.unboxInt32(Address(result, ArrayTypeDescr::offsetOfLength()), result);
         break;
       default:
         MOZ_CRASH();
     }
 }
 
-//
-// SetElem_Fallback
-//
-
-static bool
-SetElemAddHasSameShapes(ICSetElem_DenseOrUnboxedArrayAdd* stub, JSObject* obj)
-{
-    static const size_t MAX_DEPTH = ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH;
-    ICSetElem_DenseOrUnboxedArrayAddImpl<MAX_DEPTH>* nstub = stub->toImplUnchecked<MAX_DEPTH>();
-
-    if (obj->maybeShape() != nstub->shape(0))
-        return false;
-
-    JSObject* proto = obj->staticPrototype();
-    for (size_t i = 0; i < stub->protoChainDepth(); i++) {
-        if (!proto->isNative())
-            return false;
-        if (proto->as<NativeObject>().lastProperty() != nstub->shape(i + 1))
-            return false;
-        proto = obj->staticPrototype();
-        if (!proto) {
-            if (i != stub->protoChainDepth() - 1)
-                return false;
-            break;
-        }
-    }
-
-    return true;
-}
-
-static bool
-DenseOrUnboxedArraySetElemStubExists(JSContext* cx, ICStub::Kind kind,
-                                     ICSetElem_Fallback* stub, HandleObject obj)
-{
-    MOZ_ASSERT(kind == ICStub::SetElem_DenseOrUnboxedArrayAdd);
-
-    for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
-        if (iter->isSetElem_DenseOrUnboxedArrayAdd()) {
-            ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd();
-            if (JSObject::getGroup(cx, obj) == nstub->group() &&
-                SetElemAddHasSameShapes(nstub, obj))
-            {
-                return true;
-            }
-        }
-    }
-    return false;
-}
-
 static bool
 TypedArraySetElemStubExists(ICSetElem_Fallback* stub, HandleObject obj, bool expectOOB)
 {
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
         if (!iter->isSetElem_TypedArray())
             continue;
         ICSetElem_TypedArray* taStub = iter->toSetElem_TypedArray();
         if (obj->maybeShape() == taStub->shape() && taStub->expectOutOfBounds() == expectOOB)
@@ -995,86 +931,16 @@ RemoveExistingTypedArraySetElemStub(JSCo
         MOZ_ASSERT(!iter->toSetElem_TypedArray()->expectOutOfBounds());
         iter.unlink(cx);
         return true;
     }
     return false;
 }
 
 static bool
-CanOptimizeDenseOrUnboxedArraySetElem(JSObject* obj, uint32_t index,
-                                      Shape* oldShape, uint32_t oldCapacity, uint32_t oldInitLength,
-                                      bool* isAddingCaseOut, size_t* protoDepthOut)
-{
-    uint32_t initLength = GetAnyBoxedOrUnboxedInitializedLength(obj);
-    uint32_t capacity = GetAnyBoxedOrUnboxedCapacity(obj);
-
-    *isAddingCaseOut = false;
-    *protoDepthOut = 0;
-
-    // Some initial sanity checks.
-    if (initLength < oldInitLength || capacity < oldCapacity)
-        return false;
-
-    // Unboxed arrays need to be able to emit floating point code.
-    if (obj->is<UnboxedArrayObject>() && !obj->runtimeFromActiveCooperatingThread()->jitSupportsFloatingPoint)
-        return false;
-
-    Shape* shape = obj->maybeShape();
-
-    // Cannot optimize if the shape changed.
-    if (oldShape != shape)
-        return false;
-
-    // Cannot optimize if the capacity changed.
-    if (oldCapacity != capacity)
-        return false;
-
-    // Cannot optimize if the index doesn't fit within the new initialized length.
-    if (index >= initLength)
-        return false;
-
-    // Cannot optimize if the value at position after the set is a hole.
-    if (obj->isNative() && !obj->as<NativeObject>().containsDenseElement(index))
-        return false;
-
-    // At this point, if we know that the initLength did not change, then
-    // an optimized set is possible.
-    if (oldInitLength == initLength)
-        return true;
-
-    // If it did change, ensure that it changed specifically by incrementing by 1
-    // to accomodate this particular indexed set.
-    if (oldInitLength + 1 != initLength)
-        return false;
-    if (index != oldInitLength)
-        return false;
-
-    // The checks are not complete.  The object may have a setter definition,
-    // either directly, or via a prototype, or via the target object for a prototype
-    // which is a proxy, that handles a particular integer write.
-    // Scan the prototype and shape chain to make sure that this is not the case.
-    if (obj->isIndexed())
-        return false;
-    JSObject* curObj = obj->staticPrototype();
-    while (curObj) {
-        ++*protoDepthOut;
-        if (!curObj->isNative() || curObj->isIndexed())
-            return false;
-        curObj = curObj->staticPrototype();
-    }
-
-    if (*protoDepthOut > ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH)
-        return false;
-
-    *isAddingCaseOut = true;
-    return true;
-}
-
-static bool
 DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_, Value* stack,
                   HandleValue objv, HandleValue index, HandleValue rhs)
 {
     // This fallback stub may trigger debug mode toggling.
     DebugModeOSRVolatileStub<ICSetElem_Fallback*> stub(frame, stub_);
 
     RootedScript script(cx, frame->script());
     RootedScript outerScript(cx, script);
@@ -1127,24 +993,16 @@ DoSetElemFallback(JSContext* cx, Baselin
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Updated()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
             }
         }
     }
 
-    // Check the old capacity
-    uint32_t oldCapacity = 0;
-    uint32_t oldInitLength = 0;
-    if (index.isInt32() && index.toInt32() >= 0) {
-        oldCapacity = GetAnyBoxedOrUnboxedCapacity(obj);
-        oldInitLength = GetAnyBoxedOrUnboxedInitializedLength(obj);
-    }
-
     if (op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM) {
         if (!InitElemOperation(cx, pc, obj, index, rhs))
             return false;
     } else if (op == JSOP_INITELEM_ARRAY) {
         MOZ_ASSERT(uint32_t(index.toInt32()) <= INT32_MAX,
                    "the bytecode emitter must fail to compile code that would "
                    "produce JSOP_INITELEM_ARRAY with an index exceeding "
                    "int32_t range");
@@ -1194,55 +1052,16 @@ DoSetElemFallback(JSContext* cx, Baselin
                 newStub->toCacheIR_Updated()->updateStubId() = gen.updateStubId();
                 return true;
             }
         } else {
             gen.trackNotAttached();
         }
     }
 
-    // Try to generate new stubs.
-    if (IsNativeOrUnboxedDenseElementAccess(obj, index) && !rhs.isMagic(JS_ELEMENTS_HOLE)) {
-        bool addingCase;
-        size_t protoDepth;
-
-        if (CanOptimizeDenseOrUnboxedArraySetElem(obj, index.toInt32(),
-                                                  oldShape, oldCapacity, oldInitLength,
-                                                  &addingCase, &protoDepth))
-        {
-            RootedShape shape(cx, obj->maybeShape());
-            RootedObjectGroup group(cx, JSObject::getGroup(cx, obj));
-            if (!group)
-                return false;
-
-            if (addingCase &&
-                !DenseOrUnboxedArraySetElemStubExists(cx, ICStub::SetElem_DenseOrUnboxedArrayAdd,
-                                                      stub, obj))
-            {
-                JitSpew(JitSpew_BaselineIC,
-                        "  Generating SetElem_DenseOrUnboxedArrayAdd stub "
-                        "(shape=%p, group=%p, protoDepth=%" PRIuSIZE ")",
-                        shape.get(), group.get(), protoDepth);
-                ICSetElemDenseOrUnboxedArrayAddCompiler compiler(cx, obj, protoDepth);
-                ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(outerScript));
-                if (!newStub)
-                    return false;
-                if (compiler.needsUpdateStubs() &&
-                    !newStub->addUpdateStubForValue(cx, outerScript, obj, JSID_VOIDHANDLE, rhs))
-                {
-                    return false;
-                }
-
-                stub->addNewStub(newStub);
-            }
-        }
-
-        return true;
-    }
-
     if ((obj->is<TypedArrayObject>() || IsPrimitiveArrayTypedObject(obj)) &&
         index.isNumber() &&
         rhs.isNumber())
     {
         if (!cx->runtime()->jitSupportsFloatingPoint &&
             (TypedThingRequiresFloatingPoint(obj) || index.isDouble()))
         {
             return true;
@@ -1338,265 +1157,28 @@ BaselineScript::noteArrayWriteHole(uint3
 {
     ICEntry& entry = icEntryFromPCOffset(pcOffset);
     ICFallbackStub* stub = entry.fallbackStub();
 
     if (stub->isSetElem_Fallback())
         stub->toSetElem_Fallback()->noteArrayWriteHole();
 }
 
-//
-// SetElem_DenseOrUnboxedArray
-//
-
 void
 EmitUnboxedPreBarrierForBaseline(MacroAssembler &masm, const BaseIndex& address, JSValueType type)
 {
     if (type == JSVAL_TYPE_OBJECT)
         EmitPreBarrier(masm, address, MIRType::Object);
     else if (type == JSVAL_TYPE_STRING)
         EmitPreBarrier(masm, address, MIRType::String);
     else
         MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(type));
 }
 
 //
-// SetElem_DenseOrUnboxedArrayAdd
-//
-
-ICUpdatedStub*
-ICSetElemDenseOrUnboxedArrayAddCompiler::getStub(ICStubSpace* space)
-{
-    Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
-    if (!shapes.append(obj_->maybeShape()))
-        return nullptr;
-
-    if (!GetProtoShapes(obj_, protoChainDepth_, &shapes))
-        return nullptr;
-
-    JS_STATIC_ASSERT(ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH == 4);
-
-    ICUpdatedStub* stub = nullptr;
-    switch (protoChainDepth_) {
-      case 0: stub = getStubSpecific<0>(space, shapes); break;
-      case 1: stub = getStubSpecific<1>(space, shapes); break;
-      case 2: stub = getStubSpecific<2>(space, shapes); break;
-      case 3: stub = getStubSpecific<3>(space, shapes); break;
-      case 4: stub = getStubSpecific<4>(space, shapes); break;
-      default: MOZ_CRASH("ProtoChainDepth too high.");
-    }
-    if (!stub || !stub->initUpdatingChain(cx, space))
-        return nullptr;
-    return stub;
-}
-
-bool
-ICSetElemDenseOrUnboxedArrayAddCompiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    // R0 = object
-    // R1 = key
-    // Stack = { ... rhs-value, <return-addr>? }
-    Label failure, failurePopR0, failureUnstow;
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-    masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
-
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
-    Register scratchReg = regs.takeAny();
-
-    // Unbox R0 and guard on its group and, if this is a native access, its shape.
-    Register obj = masm.extractObject(R0, ExtractTemp0);
-    masm.loadPtr(Address(ICStubReg, ICSetElem_DenseOrUnboxedArrayAdd::offsetOfGroup()),
-                 scratchReg);
-    masm.branchTestObjGroup(Assembler::NotEqual, obj, scratchReg, &failure);
-    if (unboxedType_ == JSVAL_TYPE_MAGIC) {
-        masm.loadPtr(Address(ICStubReg, ICSetElem_DenseOrUnboxedArrayAddImpl<0>::offsetOfShape(0)),
-                     scratchReg);
-        masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
-    }
-
-    // Stow both R0 and R1 (object and key)
-    // But R0 and R1 still hold their values.
-    EmitStowICValues(masm, 2);
-
-    uint32_t framePushedAfterStow = masm.framePushed();
-
-    // We may need to free up some registers.
-    regs = availableGeneralRegs(0);
-    regs.take(R0);
-    regs.take(scratchReg);
-
-    // Shape guard objects on the proto chain.
-    Register protoReg = regs.takeAny();
-    for (size_t i = 0; i < protoChainDepth_; i++) {
-        masm.loadObjProto(i == 0 ? obj : protoReg, protoReg);
-        masm.branchTestPtr(Assembler::Zero, protoReg, protoReg, &failureUnstow);
-        masm.loadPtr(Address(ICStubReg, ICSetElem_DenseOrUnboxedArrayAddImpl<0>::offsetOfShape(i + 1)),
-                     scratchReg);
-        masm.branchTestObjShape(Assembler::NotEqual, protoReg, scratchReg, &failureUnstow);
-    }
-    regs.add(protoReg);
-    regs.add(scratchReg);
-
-    if (needsUpdateStubs()) {
-        // Stack is now: { ..., rhs-value, object-value, key-value, maybe?-RET-ADDR }
-        // Load rhs-value in to R0
-        masm.loadValue(Address(masm.getStackPointer(), 2 * sizeof(Value) + ICStackValueOffset), R0);
-
-        // Call the type-update stub.
-        if (!callTypeUpdateIC(masm, sizeof(Value)))
-            return false;
-    }
-
-    // Unstow R0 and R1 (object and key)
-    EmitUnstowICValues(masm, 2);
-
-    // Restore object.
-    obj = masm.extractObject(R0, ExtractTemp0);
-
-    if (needsUpdateStubs()) {
-        // Trigger post barriers here on the value being written. Fields which
-        // objects can be written to also need update stubs.
-        masm.Push(R1);
-        masm.loadValue(Address(masm.getStackPointer(), sizeof(Value) + ICStackValueOffset), R1);
-
-        LiveGeneralRegisterSet saveRegs;
-        saveRegs.add(R0);
-        saveRegs.addUnchecked(obj);
-        saveRegs.add(ICStubReg);
-        BaselineEmitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs, cx);
-
-        masm.Pop(R1);
-    }
-
-    // Reset register set.
-    regs = availableGeneralRegs(2);
-    scratchReg = regs.takeAny();
-
-    // Unbox key.
-    Register key = masm.extractInt32(R1, ExtractTemp1);
-
-    if (unboxedType_ == JSVAL_TYPE_MAGIC) {
-        // Adding element to a native object.
-
-        // Load obj->elements in scratchReg.
-        masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratchReg);
-
-        // Bounds check (key == initLength)
-        Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
-        masm.branch32(Assembler::NotEqual, initLength, key, &failure);
-
-        // Capacity check.
-        Address capacity(scratchReg, ObjectElements::offsetOfCapacity());
-        masm.branch32(Assembler::BelowOrEqual, capacity, key, &failure);
-
-        // Check for copy on write elements.
-        Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
-        masm.branchTest32(Assembler::NonZero, elementsFlags,
-                          Imm32(ObjectElements::COPY_ON_WRITE |
-                                ObjectElements::FROZEN),
-                          &failure);
-
-        // Failure is not possible now.  Free up registers.
-        regs.add(R0);
-        regs.add(R1);
-        regs.takeUnchecked(obj);
-        regs.takeUnchecked(key);
-
-        // Increment initLength before write.
-        masm.add32(Imm32(1), initLength);
-
-        // If length is now <= key, increment length before write.
-        Label skipIncrementLength;
-        Address length(scratchReg, ObjectElements::offsetOfLength());
-        masm.branch32(Assembler::Above, length, key, &skipIncrementLength);
-        masm.add32(Imm32(1), length);
-        masm.bind(&skipIncrementLength);
-
-        // Convert int32 values to double if convertDoubleElements is set. In this
-        // case the heap typeset is guaranteed to contain both int32 and double, so
-        // it's okay to store a double.
-        Label dontConvertDoubles;
-        masm.branchTest32(Assembler::Zero, elementsFlags,
-                          Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
-                          &dontConvertDoubles);
-
-        Address valueAddr(masm.getStackPointer(), ICStackValueOffset);
-
-        // Note that double arrays are only created by IonMonkey, so if we have no
-        // floating-point support Ion is disabled and there should be no double arrays.
-        if (cx->runtime()->jitSupportsFloatingPoint)
-            masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &dontConvertDoubles);
-        else
-            masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
-        masm.bind(&dontConvertDoubles);
-
-        // Write the value.  No need for pre-barrier since we're not overwriting an old value.
-        ValueOperand tmpVal = regs.takeAnyValue();
-        BaseIndex element(scratchReg, key, TimesEight);
-        masm.loadValue(valueAddr, tmpVal);
-        masm.storeValue(tmpVal, element);
-    } else {
-        // Adding element to an unboxed array.
-
-        // Bounds check (key == initLength)
-        Address initLengthAddr(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
-        masm.load32(initLengthAddr, scratchReg);
-        masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg);
-        masm.branch32(Assembler::NotEqual, scratchReg, key, &failure);
-
-        // Capacity check.
-        masm.checkUnboxedArrayCapacity(obj, RegisterOrInt32Constant(key), scratchReg, &failure);
-
-        // Load obj->elements.
-        masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg);
-
-        // Write the value first, since this can fail. No need for pre-barrier
-        // since we're not overwriting an old value.
-        masm.Push(R0);
-        Address valueAddr(masm.getStackPointer(), ICStackValueOffset + sizeof(Value));
-        masm.loadValue(valueAddr, R0);
-        BaseIndex address(scratchReg, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_)));
-        masm.storeUnboxedProperty(address, unboxedType_,
-                                  ConstantOrRegister(TypedOrValueRegister(R0)), &failurePopR0);
-        masm.Pop(R0);
-
-        // Increment initialized length.
-        masm.add32(Imm32(1), initLengthAddr);
-
-        // If length is now <= key, increment length.
-        Address lengthAddr(obj, UnboxedArrayObject::offsetOfLength());
-        Label skipIncrementLength;
-        masm.branch32(Assembler::Above, lengthAddr, key, &skipIncrementLength);
-        masm.add32(Imm32(1), lengthAddr);
-        masm.bind(&skipIncrementLength);
-    }
-
-    EmitReturnFromIC(masm);
-
-    if (failurePopR0.used()) {
-        // Failure case: restore the value of R0
-        masm.bind(&failurePopR0);
-        masm.popValue(R0);
-        masm.jump(&failure);
-    }
-
-    // Failure case - fail but first unstow R0 and R1
-    masm.bind(&failureUnstow);
-    masm.setFramePushed(framePushedAfterStow);
-    EmitUnstowICValues(masm, 2);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-//
 // SetElem_TypedArray
 //
 
 template <typename S, typename T>
 void
 BaselineStoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type, const S& value,
                           const T& dest, Register scratch, Label* failure,
                           Label* failureModifiedScratch)
@@ -4926,17 +4508,17 @@ ICTypeOf_Typed::Compiler::generateStubCo
 {
     MOZ_ASSERT(engine_ == Engine::Baseline);
     MOZ_ASSERT(type_ != JSTYPE_NULL);
     MOZ_ASSERT(type_ != JSTYPE_FUNCTION);
     MOZ_ASSERT(type_ != JSTYPE_OBJECT);
 
     Label failure;
     switch(type_) {
-      case JSTYPE_VOID:
+      case JSTYPE_UNDEFINED:
         masm.branchTestUndefined(Assembler::NotEqual, R0, &failure);
         break;
 
       case JSTYPE_STRING:
         masm.branchTestString(Assembler::NotEqual, R0, &failure);
         break;
 
       case JSTYPE_NUMBER:
@@ -5087,37 +4669,16 @@ ICTypeUpdate_SingleObject::ICTypeUpdate_
     obj_(obj)
 { }
 
 ICTypeUpdate_ObjectGroup::ICTypeUpdate_ObjectGroup(JitCode* stubCode, ObjectGroup* group)
   : ICStub(TypeUpdate_ObjectGroup, stubCode),
     group_(group)
 { }
 
-ICSetElem_DenseOrUnboxedArrayAdd::ICSetElem_DenseOrUnboxedArrayAdd(JitCode* stubCode, ObjectGroup* group,
-                                                                   size_t protoChainDepth)
-  : ICUpdatedStub(SetElem_DenseOrUnboxedArrayAdd, stubCode),
-    group_(group)
-{
-    MOZ_ASSERT(protoChainDepth <= MAX_PROTO_CHAIN_DEPTH);
-    extra_ = protoChainDepth;
-}
-
-template <size_t ProtoChainDepth>
-ICUpdatedStub*
-ICSetElemDenseOrUnboxedArrayAddCompiler::getStubSpecific(ICStubSpace* space,
-                                                         Handle<ShapeVector> shapes)
-{
-    RootedObjectGroup group(cx, JSObject::getGroup(cx, obj_));
-    if (!group)
-        return nullptr;
-    Rooted<JitCode*> stubCode(cx, getStubCode());
-    return newStub<ICSetElem_DenseOrUnboxedArrayAddImpl<ProtoChainDepth>>(space, stubCode, group, shapes);
-}
-
 ICSetElem_TypedArray::ICSetElem_TypedArray(JitCode* stubCode, Shape* shape, Scalar::Type type,
                                            bool expectOutOfBounds)
   : ICStub(SetElem_TypedArray, stubCode),
     shape_(shape)
 {
     extra_ = uint8_t(type);
     MOZ_ASSERT(extra_ == type);
     extra_ |= (static_cast<uint16_t>(expectOutOfBounds) << 8);
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -452,122 +452,16 @@ class ICSetElem_Fallback : public ICFall
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             return newStub<ICSetElem_Fallback>(space, getStubCode());
         }
     };
 };
 
-template <size_t ProtoChainDepth> class ICSetElem_DenseOrUnboxedArrayAddImpl;
-
-class ICSetElem_DenseOrUnboxedArrayAdd : public ICUpdatedStub
-{
-    friend class ICStubSpace;
-
-  public:
-    static const size_t MAX_PROTO_CHAIN_DEPTH = 4;
-
-  protected:
-    GCPtrObjectGroup group_;
-
-    ICSetElem_DenseOrUnboxedArrayAdd(JitCode* stubCode, ObjectGroup* group, size_t protoChainDepth);
-
-  public:
-    static size_t offsetOfGroup() {
-        return offsetof(ICSetElem_DenseOrUnboxedArrayAdd, group_);
-    }
-
-    GCPtrObjectGroup& group() {
-        return group_;
-    }
-    size_t protoChainDepth() const {
-        MOZ_ASSERT(extra_ <= MAX_PROTO_CHAIN_DEPTH);
-        return extra_;
-    }
-
-    template <size_t ProtoChainDepth>
-    ICSetElem_DenseOrUnboxedArrayAddImpl<ProtoChainDepth>* toImplUnchecked() {
-        return static_cast<ICSetElem_DenseOrUnboxedArrayAddImpl<ProtoChainDepth>*>(this);
-    }
-
-    template <size_t ProtoChainDepth>
-    ICSetElem_DenseOrUnboxedArrayAddImpl<ProtoChainDepth>* toImpl() {
-        MOZ_ASSERT(ProtoChainDepth == protoChainDepth());
-        return toImplUnchecked<ProtoChainDepth>();
-    }
-};
-
-template <size_t ProtoChainDepth>
-class ICSetElem_DenseOrUnboxedArrayAddImpl : public ICSetElem_DenseOrUnboxedArrayAdd
-{
-    friend class ICStubSpace;
-
-    // Note: for unboxed arrays, the first shape is null.
-    static const size_t NumShapes = ProtoChainDepth + 1;
-    mozilla::Array<GCPtrShape, NumShapes> shapes_;
-
-    ICSetElem_DenseOrUnboxedArrayAddImpl(JitCode* stubCode, ObjectGroup* group,
-                                         Handle<ShapeVector> shapes)
-      : ICSetElem_DenseOrUnboxedArrayAdd(stubCode, group, ProtoChainDepth)
-    {
-        MOZ_ASSERT(shapes.length() == NumShapes);
-        for (size_t i = 0; i < NumShapes; i++)
-            shapes_[i].init(shapes[i]);
-    }
-
-  public:
-    void traceShapes(JSTracer* trc) {
-        for (size_t i = 0; i < NumShapes; i++)
-            TraceNullableEdge(trc, &shapes_[i], "baseline-setelem-denseadd-stub-shape");
-    }
-    Shape* shape(size_t i) const {
-        MOZ_ASSERT(i < NumShapes);
-        return shapes_[i];
-    }
-    static size_t offsetOfShape(size_t idx) {
-        return offsetof(ICSetElem_DenseOrUnboxedArrayAddImpl, shapes_) + idx * sizeof(GCPtrShape);
-    }
-};
-
-class ICSetElemDenseOrUnboxedArrayAddCompiler : public ICStubCompiler {
-    RootedObject obj_;
-    size_t protoChainDepth_;
-    JSValueType unboxedType_;
-
-    MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
-
-  protected:
-    virtual int32_t getKey() const {
-        return static_cast<int32_t>(engine_) |
-              (static_cast<int32_t>(kind) << 1) |
-              (static_cast<int32_t>(protoChainDepth_) << 17) |
-              (static_cast<int32_t>(unboxedType_) << 20);
-    }
-
-  public:
-    ICSetElemDenseOrUnboxedArrayAddCompiler(JSContext* cx, HandleObject obj, size_t protoChainDepth)
-        : ICStubCompiler(cx, ICStub::SetElem_DenseOrUnboxedArrayAdd, Engine::Baseline),
-          obj_(cx, obj),
-          protoChainDepth_(protoChainDepth),
-          unboxedType_(obj->is<UnboxedArrayObject>()
-                       ? obj->as<UnboxedArrayObject>().elementType()
-                       : JSVAL_TYPE_MAGIC)
-    {}
-
-    template <size_t ProtoChainDepth>
-    ICUpdatedStub* getStubSpecific(ICStubSpace* space, Handle<ShapeVector> shapes);
-
-    ICUpdatedStub* getStub(ICStubSpace* space);
-
-    bool needsUpdateStubs() {
-        return unboxedType_ == JSVAL_TYPE_MAGIC || unboxedType_ == JSVAL_TYPE_OBJECT;
-    }
-};
-
 // Accesses scalar elements of a typed array or typed object.
 class ICSetElem_TypedArray : public ICStub
 {
     friend class ICStubSpace;
 
   protected: // Protected to silence Clang warning.
     GCPtrShape shape_;
 
--- a/js/src/jit/BaselineICList.h
+++ b/js/src/jit/BaselineICList.h
@@ -46,17 +46,16 @@ namespace jit {
     _(Call_ScriptedApplyArguments)               \
     _(Call_ScriptedFunCall)                      \
     _(Call_StringSplit)                          \
     _(Call_IsSuspendedStarGenerator)             \
                                                  \
     _(GetElem_Fallback)                          \
                                                  \
     _(SetElem_Fallback)                          \
-    _(SetElem_DenseOrUnboxedArrayAdd)            \
     _(SetElem_TypedArray)                        \
                                                  \
     _(In_Fallback)                               \
                                                  \
     _(GetName_Fallback)                          \
                                                  \
     _(BindName_Fallback)                         \
                                                  \
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -22,22 +22,16 @@ using namespace js::jit;
 using mozilla::DebugOnly;
 
 bool
 SetElemICInspector::sawOOBDenseWrite() const
 {
     if (!icEntry_)
         return false;
 
-    // Check for an element adding stub.
-    for (ICStub* stub = icEntry_->firstStub(); stub; stub = stub->next()) {
-        if (stub->isSetElem_DenseOrUnboxedArrayAdd())
-            return true;
-    }
-
     // Check for a write hole bit on the SetElem_Fallback stub.
     ICStub* stub = icEntry_->fallbackStub();
     if (stub->isSetElem_Fallback())
         return stub->toSetElem_Fallback()->hasArrayWriteHole();
 
     return false;
 }
 
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -1911,18 +1911,22 @@ SetPropIRGenerator::tryAttachStub()
             return false;
         }
 
         uint32_t index;
         Int32OperandId indexId;
         if (maybeGuardInt32Index(idVal_, setElemKeyValueId(), &index, &indexId)) {
             if (tryAttachSetDenseElement(obj, objId, index, indexId, rhsValId))
                 return true;
+            if (tryAttachSetDenseElementHole(obj, objId, index, indexId, rhsValId))
+                return true;
             if (tryAttachSetUnboxedArrayElement(obj, objId, index, indexId, rhsValId))
                 return true;
+            if (tryAttachSetUnboxedArrayElementHole(obj, objId, index, indexId, rhsValId))
+                return true;
             return false;
         }
         return false;
     }
     return false;
 }
 
 static void
@@ -2261,19 +2265,136 @@ SetPropIRGenerator::tryAttachSetDenseEle
 
     // Type inference uses JSID_VOID for the element types.
     setUpdateStubInfo(nobj->group(), JSID_VOID);
 
     trackAttached("SetDenseElement");
     return true;
 }
 
+static bool
+CanAttachAddElement(JSObject* obj, bool isInit)
+{
+    // Make sure the objects on the prototype don't have any indexed properties
+    // or that such properties can't appear without a shape change.
+    do {
+        // The first two checks are also relevant to the receiver object.
+        if (obj->isIndexed())
+            return false;
+
+        const Class* clasp = obj->getClass();
+        if ((clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_) &&
+            (clasp->getAddProperty() ||
+             clasp->getResolve() ||
+             clasp->getOpsLookupProperty() ||
+             clasp->getSetProperty() ||
+             clasp->getOpsSetProperty()))
+        {
+            return false;
+        }
+
+        // If we're initializing a property instead of setting one, the objects
+        // on the prototype are not relevant.
+        if (isInit)
+            break;
+
+        JSObject* proto = obj->staticPrototype();
+        if (!proto)
+            break;
+
+        if (!proto->isNative())
+            return false;
+
+        obj = proto;
+    } while (true);
+
+    return true;
+}
+
+static void
+ShapeGuardProtoChain(CacheIRWriter& writer, JSObject* obj, ObjOperandId objId)
+{
+    while (true) {
+        // Guard on the proto if the shape does not imply the proto. Singleton
+        // objects always trigger a shape change when the proto changes, so we
+        // don't need a guard in that case.
+        bool guardProto = obj->hasUncacheableProto() && !obj->isSingleton();
+
+        obj = obj->staticPrototype();
+        if (!obj)
+            return;
+
+        objId = writer.loadProto(objId);
+        if (guardProto)
+            writer.guardSpecificObject(objId, obj);
+        writer.guardShape(objId, obj->as<NativeObject>().shape());
+    }
+}
+
 bool
-SetPropIRGenerator::tryAttachSetUnboxedArrayElement(HandleObject obj, ObjOperandId objId, uint32_t index,
-                                                    Int32OperandId indexId, ValOperandId rhsId)
+SetPropIRGenerator::tryAttachSetDenseElementHole(HandleObject obj, ObjOperandId objId,
+                                                 uint32_t index, Int32OperandId indexId,
+                                                 ValOperandId rhsId)
+{
+    if (!obj->isNative() || rhsVal_.isMagic(JS_ELEMENTS_HOLE))
+        return false;
+
+    JSOp op = JSOp(*pc_);
+    MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
+
+    if (op == JSOP_INITHIDDENELEM)
+        return false;
+
+    NativeObject* nobj = &obj->as<NativeObject>();
+    if (nobj->getElementsHeader()->isFrozen())
+        return false;
+
+    uint32_t capacity = nobj->getDenseCapacity();
+    uint32_t initLength = nobj->getDenseInitializedLength();
+
+    // Optimize if we're adding an element at initLength or writing to a hole.
+    // Don't handle the adding case if the current accesss is in bounds, to
+    // ensure we always call noteArrayWriteHole.
+    bool isAdd = index == initLength;
+    bool isHoleInBounds = index < initLength && !nobj->containsDenseElement(index);
+    if (!isAdd && !isHoleInBounds)
+        return false;
+
+    // Checking the capacity also checks for arrays with non-writable length,
+    // as the capacity is always less than or equal to the length in this case.
+    if (index >= capacity)
+        return false;
+
+    MOZ_ASSERT(!nobj->is<TypedArrayObject>());
+
+    // Check for other indexed properties or class hooks.
+    if (!CanAttachAddElement(nobj, IsPropertyInitOp(op)))
+        return false;
+
+    writer.guardGroup(objId, nobj->group());
+    writer.guardShape(objId, nobj->shape());
+
+    // Also shape guard the proto chain, unless this is an INITELEM.
+    if (IsPropertySetOp(op))
+        ShapeGuardProtoChain(writer, obj, objId);
+
+    writer.storeDenseElementHole(objId, indexId, rhsId, isAdd);
+    writer.returnFromIC();
+
+    // Type inference uses JSID_VOID for the element types.
+    setUpdateStubInfo(nobj->group(), JSID_VOID);
+
+    trackAttached(isAdd ? "AddDenseElement" : "StoreDenseElementHole");
+    return true;
+}
+
+bool
+SetPropIRGenerator::tryAttachSetUnboxedArrayElement(HandleObject obj, ObjOperandId objId,
+                                                    uint32_t index, Int32OperandId indexId,
+                                                    ValOperandId rhsId)
 {
     if (!obj->is<UnboxedArrayObject>())
         return false;
 
     if (!cx_->runtime()->jitSupportsFloatingPoint)
         return false;
 
     if (index >= obj->as<UnboxedArrayObject>().initializedLength())
@@ -2290,16 +2411,62 @@ SetPropIRGenerator::tryAttachSetUnboxedA
     // Type inference uses JSID_VOID for the element types.
     setUpdateStubInfo(obj->group(), JSID_VOID);
 
     trackAttached("SetUnboxedArrayElement");
     return true;
 }
 
 bool
+SetPropIRGenerator::tryAttachSetUnboxedArrayElementHole(HandleObject obj, ObjOperandId objId,
+                                                        uint32_t index, Int32OperandId indexId,
+                                                        ValOperandId rhsId)
+{
+    if (!obj->is<UnboxedArrayObject>() || rhsVal_.isMagic(JS_ELEMENTS_HOLE))
+        return false;
+
+    if (!cx_->runtime()->jitSupportsFloatingPoint)
+        return false;
+
+    JSOp op = JSOp(*pc_);
+    MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
+
+    if (op == JSOP_INITHIDDENELEM)
+        return false;
+
+    // Optimize if we're adding an element at initLength. Unboxed arrays don't
+    // have holes at indexes < initLength.
+    UnboxedArrayObject* aobj = &obj->as<UnboxedArrayObject>();
+    if (index != aobj->initializedLength() || index >= aobj->capacity())
+        return false;
+
+    // Check for other indexed properties or class hooks.
+    if (!CanAttachAddElement(aobj, IsPropertyInitOp(op)))
+        return false;
+
+    writer.guardGroup(objId, aobj->group());
+
+    JSValueType elementType = aobj->group()->unboxedLayoutDontCheckGeneration().elementType();
+    EmitGuardUnboxedPropertyType(writer, elementType, rhsId);
+
+    // Also shape guard the proto chain, unless this is an INITELEM.
+    if (IsPropertySetOp(op))
+        ShapeGuardProtoChain(writer, aobj, objId);
+
+    writer.storeUnboxedArrayElementHole(objId, indexId, rhsId, elementType);
+    writer.returnFromIC();
+
+    // Type inference uses JSID_VOID for the element types.
+    setUpdateStubInfo(aobj->group(), JSID_VOID);
+
+    trackAttached("StoreUnboxedArrayElementHole");
+    return true;
+}
+
+bool
 SetPropIRGenerator::tryAttachAddSlotStub(HandleObjectGroup oldGroup, HandleShape oldShape)
 {
     AutoAssertNoPendingException aanpe(cx_);
 
     ValOperandId objValId(writer.setInputOperandId(0));
     ValOperandId rhsValId;
     if (cacheKind_ == CacheKind::SetProp) {
         rhsValId = ValOperandId(writer.setInputOperandId(1));
@@ -2414,34 +2581,17 @@ SetPropIRGenerator::tryAttachAddSlotStub
     // Shape guard the holder.
     ObjOperandId holderId = objId;
     if (!obj->isNative()) {
         MOZ_ASSERT(obj->as<UnboxedPlainObject>().maybeExpando());
         holderId = writer.guardAndLoadUnboxedExpando(objId);
     }
     writer.guardShape(holderId, oldShape);
 
-    // Shape guard the objects on the proto chain.
-    JSObject* lastObj = obj;
-    ObjOperandId lastObjId = objId;
-    while (true) {
-        // Guard on the proto if the shape does not imply the proto. Singleton
-        // objects always trigger a shape change when the proto changes, so we
-        // don't need a guard in that case.
-        bool guardProto = lastObj->hasUncacheableProto() && !lastObj->isSingleton();
-
-        lastObj = lastObj->staticPrototype();
-        if (!lastObj)
-            break;
-
-        lastObjId = writer.loadProto(lastObjId);
-        if (guardProto)
-            writer.guardSpecificObject(lastObjId, lastObj);
-        writer.guardShape(lastObjId, lastObj->as<NativeObject>().shape());
-    }
+    ShapeGuardProtoChain(writer, obj, objId);
 
     ObjectGroup* newGroup = obj->group();
 
     // Check if we have to change the object's group. If we're adding an
     // unboxed expando property, we pass the expando object to AddAndStore*Slot.
     // That's okay because we only have to do a group change if the object is a
     // PlainObject.
     bool changeGroup = oldGroup != newGroup;
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -183,17 +183,19 @@ extern const char* CacheKindNames[];
     _(StoreDynamicSlot)                   \
     _(AddAndStoreFixedSlot)               \
     _(AddAndStoreDynamicSlot)             \
     _(AllocateAndStoreDynamicSlot)        \
     _(StoreTypedObjectReferenceProperty)  \
     _(StoreTypedObjectScalarProperty)     \
     _(StoreUnboxedProperty)               \
     _(StoreDenseElement)                  \
+    _(StoreDenseElementHole)              \
     _(StoreUnboxedArrayElement)           \
+    _(StoreUnboxedArrayElementHole)       \
     _(CallNativeSetter)                   \
     _(CallScriptedSetter)                 \
     _(CallSetArrayLength)                 \
                                           \
     /* The *Result ops load a value into the cache's result register. */ \
     _(LoadFixedSlotResult)                \
     _(LoadDynamicSlotResult)              \
     _(LoadUnboxedPropertyResult)          \
@@ -658,16 +660,32 @@ class MOZ_RAII CacheIRWriter : public JS
     void storeUnboxedArrayElement(ObjOperandId obj, Int32OperandId index, ValOperandId rhs,
                                   JSValueType elementType)
     {
         writeOpWithOperandId(CacheOp::StoreUnboxedArrayElement, obj);
         writeOperandId(index);
         writeOperandId(rhs);
         buffer_.writeByte(uint32_t(elementType));
     }
+    void storeUnboxedArrayElementHole(ObjOperandId obj, Int32OperandId index, ValOperandId rhs,
+                                      JSValueType elementType)
+    {
+        writeOpWithOperandId(CacheOp::StoreUnboxedArrayElementHole, obj);
+        writeOperandId(index);
+        writeOperandId(rhs);
+        buffer_.writeByte(uint32_t(elementType));
+    }
+    void storeDenseElementHole(ObjOperandId obj, Int32OperandId index, ValOperandId rhs,
+                               bool handleAdd)
+    {
+        writeOpWithOperandId(CacheOp::StoreDenseElementHole, obj);
+        writeOperandId(index);
+        writeOperandId(rhs);
+        buffer_.writeByte(handleAdd);
+    }
     void callScriptedSetter(ObjOperandId obj, JSFunction* setter, ValOperandId rhs) {
         writeOpWithOperandId(CacheOp::CallScriptedSetter, obj);
         addStubField(uintptr_t(setter), StubField::Type::JSObject);
         writeOperandId(rhs);
     }
     void callNativeSetter(ObjOperandId obj, JSFunction* setter, ValOperandId rhs) {
         writeOpWithOperandId(CacheOp::CallNativeSetter, obj);
         addStubField(uintptr_t(setter), StubField::Type::JSObject);
@@ -1036,16 +1054,21 @@ class MOZ_RAII SetPropIRGenerator : publ
     bool tryAttachSetArrayLength(HandleObject obj, ObjOperandId objId, HandleId id,
                                  ValOperandId rhsId);
 
     bool tryAttachSetDenseElement(HandleObject obj, ObjOperandId objId, uint32_t index,
                                   Int32OperandId indexId, ValOperandId rhsId);
     bool tryAttachSetUnboxedArrayElement(HandleObject obj, ObjOperandId objId, uint32_t index,
                                          Int32OperandId indexId, ValOperandId rhsId);    
 
+    bool tryAttachSetDenseElementHole(HandleObject obj, ObjOperandId objId, uint32_t index,
+                                      Int32OperandId indexId, ValOperandId rhsId);
+    bool tryAttachSetUnboxedArrayElementHole(HandleObject obj, ObjOperandId objId, uint32_t index,
+                                             Int32OperandId indexId, ValOperandId rhsId);
+
     void trackAttached(const char* name);
 
   public:
     SetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
                        bool* isTemporarilyUnoptimizable, HandleValue lhsVal, HandleValue idVal,
                        HandleValue rhsVal);
 
     bool tryAttachStub();
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -2513,17 +2513,17 @@ IonBuilder::improveTypesAtTypeOfCompare(
     if (inputTypes->unknown())
         return Ok();
 
     // Note: we cannot remove the AnyObject type in the false branch,
     // since there are multiple ways to get an object. That is the reason
     // for the 'trueBranch' test.
     TemporaryTypeSet filter;
     const JSAtomState& names = GetJitContext()->runtime->names();
-    if (constant->toString() == TypeName(JSTYPE_VOID, names)) {
+    if (constant->toString() == TypeName(JSTYPE_UNDEFINED, names)) {
         filter.addType(TypeSet::UndefinedType(), alloc_->lifoAlloc());
         if (typeOf->inputMaybeCallableOrEmulatesUndefined() && trueBranch)
             filter.addType(TypeSet::AnyObjectType(), alloc_->lifoAlloc());
     } else if (constant->toString() == TypeName(JSTYPE_BOOLEAN, names)) {
         filter.addType(TypeSet::BooleanType(), alloc_->lifoAlloc());
     } else if (constant->toString() == TypeName(JSTYPE_NUMBER, names)) {
         filter.addType(TypeSet::Int32Type(), alloc_->lifoAlloc());
         filter.addType(TypeSet::DoubleType(), alloc_->lifoAlloc());
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -887,22 +887,34 @@ IonCacheIRCompiler::emitStoreTypedObject
 
 bool
 IonCacheIRCompiler::emitStoreDenseElement()
 {
     MOZ_CRASH("Baseline-specific op");
 }
 
 bool
+IonCacheIRCompiler::emitStoreDenseElementHole()
+{
+    MOZ_CRASH("Baseline-specific op");
+}
+
+bool
 IonCacheIRCompiler::emitStoreUnboxedArrayElement()
 {
     MOZ_CRASH("Baseline-specific op");
 }
 
 bool
+IonCacheIRCompiler::emitStoreUnboxedArrayElementHole()
+{
+    MOZ_CRASH("Baseline-specific op");
+}
+
+bool
 IonCacheIRCompiler::emitCallNativeSetter()
 {
     MOZ_CRASH("Baseline-specific op");
 }
 
 bool
 IonCacheIRCompiler::emitCallScriptedSetter()
 {
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -774,16 +774,50 @@ LIRGenerator::visitTest(MTest* test)
     }
 
     // All symbols are truthy.
     if (opd->type() == MIRType::Symbol) {
         add(new(alloc()) LGoto(ifTrue));
         return;
     }
 
+    if (opd->isCompare() && opd->isEmittedAtUses()) {
+        // Emit LBitAndBranch for cases like |if ((x & y) === 0)|.
+        MCompare* comp = opd->toCompare();
+        if ((comp->isInt32Comparison() ||
+             comp->compareType() == MCompare::Compare_UInt32) &&
+            comp->getOperand(1)->isConstant() &&
+            comp->getOperand(1)->toConstant()->isInt32(0) &&
+            comp->getOperand(0)->isBitAnd() &&
+            comp->getOperand(0)->isEmittedAtUses())
+        {
+            MDefinition* bitAnd = opd->getOperand(0);
+            MDefinition* lhs = bitAnd->getOperand(0);
+            MDefinition* rhs = bitAnd->getOperand(1);
+
+            Assembler::Condition cond = JSOpToCondition(comp->compareType(),
+                                                        comp->jsop());
+
+            if (lhs->type() == MIRType::Int32 && rhs->type() == MIRType::Int32 &&
+                (cond == Assembler::Equal || cond == Assembler::NotEqual))
+            {
+                ReorderCommutative(&lhs, &rhs, test);
+                if (cond == Assembler::Equal)
+                    cond = Assembler::Zero;
+                else if(cond == Assembler::NotEqual)
+                    cond = Assembler::NonZero;
+                else
+                    MOZ_ASSERT_UNREACHABLE("inequality operators cannot be folded");
+                lowerForBitAndAndBranch(new(alloc()) LBitAndAndBranch(ifTrue, ifFalse, cond),
+                                        test, lhs, rhs);
+                return;
+            }
+        }
+    }
+
     // Check if the operand for this test is a compare operation. If it is, we want
     // to emit an LCompare*AndBranch rather than an LTest*AndBranch, to fuse the
     // compare and jump instructions.
     if (opd->isCompare() && opd->isEmittedAtUses()) {
         MCompare* comp = opd->toCompare();
         MDefinition* left = comp->lhs();
         MDefinition* right = comp->rhs();
 
@@ -1216,20 +1250,21 @@ CanEmitBitAndAtUses(MInstruction* ins)
     if (ins->getOperand(0)->type() != MIRType::Int32 || ins->getOperand(1)->type() != MIRType::Int32)
         return false;
 
     MUseIterator iter(ins->usesBegin());
     if (iter == ins->usesEnd())
         return false;
 
     MNode* node = iter->consumer();
-    if (!node->isDefinition())
+    if (!node->isDefinition() || !node->toDefinition()->isInstruction())
         return false;
 
-    if (!node->toDefinition()->isTest())
+    MInstruction* use = node->toDefinition()->toInstruction();
+    if (!use->isTest() && !(use->isCompare() && CanEmitCompareAtUses(use)))
         return false;
 
     iter++;
     return iter == ins->usesEnd();
 }
 
 void
 LIRGenerator::visitBitAnd(MBitAnd* ins)
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -3900,17 +3900,17 @@ MTypeOf::foldsTo(TempAllocator& alloc)
         break;
       case MIRType::Symbol:
         type = JSTYPE_SYMBOL;
         break;
       case MIRType::Null:
         type = JSTYPE_OBJECT;
         break;
       case MIRType::Undefined:
-        type = JSTYPE_VOID;
+        type = JSTYPE_UNDEFINED;
         break;
       case MIRType::Boolean:
         type = JSTYPE_BOOLEAN;
         break;
       case MIRType::Object:
         if (!inputMaybeCallableOrEmulatesUndefined()) {
             // Object is not callable and does not emulate undefined, so it's
             // safe to fold to "object".
@@ -4444,17 +4444,17 @@ MCompare::tryFoldTypeOf(bool* result)
 
     if (jsop() != JSOP_STRICTEQ && jsop() != JSOP_STRICTNE &&
         jsop() != JSOP_EQ && jsop() != JSOP_NE)
     {
         return false;
     }
 
     const JSAtomState& names = GetJitContext()->runtime->names();
-    if (constant->toString() == TypeName(JSTYPE_VOID, names)) {
+    if (constant->toString() == TypeName(JSTYPE_UNDEFINED, names)) {
         if (!typeOf->input()->mightBeType(MIRType::Undefined) &&
             !typeOf->inputMaybeCallableOrEmulatesUndefined())
         {
             *result = (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE);
             return true;
         }
     } else if (constant->toString() == TypeName(JSTYPE_BOOLEAN, names)) {
         if (!typeOf->input()->mightBeType(MIRType::Boolean)) {
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -262,32 +262,16 @@ ICStub::trace(JSTracer* trc)
       }
       case ICStub::Call_StringSplit: {
         ICCall_StringSplit* callStub = toCall_StringSplit();
         TraceEdge(trc, &callStub->templateObject(), "baseline-callstringsplit-template");
         TraceEdge(trc, &callStub->expectedSep(), "baseline-callstringsplit-sep");
         TraceEdge(trc, &callStub->expectedStr(), "baseline-callstringsplit-str");
         break;
       }
-      case ICStub::SetElem_DenseOrUnboxedArrayAdd: {
-        ICSetElem_DenseOrUnboxedArrayAdd* setElemStub = toSetElem_DenseOrUnboxedArrayAdd();
-        TraceEdge(trc, &setElemStub->group(), "baseline-setelem-denseadd-group");
-
-        JS_STATIC_ASSERT(ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH == 4);
-
-        switch (setElemStub->protoChainDepth()) {
-          case 0: setElemStub->toImpl<0>()->traceShapes(trc); break;
-          case 1: setElemStub->toImpl<1>()->traceShapes(trc); break;
-          case 2: setElemStub->toImpl<2>()->traceShapes(trc); break;
-          case 3: setElemStub->toImpl<3>()->traceShapes(trc); break;
-          case 4: setElemStub->toImpl<4>()->traceShapes(trc); break;
-          default: MOZ_CRASH("Invalid proto stub.");
-        }
-        break;
-      }
       case ICStub::SetElem_TypedArray: {
         ICSetElem_TypedArray* setElemStub = toSetElem_TypedArray();
         TraceEdge(trc, &setElemStub->shape(), "baseline-setelem-typedarray-shape");
         break;
       }
       case ICStub::TypeMonitor_SingleObject: {
         ICTypeMonitor_SingleObject* monitorStub = toTypeMonitor_SingleObject();
         TraceEdge(trc, &monitorStub->object(), "baseline-monitor-singleton");
@@ -575,27 +559,16 @@ ICStubCompiler::callVM(const VMFunction&
     MOZ_ASSERT(fun.expectTailCall == NonTailCall);
     if (engine_ == Engine::Baseline)
         EmitBaselineCallVM(code, masm);
     else
         EmitIonCallVM(code, fun.explicitStackSlots(), masm);
     return true;
 }
 
-bool
-ICStubCompiler::callTypeUpdateIC(MacroAssembler& masm, uint32_t objectOffset)
-{
-    JitCode* code = cx->runtime()->jitRuntime()->getVMWrapper(DoTypeUpdateFallbackInfo);
-    if (!code)
-        return false;
-
-    EmitCallTypeUpdateIC(masm, code, objectOffset);
-    return true;
-}
-
 void
 ICStubCompiler::enterStubFrame(MacroAssembler& masm, Register scratch)
 {
     MOZ_ASSERT(engine_ == Engine::Baseline);
     EmitBaselineEnterStubFrame(masm, scratch);
 #ifdef DEBUG
     framePushedAtEnterStubFrame_ = masm.framePushed();
 #endif
--- a/js/src/jit/SharedIC.h
+++ b/js/src/jit/SharedIC.h
@@ -1062,20 +1062,16 @@ class ICStubCompiler
     void pushStubPayload(MacroAssembler& masm, Register scratch);
 
     // Emits a tail call to a VMFunction wrapper.
     MOZ_MUST_USE bool tailCallVM(const VMFunction& fun, MacroAssembler& masm);
 
     // Emits a normal (non-tail) call to a VMFunction wrapper.
     MOZ_MUST_USE bool callVM(const VMFunction& fun, MacroAssembler& masm);
 
-    // Emits a call to a type-update IC, assuming that the value to be
-    // checked is already in R0.
-    MOZ_MUST_USE bool callTypeUpdateIC(MacroAssembler& masm, uint32_t objectOffset);
-
     // A stub frame is used when a stub wants to call into the VM without
     // performing a tail call. This is required for the return address
     // to pc mapping to work.
     void enterStubFrame(MacroAssembler& masm, Register scratch);
     void leaveStubFrame(MacroAssembler& masm, bool calledIntoIon = false);
 
     // Some stubs need to emit Gecko Profiler updates.  This emits the guarding
     // jitcode for those stubs.  If profiling is not enabled, jumps to the
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -1628,17 +1628,17 @@ CodeGeneratorARM::visitCompareBitwiseAnd
 void
 CodeGeneratorARM::visitBitAndAndBranch(LBitAndAndBranch* baab)
 {
     ScratchRegisterScope scratch(masm);
     if (baab->right()->isConstant())
         masm.ma_tst(ToRegister(baab->left()), Imm32(ToInt32(baab->right())), scratch);
     else
         masm.ma_tst(ToRegister(baab->left()), ToRegister(baab->right()));
-    emitBranch(Assembler::NonZero, baab->ifTrue(), baab->ifFalse());
+    emitBranch(baab->cond(), baab->ifTrue(), baab->ifFalse());
 }
 
 void
 CodeGeneratorARM::visitWasmUint32ToDouble(LWasmUint32ToDouble* lir)
 {
     masm.convertUInt32ToDouble(ToRegister(lir->input()), ToFloatRegister(lir->output()));
 }
 
--- a/js/src/jit/arm/SharedICHelpers-arm.h
+++ b/js/src/jit/arm/SharedICHelpers-arm.h
@@ -263,73 +263,16 @@ EmitUnstowICValues(MacroAssembler& masm,
             masm.popValue(R1);
             masm.popValue(R0);
         }
         break;
     }
     masm.adjustFrame(-values * sizeof(Value));
 }
 
-inline void
-EmitCallTypeUpdateIC(MacroAssembler& masm, JitCode* code, uint32_t objectOffset)
-{
-    MOZ_ASSERT(R2 == ValueOperand(r1, r0));
-
-    // R0 contains the value that needs to be typechecked. The object we're
-    // updating is a boxed Value on the stack, at offset objectOffset from esp,
-    // excluding the return address.
-
-    // Save the current ICStubReg to stack, as well as the TailCallReg,
-    // since on ARM, the LR is live.
-    masm.push(ICStubReg);
-    masm.push(ICTailCallReg);
-
-    // This is expected to be called from within an IC, when ICStubReg is
-    // properly initialized to point to the stub.
-    masm.loadPtr(Address(ICStubReg, ICUpdatedStub::offsetOfFirstUpdateStub()),
-                 ICStubReg);
-
-    // TODO: Change r0 uses below to use masm's configurable scratch register instead.
-
-    // Load stubcode pointer from ICStubReg into ICTailCallReg.
-    masm.loadPtr(Address(ICStubReg, ICStub::offsetOfStubCode()), r0);
-
-    // Call the stubcode.
-    masm.ma_blx(r0);
-
-    // Restore the old stub reg and tailcall reg.
-    masm.pop(ICTailCallReg);
-    masm.pop(ICStubReg);
-
-    // The update IC will store 0 or 1 in R1.scratchReg() reflecting if the
-    // value in R0 type-checked properly or not.
-    Label success;
-    masm.cmp32(R1.scratchReg(), Imm32(1));
-    masm.j(Assembler::Equal, &success);
-
-    // If the IC failed, then call the update fallback function.
-    EmitBaselineEnterStubFrame(masm, R1.scratchReg());
-
-    masm.loadValue(Address(BaselineStackReg, STUB_FRAME_SIZE + objectOffset), R1);
-
-    masm.Push(R0);
-    masm.Push(R1);
-    masm.Push(ICStubReg);
-
-    // Load previous frame pointer, push BaselineFrame*.
-    masm.loadPtr(Address(BaselineFrameReg, 0), R0.scratchReg());
-    masm.pushBaselineFramePtr(R0.scratchReg(), R0.scratchReg());
-
-    EmitBaselineCallVM(code, masm);
-    EmitBaselineLeaveStubFrame(masm);
-
-    // Success at end.
-    masm.bind(&success);
-}
-
 template <typename AddrType>
 inline void
 EmitPreBarrier(MacroAssembler& masm, const AddrType& addr, MIRType type)
 {
     // On ARM, lr is clobbered by patchableCallPreBarrier. Save it first.
     masm.push(lr);
     masm.patchableCallPreBarrier(addr, type);
     masm.pop(lr);
--- a/js/src/jit/arm64/SharedICHelpers-arm64.h
+++ b/js/src/jit/arm64/SharedICHelpers-arm64.h
@@ -235,67 +235,16 @@ EmitUnstowICValues(MacroAssembler& masm,
             masm.pop(R1.valueReg(), R0.valueReg());
         break;
       default:
         MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Expected 1 or 2 values");
     }
     masm.adjustFrame(-values * sizeof(Value));
 }
 
-inline void
-EmitCallTypeUpdateIC(MacroAssembler& masm, JitCode* code, uint32_t objectOffset)
-{
-    // R0 contains the value that needs to be typechecked.
-    // The object we're updating is a boxed Value on the stack, at offset
-    // objectOffset from stack top, excluding the return address.
-    MOZ_ASSERT(R2 == ValueOperand(r0));
-
-    // Save the current ICStubReg to stack, as well as the TailCallReg,
-    // since on AArch64, the LR is live.
-    masm.push(ICStubReg, ICTailCallReg);
-
-    // This is expected to be called from within an IC, when ICStubReg
-    // is properly initialized to point to the stub.
-    masm.loadPtr(Address(ICStubReg, (int32_t)ICUpdatedStub::offsetOfFirstUpdateStub()),
-                 ICStubReg);
-
-    // Load stubcode pointer from ICStubReg into ICTailCallReg.
-    masm.loadPtr(Address(ICStubReg, ICStub::offsetOfStubCode()), ICTailCallReg);
-
-    // Call the stubcode.
-    masm.Blr(ARMRegister(ICTailCallReg, 64));
-
-    // Restore the old stub reg and tailcall reg.
-    masm.pop(ICTailCallReg, ICStubReg);
-
-    // The update IC will store 0 or 1 in R1.scratchReg() reflecting if the
-    // value in R0 type-checked properly or not.
-    Label success;
-    masm.cmp32(R1.scratchReg(), Imm32(1));
-    masm.j(Assembler::Equal, &success);
-
-    // If the IC failed, then call the update fallback function.
-    EmitBaselineEnterStubFrame(masm, R1.scratchReg());
-
-    masm.loadValue(Address(masm.getStackPointer(), STUB_FRAME_SIZE + objectOffset), R1);
-    masm.Push(R0.valueReg());
-    masm.Push(R1.valueReg());
-    masm.Push(ICStubReg);
-
-    // Load previous frame pointer, push BaselineFrame*.
-    masm.loadPtr(Address(BaselineFrameReg, 0), R0.scratchReg());
-    masm.pushBaselineFramePtr(R0.scratchReg(), R0.scratchReg());
-
-    EmitBaselineCallVM(code, masm);
-    EmitBaselineLeaveStubFrame(masm);
-
-    // Success at end.
-    masm.bind(&success);
-}
-
 template <typename AddrType>
 inline void
 EmitPreBarrier(MacroAssembler& masm, const AddrType& addr, MIRType type)
 {
     // On AArch64, lr is clobbered by patchableCallPreBarrier. Save it first.
     masm.push(lr);
     masm.patchableCallPreBarrier(addr, type);
     masm.pop(lr);
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
@@ -1731,17 +1731,17 @@ CodeGeneratorMIPSShared::visitCompareFAn
 
 void
 CodeGeneratorMIPSShared::visitBitAndAndBranch(LBitAndAndBranch* lir)
 {
     if (lir->right()->isConstant())
         masm.ma_and(ScratchRegister, ToRegister(lir->left()), Imm32(ToInt32(lir->right())));
     else
         masm.as_and(ScratchRegister, ToRegister(lir->left()), ToRegister(lir->right()));
-    emitBranch(ScratchRegister, ScratchRegister, Assembler::NonZero, lir->ifTrue(),
+    emitBranch(ScratchRegister, ScratchRegister, lir->cond(), lir->ifTrue(),
                lir->ifFalse());
 }
 
 void
 CodeGeneratorMIPSShared::visitWasmUint32ToDouble(LWasmUint32ToDouble* lir)
 {
     masm.convertUInt32ToDouble(ToRegister(lir->input()), ToFloatRegister(lir->output()));
 }
--- a/js/src/jit/mips-shared/SharedICHelpers-mips-shared.h
+++ b/js/src/jit/mips-shared/SharedICHelpers-mips-shared.h
@@ -267,70 +267,16 @@ EmitUnstowICValues(MacroAssembler& masm,
             masm.popValue(R1);
             masm.popValue(R0);
         }
         break;
     }
     masm.adjustFrame(-values * sizeof(Value));
 }
 
-inline void
-EmitCallTypeUpdateIC(MacroAssembler& masm, JitCode* code, uint32_t objectOffset)
-{
-    // R0 contains the value that needs to be typechecked.
-    // The object we're updating is a boxed Value on the stack, at offset
-    // objectOffset from $sp, excluding the return address.
-
-    // Save the current ICStubReg to stack, as well as the TailCallReg,
-    // since on mips, the $ra is live.
-    masm.subPtr(Imm32(2 * sizeof(intptr_t)), StackPointer);
-    masm.storePtr(ICStubReg, Address(StackPointer, sizeof(intptr_t)));
-    masm.storePtr(ICTailCallReg, Address(StackPointer, 0));
-
-    // This is expected to be called from within an IC, when ICStubReg
-    // is properly initialized to point to the stub.
-    masm.loadPtr(Address(ICStubReg, ICUpdatedStub::offsetOfFirstUpdateStub()),
-                 ICStubReg);
-
-    // Load stubcode pointer from ICStubReg into ICTailCallReg.
-    masm.loadPtr(Address(ICStubReg, ICStub::offsetOfStubCode()), R2.scratchReg());
-
-    // Call the stubcode.
-    masm.call(R2.scratchReg());
-
-    // Restore the old stub reg and tailcall reg.
-    masm.loadPtr(Address(StackPointer, 0), ICTailCallReg);
-    masm.loadPtr(Address(StackPointer, sizeof(intptr_t)), ICStubReg);
-    masm.addPtr(Imm32(2 * sizeof(intptr_t)), StackPointer);
-
-    // The update IC will store 0 or 1 in R1.scratchReg() reflecting if the
-    // value in R0 type-checked properly or not.
-    Label success;
-    masm.ma_b(R1.scratchReg(), Imm32(1), &success, Assembler::Equal, ShortJump);
-
-    // If the IC failed, then call the update fallback function.
-    EmitBaselineEnterStubFrame(masm, R1.scratchReg());
-
-    masm.loadValue(Address(BaselineStackReg, STUB_FRAME_SIZE + objectOffset), R1);
-
-    masm.Push(R0);
-    masm.Push(R1);
-    masm.Push(ICStubReg);
-
-    // Load previous frame pointer, push BaselineFrame*.
-    masm.loadPtr(Address(BaselineFrameReg, 0), R0.scratchReg());
-    masm.pushBaselineFramePtr(R0.scratchReg(), R0.scratchReg());
-
-    EmitBaselineCallVM(code, masm);
-    EmitBaselineLeaveStubFrame(masm);
-
-    // Success at end.
-    masm.bind(&success);
-}
-
 template <typename AddrType>
 inline void
 EmitPreBarrier(MacroAssembler& masm, const AddrType& addr, MIRType type)
 {
     // On MIPS, $ra is clobbered by patchableCallPreBarrier. Save it first.
     masm.push(ra);
     masm.patchableCallPreBarrier(addr, type);
     masm.pop(ra);
--- a/js/src/jit/none/SharedICHelpers-none.h
+++ b/js/src/jit/none/SharedICHelpers-none.h
@@ -24,17 +24,16 @@ inline void EmitBaselineTailCallVM(JitCo
 inline void EmitIonTailCallVM(JitCode*, MacroAssembler&, uint32_t) { MOZ_CRASH(); }
 inline void EmitBaselineCreateStubFrameDescriptor(MacroAssembler&, Register, uint32_t) { MOZ_CRASH(); }
 inline void EmitBaselineCallVM(JitCode*, MacroAssembler&) { MOZ_CRASH(); }
 inline void EmitIonCallVM(JitCode*, size_t, MacroAssembler&) { MOZ_CRASH(); }
 inline void EmitBaselineEnterStubFrame(MacroAssembler&, Register) { MOZ_CRASH(); }
 inline void EmitBaselineLeaveStubFrame(MacroAssembler&, bool v = false) { MOZ_CRASH(); }
 inline void EmitStowICValues(MacroAssembler&, int) { MOZ_CRASH(); }
 inline void EmitUnstowICValues(MacroAssembler&, int, bool v = false) { MOZ_CRASH(); }
-inline void EmitCallTypeUpdateIC(MacroAssembler&, JitCode*, uint32_t) { MOZ_CRASH(); }
 inline void EmitStubGuardFailure(MacroAssembler&) { MOZ_CRASH(); }
 
 template <typename T> inline void EmitPreBarrier(MacroAssembler&, T, MIRType) { MOZ_CRASH(); }
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_none_SharedICHelpers_none_h */
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -2962,19 +2962,22 @@ class LCompareVM : public LCallInstructi
 
     MCompare* mir() const {
         return mir_->toCompare();
     }
 };
 
 class LBitAndAndBranch : public LControlInstructionHelper<2, 2, 0>
 {
+    Assembler::Condition cond_;
   public:
     LIR_HEADER(BitAndAndBranch)
-    LBitAndAndBranch(MBasicBlock* ifTrue, MBasicBlock* ifFalse)
+    LBitAndAndBranch(MBasicBlock* ifTrue, MBasicBlock* ifFalse,
+                     Assembler::Condition cond = Assembler::NonZero)
+        : cond_(cond)
     {
         setSuccessor(0, ifTrue);
         setSuccessor(1, ifFalse);
     }
 
     MBasicBlock* ifTrue() const {
         return getSuccessor(0);
     }
@@ -2982,16 +2985,20 @@ class LBitAndAndBranch : public LControl
         return getSuccessor(1);
     }
     const LAllocation* left() {
         return getOperand(0);
     }
     const LAllocation* right() {
         return getOperand(1);
     }
+    Assembler::Condition cond() const {
+        MOZ_ASSERT(cond_ == Assembler::Zero || cond_ == Assembler::NonZero);
+        return cond_;
+    }
 };
 
 // Takes a value and tests whether it is null, undefined, or is an object that
 // emulates |undefined|, as determined by the JSCLASS_EMULATES_UNDEFINED class
 // flag on unwrapped objects.  See also js::EmulatesUndefined.
 class LIsNullOrLikeUndefinedV : public LInstructionHelper<1, BOX_PIECES, 2>
 {
   public:
--- a/js/src/jit/x64/SharedICHelpers-x64.h
+++ b/js/src/jit/x64/SharedICHelpers-x64.h
@@ -270,63 +270,16 @@ EmitUnstowICValues(MacroAssembler& masm,
             masm.popValue(R0);
         }
         masm.push(ICTailCallReg);
         break;
     }
     masm.adjustFrame(-values * sizeof(Value));
 }
 
-inline void
-EmitCallTypeUpdateIC(MacroAssembler& masm, JitCode* code, uint32_t objectOffset)
-{
-    // R0 contains the value that needs to be typechecked.
-    // The object we're updating is a boxed Value on the stack, at offset
-    // objectOffset from stack top, excluding the return address.
-
-    // Save the current ICStubReg to stack
-    masm.push(ICStubReg);
-
-    // This is expected to be called from within an IC, when ICStubReg
-    // is properly initialized to point to the stub.
-    masm.loadPtr(Address(ICStubReg, (int32_t) ICUpdatedStub::offsetOfFirstUpdateStub()),
-                 ICStubReg);
-
-    // Call the stubcode.
-    masm.call(Address(ICStubReg, ICStub::offsetOfStubCode()));
-
-    // Restore the old stub reg.
-    masm.pop(ICStubReg);
-
-    // The update IC will store 0 or 1 in R1.scratchReg() reflecting if the
-    // value in R0 type-checked properly or not.
-    Label success;
-    masm.cmp32(R1.scratchReg(), Imm32(1));
-    masm.j(Assembler::Equal, &success);
-
-    // If the IC failed, then call the update fallback function.
-    EmitBaselineEnterStubFrame(masm, R1.scratchReg());
-
-    masm.loadValue(Address(BaselineStackReg, STUB_FRAME_SIZE + objectOffset), R1);
-
-    masm.Push(R0);
-    masm.Push(R1);
-    masm.Push(ICStubReg);
-
-    // Load previous frame pointer, push BaselineFrame*.
-    masm.loadPtr(Address(BaselineFrameReg, 0), R0.scratchReg());
-    masm.pushBaselineFramePtr(R0.scratchReg(), R0.scratchReg());
-
-    EmitBaselineCallVM(code, masm);
-    EmitBaselineLeaveStubFrame(masm);
-
-    // Success at end.
-    masm.bind(&success);
-}
-
 template <typename AddrType>
 inline void
 EmitPreBarrier(MacroAssembler& masm, const AddrType& addr, MIRType type)
 {
     masm.patchableCallPreBarrier(addr, type);
 }
 
 inline void
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -136,17 +136,17 @@ CodeGeneratorX86Shared::visitTestFAndBra
 
 void
 CodeGeneratorX86Shared::visitBitAndAndBranch(LBitAndAndBranch* baab)
 {
     if (baab->right()->isConstant())
         masm.test32(ToRegister(baab->left()), Imm32(ToInt32(baab->right())));
     else
         masm.test32(ToRegister(baab->left()), ToRegister(baab->right()));
-    emitBranch(Assembler::NonZero, baab->ifTrue(), baab->ifFalse());
+    emitBranch(baab->cond(), baab->ifTrue(), baab->ifFalse());
 }
 
 void
 CodeGeneratorX86Shared::emitCompare(MCompare::CompareType type, const LAllocation* left, const LAllocation* right)
 {
 #ifdef JS_CODEGEN_X64
     if (type == MCompare::Compare_Object) {
         masm.cmpPtr(ToRegister(left), ToOperand(right));
--- a/js/src/jit/x86/SharedICHelpers-x86.h
+++ b/js/src/jit/x86/SharedICHelpers-x86.h
@@ -266,63 +266,16 @@ EmitUnstowICValues(MacroAssembler& masm,
             masm.popValue(R0);
         }
         masm.push(ICTailCallReg);
         break;
     }
     masm.adjustFrame(-values * sizeof(Value));
 }
 
-inline void
-EmitCallTypeUpdateIC(MacroAssembler& masm, JitCode* code, uint32_t objectOffset)
-{
-    // R0 contains the value that needs to be typechecked.
-    // The object we're updating is a boxed Value on the stack, at offset
-    // objectOffset from stack top, excluding the return address.
-
-    // Save the current ICStubReg to stack
-    masm.push(ICStubReg);
-
-    // This is expected to be called from within an IC, when ICStubReg
-    // is properly initialized to point to the stub.
-    masm.loadPtr(Address(ICStubReg, (int32_t) ICUpdatedStub::offsetOfFirstUpdateStub()),
-                 ICStubReg);
-
-    // Call the stubcode.
-    masm.call(Address(ICStubReg, ICStub::offsetOfStubCode()));
-
-    // Restore the old stub reg.
-    masm.pop(ICStubReg);
-
-    // The update IC will store 0 or 1 in R1.scratchReg() reflecting if the
-    // value in R0 type-checked properly or not.
-    Label success;
-    masm.cmp32(R1.scratchReg(), Imm32(1));
-    masm.j(Assembler::Equal, &success);
-
-    // If the IC failed, then call the update fallback function.
-    EmitBaselineEnterStubFrame(masm, R1.scratchReg());
-
-    masm.loadValue(Address(BaselineStackReg, STUB_FRAME_SIZE + objectOffset), R1);
-
-    masm.Push(R0);
-    masm.Push(R1);
-    masm.Push(ICStubReg);
-
-    // Load previous frame pointer, push BaselineFrame*.
-    masm.loadPtr(Address(BaselineFrameReg, 0), R0.scratchReg());
-    masm.pushBaselineFramePtr(R0.scratchReg(), R0.scratchReg());
-
-    EmitBaselineCallVM(code, masm);
-    EmitBaselineLeaveStubFrame(masm);
-
-    // Success at end.
-    masm.bind(&success);
-}
-
 template <typename AddrType>
 inline void
 EmitPreBarrier(MacroAssembler& masm, const AddrType& addr, MIRType type)
 {
     masm.patchableCallPreBarrier(addr, type);
 }
 
 inline void
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -420,17 +420,17 @@ MSG_DEF(JSMSG_BAD_TRAP,                1
 
 // Structured cloning
 MSG_DEF(JSMSG_SC_BAD_CLONE_VERSION,    0, JSEXN_ERR, "unsupported structured clone version")
 MSG_DEF(JSMSG_SC_BAD_SERIALIZED_DATA,  1, JSEXN_INTERNALERR, "bad serialized structured data ({0})")
 MSG_DEF(JSMSG_SC_DUP_TRANSFERABLE,     0, JSEXN_TYPEERR, "duplicate transferable for structured clone")
 MSG_DEF(JSMSG_SC_NOT_TRANSFERABLE,     0, JSEXN_TYPEERR, "invalid transferable array for structured clone")
 MSG_DEF(JSMSG_SC_UNSUPPORTED_TYPE,     0, JSEXN_TYPEERR, "unsupported type for structured data")
 MSG_DEF(JSMSG_SC_NOT_CLONABLE,         1, JSEXN_TYPEERR, "{0} cannot be cloned in this context")
-MSG_DEF(JSMSG_SC_SAB_TRANSFER,         0, JSEXN_WARN, "SharedArrayBuffer must not be in the transfer list")
+MSG_DEF(JSMSG_SC_SAB_TRANSFERABLE,     0, JSEXN_TYPEERR, "SharedArrayBuffer must not be in the transfer list")
 MSG_DEF(JSMSG_SC_SAB_DISABLED,         0, JSEXN_TYPEERR, "SharedArrayBuffer not cloned - shared memory disabled in receiver")
 
 // Debugger
 MSG_DEF(JSMSG_ASSIGN_FUNCTION_OR_NULL, 1, JSEXN_TYPEERR, "value assigned to {0} must be a function or null")
 MSG_DEF(JSMSG_DEBUG_BAD_AWAIT,         0, JSEXN_TYPEERR, "await expression received invalid value")
 MSG_DEF(JSMSG_DEBUG_BAD_LINE,          0, JSEXN_TYPEERR, "invalid line number")
 MSG_DEF(JSMSG_DEBUG_BAD_OFFSET,        0, JSEXN_TYPEERR, "invalid script offset")
 MSG_DEF(JSMSG_DEBUG_BAD_REFERENT,      2, JSEXN_TYPEERR, "{0} does not refer to {1}")
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -907,18 +907,21 @@ JS_TransplantObject(JSContext* cx, Handl
     if (origobj->compartment() != destination) {
         RootedObject newIdentityWrapper(cx, newIdentity);
         AutoCompartment ac(cx, origobj);
         if (!JS_WrapObject(cx, &newIdentityWrapper))
             MOZ_CRASH();
         MOZ_ASSERT(Wrapper::wrappedObject(newIdentityWrapper) == newIdentity);
         if (!JSObject::swap(cx, origobj, newIdentityWrapper))
             MOZ_CRASH();
-        if (!origobj->compartment()->putWrapper(cx, CrossCompartmentKey(newIdentity), origv))
+        if (!origobj->compartment()->putWrapperMaybeUpdate(cx, CrossCompartmentKey(newIdentity),
+                                                           origv))
+        {
             MOZ_CRASH();
+        }
     }
 
     // The new identity object might be one of several things. Return it to avoid
     // ambiguity.
     return newIdentity;
 }
 
 /*
@@ -1569,17 +1572,17 @@ JS_IdToValue(JSContext* cx, jsid id, Mut
 
 JS_PUBLIC_API(bool)
 JS::ToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp)
 {
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
     MOZ_ASSERT(obj != nullptr);
-    MOZ_ASSERT(hint == JSTYPE_VOID || hint == JSTYPE_STRING || hint == JSTYPE_NUMBER);
+    MOZ_ASSERT(hint == JSTYPE_UNDEFINED || hint == JSTYPE_STRING || hint == JSTYPE_NUMBER);
     vp.setObject(*obj);
     return ToPrimitiveSlow(cx, hint, vp);
 }
 
 JS_PUBLIC_API(bool)
 JS::GetFirstArgumentAsTypeHint(JSContext* cx, CallArgs args, JSType *result)
 {
     if (!args.get(0).isString()) {
@@ -1591,17 +1594,17 @@ JS::GetFirstArgumentAsTypeHint(JSContext
     }
 
     RootedString str(cx, args.get(0).toString());
     bool match;
 
     if (!EqualStrings(cx, str, cx->names().default_, &match))
         return false;
     if (match) {
-        *result = JSTYPE_VOID;
+        *result = JSTYPE_UNDEFINED;
         return true;
     }
 
     if (!EqualStrings(cx, str, cx->names().string, &match))
         return false;
     if (match) {
         *result = JSTYPE_STRING;
         return true;
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1848,27 +1848,27 @@ extern JS_PUBLIC_API(bool)
 JS_IdToValue(JSContext* cx, jsid id, JS::MutableHandle<JS::Value> vp);
 
 namespace JS {
 
 /**
  * Convert obj to a primitive value. On success, store the result in vp and
  * return true.
  *
- * The hint argument must be JSTYPE_STRING, JSTYPE_NUMBER, or JSTYPE_VOID (no
- * hint).
+ * The hint argument must be JSTYPE_STRING, JSTYPE_NUMBER, or
+ * JSTYPE_UNDEFINED (no hint).
  *
  * Implements: ES6 7.1.1 ToPrimitive(input, [PreferredType]).
  */
 extern JS_PUBLIC_API(bool)
 ToPrimitive(JSContext* cx, JS::HandleObject obj, JSType hint, JS::MutableHandleValue vp);
 
 /**
  * If args.get(0) is one of the strings "string", "number", or "default", set
- * *result to JSTYPE_STRING, JSTYPE_NUMBER, or JSTYPE_VOID accordingly and
+ * result to JSTYPE_STRING, JSTYPE_NUMBER, or JSTYPE_UNDEFINED accordingly and
  * return true. Otherwise, return false with a TypeError pending.
  *
  * This can be useful in implementing a @@toPrimitive method.
  */
 extern JS_PUBLIC_API(bool)
 GetFirstArgumentAsTypeHint(JSContext* cx, CallArgs args, JSType *result);
 
 } /* namespace JS */
--- a/js/src/jsatominlines.h
+++ b/js/src/jsatominlines.h
@@ -190,17 +190,17 @@ AtomHasher::match(const AtomStateEntry& 
 
 inline Handle<PropertyName*>
 TypeName(JSType type, const JSAtomState& names)
 {
     MOZ_ASSERT(type < JSTYPE_LIMIT);
     JS_STATIC_ASSERT(offsetof(JSAtomState, undefined) +
                      JSTYPE_LIMIT * sizeof(ImmutablePropertyNamePtr) <=
                      sizeof(JSAtomState));
-    JS_STATIC_ASSERT(JSTYPE_VOID == 0);
+    JS_STATIC_ASSERT(JSTYPE_UNDEFINED == 0);
     return (&names.undefined)[type];
 }
 
 inline Handle<PropertyName*>
 ClassName(JSProtoKey key, JSAtomState& atomState)
 {
     MOZ_ASSERT(key < JSProto_LIMIT);
     JS_STATIC_ASSERT(offsetof(JSAtomState, Null) +
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -245,18 +245,33 @@ JSCompartment::checkWrapperMapAfterMovin
 
         WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(e.front().key());
         MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
     }
 }
 #endif
 
 bool
-JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped,
-                          const js::Value& wrapper)
+JSCompartment::putNewWrapper(JSContext* cx, const CrossCompartmentKey& wrapped,
+                             const js::Value& wrapper)
+{
+    MOZ_ASSERT(wrapped.is<JSString*>() == wrapper.isString());
+    MOZ_ASSERT_IF(!wrapped.is<JSString*>(), wrapper.isObject());
+
+    if (!crossCompartmentWrappers.putNew(wrapped, wrapper)) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    return true;
+}
+
+bool
+JSCompartment::putWrapperMaybeUpdate(JSContext* cx, const CrossCompartmentKey& wrapped,
+                                     const js::Value& wrapper)
 {
     MOZ_ASSERT(wrapped.is<JSString*>() == wrapper.isString());
     MOZ_ASSERT_IF(!wrapped.is<JSString*>(), wrapper.isObject());
 
     if (!crossCompartmentWrappers.put(wrapped, wrapper)) {
         ReportOutOfMemory(cx);
         return false;
     }
@@ -337,17 +352,17 @@ JSCompartment::wrap(JSContext* cx, Mutab
         strp.set(p->value().get().toString());
         return true;
     }
 
     /* No dice. Make a copy, and cache it. */
     JSString* copy = CopyStringPure(cx, str);
     if (!copy)
         return false;
-    if (!putWrapper(cx, CrossCompartmentKey(key), StringValue(copy)))
+    if (!putNewWrapper(cx, CrossCompartmentKey(key), StringValue(copy)))
         return false;
 
     strp.set(copy);
     return true;
 }
 
 bool
 JSCompartment::getNonWrapperObjectForCurrentCompartment(JSContext* cx, MutableHandleObject obj)
@@ -427,17 +442,17 @@ JSCompartment::getOrCreateWrapper(JSCont
     RootedObject wrapper(cx, wrap(cx, existing, obj));
     if (!wrapper)
         return false;
 
     // We maintain the invariant that the key in the cross-compartment wrapper
     // map is always directly wrapped by the value.
     MOZ_ASSERT(Wrapper::wrappedObject(wrapper) == &key.get().toObject());
 
-    if (!putWrapper(cx, CrossCompartmentKey(key), ObjectValue(*wrapper))) {
+    if (!putNewWrapper(cx, CrossCompartmentKey(key), ObjectValue(*wrapper))) {
         // Enforce the invariant that all cross-compartment wrapper object are
         // in the map by nuking the wrapper if we couldn't add it.
         // Unfortunately it's possible for the wrapper to still be marked if we
         // took this path, for example if the object metadata callback stashes a
         // reference to it.
         if (wrapper->is<CrossCompartmentWrapperObject>())
             NukeCrossCompartmentWrapper(cx, wrapper);
         return false;
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -429,19 +429,16 @@ struct JSCompartment
 
   private:
     const js::AllocationMetadataBuilder *allocationMetadataBuilder;
 
     js::SavedStacks              savedStacks_;
 
     js::WrapperMap               crossCompartmentWrappers;
 
-    using CCKeyVector = mozilla::Vector<js::CrossCompartmentKey, 0, js::SystemAllocPolicy>;
-    CCKeyVector                  nurseryCCKeys;
-
     // The global environment record's [[VarNames]] list that contains all
     // names declared using FunctionDeclaration, GeneratorDeclaration, and
     // VariableDeclaration declarations in global code in this compartment.
     // Names are only removed from this list by a |delete IdentifierReference|
     // that successfully removes that global property.
     JS::GCHashSet<JSAtom*,
                   js::DefaultHasher<JSAtom*>,
                   js::SystemAllocPolicy> varNames_;
@@ -585,18 +582,20 @@ struct JSCompartment
     MOZ_MUST_USE inline bool wrap(JSContext* cx, JS::MutableHandleValue vp);
 
     MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandleString strp);
     MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandleObject obj);
     MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<js::PropertyDescriptor> desc);
     MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<JS::GCVector<JS::Value>> vec);
     MOZ_MUST_USE bool rewrap(JSContext* cx, JS::MutableHandleObject obj, JS::HandleObject existing);
 
-    MOZ_MUST_USE bool putWrapper(JSContext* cx, const js::CrossCompartmentKey& wrapped,
-                                 const js::Value& wrapper);
+    MOZ_MUST_USE bool putNewWrapper(JSContext* cx, const js::CrossCompartmentKey& wrapped,
+                                    const js::Value& wrapper);
+    MOZ_MUST_USE bool putWrapperMaybeUpdate(JSContext* cx, const js::CrossCompartmentKey& wrapped,
+                                            const js::Value& wrapper);
 
     js::WrapperMap::Ptr lookupWrapper(const js::Value& wrapped) const {
         return crossCompartmentWrappers.lookup(js::CrossCompartmentKey(wrapped));
     }
 
     void removeWrapper(js::WrapperMap::Ptr p) {
         crossCompartmentWrappers.remove(p);
     }
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -2976,17 +2976,17 @@ date_toPrimitive(JSContext* cx, unsigned
         ReportIncompatible(cx, args);
         return false;
     }
 
     // Steps 3-5.
     JSType hint;
     if (!GetFirstArgumentAsTypeHint(cx, args, &hint))
         return false;
-    if (hint == JSTYPE_VOID)
+    if (hint == JSTYPE_UNDEFINED)
         hint = JSTYPE_STRING;
 
     args.rval().set(args.thisv());
     RootedObject obj(cx, &args.thisv().toObject());
     return OrdinaryToPrimitive(cx, obj, hint, args.rval());
 }
 
 static const JSFunctionSpec date_static_methods[] = {
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1220,17 +1220,17 @@ js::GetAnyCompartmentInZone(JS::Zone* zo
     MOZ_ASSERT(!comp.done());
     return comp.get();
 }
 
 void
 JS::ObjectPtr::finalize(JSRuntime* rt)
 {
     if (IsIncrementalBarrierNeeded(rt->activeContextFromOwnThread()))
-        IncrementalObjectBarrier(value);
+        IncrementalPreWriteBarrier(value);
     value = nullptr;
 }
 
 void
 JS::ObjectPtr::finalize(JSContext* cx)
 {
     finalize(cx->runtime());
 }
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -7485,46 +7485,40 @@ JS::IsIncrementalGCInProgress(JSContext*
 }
 
 JS_PUBLIC_API(bool)
 JS::IsIncrementalBarrierNeeded(JSContext* cx)
 {
     return cx->runtime()->gc.state() == gc::State::Mark && !JS::CurrentThreadIsHeapBusy();
 }
 
-struct IncrementalReferenceBarrierFunctor {
-    template <typename T> void operator()(T* t) { T::writeBarrierPre(t); }
-};
-
 JS_PUBLIC_API(void)
-JS::IncrementalReferenceBarrier(GCCellPtr thing)
-{
-    if (!thing)
-        return;
-
-    DispatchTyped(IncrementalReferenceBarrierFunctor(), thing);
-}
-
-JS_PUBLIC_API(void)
-JS::IncrementalValueBarrier(const Value& v)
-{
-    js::GCPtrValue::writeBarrierPre(v);
-}
-
-JS_PUBLIC_API(void)
-JS::IncrementalObjectBarrier(JSObject* obj)
+JS::IncrementalPreWriteBarrier(JSObject* obj)
 {
     if (!obj)
         return;
 
     MOZ_ASSERT(!JS::CurrentThreadIsHeapMajorCollecting());
-
     JSObject::writeBarrierPre(obj);
 }
 
+struct IncrementalReadBarrierFunctor {
+    template <typename T> void operator()(T* t) { T::readBarrier(t); }
+};
+
+JS_PUBLIC_API(void)
+JS::IncrementalReadBarrier(GCCellPtr thing)
+{
+    if (!thing)
+        return;
+
+    MOZ_ASSERT(!JS::CurrentThreadIsHeapMajorCollecting());
+    DispatchTyped(IncrementalReadBarrierFunctor(), thing);
+}
+
 JS_PUBLIC_API(bool)
 JS::WasIncrementalGC(JSContext* cx)
 {
     return cx->runtime()->gc.isIncrementalGc();
 }
 
 uint64_t
 js::gc::NextCellUniqueId(JSRuntime* rt)
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2987,26 +2987,26 @@ ReportCantConvert(JSContext* cx, unsigne
         if (!str)
             return false;
     } else {
         str = nullptr;
     }
 
     RootedValue val(cx, ObjectValue(*obj));
     ReportValueError2(cx, errorNumber, JSDVG_SEARCH_STACK, val, str,
-                      hint == JSTYPE_VOID
+                      hint == JSTYPE_UNDEFINED
                       ? "primitive type"
                       : hint == JSTYPE_STRING ? "string" : "number");
     return false;
 }
 
 bool
 JS::OrdinaryToPrimitive(JSContext* cx, HandleObject obj, JSType hint, MutableHandleValue vp)
 {
-    MOZ_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID);
+    MOZ_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_UNDEFINED);
 
     Rooted<jsid> id(cx);
 
     const Class* clasp = obj->getClass();
     if (hint == JSTYPE_STRING) {
         id = NameToId(cx->names().toString);
 
         /* Optimize (new String(...)).toString(). */
@@ -3064,17 +3064,17 @@ JS::OrdinaryToPrimitive(JSContext* cx, H
     return ReportCantConvert(cx, JSMSG_CANT_CONVERT_TO, obj, hint);
 }
 
 bool
 js::ToPrimitiveSlow(JSContext* cx, JSType preferredType, MutableHandleValue vp)
 {
     // Step numbers refer to the first algorithm listed in ES6 draft rev 36
     // (2015 Mar 17) 7.1.1 ToPrimitive.
-    MOZ_ASSERT(preferredType == JSTYPE_VOID ||
+    MOZ_ASSERT(preferredType == JSTYPE_UNDEFINED ||
                preferredType == JSTYPE_STRING ||
                preferredType == JSTYPE_NUMBER);
     RootedObject obj(cx, &vp.toObject());
 
     // Steps 4-5.
     RootedId id(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().toPrimitive));
     RootedValue method(cx);
     if (!GetProperty(cx, obj, obj, id, &method))
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1046,17 +1046,17 @@ UnwatchProperty(JSContext* cx, HandleObj
 extern bool
 ToPrimitiveSlow(JSContext* cx, JSType hint, MutableHandleValue vp);
 
 inline bool
 ToPrimitive(JSContext* cx, MutableHandleValue vp)
 {
     if (vp.isPrimitive())
         return true;
-    return ToPrimitiveSlow(cx, JSTYPE_VOID, vp);
+    return ToPrimitiveSlow(cx, JSTYPE_UNDEFINED, vp);
 }
 
 inline bool
 ToPrimitive(JSContext* cx, JSType preferredType, MutableHandleValue vp)
 {
     if (vp.isPrimitive())
         return true;
     return ToPrimitiveSlow(cx, preferredType, vp);
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -59,17 +59,17 @@ enum JSVersion {
     JSVERSION_ECMA_5  = 185,
     JSVERSION_DEFAULT = 0,
     JSVERSION_UNKNOWN = -1,
     JSVERSION_LATEST  = JSVERSION_ECMA_5
 };
 
 /* Result of typeof operator enumeration. */
 enum JSType {
-    JSTYPE_VOID,                /* undefined */
+    JSTYPE_UNDEFINED,           /* undefined */
     JSTYPE_OBJECT,              /* object */
     JSTYPE_FUNCTION,            /* function */
     JSTYPE_STRING,              /* string */
     JSTYPE_NUMBER,              /* number */
     JSTYPE_BOOLEAN,             /* boolean */
     JSTYPE_NULL,                /* null */
     JSTYPE_SYMBOL,              /* symbol */
     JSTYPE_LIMIT
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -615,18 +615,21 @@ js::RemapWrapper(JSContext* cx, JSObject
 
     // Before swapping, this wrapper came out of wrap(), which enforces the
     // invariant that the wrapper in the map points directly to the key.
     MOZ_ASSERT(Wrapper::wrappedObject(wobj) == newTarget);
 
     // Update the entry in the compartment's wrapper map to point to the old
     // wrapper, which has now been updated (via reuse or swap).
     MOZ_ASSERT(wobj->is<WrapperObject>());
-    if (!wcompartment->putWrapper(cx, CrossCompartmentKey(newTarget), ObjectValue(*wobj)))
+    if (!wcompartment->putWrapperMaybeUpdate(cx, CrossCompartmentKey(newTarget),
+                                             ObjectValue(*wobj)))
+    {
         MOZ_CRASH();
+    }
 }
 
 // Remap all cross-compartment wrappers pointing to |oldTarget| to point to
 // |newTarget|. All wrappers are recomputed.
 JS_FRIEND_API(bool)
 js::RemapAllWrappersForObject(JSContext* cx, JSObject* oldTargetArg,
                               JSObject* newTargetArg)
 {
--- a/js/src/tests/browser.js
+++ b/js/src/tests/browser.js
@@ -528,28 +528,16 @@ function jsTestDriverBrowserInit()
     else if (properties.test.match(/^js1_7/))
     {
       properties.version = '1.7';
     }
     else if (properties.test.match(/^js1_8/))
     {
       properties.version = '1.8';
     }
-    else if (properties.test.match(/^ecma_6\/LexicalEnvironment/))
-    {
-      properties.version = '1.8';
-    }
-    else if (properties.test.match(/^ecma_6\/Class/))
-    {
-      properties.version = '1.8';
-    }
-    else if (properties.test.match(/^ecma_6\/extensions/))
-    {
-      properties.version = '1.8';
-    }
   }
 
   // default to language=type;text/javascript. required for
   // reftest style manifests.
   if (!properties.language)
   {
     properties.language = 'type';
     properties.mimetype = 'text/javascript';
--- a/js/src/tests/ecma_6/Class/shell.js
+++ b/js/src/tests/ecma_6/Class/shell.js
@@ -1,14 +1,8 @@
-// NOTE: This only turns on 1.8.5 in shell builds.  The browser requires the
-//       futzing in js/src/tests/browser.js (which only turns on 1.8, the most
-//       the browser supports).
-if (typeof version != 'undefined')
-  version(185);
-
 function assertThrownErrorContains(thunk, substr) {
     try {
         thunk();
     } catch (e) {
         if (e.message.indexOf(substr) !== -1)
             return;
         throw new Error("Expected error containing " + substr + ", got " + e);
     }
--- a/js/src/tests/ecma_6/DataView/detach-after-construction.js
+++ b/js/src/tests/ecma_6/DataView/detach-after-construction.js
@@ -1,10 +1,8 @@
-// |reftest| skip-if(!xulRuntime.shell) -- needs detachArrayBuffer
-
 var buf = new ArrayBuffer([1,2]);
 var bufView = new DataView(buf);
 
 detachArrayBuffer(buf);
 
 assertThrowsInstanceOf(() => bufView.getInt8(0), TypeError);
 
 if (typeof reportCompare === 'function')
--- a/js/src/tests/ecma_6/LexicalEnvironment/shell.js
+++ b/js/src/tests/ecma_6/LexicalEnvironment/shell.js
@@ -1,5 +0,0 @@
-// NOTE: This only turns on 1.8.5 in shell builds.  The browser requires the
-//       futzing in js/src/tests/browser.js (which only turns on 1.8, the most
-//       the browser supports).
-if (typeof version != 'undefined')
-  version(185);
--- a/js/src/tests/ecma_6/TypedArray/constructor-non-detached.js
+++ b/js/src/tests/ecma_6/TypedArray/constructor-non-detached.js
@@ -1,10 +1,8 @@
-// |reftest| skip-if(!xulRuntime.shell) -- needs detachArrayBuffer
-
 for (var constructor of typedArrayConstructors) {
     var buf = new constructor();
     detachArrayBuffer(buf.buffer);
     assertThrowsInstanceOf(() => new constructor(buf), TypeError);
 
     var buffer = new ArrayBuffer();
     detachArrayBuffer(buffer);
     assertThrowsInstanceOf(() => new constructor(buffer), TypeError);
--- a/js/src/tests/ecma_6/TypedArray/detached-array-buffer-checks.js
+++ b/js/src/tests/ecma_6/TypedArray/detached-array-buffer-checks.js
@@ -1,10 +1,8 @@
-// |reftest| skip-if(!xulRuntime.shell) -- needs detachArrayBuffer
-
 // Nearly every %TypedArray%.prototype method should throw a TypeError when called
 // atop a detached array buffer. Here we check verify that this holds true for
 // all relevant functions.
 let buffer = new ArrayBuffer(32);
 let array  = new Int32Array(buffer);
 detachArrayBuffer(buffer);
 
 // A nice poisoned callable to ensure that we fail on a detached buffer check
--- a/js/src/tests/ecma_6/extensions/ArrayBuffer-slice-arguments-detaching.js
+++ b/js/src/tests/ecma_6/extensions/ArrayBuffer-slice-arguments-detaching.js
@@ -1,9 +1,8 @@
-// |reftest| skip-if(!xulRuntime.shell) -- needs detachArrayBuffer()
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/
  */
 
 var gTestfile = "ArrayBuffer-slice-arguments-detaching.js";
 //-----------------------------------------------------------------------------
 var BUGNUMBER = 991981;
--- a/js/src/tests/ecma_6/extensions/DataView-construct-arguments-detaching.js
+++ b/js/src/tests/ecma_6/extensions/DataView-construct-arguments-detaching.js
@@ -1,9 +1,8 @@
-// |reftest| skip-if(!xulRuntime.shell) -- needs detachArrayBuffer()
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/
  */
 
 var gTestfile = "DataView-construct-arguments-detaching.js";
 //-----------------------------------------------------------------------------
 var BUGNUMBER = 991981;
--- a/js/src/tests/ecma_6/extensions/DataView-set-arguments-detaching.js
+++ b/js/src/tests/ecma_6/extensions/DataView-set-arguments-detaching.js
@@ -1,9 +1,8 @@
-// |reftest| skip-if(!xulRuntime.shell) -- needs detachArrayBuffer()
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/
  */
 
 var gTestfile = "DataView-set-arguments-detaching.js";
 //-----------------------------------------------------------------------------
 var BUGNUMBER = 991981;
--- a/js/src/tests/ecma_6/extensions/TypedArray-set-object-funky-length-detaches.js
+++ b/js/src/tests/ecma_6/extensions/TypedArray-set-object-funky-length-detaches.js
@@ -1,9 +1,8 @@
-// |reftest| skip-if(!xulRuntime.shell) -- needs detachArrayBuffer()
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/
  */
 
 var gTestfile = "set-object-funky-length-detaches.js";
 //-----------------------------------------------------------------------------
 var BUGNUMBER = 991981;
--- a/js/src/tests/ecma_6/extensions/TypedArray-subarray-arguments-detaching.js
+++ b/js/src/tests/ecma_6/extensions/TypedArray-subarray-arguments-detaching.js
@@ -1,9 +1,8 @@
-// |reftest| skip-if(!xulRuntime.shell) -- needs detachArrayBuffer()
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/
  */
 
 var gTestfile = "TypedArray-subarray-arguments-detaching.js";
 //-----------------------------------------------------------------------------
 var BUGNUMBER = 991981;
--- a/js/src/tests/ecma_6/extensions/element-setting-ToNumber-detaches.js
+++ b/js/src/tests/ecma_6/extensions/element-setting-ToNumber-detaches.js
@@ -1,9 +1,8 @@
-// |reftest| skip-if(!xulRuntime.shell) -- needs detachArrayBuffer()
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/
  */
 "use strict"; // make test fail when limitation below is fixed
 
 var gTestfile = 'element-setting-ToNumber-detaches.js';
 //-----------------------------------------------------------------------------
--- a/js/src/tests/ecma_6/extensions/shell.js
+++ b/js/src/tests/ecma_6/extensions/shell.js
@@ -1,5 +0,0 @@
-// NOTE: This only turns on 1.8.5 in shell builds.  The browser requires the
-//       futzing in js/src/tests/browser.js (which only turns on 1.8, the most
-//       the browser supports).
-if (typeof version != 'undefined')
-  version(185);
--- a/js/src/tests/js1_8_5/extensions/sharedtypedarray.js
+++ b/js/src/tests/js1_8_5/extensions/sharedtypedarray.js
@@ -202,25 +202,22 @@ function testNoClone() {
 
     // This tests the actual cloning functionality - should fail
     assertThrowsInstanceOf(() => serialize(b, [], {SharedArrayBuffer: 'deny'}), TypeError);
 
     // Ditto - should succeed
     assertEq(typeof serialize(b, [], {SharedArrayBuffer: 'allow'}), "object");
 }
 
-// Eventually, this will be prohibited, but for now, allow the SAB to
-// appear in the transfer list.  See bug 1302036 and bug 1302037.
-
 function testRedundantTransfer() {
-    var sab1 = b;
-    var blob = serialize(sab1, [sab1]);
-    var sab2 = deserialize(blob);
-    if (typeof sharedAddress != "undefined")
-	assertEq(sharedAddress(sab1), sharedAddress(sab2));
+    // Throws TypeError in the shell, DataCloneError in the browser.
+    assertThrowsInstanceOf(() => {
+	var sab1 = b;
+	var blob = serialize(sab1, [sab1]);
+    }, TypeError);
 }
 
 function testApplicable() {
     var sab = b;
     var x;
 
     // Just make sure we can create all the view types on shared memory.
 
--- a/js/src/tests/js1_8_5/extensions/shell.js
+++ b/js/src/tests/js1_8_5/extensions/shell.js
@@ -5,23 +5,16 @@
  */
 
 
 // The Worker constructor can take a relative URL, but different test runners
 // run in different enough environments that it doesn't all just automatically
 // work. For the shell, we use just a filename; for the browser, see browser.js.
 var workerDir = '';
 
-// explicitly turn on js185
-// XXX: The browser currently only supports up to version 1.8
-if (typeof version != 'undefined')
-{
-  version(185);
-}
-
 // Assert that cloning b does the right thing as far as we can tell.
 // Caveat: getters in b must produce the same value each time they're
 // called. We may call them several times.
 //
 // If desc is provided, then the very first thing we do to b is clone it.
 // (The self-modifying object test counts on this.)
 //
 function clone_object_check(b, desc) {
--- a/js/src/tests/js1_8_5/extensions/typedarray-copyWithin-arguments-detaching.js
+++ b/js/src/tests/js1_8_5/extensions/typedarray-copyWithin-arguments-detaching.js
@@ -1,9 +1,8 @@
-// |reftest| skip-if(!xulRuntime.shell) -- needs detachArrayBuffer()
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/
  */
 
 var gTestfile = "typedarray-copyWithin-arguments-detaching.js";
 //-----------------------------------------------------------------------------
 var BUGNUMBER = 991981;
--- a/js/src/tests/js1_8_5/extensions/typedarray-set-neutering.js
+++ b/js/src/tests/js1_8_5/extensions/typedarray-set-neutering.js
@@ -1,9 +1,8 @@
-// |reftest| skip-if(!xulRuntime.shell)
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/licenses/publicdomain/
 
 //-----------------------------------------------------------------------------
 var BUGNUMBER = 983344;
 var summary =
   "Uint8Array.prototype.set issues when this array changes during setting";
 
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -1133,17 +1133,17 @@ Debugger::wrapEnvironment(JSContext* cx,
             return false;
 
         if (!p.add(cx, environments, env, envobj)) {
             NukeDebuggerWrapper(envobj);
             return false;
         }
 
         CrossCompartmentKey key(object, env, CrossCompartmentKey::DebuggerEnvironment);
-        if (!object->compartment()->putWrapper(cx, key, ObjectValue(*envobj))) {
+        if (!object->compartment()->putNewWrapper(cx, key, ObjectValue(*envobj))) {
             NukeDebuggerWrapper(envobj);
             environments.remove(env);
             return false;
         }
 
         result.set(envobj);
     }
 
@@ -1220,17 +1220,17 @@ Debugger::wrapDebuggeeObject(JSContext* 
 
         if (!p.add(cx, objects, obj, dobj)) {
             NukeDebuggerWrapper(dobj);
             return false;
         }
 
         if (obj->compartment() != object->compartment()) {
             CrossCompartmentKey key(object, obj, CrossCompartmentKey::DebuggerObject);
-            if (!object->compartment()->putWrapper(cx, key, ObjectValue(*dobj))) {
+            if (!object->compartment()->putNewWrapper(cx, key, ObjectValue(*dobj))) {
                 NukeDebuggerWrapper(dobj);
                 objects.remove(obj);
                 ReportOutOfMemory(cx);
                 return false;
             }
         }
 
         result.set(dobj);
@@ -5516,17 +5516,17 @@ Debugger::wrapVariantReferent(JSContext*
         if (!wrapper)
             return nullptr;
 
         if (!p.add(cx, map, untaggedReferent, wrapper)) {
             NukeDebuggerWrapper(wrapper);
             return nullptr;
         }
 
-        if (!object->compartment()->putWrapper(cx, key, ObjectValue(*wrapper))) {
+        if (!object->compartment()->putNewWrapper(cx, key, ObjectValue(*wrapper))) {
             NukeDebuggerWrapper(wrapper);
             map.remove(untaggedReferent);
             ReportOutOfMemory(cx);
             return nullptr;
         }
 
     }
 
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -929,33 +929,33 @@ js::SameValue(JSContext* cx, HandleValue
     }
     return StrictlyEqual(cx, v1, v2, same);
 }
 
 JSType
 js::TypeOfObject(JSObject* obj)
 {
     if (EmulatesUndefined(obj))
-        return JSTYPE_VOID;
+        return JSTYPE_UNDEFINED;
     if (obj->isCallable())
         return JSTYPE_FUNCTION;
     return JSTYPE_OBJECT;
 }
 
 JSType
 js::TypeOfValue(const Value& v)
 {
     if (v.isNumber())
         return JSTYPE_NUMBER;
     if (v.isString())
         return JSTYPE_STRING;
     if (v.isNull())
         return JSTYPE_OBJECT;
     if (v.isUndefined())
-        return JSTYPE_VOID;
+        return JSTYPE_UNDEFINED;
     if (v.isObject())
         return TypeOfObject(&v.toObject());
     if (v.isBoolean())
         return JSTYPE_BOOLEAN;
     MOZ_ASSERT(v.isSymbol());
     return JSTYPE_SYMBOL;
 }
 
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -519,16 +519,20 @@ ReportDataCloneError(JSContext* cx,
       case JS_SCERR_TRANSFERABLE:
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_NOT_TRANSFERABLE);
         break;
 
       case JS_SCERR_UNSUPPORTED_TYPE:
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_UNSUPPORTED_TYPE);
         break;
 
+      case JS_SCERR_SAB_TRANSFERABLE:
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_SAB_TRANSFERABLE);
+        break;
+
       default:
         MOZ_CRASH("Unkown errorId");
         break;
     }
 }
 
 bool
 WriteStructuredClone(JSContext* cx, HandleValue v, JSStructuredCloneData* bufp,
@@ -1004,23 +1008,18 @@ JSStructuredCloneWriter::parseTransferab
 
         if (!JS_GetElement(cx, array, i, &v))
             return false;
 
         if (!v.isObject())
             return reportDataCloneError(JS_SCERR_TRANSFERABLE);
         tObj = &v.toObject();
 
-        // Backward compatibility, see bug 1302036 and bug 1302037.
-        if (tObj->is<SharedArrayBufferObject>()) {
-            if (!JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_WARNING, GetErrorMessage,
-                                                   nullptr, JSMSG_SC_SAB_TRANSFER))
-                return false;
-            continue;
-        }
+        if (tObj->is<SharedArrayBufferObject>())
+            return reportDataCloneError(JS_SCERR_SAB_TRANSFERABLE);
 
         // No duplicates allowed
         auto p = transferableObjects.lookupForAdd(tObj);
         if (p)
             return reportDataCloneError(JS_SCERR_DUP_TRANSFERABLE);
 
         if (!transferableObjects.add(p, tObj))
             return false;
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -113,17 +113,17 @@ XPC_WN_Shared_toPrimitive(JSContext* cx,
     if (!GetFirstArgumentAsTypeHint(cx, args, &hint))
         return false;
 
     if (hint == JSTYPE_NUMBER) {
         args.rval().set(JS_GetNaNValue(cx));
         return true;
     }
 
-    MOZ_ASSERT(hint == JSTYPE_STRING || hint == JSTYPE_VOID);
+    MOZ_ASSERT(hint == JSTYPE_STRING || hint == JSTYPE_UNDEFINED);
     ccx.SetName(ccx.GetContext()->GetStringID(XPCJSContext::IDX_TO_STRING));
     ccx.SetArgsAndResultPtr(0, nullptr, args.rval().address());
 
     XPCNativeMember* member = ccx.GetMember();
     if (member && member->IsMethod()) {
         if (!XPCWrappedNative::CallMethod(ccx))
             return false;
 
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -256,17 +256,17 @@ public:
     static MOZ_ALWAYS_INLINE bool
     StringBufferToJSVal(JSContext* cx, nsStringBuffer* buf, uint32_t length,
                         JS::MutableHandleValue rval, bool* sharedBuffer)
     {
         JS::Zone* zone = js::GetContextZone(cx);
         ZoneStringCache* cache = static_cast<ZoneStringCache*>(JS_GetZoneUserData(zone));
         if (cache && buf == cache->mBuffer && length == cache->mLength) {
             MOZ_ASSERT(JS::GetStringZone(cache->mString) == zone);
-            JS::MarkStringAsLive(zone, cache->mString);
+            JS::StringReadBarrier(cache->mString);
             rval.setString(cache->mString);
             *sharedBuffer = false;
             return true;
         }
 
         JSString* str = JS_NewExternalString(cx,
                                              static_cast<char16_t*>(buf->Data()),
                                              length, &sDOMStringFinalizer);
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1619,32 +1619,33 @@ nsRefreshDriver::DispatchPendingEvents()
     bool dummy;
     event.mTarget->DispatchEvent(event.mEvent, &dummy);
   }
 }
 
 static bool
 CollectDocuments(nsIDocument* aDocument, void* aDocArray)
 {
-  static_cast<nsCOMArray<nsIDocument>*>(aDocArray)->AppendObject(aDocument);
+  static_cast<AutoTArray<nsCOMPtr<nsIDocument>, 32>*>(aDocArray)->
+    AppendElement(aDocument);
   aDocument->EnumerateSubDocuments(CollectDocuments, aDocArray);
   return true;
 }
 
 void
 nsRefreshDriver::DispatchAnimationEvents()
 {
   if (!mPresContext) {
     return;
   }
 
-  nsCOMArray<nsIDocument> documents;
+  AutoTArray<nsCOMPtr<nsIDocument>, 32> documents;
   CollectDocuments(mPresContext->Document(), &documents);
 
-  for (int32_t i = 0; i < documents.Count(); ++i) {
+  for (uint32_t i = 0; i < documents.Length(); ++i) {
     nsIDocument* doc = documents[i];
     nsIPresShell* shell = doc->GetShell();
     if (!shell) {
       continue;
     }
 
     RefPtr<nsPresContext> context = shell->GetPresContext();
     if (!context || context->RefreshDriver() != this) {
--- a/layout/printing/nsPrintEngine.cpp
+++ b/layout/printing/nsPrintEngine.cpp
@@ -975,16 +975,23 @@ nsPrintEngine::GetCurrentPrintSettings(n
 // in the PrintSettings which is then used for Printer Preview
 nsresult
 nsPrintEngine::CheckForPrinters(nsIPrintSettings* aPrintSettings)
 {
 #if defined(XP_MACOSX) || defined(ANDROID)
   // Mac doesn't support retrieving a printer list.
   return NS_OK;
 #else
+#if defined(MOZ_X11)
+  // On Linux, default printer name should be requested on the parent side.
+  // Unless we are in the parent, we ignore this function
+  if (!XRE_IsParentProcess()) {
+    return NS_OK;
+  }
+#endif
   NS_ENSURE_ARG_POINTER(aPrintSettings);
 
   // See if aPrintSettings already has a printer
   nsXPIDLString printerName;
   nsresult rv = aPrintSettings->GetPrinterName(getter_Copies(printerName));
   if (NS_SUCCEEDED(rv) && !printerName.IsEmpty()) {
     return NS_OK;
   }
--- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -902,17 +902,21 @@ nsViewSourceChannel::GetResponseHeader(c
     if (!aHeader.Equals(NS_LITERAL_CSTRING("Content-Type"),
                         nsCaseInsensitiveCStringComparator()) &&
         !aHeader.Equals(NS_LITERAL_CSTRING("Content-Security-Policy"),
                         nsCaseInsensitiveCStringComparator()) &&
         !aHeader.Equals(NS_LITERAL_CSTRING("Content-Security-Policy-Report-Only"),
                         nsCaseInsensitiveCStringComparator()) &&
         !aHeader.Equals(NS_LITERAL_CSTRING("X-Frame-Options"),
                         nsCaseInsensitiveCStringComparator())) {
-        return NS_OK;
+        // We simulate the NS_ERROR_NOT_AVAILABLE error which is produced by
+        // GetResponseHeader via nsHttpHeaderArray::GetHeader when the entry is
+        // not present, such that it appears as though no headers except for the
+        // whitelisted ones were set on this channel.
+        return NS_ERROR_NOT_AVAILABLE;
     }
 
     return mHttpChannel->GetResponseHeader(aHeader, aValue);
 }
 
 NS_IMETHODIMP
 nsViewSourceChannel::SetResponseHeader(const nsACString & header,
                                        const nsACString & value, bool merge)
--- a/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
+++ b/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
@@ -86,16 +86,18 @@ this.InsecurePasswordUtils = {
    * i.e. passwords inside forms with http action, inside iframes with http src,
    * or on insecure web pages.
    *
    * @param {FormLike} aForm A form-like object. @See {LoginFormFactory}
    * @return {boolean} whether the form is secure
    */
   isFormSecure(aForm) {
     // Ignores window.opener, see top level documentation.
+    // ownerGlobal doesn't exist in content privileged windows.
+    // eslint-disable-next-line mozilla/use-ownerGlobal
     let isSafePage = aForm.ownerDocument.defaultView.isSecureContextIfOpenerIgnored;
     let { isFormSubmitSecure, isFormSubmitHTTP } = this._checkFormSecurity(aForm);
 
     return isSafePage && (isFormSubmitSecure || !isFormSubmitHTTP);
   },
 
   /**
    * Report insecure password fields in a form to the web console to warn developers.
--- a/toolkit/components/passwordmgr/LoginRecipes.jsm
+++ b/toolkit/components/passwordmgr/LoginRecipes.jsm
@@ -253,15 +253,17 @@ var LoginRecipesContent = {
     if (!aSelector) {
       return null;
     }
     let field = aParent.ownerDocument.querySelector(aSelector);
     if (!field) {
       log.debug("Login field selector wasn't matched:", aSelector);
       return null;
     }
+    // ownerGlobal doesn't exist in content privileged windows.
+    // eslint-disable-next-line mozilla/use-ownerGlobal
     if (!(field instanceof aParent.ownerDocument.defaultView.HTMLInputElement)) {
       log.warn("Login field isn't an <input> so ignoring it:", aSelector);
       return null;
     }
     return field;
   },
 };
--- a/toolkit/components/printing/content/printUtils.js
+++ b/toolkit/components/printing/content/printUtils.js
@@ -95,29 +95,44 @@ var PrintUtils = {
       }
     } catch (e) {
       dump("showPageSetup " + e + "\n");
       return false;
     }
     return true;
   },
 
+  getDefaultPrinterName() {
+    try {
+      let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"]
+                    .getService(Ci.nsIPrintSettingsService);
+
+      return PSSVC.defaultPrinterName;
+    } catch (e) {
+      Components.utils.reportError(e);
+    }
+
+    return null;
+  },
+
   /**
    * Starts the process of printing the contents of a window.
    *
    * @param aWindowID
    *        The outer window ID of the nsIDOMWindow to print.
    * @param aBrowser
    *        The <xul:browser> that the nsIDOMWindow for aWindowID belongs to.
    */
   printWindow(aWindowID, aBrowser) {
     let mm = aBrowser.messageManager;
+    let defaultPrinterName = this.getDefaultPrinterName();
     mm.sendAsyncMessage("Printing:Print", {
       windowID: aWindowID,
       simplifiedMode: this._shouldSimplify,
+      defaultPrinterName,
     });
   },
 
   /**
    * Deprecated.
    *
    * Starts the process of printing the contents of window.content.
    *
@@ -486,21 +501,23 @@ var PrintUtils = {
   enterPrintPreview() {
     // Send a message to the print preview browser to initialize
     // print preview. If we happen to have gotten a print preview
     // progress listener from nsIPrintingPromptService.showProgress
     // in printPreview, we add listeners to feed that progress
     // listener.
     let ppBrowser = this._listener.getPrintPreviewBrowser();
     let mm = ppBrowser.messageManager;
+    let defaultPrinterName = this.getDefaultPrinterName();
 
     let sendEnterPreviewMessage = function(browser, simplified) {
       mm.sendAsyncMessage("Printing:Preview:Enter", {
         windowID: browser.outerWindowID,
         simplifiedMode: simplified,
+        defaultPrinterName,
       });
     };
 
     // If we happen to have gotten simplify page checked, we will lazily
     // instantiate a new tab that parses the original page using ReaderMode
     // primitives. When it's ready, and in order to enter on preview, we send
     // over a message to print preview browser passing up the simplified tab as
     // reference. If not, we pass the original tab instead as content source.
--- a/toolkit/components/printingui/ipc/PrintingParent.cpp
+++ b/toolkit/components/printingui/ipc/PrintingParent.cpp
@@ -124,24 +124,34 @@ PrintingParent::ShowPrintDialog(PBrowser
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mPrintSettingsSvc->DeserializeToPrintSettings(aData, settings);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = settings->SetPrintSilent(printSilently);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  nsXPIDLString printerName;
+  settings->GetPrinterName(getter_Copies(printerName));
+#ifdef MOZ_X11
+  // Requesting the default printer name on Linux has been removed in the child,
+  // because it was causing a sandbox violation (see Bug 1329216).
+  // If no printer name is set at this point, use the print settings service
+  // to get the default printer name.
+  if (printerName.IsEmpty()) {
+    mPrintSettingsSvc->GetDefaultPrinterName(getter_Copies(printerName));
+    settings->SetPrinterName(printerName);
+  }
+  mPrintSettingsSvc->InitPrintSettingsFromPrinter(printerName, settings);
+#endif
+
   // If this is for print preview or we are printing silently then we just need
   // to initialize the print settings with anything specific from the printer.
   if (isPrintPreview || printSilently ||
       Preferences::GetBool("print.always_print_silent", printSilently)) {
-    nsXPIDLString printerName;
-    rv = settings->GetPrinterName(getter_Copies(printerName));
-    NS_ENSURE_SUCCESS(rv, rv);
-
     settings->SetIsInitializedFromPrinter(false);
     mPrintSettingsSvc->InitPrintSettingsFromPrinter(printerName, settings);
   } else {
     rv = pps->ShowPrintDialog(parentWin, wbp, settings);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (isPrintPreview) {
--- a/toolkit/components/prompts/test/.eslintrc.js
+++ b/toolkit/components/prompts/test/.eslintrc.js
@@ -1,7 +1,11 @@
 "use strict";
 
 module.exports = {
   "extends": [
     "../../../../testing/mochitest/mochitest.eslintrc.js"
-  ]
+  ],
+  "rules": {
+    // ownerGlobal doesn't exist in content privileged windows.
+    "mozilla/use-ownerGlobal": "off",
+  }
 };
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -438,17 +438,17 @@ var Printing = {
       });
     }
   },
 
   receiveMessage(message) {
     let data = message.data;
     switch (message.name) {
       case "Printing:Preview:Enter": {
-        this.enterPrintPreview(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode);
+        this.enterPrintPreview(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode, data.defaultPrinterName);
         break;
       }
 
       case "Printing:Preview:Exit": {
         this.exitPrintPreview();
         break;
       }
 
@@ -463,30 +463,30 @@ var Printing = {
       }
 
       case "Printing:Preview:UpdatePageCount": {
         this.updatePageCount();
         break;
       }
 
       case "Printing:Print": {
-        this.print(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode);
+        this.print(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode, data.defaultPrinterName);
         break;
       }
     }
   },
 
-  getPrintSettings() {
+  getPrintSettings(defaultPrinterName) {
     try {
       let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"]
                     .getService(Ci.nsIPrintSettingsService);
 
       let printSettings = PSSVC.globalPrintSettings;
       if (!printSettings.printerName) {
-        printSettings.printerName = PSSVC.defaultPrinterName;
+        printSettings.printerName = defaultPrinterName;
       }
       // First get any defaults from the printer
       PSSVC.initPrintSettingsFromPrinter(printSettings.printerName,
                                          printSettings);
       // now augment them with any values from last time
       PSSVC.initPrintSettingsFromPrefs(printSettings, true,
                                        printSettings.kInitSaveAll);
 
@@ -615,17 +615,17 @@ var Printing = {
 
       readerContent.appendChild(contentFragment);
 
       // Display reader content element
       readerContent.style.display = "block";
     });
   },
 
-  enterPrintPreview(contentWindow, simplifiedMode) {
+  enterPrintPreview(contentWindow, simplifiedMode, defaultPrinterName) {
     // We'll call this whenever we've finished reflowing the document, or if
     // we errored out while attempting to print preview (in which case, we'll
     // notify the parent that we've failed).
     let notifyEntered = (error) => {
       removeEventListener("printPreviewUpdate", onPrintPreviewReady);
       sendAsyncMessage("Printing:Preview:Entered", {
         failed: !!error,
       });
@@ -638,17 +638,17 @@ var Printing = {
     // We have to wait for the print engine to finish reflowing all of the
     // documents and subdocuments before we can tell the parent to flip to
     // the print preview UI - otherwise, the print preview UI might ask for
     // information (like the number of pages in the document) before we have
     // our PresShells set up.
     addEventListener("printPreviewUpdate", onPrintPreviewReady);
 
     try {
-      let printSettings = this.getPrintSettings();
+      let printSettings = this.getPrintSettings(defaultPrinterName);
 
       // If we happen to be on simplified mode, we need to set docURL in order
       // to generate header/footer content correctly, since simplified tab has
       // "about:blank" as its URI.
       if (printSettings && simplifiedMode)
         printSettings.docURL = contentWindow.document.baseURI;
 
       docShell.printPreview.printPreview(printSettings, contentWindow, this);
@@ -659,18 +659,18 @@ var Printing = {
       notifyEntered(error);
     }
   },
 
   exitPrintPreview() {
     docShell.printPreview.exitPrintPreview();
   },
 
-  print(contentWindow, simplifiedMode) {
-    let printSettings = this.getPrintSettings();
+  print(contentWindow, simplifiedMode, defaultPrinterName) {
+    let printSettings = this.getPrintSettings(defaultPrinterName);
 
     // If we happen to be on simplified mode, we need to set docURL in order
     // to generate header/footer content correctly, since simplified tab has
     // "about:blank" as its URI.
     if (printSettings && simplifiedMode) {
       printSettings.docURL = contentWindow.document.baseURI;
     }
 
--- a/toolkit/content/tests/mochitest/test_mousecapture.xhtml
+++ b/toolkit/content/tests/mochitest/test_mousecapture.xhtml
@@ -206,16 +206,18 @@ function runTests() {
   var topPos = window.innerHeight;
   otherWindow = window.open("data:text/html,<html><p>One</p><p style='margin-top: " + topPos + "'>Two</p><p style='margin-top: 4000px'>This is some text</p></html>", "_blank");
   SimpleTest.waitForFocus(selectionScrollCheck, otherWindow);
 }
 
 function runCaptureTest(element, callback) {
   var expectedTarget = null;
 
+  // ownerGlobal doesn't exist in content privileged windows.
+  // eslint-disable-next-line mozilla/use-ownerGlobal
   var win = element.ownerDocument.defaultView;
 
   function mouseMoved(event) {
     is(event.originalTarget, expectedTarget,
        expectedTarget.id + " target for point " + event.clientX + "," + event.clientY);
   }
   win.addEventListener("mousemove", mouseMoved);
 
--- a/toolkit/locales/en-US/chrome/global/aboutSupport.properties
+++ b/toolkit/locales/en-US/chrome/global/aboutSupport.properties
@@ -1,13 +1,13 @@
 # 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/.
 
-# LOCALIZATION NOTE (downloadsTitleFiles): Semi-colon list of plural forms.
+# LOCALIZATION NOTE (crashesTitle): Semi-colon list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 number of relevant days with crash reports
 crashesTitle=Crash Reports for the Last #1 Day;Crash Reports for the Last #1 Days
 
 # LOCALIZATION NOTE (crashesTimeMinutes): Semi-colon list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 number of minutes (between 1 and 59) which have passed since the crash
 crashesTimeMinutes=#1 minute ago;#1 minutes ago
@@ -17,17 +17,17 @@ crashesTimeMinutes=#1 minute ago;#1 minu
 # #1 number of hours (between 1 and 23) which have passed since the crash
 crashesTimeHours=#1 hour ago;#1 hours ago
 
 # LOCALIZATION NOTE (crashesTimeDays): Semi-colon list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 number of days (1 or more) which have passed since the crash
 crashesTimeDays=#1 day ago;#1 days ago
 
-# LOCALIZATION NOTE (downloadsTitleFiles): Semi-colon list of plural forms.
+# LOCALIZATION NOTE (pendingReports): Semi-colon list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 number of pending crash reports
 pendingReports=All Crash Reports (including #1 pending crash in the given time range);All Crash Reports (including #1 pending crashes in the given time range)
 
 # LOCALIZATION NOTE (rawDataCopied) Text displayed in a mobile "Toast" to user when the
 # raw data is successfully copied to the clipboard via button press.
 rawDataCopied=Raw data copied to clipboard
 
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -1222,17 +1222,20 @@ PopupNotifications.prototype = {
     // active notifications for the previous anchor as dismissed
     if (this.panel.state != "closed" && anchor != this._currentAnchorElement) {
       this._dismissOrRemoveCurrentNotifications();
     }
 
     // Ensure we move focus into the panel because it's opened through user interaction:
     this.panel.removeAttribute("noautofocus");
 
-    this._reshowNotifications(anchor);
+    // Avoid reshowing notifications that are already shown and have not been dismissed.
+    if (this.panel.state == "closed" || anchor != this._currentAnchorElement) {
+      this._reshowNotifications(anchor);
+    }
 
     // If the user re-selects the current notification, focus it.
     if (anchor == this._currentAnchorElement && this.panel.firstChild) {
       this.panel.firstChild.button.focus();
     }
   },
 
   _reshowNotifications: function PopupNotifications_reshowNotifications(anchor, browser) {
--- a/tools/lint/docs/linters/eslint-plugin-mozilla.rst
+++ b/tools/lint/docs/linters/eslint-plugin-mozilla.rst
@@ -163,16 +163,21 @@ this-top-level-scope
 Treats top-level assignments like ``this.mumble = value`` as declaring a global.
 
 Note: These are string matches so we will miss situations where the parent
 object is assigned to another variable e.g.::
 
    var b = gBrowser;
    b.content // Would not be detected as a CPOW.
 
+use-ownerGlobal
+---------------
+
+Require .ownerGlobal instead of .ownerDocument.defaultView.
+
 
 var-only-at-top-level
 ---------------------
 
 Marks all var declarations that are not at the top level invalid.
 
 
 Example
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
@@ -28,16 +28,17 @@ module.exports = {
     "no-aArgs": require("../lib/rules/no-aArgs"),
     "no-cpows-in-tests": require("../lib/rules/no-cpows-in-tests"),
     "no-single-arg-cu-import": require("../lib/rules/no-single-arg-cu-import"),
     "no-import-into-var-and-global": require("../lib/rules/no-import-into-var-and-global.js"),
     "no-useless-parameters": require("../lib/rules/no-useless-parameters"),
     "no-useless-removeEventListener": require("../lib/rules/no-useless-removeEventListener"),
     "reject-importGlobalProperties": require("../lib/rules/reject-importGlobalProperties"),
     "reject-some-requires": require("../lib/rules/reject-some-requires"),
+    "use-ownerGlobal": require("../lib/rules/use-ownerGlobal"),
     "var-only-at-top-level": require("../lib/rules/var-only-at-top-level")
   },
   rulesConfig: {
     "avoid-removeChild": 0,
     "balanced-listeners": 0,
     "import-browserjs-globals": 0,
     "import-globals": 0,
     "import-headjs-globals": 0,
@@ -46,11 +47,12 @@ module.exports = {
     "no-aArgs": 0,
     "no-cpows-in-tests": 0,
     "no-single-arg-cu-import": 0,
     "no-import-into-var-and-global": 0,
     "no-useless-parameters": 0,
     "no-useless-removeEventListener": 0,
     "reject-importGlobalProperties": 0,
     "reject-some-requires": 0,
+    "use-ownerGlobal": 0,
     "var-only-at-top-level": 0
   }
 };
new file mode 100644
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-ownerGlobal.js
@@ -0,0 +1,36 @@
+/**
+ * @fileoverview Require .ownerGlobal instead of .ownerDocument.defaultView.
+ *
+ * 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";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var helpers = require("../helpers");
+
+module.exports = function(context) {
+
+  // ---------------------------------------------------------------------------
+  // Public
+  //  --------------------------------------------------------------------------
+
+  return {
+    "MemberExpression": function(node) {
+      if (node.property.type != "Identifier" ||
+          node.property.name != "defaultView" ||
+          node.object.type != "MemberExpression" ||
+          node.object.property.type != "Identifier" ||
+          node.object.property.name != "ownerDocument") {
+        return;
+      }
+
+      context.report(node, "use .ownerGlobal of .ownerDocument.defaultView");
+    }
+  };
+};
--- a/tools/lint/eslint/eslint-plugin-mozilla/package.json
+++ b/tools/lint/eslint/eslint-plugin-mozilla/package.json
@@ -1,11 +1,11 @@
 {
   "name": "eslint-plugin-mozilla",
-  "version": "0.2.19",
+  "version": "0.2.20",
   "description": "A collection of rules that help enforce JavaScript coding standard in the Mozilla project.",
   "keywords": [
     "eslint",
     "eslintplugin",
     "eslint-plugin",
     "mozilla",
     "firefox"
   ],
new file mode 100644
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-ownerGlobal.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/use-ownerGlobal");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+function invalidCode(code) {
+  let message = "use .ownerGlobal of .ownerDocument.defaultView";
+  return {code: code, errors: [{message: message, type: "MemberExpression"}]};
+}
+
+exports.runTest = function(ruleTester) {
+  ruleTester.run("use-ownerGlobal", rule, {
+    valid: [
+      "aEvent.target.ownerGlobal;",
+      "this.DOMPointNode.ownerGlobal.getSelection();",
+      "windowToMessageManager(node.ownerGlobal);",
+    ],
+    invalid: [
+      invalidCode("aEvent.target.ownerDocument.defaultView;"),
+      invalidCode("this.DOMPointNode.ownerDocument.defaultView.getSelection();"),
+      invalidCode("windowToMessageManager(node.ownerDocument.defaultView);"),
+    ]
+  });
+};
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -2045,17 +2045,17 @@ profiler_start(int aProfileEntries, doub
     for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
       ThreadInfo* info = sRegisteredThreads->at(i);
 
       MaybeSetProfile(info);
     }
   }
 
 #ifdef MOZ_TASK_TRACER
-  if (mTaskTracer) {
+  if (gTaskTracer) {
     mozilla::tasktracer::StartLogging();
   }
 #endif
 
   gGatherer = new mozilla::ProfileGatherer(gSampler);
 
   bool mainThreadIO = hasFeature(aFeatures, aFeatureCount, "mainthreadio");
   bool privacyMode  = hasFeature(aFeatures, aFeatureCount, "privacy");
@@ -2206,17 +2206,17 @@ profiler_stop()
         delete info;
         sRegisteredThreads->erase(sRegisteredThreads->begin() + i);
         i--;
       }
     }
   }
 
 #ifdef MOZ_TASK_TRACER
-  if (mTaskTracer) {
+  if (gTaskTracer) {
     mozilla::tasktracer::StopLogging();
   }
 #endif
 
   delete gSampler;
   gSampler = nullptr;
   gBuffer = nullptr;
   gEntrySize = 0;