Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 30 Jul 2015 11:57:28 -0400
changeset 287057 1824d485bf2fde7db8ba4c71267796cf670193c3
parent 287056 5d9bca23864a9ae6462ccd5681e6084ad29ca24e (current diff)
parent 287028 f9ccbf328382d656bcce4ad418ea3a6b9049f763 (diff)
child 287058 3f22c1f8153608c2b2d84f7a69db02e167533cc0
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2g-inbound. a=merge CLOSED TREE
browser/base/content/test/general/browser_bug405137.js
browser/locales/en-US/chrome/browser/tabbrowser.dtd
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 965151 needed a CLOBBER
+Bug 1186748 needed a CLOBBER
--- a/b2g/installer/Makefile.in
+++ b/b2g/installer/Makefile.in
@@ -93,19 +93,27 @@ endif
 ifneq (,$(filter rtsp,$(NECKO_PROTOCOLS)))
 DEFINES += -DMOZ_RTSP
 endif
 
 ifdef GKMEDIAS_SHARED_LIBRARY
 DEFINES += -DGKMEDIAS_SHARED_LIBRARY
 endif
 
+DEFINES += -DMOZ_ICU_VERSION=$(MOZ_ICU_VERSION)
+ifdef MOZ_NATIVE_ICU
+DEFINES += -DMOZ_NATIVE_ICU
+endif
+ifdef MOZ_SHARED_ICU
+DEFINES += -DMOZ_SHARED_ICU
+endif
 ifdef MOZ_JEMALLOC3
 DEFINES += -DMOZ_JEMALLOC3
 endif
+DEFINES += -DMOZ_ICU_DBG_SUFFIX=$(MOZ_ICU_DBG_SUFFIX)
 
 ifdef MOZ_WIDGET_GTK
 DEFINES += -DMOZ_GTK=1
 ifdef MOZ_ENABLE_GTK3
 DEFINES += -DMOZ_GTK3=1
 endif
 endif
 
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -89,16 +89,33 @@
 #ifdef MSVC_APPCRT_DLL
 @BINPATH@/@MSVC_APPCRT_DLL@
 #endif
 #ifdef MSVC_DESKTOPCRT_DLL
 @BINPATH@/@MSVC_DESKTOPCRT_DLL@
 #endif
 #endif
 #endif
+#ifndef MOZ_NATIVE_ICU
+#ifdef MOZ_SHARED_ICU
+#ifdef XP_WIN
+@BINPATH@/icudt@MOZ_ICU_DBG_SUFFIX@@MOZ_ICU_VERSION@.dll
+@BINPATH@/icuin@MOZ_ICU_DBG_SUFFIX@@MOZ_ICU_VERSION@.dll
+@BINPATH@/icuuc@MOZ_ICU_DBG_SUFFIX@@MOZ_ICU_VERSION@.dll
+#elif defined(XP_MACOSX)
+@BINPATH@/libicudata.@MOZ_ICU_VERSION@.dylib
+@BINPATH@/libicui18n.@MOZ_ICU_VERSION@.dylib
+@BINPATH@/libicuuc.@MOZ_ICU_VERSION@.dylib
+#elif defined(XP_UNIX)
+@BINPATH@/libicudata.so.@MOZ_ICU_VERSION@
+@BINPATH@/libicui18n.so.@MOZ_ICU_VERSION@
+@BINPATH@/libicuuc.so.@MOZ_ICU_VERSION@
+#endif
+#endif
+#endif
 #ifdef MOZ_SHARED_MOZGLUE
 @BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@
 #endif
 #ifdef MOZ_REPLACE_MALLOC
 #ifndef MOZ_JEMALLOC3
 @BINPATH@/@DLL_PREFIX@replace_jemalloc@DLL_SUFFIX@
 #endif
 #endif
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -447,16 +447,22 @@ pref("browser.tabs.drawInTitlebar", true
 
 // When tabs opened by links in other tabs via a combination of
 // browser.link.open_newwindow being set to 3 and target="_blank" etc are
 // closed:
 // true   return to the tab that opened this tab (its owner)
 // false  return to the adjacent tab (old default)
 pref("browser.tabs.selectOwnerOnClose", true);
 
+#ifdef RELEASE_BUILD
+pref("browser.tabs.showAudioPlayingIcon", false);
+#else
+pref("browser.tabs.showAudioPlayingIcon", true);
+#endif
+
 pref("browser.ctrlTab.previews", false);
 
 // By default, do not export HTML at shutdown.
 // If true, at shutdown the bookmarks in your menu and toolbar will
 // be exported as HTML to the bookmarks.html file.
 pref("browser.bookmarks.autoExportHTML",          false);
 
 // The maximum number of daily bookmark backups to
--- a/browser/base/content/contentSearchUI.js
+++ b/browser/base/content/contentSearchUI.js
@@ -91,16 +91,20 @@ ContentSearchUIController.prototype = {
   },
 
   get engines() {
     return this._engines;
   },
 
   set engines(val) {
     this._engines = val;
+    if (!this._table.hidden) {
+      this._setUpOneOffButtons();
+      return;
+    }
     this._pendingOneOffRefresh = true;
   },
 
   // The selectedIndex is the index of the element with the "selected" class in
   // the list obtained by concatenating the suggestion rows, one-off buttons, and
   // search settings button.
   get selectedIndex() {
     let allElts = [...this._suggestionsList.children,
@@ -118,64 +122,34 @@ ContentSearchUIController.prototype = {
   set selectedIndex(idx) {
     // Update the table's rows, and the input when there is a selection.
     this._table.removeAttribute("aria-activedescendant");
     this.input.removeAttribute("aria-activedescendant");
 
     let allElts = [...this._suggestionsList.children,
                    ...this._oneOffButtons,
                    document.getElementById("contentSearchSettingsButton")];
-    // If we are selecting a suggestion and a one-off is selected, don't deselect it.
-    let excludeIndex = idx < this.numSuggestions && this.selectedButtonIndex > -1 ?
-                       this.numSuggestions + this.selectedButtonIndex : -1;
     for (let i = 0; i < allElts.length; ++i) {
       let elt = allElts[i];
       let ariaSelectedElt = i < this.numSuggestions ? elt.firstChild : elt;
       if (i == idx) {
         elt.classList.add("selected");
         ariaSelectedElt.setAttribute("aria-selected", "true");
         this.input.setAttribute("aria-activedescendant", ariaSelectedElt.id);
       }
-      else if (i != excludeIndex) {
+      else {
         elt.classList.remove("selected");
         ariaSelectedElt.setAttribute("aria-selected", "false");
       }
     }
   },
 
-  get selectedButtonIndex() {
-    let elts = [...this._oneOffButtons,
-                document.getElementById("contentSearchSettingsButton")];
-    for (let i = 0; i < elts.length; ++i) {
-      if (elts[i].classList.contains("selected")) {
-        return i;
-      }
-    }
-    return -1;
-  },
-
-  set selectedButtonIndex(idx) {
-    let elts = [...this._oneOffButtons,
-                document.getElementById("contentSearchSettingsButton")];
-    for (let i = 0; i < elts.length; ++i) {
-      let elt = elts[i];
-      if (i == idx) {
-        elt.classList.add("selected");
-        elt.setAttribute("aria-selected", "true");
-      }
-      else {
-        elt.classList.remove("selected");
-        elt.setAttribute("aria-selected", "false");
-      }
-    }
-  },
-
   get selectedEngineName() {
-    let selectedElt = this._oneOffsTable.querySelector(".selected");
-    if (selectedElt) {
+    let selectedElt = this._table.querySelector(".selected");
+    if (selectedElt && selectedElt.engineName) {
       return selectedElt.engineName;
     }
     return this.defaultEngine.name;
   },
 
   get numSuggestions() {
     return this._suggestionsList.children.length;
   },
@@ -215,17 +189,17 @@ ContentSearchUIController.prototype = {
     this._sendMsg("AddFormHistoryEntry", this.input.value);
   },
 
   handleEvent: function (event) {
     this["_on" + event.type[0].toUpperCase() + event.type.substr(1)](event);
   },
 
   _onCommand: function(aEvent) {
-    if (this.selectedButtonIndex == this._oneOffButtons.length) {
+    if (this.selectedIndex == this.numSuggestions + this._oneOffButtons.length) {
       // Settings button was selected.
       this._sendMsg("ManageEngines");
       return;
     }
 
     this.search(aEvent);
 
     if (aEvent) {
@@ -285,68 +259,29 @@ ContentSearchUIController.prototype = {
       this._getSuggestions();
       this.selectAndUpdateInput(-1);
     }
     this._updateSearchWithHeader();
   },
 
   _onKeypress: function (event) {
     let selectedIndexDelta = 0;
-    let selectedSuggestionDelta = 0;
-    let selectedOneOffDelta = 0;
-
     switch (event.keyCode) {
     case event.DOM_VK_UP:
-      if (this._table.hidden) {
-        return;
+      if (!this._table.hidden) {
+        selectedIndexDelta = -1;
       }
-      if (event.getModifierState("Accel")) {
-        if (event.shiftKey) {
-          selectedSuggestionDelta = -1;
-          break;
-        }
-        this._cycleCurrentEngine(true);
-        break;
-      }
-      if (event.altKey) {
-        selectedOneOffDelta = -1;
-        break;
-      }
-      selectedIndexDelta = -1;
       break;
     case event.DOM_VK_DOWN:
       if (this._table.hidden) {
         this._getSuggestions();
-        return;
-      }
-      if (event.getModifierState("Accel")) {
-        if (event.shiftKey) {
-          selectedSuggestionDelta = 1;
-          break;
-        }
-        this._cycleCurrentEngine(false);
-        break;
-      }
-      if (event.altKey) {
-        selectedOneOffDelta = 1;
-        break;
       }
-      selectedIndexDelta = 1;
-      break;
-    case event.DOM_VK_TAB:
-      if (this._table.hidden) {
-        return;
+      else {
+        selectedIndexDelta = 1;
       }
-      // Shift+tab when either the first or no one-off is selected, as well as
-      // tab when the settings button is selected, should change focus as normal.
-      if ((this.selectedButtonIndex <= 0 && event.shiftKey) ||
-          this.selectedButtonIndex == this._oneOffButtons.length && !event.shiftKey) {
-        return;
-      }
-      selectedOneOffDelta = event.shiftKey ? -1 : 1;
       break;
     case event.DOM_VK_RIGHT:
       // Allow normal caret movement until the caret is at the end of the input.
       if (this.input.selectionStart != this.input.selectionEnd ||
           this.input.selectionEnd != this.input.value.length) {
         return;
       }
       if (this.numSuggestions && this.selectedIndex >= 0 &&
@@ -357,107 +292,47 @@ ContentSearchUIController.prototype = {
       } else {
         // If we didn't select anything, make sure to remove the attributes
         // in case they were populated last time.
         this.input.removeAttribute("selection-index");
         this.input.removeAttribute("selection-kind");
       }
       this._stickyInputValue = this.input.value;
       this._hideSuggestions();
-      return;
+      break;
     case event.DOM_VK_RETURN:
       this._onCommand(event);
-      return;
+      break;
     case event.DOM_VK_DELETE:
       if (this.selectedIndex >= 0) {
         this.deleteSuggestionAtIndex(this.selectedIndex);
       }
-      return;
+      break;
     case event.DOM_VK_ESCAPE:
       if (!this._table.hidden) {
         this._hideSuggestions();
       }
-      return;
     default:
       return;
     }
 
-    let currentIndex = this.selectedIndex;
     if (selectedIndexDelta) {
-      let newSelectedIndex = currentIndex + selectedIndexDelta;
+      // Update the selection.
+      let newSelectedIndex = this.selectedIndex + selectedIndexDelta;
       if (newSelectedIndex < -1) {
         newSelectedIndex = this.numSuggestions + this._oneOffButtons.length;
       }
-      // If are moving up from the first one off, we have to deselect the one off
-      // manually because the selectedIndex setter tries to exclude the selected
-      // one-off (which is desirable for accel+shift+up/down).
-      if (currentIndex == this.numSuggestions && selectedIndexDelta == -1) {
-        this.selectedButtonIndex = -1;
-      }
-      this.selectAndUpdateInput(newSelectedIndex);
-    }
-
-    else if (selectedSuggestionDelta) {
-      let newSelectedIndex;
-      if (currentIndex >= this.numSuggestions || currentIndex == -1) {
-        // No suggestion already selected, select the first/last one appropriately.
-        newSelectedIndex = selectedSuggestionDelta == 1 ?
-                           0 : this.numSuggestions - 1;
-      }
-      else {
-        newSelectedIndex = currentIndex + selectedSuggestionDelta;
-      }
-      if (newSelectedIndex >= this.numSuggestions) {
+      else if (this.numSuggestions + this._oneOffButtons.length < newSelectedIndex) {
         newSelectedIndex = -1;
       }
       this.selectAndUpdateInput(newSelectedIndex);
-    }
 
-    else if (selectedOneOffDelta) {
-      let newSelectedIndex;
-      let currentButton = this.selectedButtonIndex;
-      if (currentButton == -1 || currentButton == this._oneOffButtons.length) {
-        // No one-off already selected, select the first/last one appropriately.
-        newSelectedIndex = selectedOneOffDelta == 1 ?
-                           0 : this._oneOffButtons.length - 1;
-      }
-      else {
-        newSelectedIndex = currentButton + selectedOneOffDelta;
-      }
-      // Allow selection of the settings button via the tab key.
-      if (newSelectedIndex == this._oneOffButtons.length &&
-          event.keyCode != event.DOM_VK_TAB) {
-        newSelectedIndex = -1;
-      }
-      this.selectedButtonIndex = newSelectedIndex;
+      // Prevent the input's caret from moving.
+      event.preventDefault();
     }
-
-    // Prevent the input's caret from moving.
-    event.preventDefault();
-  },
-
-  _currentEngineIndex: -1,
-  _cycleCurrentEngine: function (aReverse) {
-    if ((this._currentEngineIndex == this._oneOffButtons.length - 1 && !aReverse) ||
-        (this._currentEngineIndex < 0 && aReverse)) {
-      return;
-    }
-    this._currentEngineIndex += aReverse ? -1 : 1;
-    let engine;
-    if (this._currentEngineIndex == -1) {
-      engine = this._originalDefaultEngine;
-    } else {
-      let button = this._oneOffButtons[this._currentEngineIndex];
-      engine = {
-        name: button.engineName,
-        icon: button.firstChild.getAttribute("src"),
-      };
-    }
-    this._sendMsg("SetCurrentEngine", engine.name);
-    this.defaultEngine = engine;
   },
 
   _onFocus: function () {
     if (this._mousedown) {
       return;
     }
     // When the input box loses focus to something in our table, we refocus it
     // immediately. This causes the focus highlight to flicker, so we set a
@@ -476,40 +351,26 @@ ContentSearchUIController.prototype = {
       setTimeout(() => this.input.focus(), 0);
       return;
     }
     this.input.removeAttribute("keepfocus");
     this._hideSuggestions();
   },
 
   _onMousemove: function (event) {
-    let idx = this._indexOfTableItem(event.target);
-    if (idx >= this.numSuggestions) {
-      this.selectedButtonIndex = idx - this.numSuggestions;
-      return;
-    }
-    this.selectedIndex = idx;
+    this.selectedIndex = this._indexOfTableItem(event.target);
   },
 
   _onMouseup: function (event) {
     if (event.button == 2) {
       return;
     }
     this._onCommand(event);
   },
 
-  _onMouseout: function (event) {
-    // We only deselect one-off buttons and the settings button when they are
-    // moused out.
-    let idx = this._indexOfTableItem(event.originalTarget);
-    if (idx >= this.numSuggestions) {
-      this.selectedButtonIndex = -1;
-    }
-  },
-
   _onClick: function (event) {
     this._onMouseup(event);
   },
 
   _onContentSearchService: function (event) {
     let methodName = "_onMsg" + event.detail.type;
     if (methodName in this) {
       this[methodName](event.detail.data);
@@ -561,20 +422,16 @@ ContentSearchUIController.prototype = {
     if (this._table.hidden) {
       this.selectedIndex = -1;
       if (this._pendingOneOffRefresh) {
         this._setUpOneOffButtons();
         delete this._pendingOneOffRefresh;
       }
       this._table.hidden = false;
       this.input.setAttribute("aria-expanded", "true");
-      this._originalDefaultEngine = {
-        name: this.defaultEngine.name,
-        icon: this.defaultEngine.icon,
-      };
     }
   },
 
   _onMsgState: function (state) {
     this.defaultEngine = {
       name: state.currentEngine.name,
       icon: this._getFaviconURIFromBuffer(state.currentEngine.iconBuffer),
     };
@@ -585,16 +442,20 @@ ContentSearchUIController.prototype = {
     this._onMsgState(state);
   },
 
   _onMsgCurrentEngine: function (engine) {
     this.defaultEngine = {
       name: engine.name,
       icon: this._getFaviconURIFromBuffer(engine.iconBuffer),
     };
+    if (!this._table.hidden) {
+      this._setUpOneOffButtons();
+      return;
+    }
     this._pendingOneOffRefresh = true;
   },
 
   _onMsgStrings: function (strings) {
     this._strings = strings;
     this._updateDefaultEngineHeader();
     this._updateSearchWithHeader();
     document.getElementById("contentSearchSettingsButton").textContent =
@@ -706,19 +567,16 @@ ContentSearchUIController.prototype = {
   _clearSuggestionRows: function() {
     while (this._suggestionsList.firstElementChild) {
       this._suggestionsList.firstElementChild.remove();
     }
   },
 
   _hideSuggestions: function () {
     this.input.setAttribute("aria-expanded", "false");
-    this.selectedIndex = -1;
-    this.selectedButtonIndex = -1;
-    this._currentEngineIndex = -1;
     this._table.hidden = true;
   },
 
   _indexOfTableItem: function (elt) {
     if (elt.classList.contains("contentSearchOneOffItem")) {
       return this.numSuggestions + this._oneOffButtons.indexOf(elt);
     }
     if (elt.classList.contains("contentSearchSettingsButton")) {
@@ -742,17 +600,21 @@ ContentSearchUIController.prototype = {
 
     // When the search input box loses focus, we want to immediately give focus
     // back to it if the blur was because the user clicked somewhere in the table.
     // onBlur uses the _mousedown flag to detect this.
     this._table.addEventListener("mousedown", () => { this._mousedown = true; });
     document.addEventListener("mouseup", () => { delete this._mousedown; });
 
     // Deselect the selected element on mouseout if it wasn't a suggestion.
-    this._table.addEventListener("mouseout", this);
+    this._table.addEventListener("mouseout", () => {
+      if (this.selectedIndex >= this.numSuggestions) {
+        this.selectAndUpdateInput(-1);
+      }
+    });
 
     // If a search is loaded in the same tab, ensure the suggestions dropdown
     // is hidden immediately when the page starts loading and not when it first
     // appears, in order to provide timely feedback to the user.
     window.addEventListener("beforeunload", () => { this._hideSuggestions(); });
 
     let headerRow = document.createElementNS(HTML_NS, "tr");
     let header = document.createElementNS(HTML_NS, "td");
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1,19 +1,14 @@
 <?xml version="1.0"?>
 
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
-<!DOCTYPE bindings [
-<!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
-%tabBrowserDTD;
-]>
-
 <bindings id="tabBrowserBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="tabbrowser">
     <resources>
       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
@@ -3866,21 +3861,31 @@
         <parameter name="event"/>
         <body><![CDATA[
           event.stopPropagation();
           var tab = document.tooltipNode;
           if (tab.localName != "tab") {
             event.preventDefault();
             return;
           }
-          event.target.setAttribute("label",
-                                    tab.mOverCloseButton ?
-                                    tab.getAttribute("closetabtext") :
-                                    tab.getAttribute("label") +
-                                      (this.AppConstants.E10S_TESTING_ONLY && tab.linkedBrowser && tab.linkedBrowser.isRemoteBrowser ? " - e10s" : ""));
+          var stringID, label;
+          if (tab.mOverCloseButton) {
+            stringID = "tabs.closeTab.tooltip";
+          } else if (tab._overPlayingIcon) {
+            stringID = tab.linkedBrowser.audioMuted ?
+              "tabs.mutedAudio.tooltip" :
+              "tabs.playingAudio.tooltip";
+          } else {
+            label = tab.getAttribute("label") +
+                      (this.AppConstants.E10S_TESTING_ONLY && tab.linkedBrowser && tab.linkedBrowser.isRemoteBrowser ? " - e10s" : "");
+          }
+          if (stringID && !label) {
+            label = this.mStringBundle.getString(stringID);
+          }
+          event.target.setAttribute("label", label);
         ]]></body>
       </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body><![CDATA[
           switch (aEvent.type) {
             case "keydown":
@@ -4232,16 +4237,50 @@
           browser.setAttribute("crashedPageTitle", title);
           browser.docShell.displayLoadError(Cr.NS_ERROR_CONTENT_CRASHED, uri, null);
           browser.removeAttribute("crashedPageTitle");
           let tab = this.getTabForBrowser(browser);
           tab.setAttribute("crashed", true);
           this.setIcon(tab, icon);
         ]]>
       </handler>
+      <handler event="DOMMediaPlaybackStarted">
+        <![CDATA[
+          if (!Services.prefs.getBoolPref("browser.tabs.showAudioPlayingIcon") ||
+              !event.isTrusted)
+            return;
+
+          var browser = event.originalTarget;
+          var tab = this.getTabForBrowser(browser);
+          if (!tab)
+            return;
+
+          if (!tab.hasAttribute("soundplaying")) {
+            tab.setAttribute("soundplaying", true);
+            this._tabAttrModified(tab, ["soundplaying"]);
+          }
+        ]]>
+      </handler>
+      <handler event="DOMMediaPlaybackStopped">
+        <![CDATA[
+          if (!Services.prefs.getBoolPref("browser.tabs.showAudioPlayingIcon") ||
+              !event.isTrusted)
+            return;
+
+          var browser = event.originalTarget;
+          var tab = this.getTabForBrowser(browser);
+          if (!tab)
+            return;
+
+          if (tab.hasAttribute("soundplaying")) {
+            tab.removeAttribute("soundplaying");
+            this._tabAttrModified(tab, ["soundplaying"]);
+          }
+        ]]>
+      </handler>
     </handlers>
   </binding>
 
   <binding id="tabbrowser-tabbox"
            extends="chrome://global/content/bindings/tabbox.xml#tabbox">
     <implementation>
       <property name="tabs" readonly="true"
                 onget="return document.getBindingParent(this).tabContainer;"/>
@@ -5624,17 +5663,17 @@
   </binding>
 
   <binding id="tabbrowser-tab" display="xul:hbox"
            extends="chrome://global/content/bindings/tabbox.xml#tab">
     <resources>
       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
     </resources>
 
-    <content context="tabContextMenu" closetabtext="&closeTab.label;">
+    <content context="tabContextMenu">
       <xul:stack class="tab-stack" flex="1">
         <xul:hbox xbl:inherits="pinned,selected,visuallyselected,titlechanged,fadein"
                   class="tab-background">
           <xul:hbox xbl:inherits="pinned,selected,visuallyselected,titlechanged"
                     class="tab-background-start"/>
           <xul:hbox xbl:inherits="pinned,selected,visuallyselected,titlechanged"
                     class="tab-background-middle"/>
           <xul:hbox xbl:inherits="pinned,selected,visuallyselected,titlechanged"
@@ -5646,24 +5685,29 @@
                      class="tab-throbber"
                      role="presentation"
                      layer="true" />
           <xul:image xbl:inherits="src=image,fadein,pinned,selected,visuallyselected,busy,crashed"
                      anonid="tab-icon-image"
                      class="tab-icon-image"
                      validate="never"
                      role="presentation"/>
-          <xul:image xbl:inherits="crashed,busy"
+          <xul:image xbl:inherits="crashed,busy,soundplaying,pinned,muted"
+                     anonid="overlay-icon"
                      class="tab-icon-overlay"
                      role="presentation"/>
           <xul:label flex="1"
                      anonid="tab-label"
                      xbl:inherits="value=visibleLabel,crop,accesskey,fadein,pinned,selected,visuallyselected"
                      class="tab-text tab-label"
                      role="presentation"/>
+          <xul:image xbl:inherits="soundplaying,pinned,muted"
+                     anonid="soundplaying-icon"
+                     class="tab-icon-sound"
+                     role="presentation"/>
           <xul:toolbarbutton anonid="close-button"
                              xbl:inherits="fadein,pinned,selected,visuallyselected"
                              class="tab-close-button close-icon"/>
         </xul:hbox>
       </xul:stack>
     </content>
 
     <implementation>
@@ -5766,16 +5810,17 @@
           return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
         </getter>
         <setter>
           this._lastAccessed = val;
         </setter>
       </property>
 
       <field name="mOverCloseButton">false</field>
+      <field name="_overPlayingIcon">false</field>
       <field name="mCorrespondingMenuitem">null</field>
 
       <!--
       While it would make sense to track this in a field, the field will get nuked
       once the node is gone from the DOM, which causes us to think the tab is not
       closed, which causes us to make wrong decisions. So we use an expando instead.
       <field name="closing">false</field>
       -->
@@ -5822,50 +5867,87 @@
           if (tabContainer._afterHoveredTab) {
             tabContainer._afterHoveredTab.removeAttribute("afterhovered");
             tabContainer._afterHoveredTab = null;
           }
 
           tabContainer._hoveredTab = null;
         ]]></body>
       </method>
+
+      <method name="_toggleMuteAudio">
+        <body>
+        <![CDATA[
+          let tabContainer = this.parentNode;
+          let browser = this.linkedBrowser;
+          if (browser.audioMuted) {
+            browser.unmute();
+            this.removeAttribute("muted");
+          } else {
+            browser.mute();
+            this.setAttribute("muted", "true");
+          }
+          tabContainer.tabbrowser._tabAttrModified(this, ["muted"]);
+        ]]>
+        </body>
+      </method>
     </implementation>
 
     <handlers>
       <handler event="mouseover"><![CDATA[
         let anonid = event.originalTarget.getAttribute("anonid");
         if (anonid == "close-button")
           this.mOverCloseButton = true;
+        else if ((anonid == "soundplaying-icon") ||
+                 ((anonid == "overlay-icon") && this.hasAttribute("soundplaying")))
+          this._overPlayingIcon = true;
 
         this._mouseenter();
       ]]></handler>
       <handler event="mouseout"><![CDATA[
         let anonid = event.originalTarget.getAttribute("anonid");
         if (anonid == "close-button")
           this.mOverCloseButton = false;
+        else if ((anonid == "soundplaying-icon") ||
+                 ((anonid == "overlay-icon") && this.hasAttribute("soundplaying")))
+          this._overPlayingIcon = false;
 
         this._mouseleave();
       ]]></handler>
       <handler event="dragstart" phase="capturing">
         this.style.MozUserFocus = '';
       </handler>
       <handler event="mousedown" phase="capturing">
       <![CDATA[
         if (this.selected) {
           this.style.MozUserFocus = 'ignore';
           this.clientTop; // just using this to flush style updates
-        } else if (this.mOverCloseButton) {
+        } else if (this.mOverCloseButton ||
+                   this._overPlayingIcon) {
           // Prevent tabbox.xml from selecting the tab.
           event.stopPropagation();
         }
       ]]>
       </handler>
       <handler event="mouseup">
         this.style.MozUserFocus = '';
       </handler>
+      <handler event="click">
+      <![CDATA[
+        if (event.button != 0) {
+          return;
+        }
+
+        let anonid = event.originalTarget.getAttribute("anonid");
+        if ((anonid == "soundplaying-icon") ||
+            ((anonid == "overlay-icon") && this.hasAttribute("soundplaying"))) {
+          this._toggleMuteAudio();
+        }
+      ]]>
+      </handler>
     </handlers>
   </binding>
 
   <binding id="tabbrowser-alltabs-popup"
            extends="chrome://global/content/bindings/popup.xml#popup">
     <implementation implements="nsIDOMEventListener">
       <method name="_tabOnAttrModified">
         <parameter name="aEvent"/>
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -2,16 +2,17 @@
 support-files =
   POSTSearchEngine.xml
   accounts_testRemoteCommands.html
   alltabslistener.html
   app_bug575561.html
   app_subframe_bug575561.html
   authenticate.sjs
   aboutHome_content_script.js
+  audio.ogg
   browser_bug479408_sample.html
   browser_bug678392-1.html
   browser_bug678392-2.html
   browser_bug970746.xhtml
   browser_fxa_oauth.html
   browser_fxa_oauth_with_keys.html
   browser_fxa_web_channel.html
   browser_registerProtocolHandler_notification.html
@@ -45,16 +46,17 @@ support-files =
   file_bug902156_2.html
   file_bug902156_3.html
   file_bug906190_1.html
   file_bug906190_2.html
   file_bug906190_3_4.html
   file_bug906190_redirected.html
   file_bug906190.js
   file_bug906190.sjs
+  file_mediaPlayback.html
   file_mixedContentFromOnunload.html
   file_mixedContentFromOnunload_test1.html
   file_mixedContentFromOnunload_test2.html
   file_bug970276_popup1.html
   file_bug970276_popup2.html
   file_bug970276_favicon1.ico
   file_bug970276_favicon2.ico
   file_documentnavigation_frameset.html
@@ -126,16 +128,17 @@ skip-if = os == "linux" # Bug 924307
 skip-if = e10s # Bug 1093153 - no about:home support yet
 [browser_action_keyword.js]
 [browser_action_keyword_override.js]
 [browser_action_searchengine.js]
 [browser_action_searchengine_alias.js]
 [browser_addKeywordSearch.js]
 [browser_search_favicon.js]
 [browser_alltabslistener.js]
+[browser_audioTabIcon.js]
 [browser_autocomplete_a11y_label.js]
 skip-if = e10s # Bug 1101993 - times out for unknown reasons when run in the dir (works on its own)
 [browser_autocomplete_cursor.js]
 [browser_autocomplete_edit_completed.js]
 [browser_autocomplete_enter_race.js]
 [browser_autocomplete_no_title.js]
 [browser_autocomplete_autoselect.js]
 [browser_autocomplete_oldschool_wrap.js]
@@ -150,17 +153,16 @@ skip-if = buildapp == 'mulet' || toolkit
 [browser_bug304198.js]
 [browser_bug321000.js]
 skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
 [browser_bug329212.js]
 [browser_bug331772_xul_tooltiptext_in_html.js]
 [browser_bug356571.js]
 [browser_bug380960.js]
 [browser_bug386835.js]
-[browser_bug405137.js]
 [browser_bug406216.js]
 [browser_bug409481.js]
 [browser_bug409624.js]
 [browser_bug413915.js]
 [browser_bug416661.js]
 [browser_bug417483.js]
 [browser_bug419612.js]
 [browser_bug422590.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_audioTabIcon.js
@@ -0,0 +1,133 @@
+const PAGE = "https://example.com/browser/browser/base/content/test/general/file_mediaPlayback.html";
+
+function* wait_for_tab_playing_event(tab, expectPlaying) {
+  yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
+    if (event.detail.changed.indexOf("soundplaying") >= 0) {
+      is(tab.hasAttribute("soundplaying"), expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
+      return true;
+    }
+    return false;
+  });
+}
+
+function disable_non_test_mouse(disable) {
+  let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIDOMWindowUtils);
+  utils.disableNonTestMouseEvents(disable);
+}
+
+function* hover_icon(icon, tooltip) {
+  disable_non_test_mouse(true);
+
+  let popupShownPromise = BrowserTestUtils.waitForEvent(tooltip, "popupshown");
+  EventUtils.synthesizeMouse(icon, 1, 1, {type: "mouseover"});
+  EventUtils.synthesizeMouse(icon, 2, 2, {type: "mousemove"});
+  EventUtils.synthesizeMouse(icon, 3, 3, {type: "mousemove"});
+  EventUtils.synthesizeMouse(icon, 4, 4, {type: "mousemove"});
+  return popupShownPromise;
+}
+
+function leave_icon(icon) {
+  EventUtils.synthesizeMouse(icon, 0, 0, {type: "mouseout"});
+  EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+  EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+  EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+
+  disable_non_test_mouse(false);
+}
+
+function* test_tooltip(icon, expectedTooltip) {
+  let tooltip = document.getElementById("tabbrowser-tab-tooltip");
+
+  yield hover_icon(icon, tooltip);
+  is(tooltip.getAttribute("label"), expectedTooltip, "Correct tooltip expected");
+  leave_icon(icon);
+}
+
+function* test_mute_tab(tab, icon, expectMuted) {
+  let mutedPromise = BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
+    if (event.detail.changed.indexOf("muted") >= 0) {
+      is(tab.hasAttribute("muted"), expectMuted, "The tab should " + (expectMuted ? "" : "not ") + "be muted");
+      return true;
+    }
+    return false;
+  });
+
+  let activeTab = gBrowser.selectedTab;
+
+  let tooltip = document.getElementById("tabbrowser-tab-tooltip");
+
+  yield hover_icon(icon, tooltip);
+  EventUtils.synthesizeMouseAtCenter(icon, {button: 0});
+  leave_icon(icon);
+
+  is(gBrowser.selectedTab, activeTab, "Clicking on mute should not change the currently selected tab");
+
+  return mutedPromise;
+}
+
+function* test_playing_icon_on_tab(tab, browser, isPinned) {
+  let icon = document.getAnonymousElementByAttribute(tab, "anonid",
+                                                     isPinned ? "overlay-icon" : "soundplaying-icon");
+
+  yield ContentTask.spawn(browser, {}, function* () {
+    let audio = content.document.querySelector("audio");
+    audio.play();
+  });
+
+  yield wait_for_tab_playing_event(tab, true);
+
+  yield test_tooltip(icon, "This tab is playing audio");
+
+  yield test_mute_tab(tab, icon, true);
+
+  yield test_tooltip(icon, "This tab has been muted");
+
+  yield test_mute_tab(tab, icon, false);
+
+  yield test_tooltip(icon, "This tab is playing audio");
+
+  yield ContentTask.spawn(browser, {}, function* () {
+    let audio = content.document.querySelector("audio");
+    audio.pause();
+  });
+  yield wait_for_tab_playing_event(tab, false);
+}
+
+function* test_on_browser(browser) {
+  let tab = gBrowser.getTabForBrowser(browser);
+
+  // Test the icon in a normal tab.
+  yield test_playing_icon_on_tab(tab, browser, false);
+
+  gBrowser.pinTab(tab);
+
+  // Test the icon in a pinned tab.
+  yield test_playing_icon_on_tab(tab, browser, true);
+
+  gBrowser.unpinTab(tab);
+
+  // Retest with another browser in the foreground tab
+  if (gBrowser.selectedBrowser.currentURI.spec == PAGE) {
+    yield BrowserTestUtils.withNewTab({
+      gBrowser,
+      url: "data:text/html,test"
+    }, () => test_on_browser(browser));
+  }
+}
+
+add_task(function*() {
+  yield new Promise((resolve) => {
+    SpecialPowers.pushPrefEnv({"set": [
+                                ["media.useAudioChannelService", true],
+                                ["browser.tabs.showAudioPlayingIcon", true],
+                              ]}, resolve);
+  });
+});
+
+add_task(function* test_page() {
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: PAGE
+  }, test_on_browser);
+});
deleted file mode 100644
--- a/browser/base/content/test/general/browser_bug405137.js
+++ /dev/null
@@ -1,5 +0,0 @@
-function test(){
-  var tab = gBrowser.addTab();
-  ok(tab.getAttribute("closetabtext") != "", "tab has non-empty closetabtext");
-  gBrowser.removeTab(tab);  
-}
--- a/browser/base/content/test/general/browser_contentSearchUI.js
+++ b/browser/base/content/test/general/browser_contentSearchUI.js
@@ -97,17 +97,17 @@ add_task(function* rightLeftKeys() {
 
   state = yield msg("key", "VK_DOWN");
   checkState(state, "xfoo", ["xfoo", "xbar"], 0);
 
   // This should make the xfoo suggestion sticky.  To make sure it sticks,
   // trigger suggestions again and cycle through them by pressing Down until
   // nothing is selected again.
   state = yield msg("key", "VK_RIGHT");
-  checkState(state, "xfoo", [], -1);
+  checkState(state, "xfoo", [], 0);
 
   state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
   checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
 
   state = yield msg("key", "VK_DOWN");
   checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0);
 
   state = yield msg("key", "VK_DOWN");
@@ -120,212 +120,30 @@ add_task(function* rightLeftKeys() {
   checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 3);
 
   state = yield msg("key", "VK_DOWN");
   checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
 
   yield msg("reset");
 });
 
-add_task(function* tabKey() {
-  yield setUp();
-  yield msg("key", { key: "x", waitForSuggestions: true });
-
-  let state = yield msg("key", "VK_TAB");
-  checkState(state, "x", ["xfoo", "xbar"], 2);
-
-  state = yield msg("key", "VK_TAB");
-  checkState(state, "x", ["xfoo", "xbar"], 3);
-
-  state = yield msg("key", { key: "VK_TAB", modifiers: { shiftKey: true }});
-  checkState(state, "x", ["xfoo", "xbar"], 2);
-
-  state = yield msg("key", { key: "VK_TAB", modifiers: { shiftKey: true }});
-  checkState(state, "x", [], -1);
-
-  yield setUp();
-
-  yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
-
-  for (let i = 0; i < 3; ++i) {
-    state = yield msg("key", "VK_TAB");
-  }
-  checkState(state, "x", [], -1);
-
-  yield setUp();
-
-  yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "xfoo", ["xfoo", "xbar"], 0);
-
-  state = yield msg("key", "VK_TAB");
-  checkState(state, "xfoo", ["xfoo", "xbar"], 0, 0);
-
-  state = yield msg("key", "VK_TAB");
-  checkState(state, "xfoo", ["xfoo", "xbar"], 0, 1);
-
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
-
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "x", ["xfoo", "xbar"], 2);
-
-  state = yield msg("key", "VK_UP");
-  checkState(state, "xbar", ["xfoo", "xbar"], 1);
-
-  state = yield msg("key", "VK_TAB");
-  checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
-
-  state = yield msg("key", "VK_TAB");
-  checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
-
-  state = yield msg("key", "VK_TAB");
-  checkState(state, "xbar", [], -1);
-
-  yield msg("reset");
-});
-
-add_task(function* cycleSuggestions() {
-  yield setUp();
-  yield msg("key", { key: "x", waitForSuggestions: true });
-
-  let cycle = Task.async(function* (aSelectedButtonIndex) {
-    let modifiers = {
-      shiftKey: true,
-      accelKey: true,
-    };
-  
-    let state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
-    checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
-  
-    state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
-    checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex);
-  
-    state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
-    checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
-  
-    state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
-    checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
-  
-    state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
-    checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
-  
-    state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
-    checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex);
-  
-    state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
-    checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
-  
-    state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
-    checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
-  });
-  
-  yield cycle();
-
-  // Repeat with a one-off selected.
-  let state = yield msg("key", "VK_TAB");
-  checkState(state, "x", ["xfoo", "xbar"], 2);
-  yield cycle(0);
-
-  // Repeat with the settings button selected.
-  state = yield msg("key", "VK_TAB");
-  checkState(state, "x", ["xfoo", "xbar"], 3);
-  yield cycle(1);
-
-  yield msg("reset");
-});
-
-add_task(function* cycleOneOffs() {
-  yield setUp();
-  yield msg("key", { key: "x", waitForSuggestions: true });
-
-  yield msg("addDuplicateOneOff");
-
-  let state = yield msg("key", "VK_DOWN");
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "xbar", ["xfoo", "xbar"], 1);
-
-  let modifiers = {
-    altKey: true,
-  };
-
-  state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
-  checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
-
-  state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
-  checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
-
-  state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
-  checkState(state, "xbar", ["xfoo", "xbar"], 1);
-
-  state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
-  checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
-
-  state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
-  checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
-
-  state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
-  checkState(state, "xbar", ["xfoo", "xbar"], 1);
-
-  // If the settings button is selected, pressing alt+up/down should select the
-  // last/first one-off respectively (and deselect the settings button).
-  yield msg("key", "VK_TAB");
-  yield msg("key", "VK_TAB");
-  state = yield msg("key", "VK_TAB"); // Settings button selected.
-  checkState(state, "xbar", ["xfoo", "xbar"], 1, 2);
-
-  state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
-  checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
-
-  state = yield msg("key", "VK_TAB");
-  checkState(state, "xbar", ["xfoo", "xbar"], 1, 2);
-
-  state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
-  checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
-
-  yield msg("removeLastOneOff");
-  yield msg("reset");
-});
-
 add_task(function* mouse() {
   yield setUp();
 
   let state = yield msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
-  state = yield msg("mousemove", 0);
-  checkState(state, "x", ["xfoo", "xbar"], 0);
-
-  state = yield msg("mousemove", 1);
-  checkState(state, "x", ["xfoo", "xbar"], 1);
-
-  state = yield msg("mousemove", 2);
-  checkState(state, "x", ["xfoo", "xbar"], 1, 0);
-
-  state = yield msg("mousemove", 3);
-  checkState(state, "x", ["xfoo", "xbar"], 1, 1);
+  for (let i = 0; i < 4; ++i) {
+    state = yield msg("mousemove", i);
+    checkState(state, "x", ["xfoo", "xbar"], i);
+  }
 
   state = yield msg("mousemove", -1);
-  checkState(state, "x", ["xfoo", "xbar"], 1);
-
-  yield msg("reset");
-  yield setUp();
-
-  state = yield msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
-  state = yield msg("mousemove", 0);
-  checkState(state, "x", ["xfoo", "xbar"], 0);
-
-  state = yield msg("mousemove", 2);
-  checkState(state, "x", ["xfoo", "xbar"], 0, 0);
-
-  state = yield msg("mousemove", -1);
-  checkState(state, "x", ["xfoo", "xbar"], 0);
-
   yield msg("reset");
 });
 
 add_task(function* formHistory() {
   yield setUp();
 
   // Type an X and add it to form history.
   let state = yield msg("key", { key: "x", waitForSuggestions: true });
@@ -374,43 +192,16 @@ add_task(function* formHistory() {
 
   // Type an X again.  The form history entry should still be gone.
   state = yield msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
   yield msg("reset");
 });
 
-add_task(function* cycleEngines() {
-  yield setUp();
-  yield msg("key", "VK_DOWN");
-
-  function promiseEngineChange(newEngineName) {
-    let deferred = Promise.defer();
-    Services.obs.addObserver(function resolver(subj, topic, data) {
-      if (data != "engine-current") {
-        return;
-      }
-      is(subj.name, newEngineName, "Engine cycled correctly");
-      Services.obs.removeObserver(resolver, "browser-search-engine-modified");
-      deferred.resolve();
-    }, "browser-search-engine-modified", false);
-  }
-
-  let p = promiseEngineChange(TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME);
-  yield msg("key", { key: "VK_DOWN", modifiers: { accelKey: true }});
-  yield p;
-
-  p = promiseEngineChange(TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME);
-  yield msg("key", { key: "VK_UP", modifiers: { accelKey: true }});
-  yield p;
-
-  yield msg("reset");
-});
-
 add_task(function* search() {
   yield setUp();
 
   let modifiers = {};
   ["altKey", "ctrlKey", "metaKey", "shiftKey"].forEach(k => modifiers[k] = true);
 
   // Test typing a query and pressing enter.
   let p = msg("waitForSearch");
@@ -501,67 +292,29 @@ add_task(function* search() {
   eventData.searchString = "x";
   eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME;
   delete eventData.selection;
   SimpleTest.isDeeply(eventData, mesg, "Search event data");
 
   yield promiseTab();
   yield setUp();
 
-  // Test selecting a suggestion, then clicking a one-off without deselecting the
-  // suggestion.
-  yield msg("key", { key: "x", waitForSuggestions: true });
-  p = msg("waitForSearch");
-  yield msg("mousemove", 1);
-  yield msg("mousemove", 3);
-  yield msg("click", { eltIdx: 3, modifiers: modifiers });
-  mesg = yield p;
-  eventData.searchString = "xfoo"
-  eventData.selection = {
-    index: 1,
-    kind: "mouse",
-  };
-  SimpleTest.isDeeply(eventData, mesg, "Search event data");
-
-  yield promiseTab();
-  yield setUp();
-
-  // Same as above, but with the keyboard.
-  delete modifiers.button;
-  yield msg("key", { key: "x", waitForSuggestions: true });
-  p = msg("waitForSearch");
-  yield msg("key", "VK_DOWN");
-  yield msg("key", "VK_DOWN");
-  yield msg("key", "VK_TAB");
-  yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
-  mesg = yield p;
-  eventData.selection = {
-    index: 1,
-    kind: "key",
-  };
-  SimpleTest.isDeeply(eventData, mesg, "Search event data");
-
-  yield promiseTab();
-  yield setUp();
-
   // Test searching when using IME composition.
   let state = yield msg("startComposition", { data: "" });
   checkState(state, "", [], -1);
   state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
   checkState(state, "x", [{ str: "x", type: "formHistory" },
                           { str: "xfoo", type: "formHistory" }, "xbar"], -1);
   yield msg("commitComposition");
   delete modifiers.button;
   p = msg("waitForSearch");
   yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
   mesg = yield p;
-  eventData.searchString = "x"
   eventData.originalEvent = modifiers;
   eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
-  delete eventData.selection;
   SimpleTest.isDeeply(eventData, mesg, "Search event data");
 
   yield promiseTab();
   yield setUp();
 
   state = yield msg("startComposition", { data: "" });
   checkState(state, "", [], -1);
   state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
@@ -670,49 +423,36 @@ function msg(type, data=null) {
     }
     gMsgMan.removeMessageListener(TEST_MSG, onMsg);
     deferred.resolve(msg.data.data);
   });
   return deferred.promise;
 }
 
 function checkState(actualState, expectedInputVal, expectedSuggestions,
-                    expectedSelectedIdx, expectedSelectedButtonIdx) {
+                    expectedSelectedIdx) {
   expectedSuggestions = expectedSuggestions.map(sugg => {
     return typeof(sugg) == "object" ? sugg : {
       str: sugg,
       type: "remote",
     };
   });
 
-  if (expectedSelectedIdx == -1 && expectedSelectedButtonIdx != undefined) {
-    expectedSelectedIdx = expectedSuggestions.length + expectedSelectedButtonIdx;
-  }
-  
   let expectedState = {
     selectedIndex: expectedSelectedIdx,
     numSuggestions: expectedSuggestions.length,
     suggestionAtIndex: expectedSuggestions.map(s => s.str),
     isFormHistorySuggestionAtIndex:
       expectedSuggestions.map(s => s.type == "formHistory"),
 
     tableHidden: expectedSuggestions.length == 0,
 
     inputValue: expectedInputVal,
     ariaExpanded: expectedSuggestions.length == 0 ? "false" : "true",
   };
-  if (expectedSelectedButtonIdx != undefined) {
-    expectedState.selectedButtonIndex = expectedSelectedButtonIdx;
-  }
-  else if (expectedSelectedIdx < expectedSuggestions.length) {
-    expectedState.selectedButtonIndex = -1;
-  }
-  else {
-    expectedState.selectedButtonIndex = expectedSelectedIdx - expectedSuggestions.length;
-  }
 
   SimpleTest.isDeeply(actualState, expectedState, "State");
 }
 
 var gMsgMan;
 
 function promiseTab() {
   let deferred = Promise.defer();
--- a/browser/base/content/test/general/contentSearchUI.js
+++ b/browser/base/content/test/general/contentSearchUI.js
@@ -119,37 +119,21 @@ let messageHandlers = {
     ack("click");
   },
 
   addInputValueToFormHistory: function () {
     gController.addInputValueToFormHistory();
     ack("addInputValueToFormHistory");
   },
 
-  addDuplicateOneOff: function () {
-    let btn = gController._oneOffButtons[gController._oneOffButtons.length - 1];
-    let newBtn = btn.cloneNode(true);
-    btn.parentNode.appendChild(newBtn);
-    gController._oneOffButtons.push(newBtn);
-    ack("addDuplicateOneOff");
-  },
-
-  removeLastOneOff: function () {
-    gController._oneOffButtons.pop().remove();
-    ack("removeLastOneOff");
-  },
-
   reset: function () {
-    // Reset both the input and suggestions by select all + delete. If there was
-    // no text entered, this won't have any effect, so also escape to ensure the
-    // suggestions table is closed.
+    // Reset both the input and suggestions by select all + delete.
     gController.input.focus();
     content.synthesizeKey("a", { accelKey: true });
     content.synthesizeKey("VK_DELETE", {});
-    content.synthesizeKey("VK_ESCAPE", {});
     ack("reset");
   },
 };
 
 function ack(aType, aData) {
   sendAsyncMessage(TEST_MSG, { type: aType, data: aData || currentState() });
 }
 
@@ -176,17 +160,16 @@ function waitForContentSearchEvent(messa
     mm.removeMessageListener("ContentSearch", listener);
     cb(aMsg.data.data);
   });
 }
 
 function currentState() {
   let state = {
     selectedIndex: gController.selectedIndex,
-    selectedButtonIndex: gController.selectedButtonIndex,
     numSuggestions: gController._table.hidden ? 0 : gController.numSuggestions,
     suggestionAtIndex: [],
     isFormHistorySuggestionAtIndex: [],
 
     tableHidden: gController._table.hidden,
 
     inputValue: gController.input.value,
     ariaExpanded: gController.input.getAttribute("aria-expanded"),
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/file_mediaPlayback.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<audio src="audio.ogg" controls loop>
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -1375,17 +1375,19 @@ html[dir="rtl"] .standalone .room-conver
   background-image: url("../../img/gum-others.svg");
   background-position: top center;
   /* The background-image is scaled up at 2x the instrinsic size
      (witdh & height) to make it easier to see. */
   background-size: 202px 122px;
   background-repeat: no-repeat;
   border: 1rem #fff solid;
   box-shadow: 0 0 5px #000;
-  margin: 0;
+  margin: auto;
+  /* `width` here is specified by the design spec. */
+  width: 250px;
 }
 
 .standalone .prompt-media-message.chrome {
   background-image: url("../../img/gum-chrome.svg");
 }
 
 .standalone .prompt-media-message.firefox {
   background-image: url("../../img/gum-firefox.svg");
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -1930,16 +1930,17 @@ MarkupContainer.prototype = {
 
     let isMiddleClick = event.button === 1;
     let isMetaClick = event.button === 0 && (event.metaKey || event.ctrlKey);
 
     if (isMiddleClick || isMetaClick) {
       let link = target.dataset.link;
       let type = target.dataset.type;
       this.markup._inspector.followAttributeLink(type, link);
+      return;
     }
 
     // Start dragging the container after a delay.
     this.markup._dragStartEl = target;
     setTimeout(() => {
       // Make sure the mouse is still down and on target.
       if (!this._isMouseDown || this.markup._dragStartEl !== target ||
           this.node.isPseudoElement || this.node.isAnonymous ||
--- a/browser/devtools/markupview/test/browser_markupview_dragdrop_isDragging.js
+++ b/browser/devtools/markupview/test/browser_markupview_dragdrop_isDragging.js
@@ -20,27 +20,42 @@ add_task(function*() {
   el._onMouseDown({
     target: el.tagLine,
     pageX: rect.x,
     pageY: rect.y,
     stopPropagation: function() {},
     preventDefault: function() {}
   });
 
-  is(el.isDragging, false, "isDragging should not be set to true immedietly");
+  ok(!el.isDragging, "isDragging should not be set to true immediately");
 
   info("Waiting " + (GRAB_DELAY + 1) + "ms");
   yield wait(GRAB_DELAY + 1);
   ok(el.isDragging, "isDragging true after GRAB_DELAY has passed");
 
   let dropCompleted = once(inspector.markup, "drop-completed");
 
   info("Simulating mouseUp on #test");
   el._onMouseUp({
     target: el.tagLine,
     pageX: rect.x,
     pageY: rect.y
   });
 
   yield dropCompleted;
 
-  is(el.isDragging, false, "isDragging false after mouseUp");
+  ok(!el.isDragging, "isDragging false after mouseUp");
+
+  info("Simulating middle click on #test");
+  el._onMouseDown({
+    target: el.tagLine,
+    button: 1,
+    pageX: rect.x,
+    pageY: rect.y,
+    stopPropagation: function() {},
+    preventDefault: function() {}
+  });
+  ok(!el.isDragging, "isDragging should not be set to true immediately");
+
+  info("Waiting " + (GRAB_DELAY + 1) + "ms");
+  yield wait(GRAB_DELAY + 1);
+  ok(!el.isDragging, "isDragging never starts after middle click after mouseUp");
 });
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -106,16 +106,17 @@ support-files =
   test-mutation.html
   test-network-request.html
   test-network.html
   test-observe-http-ajax.html
   test-own-console.html
   test-property-provider.html
   test-repeated-messages.html
   test-result-format-as-string.html
+  test-trackingprotection-securityerrors.html
   test-webconsole-error-observer.html
   test_bug_770099_violation.html
   test_bug_770099_violation.html^headers^
   test-autocomplete-in-stackframe.html
   testscript.js
   test-bug_923281_console_log_filter.html
   test-bug_923281_test1.js
   test-bug_923281_test2.js
@@ -345,16 +346,18 @@ skip-if = e10s # Bug 1042253 - webconsol
 [browser_webconsole_property_provider.js]
 skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
 [browser_webconsole_scratchpad_panel_link.js]
 [browser_webconsole_split.js]
 [browser_webconsole_split_escape_key.js]
 [browser_webconsole_split_focus.js]
 [browser_webconsole_split_persist.js]
 skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
+[browser_webconsole_trackingprotection_errors.js]
+tags = trackingprotection
 [browser_webconsole_view_source.js]
 skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s (expectUncaughtException)
 [browser_webconsole_reflow.js]
 [browser_webconsole_log_file_filter.js]
 [browser_webconsole_expandable_timestamps.js]
 [browser_webconsole_autocomplete_in_debugger_stackframe.js]
 skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
 [browser_webconsole_autocomplete_popup_close_on_tab_switch.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_trackingprotection_errors.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Load a page with tracking elements that get blocked and make sure that a
+// 'learn more' link shows up in the webconsole.
+
+"use strict";
+
+const TEST_URI = "http://tracking.example.org/browser/browser/devtools/webconsole/test/test-trackingprotection-securityerrors.html";
+const LEARN_MORE_URI = "https://developer.mozilla.org/Firefox/Privacy/Tracking_Protection";
+const PREF = "privacy.trackingprotection.enabled";
+const {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+registerCleanupFunction(function() {
+  Services.prefs.clearUserPref(PREF);
+  UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+add_task(function* testMessagesAppear() {
+  yield UrlClassifierTestUtils.addTestTrackers();
+  Services.prefs.setBoolPref(PREF, true);
+
+  let { browser } = yield loadTab(TEST_URI);
+
+  let hud = yield openConsole();
+
+  let results = yield waitForMessages({
+    webconsole: hud,
+    messages: [
+      {
+        name: "Was blocked because tracking protection is enabled",
+        text: "The resource at \"http://tracking.example.com/\" was blocked because tracking protection is enabled",
+        category: CATEGORY_SECURITY,
+        severity: SEVERITY_WARNING,
+        objects: true,
+      },
+    ],
+  });
+
+  yield testClickOpenNewTab(hud, results[0]);
+});
+
+function testClickOpenNewTab(hud, match) {
+  let warningNode = match.clickableElements[0];
+  ok(warningNode, "link element");
+  ok(warningNode.classList.contains("learn-more-link"), "link class name");
+  return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-trackingprotection-securityerrors.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<!-- 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/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+  <head>
+    <meta charset="utf8">
+  </head>
+  <body>
+    <iframe src="http://tracking.example.com/"></iframe>
+  </body>
+</html>
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -37,16 +37,18 @@ loader.lazyImporter(this, "gDevTools", "
 
 const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/docs/Security/MixedContent";
 
+const TRACKING_PROTECTION_LEARN_MORE = "https://developer.mozilla.org/Firefox/Privacy/Tracking_Protection";
+
 const INSECURE_PASSWORDS_LEARN_MORE = "https://developer.mozilla.org/docs/Security/InsecurePasswords";
 
 const STRICT_TRANSPORT_SECURITY_LEARN_MORE = "https://developer.mozilla.org/docs/Security/HTTP_Strict_Transport_Security";
 
 const WEAK_SIGNATURE_ALGORITHM_LEARN_MORE = "https://developer.mozilla.org/docs/Security/Weak_Signature_Algorithm";
 
 const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
 
@@ -1674,16 +1676,19 @@ WebConsoleFrame.prototype = {
       url = MIXED_CONTENT_LEARN_MORE;
      break;
      case "Invalid HSTS Headers":
       url = STRICT_TRANSPORT_SECURITY_LEARN_MORE;
      break;
      case "SHA-1 Signature":
       url = WEAK_SIGNATURE_ALGORITHM_LEARN_MORE;
      break;
+     case "Tracking Protection":
+      url = TRACKING_PROTECTION_LEARN_MORE;
+     break;
      default:
       // Unknown category. Return without adding more info node.
       return;
     }
 
     this.addLearnMoreWarningNode(aNode, url);
   },
 
deleted file mode 100644
--- a/browser/locales/en-US/chrome/browser/tabbrowser.dtd
+++ /dev/null
@@ -1,5 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!ENTITY  closeTab.label         "Close Tab">
--- a/browser/locales/en-US/chrome/browser/tabbrowser.properties
+++ b/browser/locales/en-US/chrome/browser/tabbrowser.properties
@@ -24,8 +24,12 @@ tabs.closeWarningTitle=Confirm close
 # LOCALIZATION NOTE (tabs.closeWarningMultiple):
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # The singular form is not considered since this string is used only for
 # multiple tabs.
 tabs.closeWarningMultiple=;You are about to close #1 tabs. Are you sure you want to continue?
 tabs.closeButtonMultiple=Close tabs
 tabs.closeWarningPromptMe=Warn me when I attempt to close multiple tabs
+
+tabs.closeTab.tooltip=Close tab
+tabs.playingAudio.tooltip=This tab is playing audio
+tabs.mutedAudio.tooltip=This tab has been muted
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -89,17 +89,16 @@
     locale/browser/sanitize.dtd                    (%chrome/browser/sanitize.dtd)
     locale/browser/search.properties               (%chrome/browser/search.properties)
     locale/browser/searchbar.dtd                   (%chrome/browser/searchbar.dtd)
     locale/browser/sitePermissions.properties      (%chrome/browser/sitePermissions.properties)
     locale/browser/engineManager.dtd               (%chrome/browser/engineManager.dtd)
     locale/browser/engineManager.properties        (%chrome/browser/engineManager.properties)
     locale/browser/setDesktopBackground.dtd        (%chrome/browser/setDesktopBackground.dtd)
     locale/browser/shellservice.properties         (%chrome/browser/shellservice.properties)
-    locale/browser/tabbrowser.dtd                  (%chrome/browser/tabbrowser.dtd)
     locale/browser/tabbrowser.properties           (%chrome/browser/tabbrowser.properties)
     locale/browser/tabview.properties              (%chrome/browser/tabview.properties)
     locale/browser/taskbar.properties              (%chrome/browser/taskbar.properties)
     locale/browser/translation.dtd                 (%chrome/browser/translation.dtd)
     locale/browser/translation.properties          (%chrome/browser/translation.properties)
     locale/browser/webrtcIndicator.properties      (%chrome/browser/webrtcIndicator.properties)
     locale/browser/downloads/downloads.dtd         (%chrome/browser/downloads/downloads.dtd)
     locale/browser/downloads/downloads.properties  (%chrome/browser/downloads/downloads.properties)
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -238,16 +238,18 @@ browser.jar:
   skin/classic/browser/tabbrowser/alltabs.png         (tabbrowser/alltabs.png)
   skin/classic/browser/tabbrowser/alltabs-inverted.png (tabbrowser/alltabs-inverted.png)
   skin/classic/browser/tabbrowser/connecting.png      (tabbrowser/connecting.png)
   skin/classic/browser/tabbrowser/crashed.svg         (../shared/tabbrowser/crashed.svg)
   skin/classic/browser/tabbrowser/loading.png         (tabbrowser/loading.png)
   skin/classic/browser/tabbrowser/tab-active-middle.png     (tabbrowser/tab-active-middle.png)
   skin/classic/browser/tabbrowser/tab-arrow-left.png        (tabbrowser/tab-arrow-left.png)
   skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
+  skin/classic/browser/tabbrowser/tab-audio.svg       (../shared/tabbrowser/tab-audio.svg)
+  skin/classic/browser/tabbrowser/tab-audio-small.svg (../shared/tabbrowser/tab-audio-small.svg)
   skin/classic/browser/tabbrowser/tab-background-end.png    (tabbrowser/tab-background-end.png)
   skin/classic/browser/tabbrowser/tab-background-middle.png (tabbrowser/tab-background-middle.png)
   skin/classic/browser/tabbrowser/tab-background-start.png  (tabbrowser/tab-background-start.png)
   skin/classic/browser/tabbrowser/tab-overflow-indicator.png (../shared/tabbrowser/tab-overflow-indicator.png)
 
 # NOTE: The following two files (tab-selected-end.svg, tab-selected-start.svg) get pre-processed in
 #       Makefile.in with a non-default marker of "%" and the result of that gets packaged.
   skin/classic/browser/tabbrowser/tab-selected-end.svg      (tab-selected-end.svg)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -332,16 +332,18 @@ browser.jar:
   skin/classic/browser/tabbrowser/tab-arrow-left.png                     (tabbrowser/tab-arrow-left.png)
   skin/classic/browser/tabbrowser/tab-arrow-left@2x.png                  (tabbrowser/tab-arrow-left@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png            (tabbrowser/tab-arrow-left-inverted.png)
   skin/classic/browser/tabbrowser/tab-arrow-left-inverted@2x.png         (tabbrowser/tab-arrow-left-inverted@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-right.png                    (tabbrowser/tab-arrow-right.png)
   skin/classic/browser/tabbrowser/tab-arrow-right@2x.png                 (tabbrowser/tab-arrow-right@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-right-inverted.png           (tabbrowser/tab-arrow-right-inverted.png)
   skin/classic/browser/tabbrowser/tab-arrow-right-inverted@2x.png        (tabbrowser/tab-arrow-right-inverted@2x.png)
+  skin/classic/browser/tabbrowser/tab-audio.svg                          (../shared/tabbrowser/tab-audio.svg)
+  skin/classic/browser/tabbrowser/tab-audio-small.svg                    (../shared/tabbrowser/tab-audio-small.svg)
   skin/classic/browser/tabbrowser/tab-background-end.png                 (tabbrowser/tab-background-end.png)
   skin/classic/browser/tabbrowser/tab-background-end@2x.png              (tabbrowser/tab-background-end@2x.png)
   skin/classic/browser/tabbrowser/tab-background-middle.png              (tabbrowser/tab-background-middle.png)
   skin/classic/browser/tabbrowser/tab-background-middle@2x.png           (tabbrowser/tab-background-middle@2x.png)
   skin/classic/browser/tabbrowser/tab-background-start.png               (tabbrowser/tab-background-start.png)
   skin/classic/browser/tabbrowser/tab-background-start@2x.png            (tabbrowser/tab-background-start@2x.png)
   skin/classic/browser/tabbrowser/tab-overflow-indicator.png             (../shared/tabbrowser/tab-overflow-indicator.png)
 
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/tab-audio-small.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
+  <style>
+    use:not(:target) {
+      display: none;
+    }
+    .icon {
+      fill: #4d4d4d;
+    }
+    .icon.hover {
+      fill: #333333;
+    }
+    .icon.pressed {
+      fill: #000;
+    }
+    .icon.dark {
+      fill: #ccc;
+    }
+    .icon.dark.hover {
+      fill: #b2b2b2;
+    }
+    .icon.dark.pressed {
+      fill: #fff;
+    }
+    .muted {
+      opacity: .7;
+      stroke: #4d4d4d;
+      stroke-width: 0;
+    }
+    .muted.hover {
+      opacity: .85;
+      stroke: #333333;
+    }
+    .muted.pressed {
+      opacity: 1;
+      stroke: #000;
+    }
+    .muted.dark {
+      stroke: #ccc;
+    }
+    .muted.dark.hover {
+      stroke: #b2b2b2;
+    }
+    .muted.dark.pressed {
+      stroke: #fff;
+    }
+  </style>
+  <defs>
+    <clipPath id="clip-wave">
+      <path d="M 11,7 l 3,-8 l 2,0 l 0,18 l -2,0 l -3,-8 z" />
+    </clipPath>
+    <mask id="disabled-cutout">
+      <rect width="16" height="16" fill="#fff" />
+      <line x1="4" y1="14" x2="14" y2="4" stroke="#000" stroke-width="2" />
+    </mask>
+    <g id="shape-tab-audio">
+      <rect x="3" y="6" width="5" height="4" rx="1" ry="1" />
+      <polygon points="5.5,6.5 9,3 9,13 5.5,9.5" />
+      <path d="M 10,6.5 a 1.5 1.5 0 0,1 0,3 z" />
+      <path d="M 10,4 a 4 4 0 0,1 0,8 l 0,-1 a 3 3 0 0,0 0,-6 z"  clip-path="url(#clip-wave)" />
+    </g>
+    <g id="shape-tab-audio-muted">
+      <g mask="url(#disabled-cutout)">
+        <rect x="4" y="6" width="5" height="4" rx="1" ry="1" />
+        <polygon points="6.5,6.5 10,3 10,13 6.5,9.5" />
+      </g>
+      <line x1="3" y1="14" x2="13" y2="4" stroke-width="1.5" />
+    </g>
+  </defs>
+  <use id="tab-audio" class="icon" xlink:href="#shape-tab-audio"/>
+  <use id="tab-audio-hover" class="icon hover" xlink:href="#shape-tab-audio"/>
+  <use id="tab-audio-pressed" class="icon pressed" xlink:href="#shape-tab-audio"/>
+  <use id="tab-audio-muted" class="icon muted" xlink:href="#shape-tab-audio-muted" />
+  <use id="tab-audio-muted-hover" class="icon muted hover" xlink:href="#shape-tab-audio-muted" />
+  <use id="tab-audio-muted-pressed" class="icon muted pressed" xlink:href="#shape-tab-audio-muted" />
+  <use id="tab-audio-dark" class="icon dark" xlink:href="#shape-tab-audio"/>
+  <use id="tab-audio-dark-hover" class="icon hover dark" xlink:href="#shape-tab-audio"/>
+  <use id="tab-audio-dark-pressed" class="icon pressed dark" xlink:href="#shape-tab-audio"/>
+  <use id="tab-audio-muted-dark" class="icon muted dark" xlink:href="#shape-tab-audio-muted" />
+  <use id="tab-audio-muted-dark-hover" class="icon muted hover dark" xlink:href="#shape-tab-audio-muted" />
+  <use id="tab-audio-muted-dark-pressed" class="icon muted pressed dark" xlink:href="#shape-tab-audio-muted" />
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/tab-audio.svg
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
+  <style>
+    use:not(:target) {
+      display: none;
+    }
+    .icon {
+      fill: #666;
+    }
+    .icon.hover {
+      fill: #4d4d4d;
+    }
+    .icon.pressed {
+      fill: #000;
+    }
+    .icon.dark {
+      fill: #999;
+    }
+    .icon.dark.hover {
+      fill: #b2b2b2;
+    }
+    .icon.dark.pressed {
+      fill: #fff;
+    }
+    .muted {
+      opacity: .7;
+      stroke: #666;
+      stroke-width: 0;
+    }
+    .muted.hover {
+      opacity: .85;
+      stroke: #4d4d4d;
+    }
+    .muted.pressed {
+      opacity: 1;
+      stroke: #000;
+    }
+    .muted.dark {
+      stroke: #999;
+    }
+    .muted.dark.hover {
+      stroke: #b2b2b2;
+    }
+    .muted.dark.pressed {
+      stroke: #fff;
+    }
+  </style>
+  <defs>
+    <clipPath id="clip-wave">
+      <path d="M 10,7 l 3,-8 l 2,0 l 0,18 l -2,0 l -3,-8 z" />
+    </clipPath>
+    <mask id="disabled-cutout">
+      <rect width="16" height="16" fill="#fff" />
+      <line x1="4" y1="14" x2="14" y2="4" stroke="#000" stroke-width="2" />
+    </mask>
+    <g id="shape-tab-audio">
+      <rect x="2" y="5" width="6" height="6" rx="2" ry="2" />
+      <polygon points="4,6 9,2 9,14 4,10" />
+      <path d="M 10,7 a 1 1 0 0,1 0,2 z" />
+      <path d="M 10,5 a 3 3 0 0,1 0,6 l 0,-1 a 2 2 0 0,0 0,-4 z"  clip-path="url(#clip-wave)" />
+      <path d="M 10,3 a 5 5 0 0,1 0,10 l 0,-1 a 4 4 0 0,0 0,-8 z" clip-path="url(#clip-wave)" />
+    </g>
+    <g id="shape-tab-audio-muted">
+      <g mask="url(#disabled-cutout)">
+        <rect x="3" y="5" width="6" height="6" rx="2" ry="2" />
+        <polygon points="5,6 10,2 10,14 5,10" />
+      </g>
+      <line x1="2" y1="13" x2="14" y2="3" stroke-width="1.5" />
+    </g>
+  </defs>
+  <use id="tab-audio" class="icon" xlink:href="#shape-tab-audio"/>
+  <use id="tab-audio-hover" class="icon hover" xlink:href="#shape-tab-audio"/>
+  <use id="tab-audio-pressed" class="icon pressed" xlink:href="#shape-tab-audio"/>
+  <use id="tab-audio-muted" class="icon muted" xlink:href="#shape-tab-audio-muted" />
+  <use id="tab-audio-muted-hover" class="icon muted hover" xlink:href="#shape-tab-audio-muted" />
+  <use id="tab-audio-muted-pressed" class="icon muted pressed" xlink:href="#shape-tab-audio-muted" />
+  <use id="tab-audio-dark" class="icon dark" xlink:href="#shape-tab-audio"/>
+  <use id="tab-audio-dark-hover" class="icon hover dark" xlink:href="#shape-tab-audio"/>
+  <use id="tab-audio-dark-pressed" class="icon pressed dark" xlink:href="#shape-tab-audio"/>
+  <use id="tab-audio-muted-dark" class="icon muted dark" xlink:href="#shape-tab-audio-muted" />
+  <use id="tab-audio-muted-dark-hover" class="icon muted hover dark" xlink:href="#shape-tab-audio-muted" />
+  <use id="tab-audio-muted-dark-pressed" class="icon muted pressed dark" xlink:href="#shape-tab-audio-muted" />
+</svg>
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -67,16 +67,17 @@
 }
 
 .tab-content[pinned] {
   -moz-padding-end: 3px;
 }
 
 .tab-throbber,
 .tab-icon-image,
+.tab-icon-sound,
 .tab-close-button {
   margin-top: 1px;
 }
 
 .tab-throbber,
 .tab-icon-image {
   height: 16px;
   width: 16px;
@@ -85,26 +86,78 @@
 
 .tab-icon-image {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
 }
 
 .tab-icon-overlay {
   width: 16px;
   height: 16px;
-  margin-top: 10px;
+  margin-top: -12px;
   -moz-margin-start: -16px;
   display: none;
 }
 
 .tab-icon-overlay[crashed] {
   display: -moz-box;
   list-style-image: url("chrome://browser/skin/tabbrowser/crashed.svg");
 }
 
+.tab-icon-overlay[soundplaying][pinned] {
+  display: -moz-box;
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio");
+  border-radius: 8px;
+}
+
+.tab-icon-overlay[soundplaying][pinned]:hover {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-hover");
+  background-color: white;
+}
+
+.tab-icon-overlay[soundplaying][pinned]:hover:active {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-pressed");
+  background-color: white;
+}
+
+.tab-icon-overlay[muted][pinned] {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted");
+}
+
+.tab-icon-overlay[muted][pinned]:hover {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted-hover");
+}
+
+.tab-icon-overlay[muted][pinned]:hover:active {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted-pressed");
+}
+
+#TabsToolbar[brighttext] .tab-icon-overlay[soundplaying][pinned] {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-dark");
+}
+
+#TabsToolbar[brighttext] .tab-icon-overlay[soundplaying][pinned]:hover {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-hover");
+}
+
+#TabsToolbar[brighttext] .tab-icon-overlay[soundplaying][pinned]:hover:active {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-pressed");
+}
+
+#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned] {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted");
+}
+
+#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned]:hover {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted-hover");
+}
+
+#TabsToolbar[brighttext] .tab-icon-overlay[muted][pinned]:hover:active {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted-pressed");
+}
+
 .tab-throbber[busy] {
   list-style-image: url("chrome://browser/skin/tabbrowser/connecting.png");
 }
 
 .tab-throbber[progress] {
   list-style-image: url("chrome://browser/skin/tabbrowser/loading.png");
 }
 
@@ -114,16 +167,76 @@
 }
 
 .tab-close-button {
   -moz-margin-start: 4px;
   -moz-margin-end: -2px;
   padding: 0;
 }
 
+.tab-icon-sound {
+  -moz-margin-start: 4px;
+  width: 16px;
+  height: 16px;
+  padding: 0;
+}
+
+.tab-icon-sound:not([soundplaying]),
+.tab-icon-sound[pinned] {
+  display: none;
+}
+
+.tab-icon-sound[soundplaying] {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio");
+}
+
+.tab-icon-sound[soundplaying]:hover {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-hover");
+}
+
+.tab-icon-sound[soundplaying]:hover:active {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-pressed");
+}
+
+.tab-icon-sound[muted] {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted");
+}
+
+.tab-icon-sound[muted]:hover {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-hover");
+}
+
+.tab-icon-sound[muted]:hover:active {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-pressed");
+}
+
+#TabsToolbar[brighttext] .tab-icon-sound[soundplaying] {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-dark");
+}
+
+#TabsToolbar[brighttext] .tab-icon-sound[soundplaying]:hover {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-dark-hover");
+}
+
+#TabsToolbar[brighttext] .tab-icon-sound[soundplaying]:hover:active {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-dark-pressed");
+}
+
+#TabsToolbar[brighttext] .tab-icon-sound[muted] {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-dark");
+}
+
+#TabsToolbar[brighttext] .tab-icon-sound[muted]:hover {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-dark-hover");
+}
+
+#TabsToolbar[brighttext] .tab-icon-sound[muted]:hover:active {
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted-dark-pressed");
+}
+
 .tab-background,
 .tabs-newtab-button {
   /* overlap the tab curves */
   -moz-margin-end: -@tabCurveHalfWidth@;
   -moz-margin-start: -@tabCurveHalfWidth@;
 }
 
 .tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox {
@@ -295,16 +408,18 @@
 
 /* Tab pointer-events */
 .tabbrowser-tab {
   pointer-events: none;
 }
 
 .tab-background-middle,
 .tabs-newtab-button,
+.tab-icon-overlay[soundplaying],
+.tab-icon-sound,
 .tab-close-button {
   pointer-events: auto;
 }
 
 /* Pinned tabs */
 
 /* Pinned tab separators need position: absolute when positioned (during overflow). */
 #tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned]::before {
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -338,16 +338,18 @@ browser.jar:
         skin/classic/browser/tabbrowser/tab-active-middle.png        (tabbrowser/tab-active-middle.png)
         skin/classic/browser/tabbrowser/tab-active-middle@2x.png     (tabbrowser/tab-active-middle@2x.png)
         skin/classic/browser/tabbrowser/tab-arrow-left.png           (tabbrowser/tab-arrow-left.png)
         skin/classic/browser/tabbrowser/tab-arrow-left@2x.png        (tabbrowser/tab-arrow-left@2x.png)
         skin/classic/browser/tabbrowser/tab-arrow-left-XPVista7.png  (tabbrowser/tab-arrow-left-XPVista7.png)
         skin/classic/browser/tabbrowser/tab-arrow-left-XPVista7@2x.png  (tabbrowser/tab-arrow-left-XPVista7@2x.png)
         skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png  (tabbrowser/tab-arrow-left-inverted.png)
         skin/classic/browser/tabbrowser/tab-arrow-left-inverted@2x.png  (tabbrowser/tab-arrow-left-inverted@2x.png)
+        skin/classic/browser/tabbrowser/tab-audio.svg                (../shared/tabbrowser/tab-audio.svg)
+        skin/classic/browser/tabbrowser/tab-audio-small.svg          (../shared/tabbrowser/tab-audio-small.svg)
         skin/classic/browser/tabbrowser/tab-background-start.png     (tabbrowser/tab-background-start.png)
         skin/classic/browser/tabbrowser/tab-background-start@2x.png  (tabbrowser/tab-background-start@2x.png)
         skin/classic/browser/tabbrowser/tab-background-middle.png    (tabbrowser/tab-background-middle.png)
         skin/classic/browser/tabbrowser/tab-background-middle@2x.png (tabbrowser/tab-background-middle@2x.png)
         skin/classic/browser/tabbrowser/tab-background-end.png       (tabbrowser/tab-background-end.png)
         skin/classic/browser/tabbrowser/tab-background-end@2x.png    (tabbrowser/tab-background-end@2x.png)
         skin/classic/browser/tabbrowser/tab-background-start-preWin10.png     (tabbrowser/tab-background-start-preWin10.png)
         skin/classic/browser/tabbrowser/tab-background-start-preWin10@2x.png  (tabbrowser/tab-background-start-preWin10@2x.png)
--- a/build/clang-plugin/clang-plugin.cpp
+++ b/build/clang-plugin/clang-plugin.cpp
@@ -91,16 +91,21 @@ private:
     virtual void run(const MatchFinder::MatchResult &Result);
   };
 
   class ExplicitOperatorBoolChecker : public MatchFinder::MatchCallback {
   public:
     virtual void run(const MatchFinder::MatchResult &Result);
   };
 
+  class NoDuplicateRefCntMemberChecker : public MatchFinder::MatchCallback {
+  public:
+    virtual void run(const MatchFinder::MatchResult &Result);
+  };
+
   class NeedsNoVTableTypeChecker : public MatchFinder::MatchCallback {
   public:
     virtual void run(const MatchFinder::MatchResult &Result);
   };
 
   class NonMemMovableChecker : public MatchFinder::MatchCallback {
   public:
     virtual void run(const MatchFinder::MatchResult &Result);
@@ -110,16 +115,17 @@ private:
   ScopeChecker globalClassChecker;
   NonHeapClassChecker nonheapClassChecker;
   ArithmeticArgChecker arithmeticArgChecker;
   TrivialCtorDtorChecker trivialCtorDtorChecker;
   NaNExprChecker nanExprChecker;
   NoAddRefReleaseOnReturnChecker noAddRefReleaseOnReturnChecker;
   RefCountedInsideLambdaChecker refCountedInsideLambdaChecker;
   ExplicitOperatorBoolChecker explicitOperatorBoolChecker;
+  NoDuplicateRefCntMemberChecker noDuplicateRefCntMemberChecker;
   NeedsNoVTableTypeChecker needsNoVTableTypeChecker;
   NonMemMovableChecker nonMemMovableChecker;
   MatchFinder astMatcher;
 };
 
 namespace {
 
 std::string getDeclarationNamespace(const Decl *decl) {
@@ -576,16 +582,58 @@ bool IsInSystemHeader(const ASTContext &
   auto &SourceManager = AC.getSourceManager();
   auto ExpansionLoc = SourceManager.getExpansionLoc(D.getLocStart());
   if (ExpansionLoc.isInvalid()) {
     return false;
   }
   return SourceManager.isInSystemHeader(ExpansionLoc);
 }
 
+const FieldDecl *getClassRefCntMember(const CXXRecordDecl *D) {
+  for (RecordDecl::field_iterator field = D->field_begin(), e = D->field_end();
+       field != e; ++field) {
+    if (field->getName() == "mRefCnt") {
+      return *field;
+    }
+  }
+  return 0;
+}
+
+const FieldDecl *getClassRefCntMember(QualType T) {
+  while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
+    T = arrTy->getElementType();
+  CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
+  return clazz ? getClassRefCntMember(clazz) : 0;
+}
+
+const FieldDecl *getBaseRefCntMember(QualType T);
+
+const FieldDecl *getBaseRefCntMember(const CXXRecordDecl *D) {
+  const FieldDecl *refCntMember = getClassRefCntMember(D);
+  if (refCntMember && isClassRefCounted(D)) {
+    return refCntMember;
+  }
+
+  for (CXXRecordDecl::base_class_const_iterator base = D->bases_begin(), e = D->bases_end();
+       base != e; ++base) {
+    refCntMember = getBaseRefCntMember(base->getType());
+    if (refCntMember) {
+      return refCntMember;
+    }
+  }
+  return 0;
+}
+
+const FieldDecl *getBaseRefCntMember(QualType T) {
+  while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
+    T = arrTy->getElementType();
+  CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
+  return clazz ? getBaseRefCntMember(clazz) : 0;
+}
+
 bool typeHasVTable(QualType T) {
   while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
     T = arrTy->getElementType();
   CXXRecordDecl* offender = T->getAsCXXRecordDecl();
   return offender && offender->hasDefinition() && offender->isDynamicClass();
 }
 
 }
@@ -737,16 +785,20 @@ AST_POLYMORPHIC_MATCHER_P(equalsBoundNod
   };
   Visitor visitor(Node, ID, haveMatchingResult);
   bindings.visitMatches(&visitor);
   return haveMatchingResult;
 }
 
 #endif
 
+AST_MATCHER(CXXRecordDecl, hasRefCntMember) {
+  return isClassRefCounted(&Node) && getClassRefCntMember(&Node);
+}
+
 AST_MATCHER(QualType, hasVTable) {
   return typeHasVTable(Node);
 }
 
 AST_MATCHER(CXXRecordDecl, hasNeedsNoVTableTypeAttr) {
   return MozChecker::hasCustomAnnotation(&Node, "moz_needs_no_vtable_type");
 }
 
@@ -979,16 +1031,20 @@ DiagnosticsMatcher::DiagnosticsMatcher()
 
   // Older clang versions such as the ones used on the infra recognize these
   // conversions as 'operator _Bool', but newer clang versions recognize these
   // as 'operator bool'.
   astMatcher.addMatcher(methodDecl(anyOf(hasName("operator bool"),
                                          hasName("operator _Bool"))).bind("node"),
     &explicitOperatorBoolChecker);
 
+  astMatcher.addMatcher(recordDecl(allOf(decl().bind("decl"),
+                                         hasRefCntMember())),
+                        &noDuplicateRefCntMemberChecker);
+
   astMatcher.addMatcher(classTemplateSpecializationDecl(
              allOf(hasAnyTemplateArgument(refersToType(hasVTable())),
                    hasNeedsNoVTableTypeAttr())).bind("node"),
      &needsNoVTableTypeChecker);
 
   // Handle non-mem-movable template specializations
   astMatcher.addMatcher(classTemplateSpecializationDecl(
       allOf(needsMemMovable(),
@@ -1172,16 +1228,42 @@ void DiagnosticsMatcher::ExplicitOperato
       !MozChecker::hasCustomAnnotation(method, "moz_implicit") &&
       !IsInSystemHeader(method->getASTContext(), *method) &&
       isInterestingDeclForImplicitConversion(method)) {
     Diag.Report(method->getLocStart(), errorID) << clazz;
     Diag.Report(method->getLocStart(), noteID) << "'operator bool'";
   }
 }
 
+void DiagnosticsMatcher::NoDuplicateRefCntMemberChecker::run(
+    const MatchFinder::MatchResult &Result) {
+  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
+  unsigned warningID = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Error, "Refcounted record %0 has multiple mRefCnt members");
+  unsigned note1ID = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Note, "Superclass %0 also has an mRefCnt member");
+  unsigned note2ID = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Note, "Consider using the _INHERITED macros for AddRef and Release here");
+
+  const CXXRecordDecl *decl = Result.Nodes.getNodeAs<CXXRecordDecl>("decl");
+  const FieldDecl *refCntMember = getClassRefCntMember(decl);
+  assert(refCntMember && "The matcher checked to make sure we have a refCntMember");
+
+  // Check every superclass for whether it has a base with a refcnt member, and warn for those which do
+  for (CXXRecordDecl::base_class_const_iterator base = decl->bases_begin(), e = decl->bases_end();
+       base != e; ++base) {
+    const FieldDecl *baseRefCntMember = getBaseRefCntMember(base->getType());
+    if (baseRefCntMember) {
+      Diag.Report(decl->getLocStart(), warningID) << decl;
+      Diag.Report(baseRefCntMember->getLocStart(), note1ID) << baseRefCntMember->getParent();
+      Diag.Report(refCntMember->getLocStart(), note2ID);
+    }
+  }
+}
+
 void DiagnosticsMatcher::NeedsNoVTableTypeChecker::run(
     const MatchFinder::MatchResult &Result) {
   DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
   unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
       DiagnosticIDs::Error, "%0 cannot be instantiated because %1 has a VTable");
   unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
       DiagnosticIDs::Note, "bad instantiation of %0 requested here");
 
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/tests/TestNoDuplicateRefCntMember.cpp
@@ -0,0 +1,38 @@
+class C1 {};
+
+class RC1 {
+public:
+  virtual void AddRef();
+  virtual void Release();
+
+private:
+  int mRefCnt; // expected-note 2 {{Superclass 'RC1' also has an mRefCnt member}}
+};
+
+class RC2 : public RC1 { // expected-error {{Refcounted record 'RC2' has multiple mRefCnt members}}
+public:
+  virtual void AddRef();
+  virtual void Release();
+
+private:
+  int mRefCnt; // expected-note {{Consider using the _INHERITED macros for AddRef and Release here}}
+};
+
+class C2 : public RC1 {};
+
+class RC3 : public RC1 {};
+
+class RC4 : public RC3, public C2 {};
+
+class RC5 : public RC1 {};
+
+class RC6 : public C1, public RC5 { // expected-error {{Refcounted record 'RC6' has multiple mRefCnt members}}
+public:
+  virtual void AddRef();
+  virtual void Release();
+
+private:
+  int mRefCnt; // expected-note {{Consider using the _INHERITED macros for AddRef and Release here}}
+};
+
+class Predecl;
--- a/build/clang-plugin/tests/moz.build
+++ b/build/clang-plugin/tests/moz.build
@@ -12,16 +12,17 @@ SOURCES += [
     'TestMultipleAnnotations.cpp',
     'TestMustOverride.cpp',
     'TestMustUse.cpp',
     'TestNANTestingExpr.cpp',
     'TestNANTestingExprC.c',
     'TestNeedsNoVTableType.cpp',
     'TestNoAddRefReleaseOnReturn.cpp',
     'TestNoArithmeticExprInArgument.cpp',
+    'TestNoDuplicateRefCntMember.cpp',
     'TestNonHeapClass.cpp',
     'TestNonMemMovable.cpp',
     'TestNoRefcountedInsideLambdas.cpp',
     'TestStackClass.cpp',
     'TestTrivialCtorDtor.cpp',
 ]
 
 DISABLE_STL_WRAPPING = True
--- a/build/unix/mozconfig.gtk
+++ b/build/unix/mozconfig.gtk
@@ -27,16 +27,24 @@ if [ -d "$topsrcdir/gtk3" ]; then
   $topsrcdir/gtk3/usr/local/bin/pango-querymodules > $topsrcdir/gtk3/usr/local/etc/pango/pango.modules
 
   # same with gdb-pixbuf and loaders.cache
   LD_LIBRARY_PATH=$topsrcdir/gtk3/usr/local/lib \
   GDK_PIXBUF_MODULE_FILE=$topsrcdir/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache \
   GDK_PIXBUF_MODULEDIR=$topsrcdir/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders \
   $topsrcdir/gtk3/usr/local/bin/gdk-pixbuf-query-loaders > $topsrcdir/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache
 
+  # The fontconfig version in the tooltool package has known uses of
+  # uninitialized memory when creating its cache, and while most users
+  # will already have an existing cache, running Firefox on automation
+  # will create it. Combined with valgrind, this generates irrelevant
+  # errors.
+  # So create the fontconfig cache beforehand.
+  $TOOLTOOL_DIR/gtk3/usr/local/bin/fc-cache
+
   # mock build environment doesn't have fonts in /usr/share/fonts, but
   # has some in /usr/share/X11/fonts. Add this directory to the
   # fontconfig configuration without changing the gtk3 tooltool package.
   cat << EOF > $topsrcdir/gtk3/usr/local/etc/fonts/local.conf
 <?xml version="1.0"?>
 <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
 <fontconfig>
   <dir>/usr/share/X11/fonts</dir>
--- a/build/valgrind/mach_commands.py
+++ b/build/valgrind/mach_commands.py
@@ -106,16 +106,23 @@ class MachCommands(MachCommandBase):
                 '--smc-check=all-non-file',
                 '--vex-iropt-register-updates=allregs-at-mem-access',
                 '--gen-suppressions=all',
                 '--num-callers=36',
                 '--leak-check=full',
                 '--show-possibly-lost=no',
                 '--track-origins=yes',
                 '--trace-children=yes',
+                # The gstreamer plugin scanner can run as part of executing
+                # firefox, but is an external program. In some weird cases,
+                # valgrind finds errors while executing __libc_freeres when
+                # it runs, but those are not relevant, as it's related to
+                # executing third party code. So don't trace
+                # gst-plugin-scanner.
+                '--trace-children-skip=*/gst-plugin-scanner',
                 '-v',  # Enable verbosity to get the list of used suppressions
             ]
 
             for s in suppressions:
                 valgrind_args.append('--suppressions=' + s)
 
             supps_dir = os.path.join(build_dir, 'valgrind')
             supps_file1 = os.path.join(supps_dir, 'cross-architecture.sup')
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -46,16 +46,17 @@
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/dom/ipc/BlobParent.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/TextDecoder.h"
 #include "mozilla/dom/TouchEvent.h"
 #include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
@@ -7968,8 +7969,27 @@ nsContentUtils::SetFetchReferrerURIWithP
 
   if (!referrerURI) {
     referrerURI = principalURI;
   }
 
   net::ReferrerPolicy referrerPolicy = aDoc->GetReferrerPolicy();
   return aChannel->SetReferrerWithPolicy(referrerURI, referrerPolicy);
 }
+
+// static
+bool
+nsContentUtils::PushEnabled(JSContext* aCx, JSObject* aObj)
+{
+  if (NS_IsMainThread()) {
+    return Preferences::GetBool("dom.push.enabled", false);
+  }
+
+  using namespace workers;
+
+  // Otherwise, check the pref via the WorkerPrivate
+  WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+  if (!workerPrivate) {
+    return false;
+  }
+
+  return workerPrivate->PushEnabled();
+}
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2434,16 +2434,18 @@ public:
    * aDoc may be null.
    *
    * https://w3c.github.io/webappsec/specs/referrer-policy/#determine-requests-referrer
    */
   static nsresult SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
                                                 nsIDocument* aDoc,
                                                 nsIHttpChannel* aChannel);
 
+  static bool PushEnabled(JSContext* aCx, JSObject* aObj);
+
 private:
   static bool InitializeEventTable();
 
   static nsresult EnsureStringBundle(PropertiesFile aFile);
 
   static bool CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
                                 nsIPrincipal* aPrincipal);
 
--- a/dom/base/nsDOMMutationObserver.cpp
+++ b/dom/base/nsDOMMutationObserver.cpp
@@ -683,17 +683,21 @@ nsDOMMutationObserver::TakeRecords(
 {
   aRetVal.Clear();
   aRetVal.SetCapacity(mPendingMutationCount);
   nsRefPtr<nsDOMMutationRecord> current;
   current.swap(mFirstPendingMutation);
   for (uint32_t i = 0; i < mPendingMutationCount; ++i) {
     nsRefPtr<nsDOMMutationRecord> next;
     current->mNext.swap(next);
-    *aRetVal.AppendElement() = current.forget();
+    if (!mMergeAttributeRecords ||
+        !MergeableAttributeRecord(aRetVal.SafeLastElement(nullptr),
+                                  current)) {
+      *aRetVal.AppendElement() = current.forget();
+    }
     current.swap(next);
   }
   ClearPendingRecords();
 }
 
 void
 nsDOMMutationObserver::GetObservingInfo(
                          nsTArray<Nullable<MutationObservingInfo>>& aResult,
@@ -740,16 +744,31 @@ nsDOMMutationObserver::Constructor(const
   }
   MOZ_ASSERT(window->IsInnerWindow());
   bool isChrome = nsContentUtils::IsChromeDoc(window->GetExtantDoc());
   nsRefPtr<nsDOMMutationObserver> observer =
     new nsDOMMutationObserver(window.forget(), aCb, isChrome);
   return observer.forget();
 }
 
+
+bool
+nsDOMMutationObserver::MergeableAttributeRecord(nsDOMMutationRecord* aOldRecord,
+                                                nsDOMMutationRecord* aRecord)
+{
+  MOZ_ASSERT(mMergeAttributeRecords);
+  return
+    aOldRecord &&
+    aOldRecord->mType == nsGkAtoms::attributes &&
+    aOldRecord->mType == aRecord->mType &&
+    aOldRecord->mTarget == aRecord->mTarget &&
+    aOldRecord->mAttrName == aRecord->mAttrName &&
+    aOldRecord->mAttrNamespace.Equals(aRecord->mAttrNamespace);
+}
+
 void
 nsDOMMutationObserver::HandleMutation()
 {
   NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "Whaat!");
   NS_ASSERTION(mCurrentMutations.IsEmpty(),
                "Still generating MutationRecords?");
 
   mWaitingForRun = false;
@@ -771,17 +790,22 @@ nsDOMMutationObserver::HandleMutation()
   if (mutations.SetCapacity(mPendingMutationCount, mozilla::fallible)) {
     // We can't use TakeRecords easily here, because it deals with a
     // different type of array, and we want to optimize out any extra copying.
     nsRefPtr<nsDOMMutationRecord> current;
     current.swap(mFirstPendingMutation);
     for (uint32_t i = 0; i < mPendingMutationCount; ++i) {
       nsRefPtr<nsDOMMutationRecord> next;
       current->mNext.swap(next);
-      *mutations.AppendElement(mozilla::fallible) = current;
+      if (!mMergeAttributeRecords ||
+          !MergeableAttributeRecord(mutations.Length() ?
+                                      mutations.LastElement().get() : nullptr,
+                                    current)) {
+        *mutations.AppendElement(mozilla::fallible) = current;
+      }
       current.swap(next);
     }
   }
   ClearPendingRecords();
 
   mozilla::ErrorResult rv;
   mCallback->Call(this, mutations, *this, rv);
 }
--- a/dom/base/nsDOMMutationObserver.h
+++ b/dom/base/nsDOMMutationObserver.h
@@ -451,17 +451,18 @@ private:
 class nsDOMMutationObserver final : public nsISupports,
                                     public nsWrapperCache
 {
 public:
   nsDOMMutationObserver(already_AddRefed<nsPIDOMWindow>&& aOwner,
                         mozilla::dom::MutationCallback& aCb,
                         bool aChrome)
   : mOwner(aOwner), mLastPendingMutation(nullptr), mPendingMutationCount(0),
-    mCallback(&aCb), mWaitingForRun(false), mIsChrome(aChrome), mId(++sCount)
+    mCallback(&aCb), mWaitingForRun(false), mIsChrome(aChrome),
+    mMergeAttributeRecords(false), mId(++sCount)
   {
   }
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationObserver)
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_MUTATION_OBSERVER_IID)
 
   static already_AddRefed<nsDOMMutationObserver>
   Constructor(const mozilla::dom::GlobalObject& aGlobal,
@@ -493,16 +494,32 @@ public:
 
   void HandleMutation();
 
   void GetObservingInfo(nsTArray<Nullable<MutationObservingInfo>>& aResult,
                         mozilla::ErrorResult& aRv);
 
   mozilla::dom::MutationCallback* MutationCallback() { return mCallback; }
 
+  bool MergeAttributeRecords()
+  {
+    return mMergeAttributeRecords;
+  }
+
+  void SetMergeAttributeRecords(bool aVal)
+  {
+    mMergeAttributeRecords = aVal;
+  }
+
+  // If both records are for 'attributes' type and for the same target and
+  // attribute name and namespace are the same, we can skip the newer record.
+  // aOldRecord->mPrevValue holds the original value, if observed.
+  bool MergeableAttributeRecord(nsDOMMutationRecord* aOldRecord,
+                                nsDOMMutationRecord* aRecord);
+
   void AppendMutationRecord(already_AddRefed<nsDOMMutationRecord> aRecord)
   {
     nsRefPtr<nsDOMMutationRecord> record = aRecord;
     MOZ_ASSERT(record);
     if (!mLastPendingMutation) {
       MOZ_ASSERT(!mFirstPendingMutation);
       mFirstPendingMutation = record.forget();
       mLastPendingMutation = mFirstPendingMutation;
@@ -580,16 +597,17 @@ protected:
   nsRefPtr<nsDOMMutationRecord>                      mFirstPendingMutation;
   nsDOMMutationRecord*                               mLastPendingMutation;
   uint32_t                                           mPendingMutationCount;
 
   nsRefPtr<mozilla::dom::MutationCallback>           mCallback;
 
   bool                                               mWaitingForRun;
   bool                                               mIsChrome;
+  bool                                               mMergeAttributeRecords;
 
   uint64_t                                           mId;
 
   static uint64_t                                    sCount;
   static nsAutoTArray<nsRefPtr<nsDOMMutationObserver>, 4>* sScheduledMutationObservers;
   static nsDOMMutationObserver*                      sCurrentObserver;
 
   static uint32_t                                    sMutationLevel;
--- a/dom/base/test/test_mutationobservers.html
+++ b/dom/base/test/test_mutationobservers.html
@@ -738,24 +738,185 @@ function testStyleRemoveProperty2() {
   m = new M(function(records, observer) {
     is(records.length, 1, "number of records");
     is(records[0].type, "attributes", "record.type");
     is(records[0].attributeName, "data-test", "record.attributeName");
     is(div.getAttribute("style"), null, "style attribute after unsuccessful removeProperty");
     observer.disconnect();
     m = null;
     div.removeAttribute("data-test");
-    then();
+    then(testAttributeRecordMerging1);
   });
   m.observe(div, { attributes: true });
   is(div.getAttribute("style"), null, "style attribute before unsuccessful removeProperty");
   div.style.removeProperty("color");  // shouldn't generate any mutation records
   div.setAttribute("data-test", "a");
 }
 
+function testAttributeRecordMerging1() {
+  ok(true, "testAttributeRecordMerging1");
+  var m = new M(function(records, observer) {
+    is(records.length, 2);
+    is(records[0].type, "attributes");
+    is(records[0].target, div);
+    is(records[0].attributeName, "foo");
+    is(records[0].attributeNamespace, null);
+    is(records[0].oldValue, null);
+
+    is(records[1].type, "attributes");
+    is(records[1].target, div.firstChild);
+    is(records[1].attributeName, "foo");
+    is(records[1].attributeNamespace, null);
+    is(records[1].oldValue, null);
+    observer.disconnect();
+    div.innerHTML = "";
+    div.removeAttribute("foo");
+    then(testAttributeRecordMerging2);
+  });
+  m.observe(div, {
+      attributes: true,
+      subtree: true
+    });
+  SpecialPowers.wrap(m).mergeAttributeRecords = true;
+
+  div.setAttribute("foo", "bar_1");
+  div.setAttribute("foo", "bar_2");
+  div.innerHTML = "<div></div>";
+  div.firstChild.setAttribute("foo", "bar_1");
+  div.firstChild.setAttribute("foo", "bar_2");
+}
+
+function testAttributeRecordMerging2() {
+  ok(true, "testAttributeRecordMerging2");
+  var m = new M(function(records, observer) {
+    is(records.length, 2);
+    is(records[0].type, "attributes");
+    is(records[0].target, div);
+    is(records[0].attributeName, "foo");
+    is(records[0].attributeNamespace, null);
+    is(records[0].oldValue, "initial");
+
+    is(records[1].type, "attributes");
+    is(records[1].target, div.firstChild);
+    is(records[1].attributeName, "foo");
+    is(records[1].attributeNamespace, null);
+    is(records[1].oldValue, "initial");
+    observer.disconnect();
+    div.innerHTML = "";
+    div.removeAttribute("foo");
+    then(testAttributeRecordMerging3);
+  });
+
+  div.setAttribute("foo", "initial");
+  div.innerHTML = "<div></div>";
+  div.firstChild.setAttribute("foo", "initial");
+  m.observe(div, {
+      attributes: true,
+      subtree: true,
+      attributeOldValue: true
+    });
+  SpecialPowers.wrap(m).mergeAttributeRecords = true;
+
+  div.setAttribute("foo", "bar_1");
+  div.setAttribute("foo", "bar_2");
+  div.firstChild.setAttribute("foo", "bar_1");
+  div.firstChild.setAttribute("foo", "bar_2");
+}
+
+function testAttributeRecordMerging3() {
+  ok(true, "testAttributeRecordMerging3");
+  var m = new M(function(records, observer) {
+    is(records.length, 4);
+    is(records[0].type, "attributes");
+    is(records[0].target, div);
+    is(records[0].attributeName, "foo");
+    is(records[0].attributeNamespace, null);
+    is(records[0].oldValue, "initial");
+
+    is(records[1].type, "attributes");
+    is(records[1].target, div.firstChild);
+    is(records[1].attributeName, "foo");
+    is(records[1].attributeNamespace, null);
+    is(records[1].oldValue, "initial");
+    
+    is(records[2].type, "attributes");
+    is(records[2].target, div);
+    is(records[2].attributeName, "foo");
+    is(records[2].attributeNamespace, null);
+    is(records[2].oldValue, "bar_1");
+
+    is(records[3].type, "attributes");
+    is(records[3].target, div.firstChild);
+    is(records[3].attributeName, "foo");
+    is(records[3].attributeNamespace, null);
+    is(records[3].oldValue, "bar_1");
+
+    observer.disconnect();
+    div.innerHTML = "";
+    div.removeAttribute("foo");
+    then(testAttributeRecordMerging4);
+  });
+
+  div.setAttribute("foo", "initial");
+  div.innerHTML = "<div></div>";
+  div.firstChild.setAttribute("foo", "initial");
+  m.observe(div, {
+      attributes: true,
+      subtree: true,
+      attributeOldValue: true
+    });
+  SpecialPowers.wrap(m).mergeAttributeRecords = true;
+
+  // No merging should happen.
+  div.setAttribute("foo", "bar_1");
+  div.firstChild.setAttribute("foo", "bar_1");
+  div.setAttribute("foo", "bar_2");
+  div.firstChild.setAttribute("foo", "bar_2");
+}
+
+function testAttributeRecordMerging4() {
+  ok(true, "testAttributeRecordMerging4");
+  var m = new M(function(records, observer) {
+  });
+
+  div.setAttribute("foo", "initial");
+  div.innerHTML = "<div></div>";
+  div.firstChild.setAttribute("foo", "initial");
+  m.observe(div, {
+      attributes: true,
+      subtree: true,
+      attributeOldValue: true
+    });
+  SpecialPowers.wrap(m).mergeAttributeRecords = true;
+
+  div.setAttribute("foo", "bar_1");
+  div.setAttribute("foo", "bar_2");
+  div.firstChild.setAttribute("foo", "bar_1");
+  div.firstChild.setAttribute("foo", "bar_2");
+
+  var records = m.takeRecords();
+
+  is(records.length, 2);
+  is(records[0].type, "attributes");
+  is(records[0].target, div);
+  is(records[0].attributeName, "foo");
+  is(records[0].attributeNamespace, null);
+  is(records[0].oldValue, "initial");
+
+  is(records[1].type, "attributes");
+  is(records[1].target, div.firstChild);
+  is(records[1].attributeName, "foo");
+  is(records[1].attributeNamespace, null);
+  is(records[1].oldValue, "initial");
+  m.disconnect();
+  div.innerHTML = "";
+  div.removeAttribute("foo");
+  then();
+}
+
 SimpleTest.waitForExplicitFinish();
 
 </script>
 </pre>
 <div id="log">
 </div>
 </body>
 </html>
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -854,16 +854,19 @@ private:
 };
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(CanvasRenderingContext2D)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D)
+  // Make sure we remove ourselves from the list of demotable contexts (raw pointers),
+  // since we're logically destructed at this point.
+  CanvasRenderingContext2D::RemoveDemotableContext(tmp);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement)
   for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) {
     ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::STROKE]);
     ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]);
     ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::STROKE]);
     ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]);
   }
   for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) {
@@ -1233,16 +1236,20 @@ void CanvasRenderingContext2D::Demote()
   if (SwitchRenderingMode(RenderingMode::SoftwareBackendMode)) {
     RemoveDemotableContext(this);
   }
 }
 
 std::vector<CanvasRenderingContext2D*>&
 CanvasRenderingContext2D::DemotableContexts()
 {
+  // This is a list of raw pointers to cycle-collected objects. We need to ensure
+  // that we remove elements from it during UNLINK (which can happen considerably before
+  // the actual destructor) since the object is logically destroyed at that point
+  // and will be in an inconsistant state.
   static std::vector<CanvasRenderingContext2D*> contexts;
   return contexts;
 }
 
 void
 CanvasRenderingContext2D::DemoteOldestContextIfNecessary()
 {
   const size_t kMaxContexts = 64;
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -680,16 +680,19 @@ protected:
   nsString& GetFont()
   {
     /* will initilize the value if not set, else does nothing */
     GetCurrentFontStyle();
 
     return CurrentState().font;
   }
 
+  // This function maintains a list of raw pointers to cycle-collected
+  // objects. We need to ensure that no entries persist beyond unlink,
+  // since the objects are logically destructed at that point.
   static std::vector<CanvasRenderingContext2D*>& DemotableContexts();
   static void DemoteOldestContextIfNecessary();
 
   static void AddDemotableContext(CanvasRenderingContext2D* context);
   static void RemoveDemotableContext(CanvasRenderingContext2D* context);
 
   RenderingMode mRenderingMode;
 
--- a/dom/canvas/WebGL2Context.cpp
+++ b/dom/canvas/WebGL2Context.cpp
@@ -53,19 +53,17 @@ WebGL2Context::WrapObject(JSContext* cx,
 
 // These WebGL 1 extensions are natively supported by WebGL 2.
 static const WebGLExtensionID kNativelySupportedExtensions[] = {
     WebGLExtensionID::ANGLE_instanced_arrays,
     WebGLExtensionID::EXT_blend_minmax,
     WebGLExtensionID::EXT_sRGB,
     WebGLExtensionID::OES_element_index_uint,
     WebGLExtensionID::OES_standard_derivatives,
-    WebGLExtensionID::OES_texture_float,
     WebGLExtensionID::OES_texture_float_linear,
-    WebGLExtensionID::OES_texture_half_float,
     WebGLExtensionID::OES_texture_half_float_linear,
     WebGLExtensionID::OES_vertex_array_object,
     WebGLExtensionID::WEBGL_depth_texture,
     WebGLExtensionID::WEBGL_draw_buffers
 };
 
 static const gl::GLFeature kRequiredFeatures[] = {
     gl::GLFeature::blend_minmax,
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -534,102 +534,100 @@ HasAcceleratedLayers(const nsCOMPtr<nsIG
     gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &status);
     if (status)
         return true;
 
     return false;
 }
 
 static already_AddRefed<GLContext>
-CreateHeadlessNativeGL(bool forceEnabled, const nsCOMPtr<nsIGfxInfo>& gfxInfo,
-                       bool requireCompatProfile, WebGLContext* webgl)
+CreateHeadlessNativeGL(CreateContextFlags flags, const nsCOMPtr<nsIGfxInfo>& gfxInfo,
+                       WebGLContext* webgl)
 {
-    if (!forceEnabled &&
+    if (!(flags & CreateContextFlags::FORCE_ENABLE_HARDWARE) &&
         IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_OPENGL))
     {
         webgl->GenerateWarning("Refused to create native OpenGL context"
                                " because of blacklisting.");
         return nullptr;
     }
 
-    nsRefPtr<GLContext> gl = gl::GLContextProvider::CreateHeadless(requireCompatProfile);
+    nsRefPtr<GLContext> gl = gl::GLContextProvider::CreateHeadless(flags);
     if (!gl) {
         webgl->GenerateWarning("Error during native OpenGL init.");
         return nullptr;
     }
     MOZ_ASSERT(!gl->IsANGLE());
 
     return gl.forget();
 }
 
 // Note that we have a separate call for ANGLE and EGL, even though
 // right now, we get ANGLE implicitly by using EGL on Windows.
 // Eventually, we want to be able to pick ANGLE-EGL or native EGL.
 static already_AddRefed<GLContext>
-CreateHeadlessANGLE(bool forceEnabled, const nsCOMPtr<nsIGfxInfo>& gfxInfo,
-                    bool requireCompatProfile, WebGLContext* webgl)
+CreateHeadlessANGLE(CreateContextFlags flags, const nsCOMPtr<nsIGfxInfo>& gfxInfo,
+                    WebGLContext* webgl)
 {
     nsRefPtr<GLContext> gl;
 
 #ifdef XP_WIN
-    gl = gl::GLContextProviderEGL::CreateHeadless(requireCompatProfile,
-                                                  forceEnabled);
+    gl = gl::GLContextProviderEGL::CreateHeadless(flags);
     if (!gl) {
         webgl->GenerateWarning("Error during ANGLE OpenGL init.");
         return nullptr;
     }
     MOZ_ASSERT(gl->IsANGLE());
 #endif
 
     return gl.forget();
 }
 
 static already_AddRefed<GLContext>
-CreateHeadlessEGL(bool forceEnabled, bool requireCompatProfile,
-                  WebGLContext* webgl)
+CreateHeadlessEGL(CreateContextFlags flags, WebGLContext* webgl)
 {
     nsRefPtr<GLContext> gl;
 
 #ifdef ANDROID
-    gl = gl::GLContextProviderEGL::CreateHeadless(requireCompatProfile);
+    gl = gl::GLContextProviderEGL::CreateHeadless(flags);
     if (!gl) {
         webgl->GenerateWarning("Error during EGL OpenGL init.");
         return nullptr;
     }
     MOZ_ASSERT(!gl->IsANGLE());
 #endif
 
     return gl.forget();
 }
 
 static already_AddRefed<GLContext>
-CreateHeadlessGL(bool forceEnabled, const nsCOMPtr<nsIGfxInfo>& gfxInfo,
+CreateHeadlessGL(CreateContextFlags flags, const nsCOMPtr<nsIGfxInfo>& gfxInfo,
                  WebGLContext* webgl)
 {
     bool preferEGL = PR_GetEnv("MOZ_WEBGL_PREFER_EGL");
     bool disableANGLE = Preferences::GetBool("webgl.disable-angle", false);
 
     if (PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL"))
         disableANGLE = true;
 
-    bool requireCompatProfile = webgl->IsWebGL2() ? false : true;
+    if (!webgl->IsWebGL2()) {
+        flags |= CreateContextFlags::REQUIRE_COMPAT_PROFILE;
+    }
 
     nsRefPtr<GLContext> gl;
 
     if (preferEGL)
-        gl = CreateHeadlessEGL(forceEnabled, requireCompatProfile, webgl);
+        gl = CreateHeadlessEGL(flags, webgl);
 
     if (!gl && !disableANGLE) {
-        gl = CreateHeadlessANGLE(forceEnabled, gfxInfo, requireCompatProfile,
-                                 webgl);
+        gl = CreateHeadlessANGLE(flags, gfxInfo, webgl);
     }
 
     if (!gl) {
-        gl = CreateHeadlessNativeGL(forceEnabled, gfxInfo,
-                                    requireCompatProfile, webgl);
+        gl = CreateHeadlessNativeGL(flags, gfxInfo, webgl);
     }
 
     return gl.forget();
 }
 
 // Try to create a dummy offscreen with the given caps.
 static bool
 CreateOffscreenWithCaps(GLContext* gl, const SurfaceCaps& caps)
@@ -744,17 +742,20 @@ WebGLContext::CreateOffscreenGL(bool for
             // XXX we really want "AsSurfaceAllocator" here for generality
             layers::ShadowLayerForwarder* forwarder = layerManager->AsShadowForwarder();
             if (forwarder)
                 surfAllocator = static_cast<layers::ISurfaceAllocator*>(forwarder);
         }
     }
 #endif
 
-    gl = CreateHeadlessGL(forceEnabled, gfxInfo, this);
+    CreateContextFlags flags = forceEnabled ? CreateContextFlags::FORCE_ENABLE_HARDWARE :
+                                              CreateContextFlags::NONE;
+
+    gl = CreateHeadlessGL(flags, gfxInfo, this);
 
     do {
         if (!gl)
             break;
 
         if (!CreateOffscreen(gl, mOptions, gfxInfo, this, surfAllocator))
             break;
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -7,16 +7,17 @@
 
 #include "WebGLActiveInfo.h"
 #include "WebGLContextUtils.h"
 #include "WebGLBuffer.h"
 #include "WebGLVertexAttribData.h"
 #include "WebGLShader.h"
 #include "WebGLProgram.h"
 #include "WebGLUniformLocation.h"
+#include "WebGLFormats.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLShaderPrecisionFormat.h"
 #include "WebGLTexture.h"
 #include "WebGLExtensions.h"
 #include "WebGLVertexArray.h"
 
 #include "nsDebug.h"
@@ -2265,52 +2266,26 @@ WebGLContext::RenderbufferStorage_base(c
     }
 
     if (width > mGLMaxRenderbufferSize || height > mGLMaxRenderbufferSize) {
         ErrorInvalidValue("%s: Width or height exceeds maximum renderbuffer"
                           " size.", funcName);
         return;
     }
 
+    // Convert DEPTH_STENCIL to sized type for testing
+    GLenum sizedInternalFormat = internalFormat;
+    if (sizedInternalFormat == LOCAL_GL_DEPTH_STENCIL)
+        sizedInternalFormat = LOCAL_GL_DEPTH24_STENCIL8;
+
     bool isFormatValid = false;
-    switch (internalFormat) {
-    case LOCAL_GL_RGBA4:
-    case LOCAL_GL_RGB5_A1:
-    case LOCAL_GL_RGB565:
-    case LOCAL_GL_DEPTH_COMPONENT16:
-    case LOCAL_GL_STENCIL_INDEX8:
-    case LOCAL_GL_DEPTH_STENCIL:
-        isFormatValid = true;
-        break;
-
-    case LOCAL_GL_SRGB8_ALPHA8_EXT:
-        if (IsExtensionEnabled(WebGLExtensionID::EXT_sRGB))
-            isFormatValid = true;
-        break;
-
-    case LOCAL_GL_RGB16F:
-    case LOCAL_GL_RGBA16F:
-        if (IsExtensionEnabled(WebGLExtensionID::OES_texture_half_float) &&
-            IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float))
-        {
-            isFormatValid = true;
-        }
-        break;
-
-    case LOCAL_GL_RGB32F:
-    case LOCAL_GL_RGBA32F:
-        if (IsExtensionEnabled(WebGLExtensionID::OES_texture_float) &&
-            IsExtensionEnabled(WebGLExtensionID::WEBGL_color_buffer_float))
-        {
-            isFormatValid = true;
-        }
-        break;
-
-    default:
-        break;
+    const webgl::FormatInfo* info = webgl::GetInfoBySizedFormat(sizedInternalFormat);
+    if (info) {
+        const webgl::FormatUsageInfo* usage = mFormatUsage->GetUsage(info);
+        isFormatValid = usage && usage->asRenderbuffer;
     }
 
     if (!isFormatValid) {
         ErrorInvalidEnumInfo("`internalFormat`", funcName, internalFormat);
         return;
     }
 
     // certain OpenGL ES renderbuffer formats may not exist on desktop OpenGL
--- a/dom/canvas/WebGLExtensionColorBufferFloat.cpp
+++ b/dom/canvas/WebGLExtensionColorBufferFloat.cpp
@@ -2,23 +2,44 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLExtensions.h"
 
 #include "GLContext.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "WebGLContext.h"
+#include "WebGLFormats.h"
 
 namespace mozilla {
 
+using mozilla::webgl::EffectiveFormat;
+
 WebGLExtensionColorBufferFloat::WebGLExtensionColorBufferFloat(WebGLContext* webgl)
     : WebGLExtensionBase(webgl)
 {
     MOZ_ASSERT(IsSupported(webgl), "Don't construct extension if unsupported.");
+
+    webgl::FormatUsageAuthority* authority = webgl->mFormatUsage.get();
+
+    auto updateUsage = [authority](EffectiveFormat effectiveFormat) {
+        webgl::FormatUsageInfo* usage = authority->GetUsage(effectiveFormat);
+        MOZ_ASSERT(usage);
+        usage->asRenderbuffer = usage->isRenderable = true;
+    };
+
+    // Ensure require formats are initialized.
+    WebGLExtensionTextureFloat::InitWebGLFormats(authority);
+
+    // Update usage to allow asRenderbuffer and isRenderable
+    updateUsage(EffectiveFormat::RGBA32F);
+    updateUsage(EffectiveFormat::RGB32F);
+    updateUsage(EffectiveFormat::Luminance32FAlpha32F);
+    updateUsage(EffectiveFormat::Luminance32F);
+    updateUsage(EffectiveFormat::Alpha32F);
 }
 
 WebGLExtensionColorBufferFloat::~WebGLExtensionColorBufferFloat()
 {
 }
 
 bool
 WebGLExtensionColorBufferFloat::IsSupported(const WebGLContext* webgl)
--- a/dom/canvas/WebGLExtensionColorBufferHalfFloat.cpp
+++ b/dom/canvas/WebGLExtensionColorBufferHalfFloat.cpp
@@ -2,23 +2,44 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLExtensions.h"
 
 #include "GLContext.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "WebGLContext.h"
+#include "WebGLFormats.h"
 
 namespace mozilla {
 
+using mozilla::webgl::EffectiveFormat;
+
 WebGLExtensionColorBufferHalfFloat::WebGLExtensionColorBufferHalfFloat(WebGLContext* webgl)
     : WebGLExtensionBase(webgl)
 {
     MOZ_ASSERT(IsSupported(webgl), "Don't construct extension if unsupported.");
+
+    webgl::FormatUsageAuthority* authority = webgl->mFormatUsage.get();
+
+    auto updateUsage = [authority](EffectiveFormat effectiveFormat) {
+        webgl::FormatUsageInfo* usage = authority->GetUsage(effectiveFormat);
+        MOZ_ASSERT(usage);
+        usage->asRenderbuffer = usage->isRenderable = true;
+    };
+
+    // Ensure require formats are initialized.
+    WebGLExtensionTextureHalfFloat::InitWebGLFormats(authority);
+
+    // Update usage to allow asRenderbuffer and isRenderable
+    updateUsage(EffectiveFormat::RGBA16F);
+    updateUsage(EffectiveFormat::RGB16F);
+    updateUsage(EffectiveFormat::Luminance16FAlpha16F);
+    updateUsage(EffectiveFormat::Luminance16F);
+    updateUsage(EffectiveFormat::Alpha16F);
 }
 
 WebGLExtensionColorBufferHalfFloat::~WebGLExtensionColorBufferHalfFloat()
 {
 }
 
 bool
 WebGLExtensionColorBufferHalfFloat::IsSupported(const WebGLContext* webgl)
--- a/dom/canvas/WebGLExtensionSRGB.cpp
+++ b/dom/canvas/WebGLExtensionSRGB.cpp
@@ -3,31 +3,50 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLExtensions.h"
 
 #include "GLContext.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "WebGLContext.h"
+#include "WebGLFormats.h"
 
 namespace mozilla {
 
+using mozilla::webgl::EffectiveFormat;
+
+
 WebGLExtensionSRGB::WebGLExtensionSRGB(WebGLContext* webgl)
     : WebGLExtensionBase(webgl)
 {
     MOZ_ASSERT(IsSupported(webgl), "Don't construct extension if unsupported.");
 
     gl::GLContext* gl = webgl->GL();
     if (!gl->IsGLES()) {
         // Desktop OpenGL requires the following to be enabled in order to
         // support sRGB operations on framebuffers.
         gl->MakeCurrent();
         gl->fEnable(LOCAL_GL_FRAMEBUFFER_SRGB_EXT);
     }
+
+    webgl::FormatUsageAuthority* authority = webgl->mFormatUsage.get();
+
+    auto addFormatIfMissing = [authority](EffectiveFormat effectiveFormat,
+                                          GLenum unpackFormat, GLenum unpackType,
+                                          bool asRenderbuffer)
+        {
+            if (!authority->GetUsage(effectiveFormat)) {
+                authority->AddFormat(effectiveFormat, asRenderbuffer, asRenderbuffer, true, true);
+                authority->AddUnpackOption(unpackFormat, unpackType, effectiveFormat);
+            }
+        };
+
+    addFormatIfMissing(EffectiveFormat::SRGB8       , LOCAL_GL_SRGB      , LOCAL_GL_UNSIGNED_BYTE, false);
+    addFormatIfMissing(EffectiveFormat::SRGB8_ALPHA8, LOCAL_GL_SRGB_ALPHA, LOCAL_GL_UNSIGNED_BYTE, true);
 }
 
 WebGLExtensionSRGB::~WebGLExtensionSRGB()
 {
 }
 
 bool
 WebGLExtensionSRGB::IsSupported(const WebGLContext* webgl)
--- a/dom/canvas/WebGLExtensionTextureFloat.cpp
+++ b/dom/canvas/WebGLExtensionTextureFloat.cpp
@@ -1,22 +1,65 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLExtensions.h"
 
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "WebGLContext.h"
+#include "WebGLFormats.h"
 
 namespace mozilla {
 
+using mozilla::webgl::EffectiveFormat;
+
+void
+WebGLExtensionTextureFloat::InitWebGLFormats(webgl::FormatUsageAuthority* authority)
+{
+    MOZ_ASSERT(authority);
+
+    auto addFormatIfMissing = [authority](EffectiveFormat effectiveFormat)
+        {
+            if (!authority->GetUsage(effectiveFormat)) {
+                authority->AddFormat(effectiveFormat, false, false, false, false);
+            }
+        };
+
+    // Populate authority with any missing effective formats.
+    addFormatIfMissing(EffectiveFormat::RGBA32F);
+    addFormatIfMissing(EffectiveFormat::RGB32F);
+    addFormatIfMissing(EffectiveFormat::Luminance32FAlpha32F);
+    addFormatIfMissing(EffectiveFormat::Luminance32F);
+    addFormatIfMissing(EffectiveFormat::Alpha32F);
+}
+
 WebGLExtensionTextureFloat::WebGLExtensionTextureFloat(WebGLContext* webgl)
     : WebGLExtensionBase(webgl)
 {
+    webgl::FormatUsageAuthority* authority = webgl->mFormatUsage.get();
+
+    auto updateUsage = [authority](EffectiveFormat effectiveFormat,
+                                   GLenum unpackFormat, GLenum unpackType)
+        {
+            webgl::FormatUsageInfo* usage = authority->GetUsage(effectiveFormat);
+            MOZ_ASSERT(usage);
+            usage->asTexture = true;
+            authority->AddUnpackOption(unpackFormat, unpackType, effectiveFormat);
+        };
+
+    // Ensure require formats are initialized.
+    InitWebGLFormats(authority);
+
+    // Update usage to allow asTexture and add unpack
+    updateUsage(EffectiveFormat::RGBA32F             , LOCAL_GL_RGBA           , LOCAL_GL_FLOAT);
+    updateUsage(EffectiveFormat::RGB32F              , LOCAL_GL_RGB            , LOCAL_GL_FLOAT);
+    updateUsage(EffectiveFormat::Luminance32FAlpha32F, LOCAL_GL_LUMINANCE_ALPHA, LOCAL_GL_FLOAT);
+    updateUsage(EffectiveFormat::Luminance32F        , LOCAL_GL_LUMINANCE      , LOCAL_GL_FLOAT);
+    updateUsage(EffectiveFormat::Alpha32F            , LOCAL_GL_ALPHA          , LOCAL_GL_FLOAT);
 }
 
 WebGLExtensionTextureFloat::~WebGLExtensionTextureFloat()
 {
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionTextureFloat, OES_texture_float)
 
--- a/dom/canvas/WebGLExtensionTextureFloatLinear.cpp
+++ b/dom/canvas/WebGLExtensionTextureFloatLinear.cpp
@@ -1,22 +1,46 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLExtensions.h"
 
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "WebGLContext.h"
+#include "WebGLFormats.h"
 
 namespace mozilla {
 
+using mozilla::webgl::EffectiveFormat;
+
 WebGLExtensionTextureFloatLinear::WebGLExtensionTextureFloatLinear(WebGLContext* webgl)
     : WebGLExtensionBase(webgl)
 {
+    // This update requires that the authority already be populated by
+    // WebGLExtensionTextureFloat.  Enabling extensions to control
+    // features is a mess in WebGL
+
+    webgl::FormatUsageAuthority* authority = webgl->mFormatUsage.get();
+
+    auto updateUsage = [authority](EffectiveFormat effectiveFormat) {
+        webgl::FormatUsageInfo* usage = authority->GetUsage(effectiveFormat);
+        MOZ_ASSERT(usage);
+        usage->isFilterable = true;
+    };
+
+    // Ensure require formats are initialized.
+    WebGLExtensionTextureFloat::InitWebGLFormats(authority);
+
+    // Update usage to allow isFilterable
+    updateUsage(EffectiveFormat::RGBA32F);
+    updateUsage(EffectiveFormat::RGB32F);
+    updateUsage(EffectiveFormat::Luminance32FAlpha32F);
+    updateUsage(EffectiveFormat::Luminance32F);
+    updateUsage(EffectiveFormat::Alpha32F);
 }
 
 WebGLExtensionTextureFloatLinear::~WebGLExtensionTextureFloatLinear()
 {
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionTextureFloatLinear, OES_texture_float_linear)
 
--- a/dom/canvas/WebGLExtensionTextureHalfFloat.cpp
+++ b/dom/canvas/WebGLExtensionTextureHalfFloat.cpp
@@ -1,22 +1,64 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLExtensions.h"
 
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "WebGLContext.h"
+#include "WebGLFormats.h"
 
 namespace mozilla {
 
+using mozilla::webgl::EffectiveFormat;
+
+void
+WebGLExtensionTextureHalfFloat::InitWebGLFormats(webgl::FormatUsageAuthority* authority)
+{
+    MOZ_ASSERT(authority);
+
+    auto addFormatIfMissing = [authority](EffectiveFormat effectiveFormat)
+        {
+            if (!authority->GetUsage(effectiveFormat)) {
+                authority->AddFormat(effectiveFormat, false, false, false, false);
+            }
+        };
+
+    // Populate authority with any missing effective formats.
+    addFormatIfMissing(EffectiveFormat::RGBA16F);
+    addFormatIfMissing(EffectiveFormat::RGB16F);
+    addFormatIfMissing(EffectiveFormat::Luminance16FAlpha16F);
+    addFormatIfMissing(EffectiveFormat::Luminance16F);
+    addFormatIfMissing(EffectiveFormat::Alpha16F);
+}
+
 WebGLExtensionTextureHalfFloat::WebGLExtensionTextureHalfFloat(WebGLContext* webgl)
     : WebGLExtensionBase(webgl)
 {
+    webgl::FormatUsageAuthority* authority = webgl->mFormatUsage.get();
+
+    auto updateUsage = [authority](EffectiveFormat effectiveFormat,
+                                   GLenum unpackFormat, GLenum unpackType)
+        {
+            webgl::FormatUsageInfo* usage = authority->GetUsage(effectiveFormat);
+            MOZ_ASSERT(usage);
+            usage->asTexture = true;
+            authority->AddUnpackOption(unpackFormat, unpackType, effectiveFormat);
+        };
+
+    InitWebGLFormats(authority);
+
+    // Update usage to allow asTexture and add unpack
+    updateUsage(EffectiveFormat::RGBA16F             , LOCAL_GL_RGBA           , LOCAL_GL_HALF_FLOAT_OES);
+    updateUsage(EffectiveFormat::RGB16F              , LOCAL_GL_RGB            , LOCAL_GL_HALF_FLOAT_OES);
+    updateUsage(EffectiveFormat::Luminance16FAlpha16F, LOCAL_GL_LUMINANCE_ALPHA, LOCAL_GL_HALF_FLOAT_OES);
+    updateUsage(EffectiveFormat::Luminance16F        , LOCAL_GL_LUMINANCE      , LOCAL_GL_HALF_FLOAT_OES);
+    updateUsage(EffectiveFormat::Alpha16F            , LOCAL_GL_ALPHA          , LOCAL_GL_HALF_FLOAT_OES);
 }
 
 WebGLExtensionTextureHalfFloat::~WebGLExtensionTextureHalfFloat()
 {
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionTextureHalfFloat, OES_texture_half_float)
 
--- a/dom/canvas/WebGLExtensionTextureHalfFloatLinear.cpp
+++ b/dom/canvas/WebGLExtensionTextureHalfFloatLinear.cpp
@@ -1,22 +1,46 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLExtensions.h"
 
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "WebGLContext.h"
+#include "WebGLFormats.h"
 
 namespace mozilla {
 
+using mozilla::webgl::EffectiveFormat;
+
 WebGLExtensionTextureHalfFloatLinear::WebGLExtensionTextureHalfFloatLinear(WebGLContext* webgl)
     : WebGLExtensionBase(webgl)
 {
+    // This update requires that the authority already be populated by
+    // WebGLExtensionTextureHalfFloat.  Enabling extensions to control
+    // features is a mess in WebGL
+
+    webgl::FormatUsageAuthority* authority = webgl->mFormatUsage.get();
+
+    auto updateUsage = [authority](EffectiveFormat effectiveFormat) {
+        webgl::FormatUsageInfo* usage = authority->GetUsage(effectiveFormat);
+        MOZ_ASSERT(usage);
+        usage->isFilterable = true;
+    };
+
+    // Ensure require formats are initialized.
+    WebGLExtensionTextureHalfFloat::InitWebGLFormats(authority);
+
+    // Update usage to allow isFilterable
+    updateUsage(EffectiveFormat::RGBA16F);
+    updateUsage(EffectiveFormat::RGB16F);
+    updateUsage(EffectiveFormat::Luminance16FAlpha16F);
+    updateUsage(EffectiveFormat::Luminance16F);
+    updateUsage(EffectiveFormat::Alpha16F);
 }
 
 WebGLExtensionTextureHalfFloatLinear::~WebGLExtensionTextureHalfFloatLinear()
 {
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionTextureHalfFloatLinear, OES_texture_half_float_linear)
 
--- a/dom/canvas/WebGLExtensions.h
+++ b/dom/canvas/WebGLExtensions.h
@@ -15,16 +15,20 @@
 
 namespace mozilla {
 
 namespace dom {
 template<typename T>
 class Sequence;
 } // namespace dom
 
+namespace webgl {
+class FormatUsageAuthority;
+} // namespace webgl
+
 class WebGLContext;
 class WebGLShader;
 class WebGLQuery;
 class WebGLTimerQuery;
 class WebGLVertexArray;
 
 class WebGLExtensionBase
     : public nsWrapperCache
@@ -205,16 +209,18 @@ public:
 
     DECL_WEBGL_EXTENSION_GOOP
 };
 
 class WebGLExtensionTextureFloat
     : public WebGLExtensionBase
 {
 public:
+    static void InitWebGLFormats(webgl::FormatUsageAuthority* authority);
+
     explicit WebGLExtensionTextureFloat(WebGLContext*);
     virtual ~WebGLExtensionTextureFloat();
 
     DECL_WEBGL_EXTENSION_GOOP
 };
 
 class WebGLExtensionTextureFloatLinear
     : public WebGLExtensionBase
@@ -225,16 +231,18 @@ public:
 
     DECL_WEBGL_EXTENSION_GOOP
 };
 
 class WebGLExtensionTextureHalfFloat
     : public WebGLExtensionBase
 {
 public:
+    static void InitWebGLFormats(webgl::FormatUsageAuthority* authority);
+
     explicit WebGLExtensionTextureHalfFloat(WebGLContext*);
     virtual ~WebGLExtensionTextureHalfFloat();
 
     DECL_WEBGL_EXTENSION_GOOP
 };
 
 class WebGLExtensionTextureHalfFloatLinear
     : public WebGLExtensionBase
--- a/dom/canvas/WebGLFormats.cpp
+++ b/dom/canvas/WebGLFormats.cpp
@@ -263,16 +263,26 @@ InitFormatInfoMap()
     AddFormatInfo(FOO(COMPRESSED_RGB_PVRTC_4BPPV1 ), 0, UnsizedFormat::RGB , ComponentType::NormUInt);
     AddFormatInfo(FOO(COMPRESSED_RGBA_PVRTC_4BPPV1), 0, UnsizedFormat::RGBA, ComponentType::NormUInt);
     AddFormatInfo(FOO(COMPRESSED_RGB_PVRTC_2BPPV1 ), 0, UnsizedFormat::RGB , ComponentType::NormUInt);
     AddFormatInfo(FOO(COMPRESSED_RGBA_PVRTC_2BPPV1), 0, UnsizedFormat::RGBA, ComponentType::NormUInt);
 
     // OES_compressed_ETC1_RGB8_texture
     AddFormatInfo(FOO(ETC1_RGB8), 0, UnsizedFormat::RGB, ComponentType::NormUInt);
 
+    // OES_texture_float
+    AddFormatInfo(FOO(Luminance32FAlpha32F), 2, UnsizedFormat::LA, ComponentType::Float);
+    AddFormatInfo(FOO(Luminance32F        ), 1, UnsizedFormat::L , ComponentType::Float);
+    AddFormatInfo(FOO(Alpha32F            ), 1, UnsizedFormat::A , ComponentType::Float);
+
+    // OES_texture_half_float
+    AddFormatInfo(FOO(Luminance16FAlpha16F), 2, UnsizedFormat::LA, ComponentType::Float);
+    AddFormatInfo(FOO(Luminance16F        ), 1, UnsizedFormat::L , ComponentType::Float);
+    AddFormatInfo(FOO(Alpha16F            ), 1, UnsizedFormat::A , ComponentType::Float);
+
 #undef FOO
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 
 static void
 AddUnpackTuple(GLenum unpackFormat, GLenum unpackType, EffectiveFormat effectiveFormat)
 {
@@ -291,24 +301,34 @@ InitUnpackTupleMap()
     AddUnpackTuple(LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_SHORT_5_5_5_1, EffectiveFormat::RGB5_A1);
     AddUnpackTuple(LOCAL_GL_RGB , LOCAL_GL_UNSIGNED_BYTE         , EffectiveFormat::RGB8   );
     AddUnpackTuple(LOCAL_GL_RGB , LOCAL_GL_UNSIGNED_SHORT_5_6_5  , EffectiveFormat::RGB565 );
 
     AddUnpackTuple(LOCAL_GL_LUMINANCE_ALPHA, LOCAL_GL_UNSIGNED_BYTE, EffectiveFormat::Luminance8Alpha8);
     AddUnpackTuple(LOCAL_GL_LUMINANCE      , LOCAL_GL_UNSIGNED_BYTE, EffectiveFormat::Luminance8      );
     AddUnpackTuple(LOCAL_GL_ALPHA          , LOCAL_GL_UNSIGNED_BYTE, EffectiveFormat::Alpha8          );
 
-    AddUnpackTuple(LOCAL_GL_RGB , LOCAL_GL_FLOAT     , EffectiveFormat::RGB32F );
-    AddUnpackTuple(LOCAL_GL_RGBA, LOCAL_GL_FLOAT     , EffectiveFormat::RGBA32F);
-    AddUnpackTuple(LOCAL_GL_RGB , LOCAL_GL_HALF_FLOAT, EffectiveFormat::RGB16F );
-    AddUnpackTuple(LOCAL_GL_RGBA, LOCAL_GL_HALF_FLOAT, EffectiveFormat::RGBA16F);
+    AddUnpackTuple(LOCAL_GL_RGB            , LOCAL_GL_FLOAT, EffectiveFormat::RGB32F );
+    AddUnpackTuple(LOCAL_GL_RGBA           , LOCAL_GL_FLOAT, EffectiveFormat::RGBA32F);
+    AddUnpackTuple(LOCAL_GL_LUMINANCE_ALPHA, LOCAL_GL_FLOAT, EffectiveFormat::Luminance32FAlpha32F);
+    AddUnpackTuple(LOCAL_GL_LUMINANCE      , LOCAL_GL_FLOAT, EffectiveFormat::Luminance32F);
+    AddUnpackTuple(LOCAL_GL_ALPHA          , LOCAL_GL_FLOAT, EffectiveFormat::Alpha32F);
+
+    AddUnpackTuple(LOCAL_GL_RGB            , LOCAL_GL_HALF_FLOAT, EffectiveFormat::RGB16F );
+    AddUnpackTuple(LOCAL_GL_RGBA           , LOCAL_GL_HALF_FLOAT, EffectiveFormat::RGBA16F);
+    AddUnpackTuple(LOCAL_GL_LUMINANCE_ALPHA, LOCAL_GL_HALF_FLOAT, EffectiveFormat::Luminance16FAlpha16F);
+    AddUnpackTuple(LOCAL_GL_LUMINANCE      , LOCAL_GL_HALF_FLOAT, EffectiveFormat::Luminance16F);
+    AddUnpackTuple(LOCAL_GL_ALPHA          , LOCAL_GL_HALF_FLOAT, EffectiveFormat::Alpha16F);
 
     // Everyone's favorite problem-child:
-    AddUnpackTuple(LOCAL_GL_RGB , LOCAL_GL_HALF_FLOAT_OES, EffectiveFormat::RGB16F );
-    AddUnpackTuple(LOCAL_GL_RGBA, LOCAL_GL_HALF_FLOAT_OES, EffectiveFormat::RGBA16F);
+    AddUnpackTuple(LOCAL_GL_RGB            , LOCAL_GL_HALF_FLOAT_OES, EffectiveFormat::RGB16F );
+    AddUnpackTuple(LOCAL_GL_RGBA           , LOCAL_GL_HALF_FLOAT_OES, EffectiveFormat::RGBA16F);
+    AddUnpackTuple(LOCAL_GL_LUMINANCE_ALPHA, LOCAL_GL_HALF_FLOAT_OES, EffectiveFormat::Luminance16FAlpha16F);
+    AddUnpackTuple(LOCAL_GL_LUMINANCE      , LOCAL_GL_HALF_FLOAT_OES, EffectiveFormat::Luminance16F);
+    AddUnpackTuple(LOCAL_GL_ALPHA          , LOCAL_GL_HALF_FLOAT_OES, EffectiveFormat::Alpha16F);
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 
 static void
 AddSizedFormat(GLenum sizedFormat, EffectiveFormat effectiveFormat)
 {
     const FormatInfo* info = GetFormatInfo_NoLock(effectiveFormat);
@@ -742,17 +762,17 @@ FormatUsageAuthority::CreateForWebGL2()
     ret->AddUnpackOption(LOCAL_GL_ALPHA          , LOCAL_GL_UNSIGNED_BYTE, EffectiveFormat::Alpha8          );
 
     return Move(ret);
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 
 FormatUsageInfo*
-FormatUsageAuthority::GetInfo(EffectiveFormat format)
+FormatUsageAuthority::GetUsage(EffectiveFormat format)
 {
     auto itr = mInfoMap.find(format);
 
     if (itr == mInfoMap.end())
         return nullptr;
 
     return &(itr->second);
 }
@@ -772,24 +792,22 @@ FormatUsageAuthority::AddFormat(Effectiv
     AlwaysInsert(mInfoMap, format, usage);
 }
 
 void
 FormatUsageAuthority::AddUnpackOption(GLenum unpackFormat, GLenum unpackType,
                                       EffectiveFormat effectiveFormat)
 {
     const UnpackTuple unpack = { unpackFormat, unpackType };
-    FormatUsageInfo* usage = GetInfo(effectiveFormat);
+    FormatUsageInfo* usage = GetUsage(effectiveFormat);
     MOZ_RELEASE_ASSERT(usage);
     if (!usage)
         return;
 
     MOZ_RELEASE_ASSERT(usage->asTexture);
 
     auto res = usage->validUnpacks.insert(unpack);
     bool didInsert = res.second;
     MOZ_ALWAYS_TRUE(didInsert);
 }
 
-//////////////////////////////////////////////////////////////////////////////////////////
-
 } // namespace webgl
 } // namespace mozilla
--- a/dom/canvas/WebGLFormats.h
+++ b/dom/canvas/WebGLFormats.h
@@ -126,16 +126,26 @@ enum class EffectiveFormat : EffectiveFo
     COMPRESSED_RGB_PVRTC_4BPPV1,
     COMPRESSED_RGBA_PVRTC_4BPPV1,
     COMPRESSED_RGB_PVRTC_2BPPV1,
     COMPRESSED_RGBA_PVRTC_2BPPV1,
 
     // OES_compressed_ETC1_RGB8_texture
     ETC1_RGB8,
 
+    // OES_texture_float
+    Luminance32FAlpha32F,
+    Luminance32F,
+    Alpha32F,
+
+    // OES_texture_half_float
+    Luminance16FAlpha16F,
+    Luminance16F,
+    Alpha16F,
+
     MAX,
 };
 
 enum class UnsizedFormat : uint8_t {
     R,
     RG,
     RGB,
     RGBA,
@@ -236,22 +246,20 @@ private:
 
 public:
     void AddFormat(EffectiveFormat format, bool asRenderbuffer, bool isRenderable,
                    bool asTexture, bool isFilterable);
 
     void AddUnpackOption(GLenum unpackFormat, GLenum unpackType,
                          EffectiveFormat effectiveFormat);
 
-    FormatUsageInfo* GetInfo(EffectiveFormat format);
+    FormatUsageInfo* GetUsage(EffectiveFormat format);
 
-    FormatUsageInfo* GetInfo(const FormatInfo* format)
+    FormatUsageInfo* GetUsage(const FormatInfo* format)
     {
-        return GetInfo(format->effectiveFormat);
+        return GetUsage(format->effectiveFormat);
     }
 };
 
-////////////////////////////////////////
-
 } // namespace webgl
 } // namespace mozilla
 
 #endif // WEBGL_FORMATS_H_
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -181,17 +181,17 @@ public:
   }
 
 protected:
   bool EnsureGLContext() {
     if (mGLContext) {
       return true;
     }
 
-    mGLContext = GLContextProvider::CreateHeadless(false);
+    mGLContext = GLContextProvider::CreateHeadless(CreateContextFlags::NONE);
     return mGLContext;
   }
 
   layers::ImageContainer* mImageContainer;
   const VideoInfo& mConfig;
   RefPtr<AndroidSurfaceTexture> mSurfaceTexture;
   nsRefPtr<GLContext> mGLContext;
 };
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -64,18 +64,16 @@ struct NotificationStrings
   const nsString mBehavior;
   const nsString mServiceWorkerRegistrationID;
 };
 
 class ScopeCheckingGetCallback : public nsINotificationStorageCallback
 {
   const nsString mScope;
 public:
-  NS_DECL_ISUPPORTS
-
   explicit ScopeCheckingGetCallback(const nsAString& aScope)
     : mScope(aScope)
   {}
 
   NS_IMETHOD Handle(const nsAString& aID,
                     const nsAString& aTitle,
                     const nsAString& aDir,
                     const nsAString& aLang,
@@ -116,18 +114,16 @@ public:
 
 protected:
   virtual ~ScopeCheckingGetCallback()
   {}
 
   nsTArray<NotificationStrings> mStrings;
 };
 
-NS_IMPL_ISUPPORTS(ScopeCheckingGetCallback, nsINotificationStorageCallback)
-
 class NotificationStorageCallback final : public ScopeCheckingGetCallback
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback)
 
   NotificationStorageCallback(nsIGlobalObject* aWindow, const nsAString& aScope,
                               Promise* aPromise)
@@ -185,17 +181,19 @@ private:
   const nsString mScope;
 };
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback)
 NS_IMPL_CYCLE_COLLECTION(NotificationStorageCallback, mWindow, mPromise);
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback)
-NS_INTERFACE_MAP_END_INHERITING(ScopeCheckingGetCallback)
+  NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
 
 class NotificationGetRunnable final : public nsRunnable
 {
   const nsString mOrigin;
   const nsString mTag;
   nsCOMPtr<nsINotificationStorageCallback> mCallback;
 public:
   NotificationGetRunnable(const nsAString& aOrigin,
@@ -1761,17 +1759,17 @@ public:
     mPromiseProxy->CleanUp(aCx);
   }
 };
 
 class WorkerGetCallback final : public ScopeCheckingGetCallback
 {
   nsRefPtr<PromiseWorkerProxy> mPromiseProxy;
 public:
-  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_ISUPPORTS
 
   WorkerGetCallback(PromiseWorkerProxy* aProxy, const nsAString& aScope)
     : ScopeCheckingGetCallback(aScope), mPromiseProxy(aProxy)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aProxy);
   }
 
@@ -1803,17 +1801,17 @@ public:
     return NS_OK;
   }
 
 private:
   ~WorkerGetCallback()
   {}
 };
 
-NS_IMPL_ISUPPORTS_INHERITED0(WorkerGetCallback, ScopeCheckingGetCallback)
+NS_IMPL_ISUPPORTS(WorkerGetCallback, nsINotificationStorageCallback)
 
 class WorkerGetRunnable final : public nsRunnable
 {
   nsRefPtr<PromiseWorkerProxy> mPromiseProxy;
   const nsString mTag;
   const nsString mScope;
 public:
   WorkerGetRunnable(WorkerPrivate* aWorkerPrivate,
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -87,18 +87,17 @@ private:
   bool mCanceled;
 };
 
 static nsRefPtr<GLContext> sPluginContext = nullptr;
 
 static bool EnsureGLContext()
 {
   if (!sPluginContext) {
-    bool requireCompatProfile = true;
-    sPluginContext = GLContextProvider::CreateHeadless(requireCompatProfile);
+    sPluginContext = GLContextProvider::CreateHeadless(CreateContextFlags::REQUIRE_COMPAT_PROFILE);
   }
 
   return sPluginContext != nullptr;
 }
 
 class SharedPluginTexture final {
 public:
   NS_INLINE_DECL_REFCOUNTING(SharedPluginTexture)
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -124,33 +124,16 @@ PushSubscription::Constructor(GlobalObje
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal, mPrincipal)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscription)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-// PushManager
-// static
-bool
-PushManager::Enabled(JSContext* aCx, JSObject* aObj)
-{
-  if (NS_IsMainThread()) {
-    return Preferences::GetBool("dom.push.enabled", false);
-  }
-
-  // XXXnsm: As of this patch it seems like Push will be enabled before or with
-  // ServiceWorkers, so this seems OK for now.
-  ServiceWorkerGlobalScope* scope = nullptr;
-  nsresult rv = UnwrapObject<prototypes::id::ServiceWorkerGlobalScope_workers,
-                             mozilla::dom::ServiceWorkerGlobalScopeBinding_workers::NativeType>(aObj, scope);
-  return NS_SUCCEEDED(rv);
-}
-
 PushManager::PushManager(nsIGlobalObject* aGlobal, const nsAString& aScope)
   : mGlobal(aGlobal), mScope(aScope)
 {
   AssertIsOnMainThread();
 }
 
 PushManager::~PushManager()
 {}
--- a/dom/push/PushManager.h
+++ b/dom/push/PushManager.h
@@ -101,19 +101,16 @@ private:
 
 class PushManager final : public nsISupports
                         , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushManager)
 
-  static bool
-  Enabled(JSContext* aCx, JSObject* aObj);
-
   explicit PushManager(nsIGlobalObject* aGlobal, const nsAString& aScope);
 
   nsIGlobalObject*
   GetParentObject() const
   {
     return mGlobal;
   }
 
--- a/dom/webidl/MutationObserver.webidl
+++ b/dom/webidl/MutationObserver.webidl
@@ -42,16 +42,18 @@ interface MutationObserver {
   void observe(Node target, optional MutationObserverInit options);
   void disconnect();
   sequence<MutationRecord> takeRecords();
 
   [ChromeOnly, Throws]
   sequence<MutationObservingInfo?> getObservingInfo();
   [ChromeOnly]
   readonly attribute MutationCallback mutationCallback;
+  [ChromeOnly]
+  attribute boolean mergeAttributeRecords;
 };
 
 callback MutationCallback = void (sequence<MutationRecord> mutations, MutationObserver observer);
 
 dictionary MutationObserverInit {
   boolean childList = false;
   boolean attributes;
   boolean characterData;
--- a/dom/webidl/PushEvent.webidl
+++ b/dom/webidl/PushEvent.webidl
@@ -2,18 +2,20 @@
 /* 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/.
  *
  * The origin of this IDL file is
  * https://w3c.github.io/push-api/
  */
 
-[Constructor(DOMString type, optional PushEventInit eventInitDict), Exposed=ServiceWorker]
+[Constructor(DOMString type, optional PushEventInit eventInitDict),
+ Func="nsContentUtils::PushEnabled",
+ Exposed=ServiceWorker]
 interface PushEvent : ExtendableEvent {
   readonly attribute PushMessageData data;
 };
 
 typedef USVString PushMessageDataInit;
 
 dictionary PushEventInit : ExtendableEventInit {
   PushMessageDataInit data;
-};
\ No newline at end of file
+};
--- a/dom/webidl/PushManager.webidl
+++ b/dom/webidl/PushManager.webidl
@@ -17,17 +17,17 @@ interface PushManagerImpl {
     Promise<PushPermissionState> permissionState();
 
     // We need a setter in the bindings so that the C++ can use it,
     // but we don't want it exposed to client JS.  WebPushMethodHider
     // always returns false.
     [Func="ServiceWorkerRegistration::WebPushMethodHider"] void setScope(DOMString scope);
 };
 
-[Exposed=(Window,Worker), Func="mozilla::dom::PushManager::Enabled"]
+[Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled"]
 interface PushManager {
   [ChromeOnly, Throws, Exposed=Window]
   void setPushManagerImpl(PushManagerImpl store);
 
   [Throws]
   Promise<PushSubscription>     subscribe();
   [Throws]
   Promise<PushSubscription?>    getSubscription();
--- a/dom/webidl/PushMessageData.webidl
+++ b/dom/webidl/PushMessageData.webidl
@@ -2,17 +2,18 @@
 /* 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/.
  *
  * The origin of this IDL file is
  * https://w3c.github.io/push-api/
  */
 
-[Exposed=ServiceWorker]
+[Func="nsContentUtils::PushEnabled",
+ Exposed=ServiceWorker]
 interface PushMessageData
 {
     // FIXME(nsm): Bug 1149195.
     // These methods will be exposed once encryption is supported.
     // ArrayBuffer arrayBuffer();
     // Blob        blob();
     // object      json();
     // USVString   text();
--- a/dom/webidl/PushSubscription.webidl
+++ b/dom/webidl/PushSubscription.webidl
@@ -4,17 +4,17 @@
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * The origin of this IDL file is
 * https://w3c.github.io/push-api/
 */
 
 interface Principal;
 
-[Exposed=(Window,Worker), Func="mozilla::dom::PushManager::Enabled",
+[Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled",
  ChromeConstructor(DOMString pushEndpoint, DOMString scope)]
 interface PushSubscription
 {
     readonly attribute USVString endpoint;
     [Throws]
     Promise<boolean> unsubscribe();
     jsonifier;
 
--- a/dom/webidl/ServiceWorkerRegistration.webidl
+++ b/dom/webidl/ServiceWorkerRegistration.webidl
@@ -23,12 +23,12 @@ interface ServiceWorkerRegistration : Ev
   Promise<boolean> unregister();
 
   // event
   attribute EventHandler onupdatefound;
 };
 
 partial interface ServiceWorkerRegistration {
 #ifndef MOZ_SIMPLEPUSH
-  [Throws, Exposed=(Window,Worker), Func="mozilla::dom::PushManager::Enabled"]
+  [Throws, Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled"]
   readonly attribute PushManager pushManager;
 #endif
 };
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -161,16 +161,17 @@ static_assert(MAX_WORKERS_PER_DOMAIN >= 
 #define PREF_WORKERS_PERFORMANCE_LOGGING_ENABLED "dom.performance.enable_user_timing_logging"
 #define PREF_DOM_WORKERNOTIFICATION_ENABLED  "dom.webnotifications.enabled"
 #define PREF_WORKERS_LATEST_JS_VERSION "dom.workers.latestJSVersion"
 #define PREF_INTL_ACCEPT_LANGUAGES     "intl.accept_languages"
 #define PREF_SERVICEWORKERS_ENABLED    "dom.serviceWorkers.enabled"
 #define PREF_SERVICEWORKERS_TESTING_ENABLED "dom.serviceWorkers.testing.enabled"
 #define PREF_INTERCEPTION_ENABLED      "dom.serviceWorkers.interception.enabled"
 #define PREF_INTERCEPTION_OPAQUE_ENABLED "dom.serviceWorkers.interception.opaque.enabled"
+#define PREF_PUSH_ENABLED              "dom.push.enabled"
 
 namespace {
 
 const uint32_t kNoIndex = uint32_t(-1);
 
 const JS::ContextOptions kRequiredContextOptions =
   JS::ContextOptions().setDontReportUncaught(true);
 
@@ -1944,16 +1945,20 @@ RuntimeService::Init()
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                   WorkerPrefChanged,
                                   PREF_WORKERS_PERFORMANCE_LOGGING_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_PERFORMANCE_LOGGING_ENABLED))) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                   WorkerPrefChanged,
                                   PREF_SERVICEWORKERS_TESTING_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_SERVICEWORKERS_TESTING))) ||
+      NS_FAILED(Preferences::RegisterCallbackAndCall(
+                                  WorkerPrefChanged,
+                                  PREF_PUSH_ENABLED,
+                                  reinterpret_cast<void *>(WORKERPREF_PUSH))) ||
       NS_FAILED(Preferences::RegisterCallback(LoadRuntimeOptions,
                                               PREF_JS_OPTIONS_PREFIX,
                                               nullptr)) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                                    LoadRuntimeOptions,
                                                    PREF_WORKERS_OPTIONS_PREFIX,
                                                    nullptr)) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(PrefLanguagesChanged,
@@ -2171,16 +2176,20 @@ RuntimeService::Cleanup()
         NS_FAILED(Preferences::UnregisterCallback(
                                   WorkerPrefChanged,
                                   PREF_DOM_CACHES_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) ||
         NS_FAILED(Preferences::UnregisterCallback(
                                   WorkerPrefChanged,
                                   PREF_DOM_WORKERNOTIFICATION_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_DOM_WORKERNOTIFICATION))) ||
+        NS_FAILED(Preferences::UnregisterCallback(
+                                  WorkerPrefChanged,
+                                  PREF_PUSH_ENABLED,
+                                  reinterpret_cast<void *>(WORKERPREF_PUSH))) ||
 #if DUMP_CONTROLLED_BY_PREF
         NS_FAILED(Preferences::UnregisterCallback(
                                   WorkerPrefChanged,
                                   PREF_DOM_WINDOW_DUMP_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_DUMP))) ||
 #endif
 #ifdef JS_GC_ZEAL
         NS_FAILED(Preferences::UnregisterCallback(
@@ -2720,16 +2729,17 @@ RuntimeService::WorkerPrefChanged(const 
     case WORKERPREF_PERFORMANCE_LOGGING_ENABLED:
 #ifdef DUMP_CONTROLLED_BY_PREF
     case WORKERPREF_DUMP:
 #endif
     case WORKERPREF_INTERCEPTION_ENABLED:
     case WORKERPREF_INTERCEPTION_OPAQUE_ENABLED:
     case WORKERPREF_SERVICEWORKERS:
     case WORKERPREF_SERVICEWORKERS_TESTING:
+    case WORKERPREF_PUSH:
       sDefaultPreferences[key] = Preferences::GetBool(aPrefName, false);
       break;
 
     default:
       MOZ_ASSERT_UNREACHABLE("Invalid pref key");
       break;
   }
 
--- a/dom/workers/ServiceWorkerEvents.h
+++ b/dom/workers/ServiceWorkerEvents.h
@@ -15,16 +15,17 @@
 #include "mozilla/dom/workers/bindings/ServiceWorker.h"
 
 #ifndef MOZ_SIMPLEPUSH
 #include "mozilla/dom/PushEventBinding.h"
 #include "mozilla/dom/PushMessageDataBinding.h"
 #endif
 
 #include "nsProxyRelease.h"
+#include "nsContentUtils.h"
 
 class nsIInterceptedChannel;
 
 namespace mozilla {
 namespace dom {
 class Blob;
 class Request;
 class ResponseOrPromise;
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -1324,16 +1324,23 @@ public:
   bool
   PerformanceLoggingEnabled() const
   {
     AssertIsOnWorkerThread();
     return mPreferences[WORKERPREF_PERFORMANCE_LOGGING_ENABLED];
   }
 
   bool
+  PushEnabled() const
+  {
+    AssertIsOnWorkerThread();
+    return mPreferences[WORKERPREF_PUSH];
+  }
+
+  bool
   OnLine() const
   {
     AssertIsOnWorkerThread();
     return mOnLine;
   }
 
   void
   StopSyncLoop(nsIEventTarget* aSyncLoopTarget, bool aResult);
--- a/dom/workers/Workers.h
+++ b/dom/workers/Workers.h
@@ -201,16 +201,17 @@ enum WorkerPreference
   WORKERPREF_DOM_CACHES, // dom.caches.enabled
   WORKERPREF_SERVICEWORKERS, // dom.serviceWorkers.enabled
   WORKERPREF_INTERCEPTION_ENABLED, // dom.serviceWorkers.interception.enabled
   WORKERPREF_DOM_WORKERNOTIFICATION, // dom.webnotifications.workers.enabled
   WORKERPREF_DOM_CACHES_TESTING, // dom.caches.testing.enabled
   WORKERPREF_SERVICEWORKERS_TESTING, // dom.serviceWorkers.testing.enabled
   WORKERPREF_INTERCEPTION_OPAQUE_ENABLED, // dom.serviceWorkers.interception.opaque.enabled
   WORKERPREF_PERFORMANCE_LOGGING_ENABLED, // dom.performance.enable_user_timing_logging
+  WORKERPREF_PUSH, // dom.push.enabled
   WORKERPREF_COUNT
 };
 
 // Implemented in WorkerPrivate.cpp
 
 struct WorkerLoadInfo
 {
   // All of these should be released in WorkerPrivateParent::ForgetMainThreadObjects.
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -43,18 +43,16 @@ var ecmaGlobals =
     "Iterator",
     "JSON",
     "Map",
     "Math",
     "NaN",
     "Number",
     "Object",
     "Proxy",
-    "PushEvent",
-    "PushMessageData",
     "RangeError",
     "ReferenceError",
     "Reflect",
     "RegExp",
     "Set",
     {name: "SharedArrayBuffer", nightly: true},
     {name: "SharedInt8Array", nightly: true},
     {name: "SharedUint8Array", nightly: true},
@@ -169,19 +167,23 @@ var interfaceNamesInGlobalScope =
     "PerformanceEntry",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMark",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMeasure",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Promise",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "PushManager",
+    { name: "PushEvent", b2g: false, android: false, release: false },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "PushManager", b2g: false, android: false, release: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "PushSubscription",
+    { name: "PushMessageData", b2g: false, android: false, release: false },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "PushSubscription", b2g: false, android: false, release: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Request",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Response",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ServiceWorker",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ServiceWorkerGlobalScope",
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -159,16 +159,20 @@ var interfaceNamesInGlobalScope =
     "PerformanceEntry",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMark",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PerformanceMeasure",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Promise",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "PushManager", b2g: false, android: false, release: false },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "PushSubscription", b2g: false, android: false, release: false },
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "Request",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Response",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "ServiceWorkerRegistration", release: false, b2g: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "TextDecoder",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -425,21 +425,21 @@ DrawTargetSkia::DrawSurfaceWithShadow(So
 
   mCanvas->save();
   mCanvas->resetMatrix();
 
   TempBitmap bitmap = GetBitmapForSurface(aSurface);
 
   SkPaint paint;
 
-  SkImageFilter* filter = SkDropShadowImageFilter::Create(aOffset.x, aOffset.y,
-                                                          aSigma, aSigma,
-                                                          ColorToSkColor(aColor, 1.0));
+  SkAutoTUnref<SkImageFilter> filter(SkDropShadowImageFilter::Create(aOffset.x, aOffset.y,
+                                                                     aSigma, aSigma,
+                                                                     ColorToSkColor(aColor, 1.0)));
 
-  paint.setImageFilter(filter);
+  paint.setImageFilter(filter.get());
   paint.setXfermodeMode(GfxOpToSkiaOp(aOperator));
 
   mCanvas->drawBitmap(bitmap.mBitmap, aDest.x, aDest.y, &paint);
   mCanvas->restore();
 }
 
 void
 DrawTargetSkia::FillRect(const Rect &aRect,
--- a/gfx/gl/GLContextProvider.h
+++ b/gfx/gl/GLContextProvider.h
@@ -4,24 +4,35 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GLCONTEXTPROVIDER_H_
 #define GLCONTEXTPROVIDER_H_
 
 #include "GLContextTypes.h"
 #include "nsAutoPtr.h"
 #include "SurfaceTypes.h"
+#include "mozilla/TypedEnumBits.h"
 
 #include "nsSize.h" // for gfx::IntSize (needed by GLContextProviderImpl.h below)
 
 class nsIWidget;
 
 namespace mozilla {
 namespace gl {
 
+enum class CreateContextFlags : int8_t {
+    NONE = 0,
+    REQUIRE_COMPAT_PROFILE = 1 << 0,
+    // Force the use of hardware backed GL, don't allow software implementations.
+    FORCE_ENABLE_HARDWARE = 1 << 1,
+    /* Don't force discrete GPU to be used (if applicable) */
+    ALLOW_OFFLINE_RENDERER =  1 << 2,
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CreateContextFlags)
+
 #define IN_GL_CONTEXT_PROVIDER_H
 
 // Null is always there
 #define GL_CONTEXT_PROVIDER_NAME GLContextProviderNull
 #include "GLContextProviderImpl.h"
 #undef GL_CONTEXT_PROVIDER_NAME
 
 #ifdef XP_WIN
--- a/gfx/gl/GLContextProviderCGL.mm
+++ b/gfx/gl/GLContextProviderCGL.mm
@@ -182,16 +182,21 @@ static const NSOpenGLPixelFormatAttribut
     NSOpenGLPFADoubleBuffer,
     0
 };
 
 static const NSOpenGLPixelFormatAttribute kAttribs_offscreen[] = {
     0
 };
 
+static const NSOpenGLPixelFormatAttribute kAttribs_offscreen_allow_offline[] = {
+    NSOpenGLPFAAllowOfflineRenderers,
+    0
+};
+
 static const NSOpenGLPixelFormatAttribute kAttribs_offscreen_accel[] = {
     NSOpenGLPFAAccelerated,
     0
 };
 
 static const NSOpenGLPixelFormatAttribute kAttribs_offscreen_coreProfile[] = {
     NSOpenGLPFAAccelerated,
     NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
@@ -248,71 +253,79 @@ GLContextProviderCGL::CreateForWindow(ns
         [context release];
         return nullptr;
     }
 
     return glContext.forget();
 }
 
 static already_AddRefed<GLContextCGL>
-CreateOffscreenFBOContext(bool requireCompatProfile)
+CreateOffscreenFBOContext(CreateContextFlags flags)
 {
     if (!sCGLLibrary.EnsureInitialized()) {
         return nullptr;
     }
 
     ContextProfile profile;
     NSOpenGLContext* context = nullptr;
 
-    if (!requireCompatProfile) {
+    if (!(flags & CreateContextFlags::REQUIRE_COMPAT_PROFILE)) {
         profile = ContextProfile::OpenGLCore;
         context = CreateWithFormat(kAttribs_offscreen_coreProfile);
     }
     if (!context) {
         profile = ContextProfile::OpenGLCompatibility;
 
-        if (gfxPrefs::RequireHardwareGL())
-            context = CreateWithFormat(kAttribs_offscreen_accel);
-        else
-            context = CreateWithFormat(kAttribs_offscreen);
+        if (flags & CreateContextFlags::ALLOW_OFFLINE_RENDERER) {
+          if (gfxPrefs::RequireHardwareGL())
+              context = CreateWithFormat(kAttribs_singleBuffered);
+          else
+              context = CreateWithFormat(kAttribs_offscreen_allow_offline);
+
+        } else {
+          if (gfxPrefs::RequireHardwareGL())
+              context = CreateWithFormat(kAttribs_offscreen_accel);
+          else
+              context = CreateWithFormat(kAttribs_offscreen);
+        }
     }
     if (!context) {
         NS_WARNING("Failed to create NSOpenGLContext.");
         return nullptr;
     }
 
     SurfaceCaps dummyCaps = SurfaceCaps::Any();
     nsRefPtr<GLContextCGL> glContext = new GLContextCGL(dummyCaps, context,
                                                         true, profile);
 
     return glContext.forget();
 }
 
 already_AddRefed<GLContext>
-GLContextProviderCGL::CreateHeadless(bool requireCompatProfile, bool forceEnabled)
+GLContextProviderCGL::CreateHeadless(CreateContextFlags flags)
 {
     nsRefPtr<GLContextCGL> gl;
-    gl = CreateOffscreenFBOContext(requireCompatProfile);
+    gl = CreateOffscreenFBOContext(flags);
     if (!gl)
         return nullptr;
 
     if (!gl->Init()) {
         NS_WARNING("Failed during Init.");
         return nullptr;
     }
 
     return gl.forget();
 }
 
 already_AddRefed<GLContext>
 GLContextProviderCGL::CreateOffscreen(const IntSize& size,
                                       const SurfaceCaps& caps,
-                                      bool requireCompatProfile)
+                                      CreateContextFlags flags)
 {
-    nsRefPtr<GLContext> glContext = CreateHeadless(requireCompatProfile);
+    nsRefPtr<GLContext> glContext = CreateHeadless(flags);
     if (!glContext->InitOffscreen(size, caps)) {
         NS_WARNING("Failed during InitOffscreen.");
         return nullptr;
     }
 
     return glContext.forget();
 }
 
@@ -325,17 +338,17 @@ GLContextProviderCGL::GetGlobalContext()
         return nullptr;
     }
 
     if (!gGlobalContext) {
         // There are bugs in some older drivers with pbuffers less
         // than 16x16 in size; also 16x16 is POT so that we can do
         // a FBO with it on older video cards.  A FBO context for
         // sharing is preferred since it has no associated target.
-        gGlobalContext = CreateOffscreenFBOContext(false);
+        gGlobalContext = CreateOffscreenFBOContext(CreateContextFlags::NONE);
         if (!gGlobalContext || !static_cast<GLContextCGL*>(gGlobalContext.get())->Init()) {
             NS_WARNING("Couldn't init gGlobalContext.");
             gGlobalContext = nullptr;
             return nullptr;
         }
     }
 
     return gGlobalContext;
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -928,19 +928,19 @@ GLContextEGL::CreateEGLPixmapOffscreenCo
     }
 
     glContext->HoldSurface(thebesSurface);
 
     return glContext.forget();
 }
 
 already_AddRefed<GLContext>
-GLContextProviderEGL::CreateHeadless(bool requireCompatProfile, bool forceEnabled)
+GLContextProviderEGL::CreateHeadless(CreateContextFlags flags)
 {
-    if (!sEGLLibrary.EnsureInitialized(forceEnabled)) {
+    if (!sEGLLibrary.EnsureInitialized(bool(flags & CreateContextFlags::FORCE_ENABLE_HARDWARE))) {
         return nullptr;
     }
 
     mozilla::gfx::IntSize dummySize = mozilla::gfx::IntSize(16, 16);
     nsRefPtr<GLContext> glContext;
     glContext = GLContextEGL::CreateEGLPBufferOffscreenContext(dummySize);
     if (!glContext)
         return nullptr;
@@ -948,19 +948,19 @@ GLContextProviderEGL::CreateHeadless(boo
     return glContext.forget();
 }
 
 // Under EGL, on Android, pbuffers are supported fine, though
 // often without the ability to texture from them directly.
 already_AddRefed<GLContext>
 GLContextProviderEGL::CreateOffscreen(const mozilla::gfx::IntSize& size,
                                       const SurfaceCaps& caps,
-                                      bool requireCompatProfile)
+                                      CreateContextFlags flags)
 {
-    nsRefPtr<GLContext> glContext = CreateHeadless(requireCompatProfile);
+    nsRefPtr<GLContext> glContext = CreateHeadless(flags);
     if (!glContext)
         return nullptr;
 
     if (!glContext->InitOffscreen(size, caps))
         return nullptr;
 
     return glContext.forget();
 }
--- a/gfx/gl/GLContextProviderGLX.cpp
+++ b/gfx/gl/GLContextProviderGLX.cpp
@@ -1210,32 +1210,32 @@ DONE_CREATING_PIXMAP:
                                                   true,
                                                   xsurface);
     }
 
     return glContext.forget();
 }
 
 already_AddRefed<GLContext>
-GLContextProviderGLX::CreateHeadless(bool requireCompatProfile, bool forceEnabled)
+GLContextProviderGLX::CreateHeadless(CreateContextFlags)
 {
     IntSize dummySize = IntSize(16, 16);
     nsRefPtr<GLContext> glContext = CreateOffscreenPixmapContext(dummySize);
     if (!glContext)
         return nullptr;
 
     return glContext.forget();
 }
 
 already_AddRefed<GLContext>
 GLContextProviderGLX::CreateOffscreen(const IntSize& size,
                                       const SurfaceCaps& caps,
-                                      bool requireCompatProfile)
+                                      CreateContextFlags flags)
 {
-    nsRefPtr<GLContext> glContext = CreateHeadless(requireCompatProfile);
+    nsRefPtr<GLContext> glContext = CreateHeadless(flags);
     if (!glContext)
         return nullptr;
 
     if (!glContext->InitOffscreen(size, caps))
         return nullptr;
 
     return glContext.forget();
 }
--- a/gfx/gl/GLContextProviderImpl.h
+++ b/gfx/gl/GLContextProviderImpl.h
@@ -49,29 +49,31 @@ public:
      *
      * The offscreen context returned by this method will always have
      * the ability to be rendered into a context created by a window.
      * It might or might not share resources with the global context;
      * query GetSharedContext() for a non-null result to check.  If
      * resource sharing can be avoided on the target platform, it will
      * be, in order to isolate the offscreen context.
      *
-     * @param aSize The initial size of this offscreen context.
-     * @param aFormat The ContextFormat for this offscreen context.
+     * @param size The initial size of this offscreen context.
+     * @param caps The SurfaceCaps for this offscreen context.
+     * @param flags The set of CreateContextFlags to be used for this
+     *              offscreen context.
      *
      * @return Context to use for offscreen rendering
      */
     static already_AddRefed<GLContext>
     CreateOffscreen(const mozilla::gfx::IntSize& size,
                     const SurfaceCaps& caps,
-                    bool requireCompatProfile);
+                    CreateContextFlags flags);
 
     // Just create a context. We'll add offscreen stuff ourselves.
     static already_AddRefed<GLContext>
-    CreateHeadless(bool requireCompatProfile, bool forceEnabled = false);
+    CreateHeadless(CreateContextFlags flags);
 
     /**
      * Create wrapping Gecko GLContext for external gl context.
      *
      * @param aContext External context which will be wrapped by Gecko GLContext.
      * @param aSurface External surface which is used for external context.
      *
      * @return Wrapping Context to use for rendering
--- a/gfx/gl/GLContextProviderNull.cpp
+++ b/gfx/gl/GLContextProviderNull.cpp
@@ -18,23 +18,23 @@ already_AddRefed<GLContext>
 GLContextProviderNull::CreateWrappingExisting(void*, void*)
 {
     return nullptr;
 }
 
 already_AddRefed<GLContext>
 GLContextProviderNull::CreateOffscreen(const gfx::IntSize&,
                                        const SurfaceCaps&,
-                                       bool)
+                                       CreateContextFlags)
 {
     return nullptr;
 }
 
 already_AddRefed<GLContext>
-GLContextProviderNull::CreateHeadless(bool)
+GLContextProviderNull::CreateHeadless(CreateContextFlags)
 {
     return nullptr;
 }
 
 GLContext*
 GLContextProviderNull::GetGlobalContext()
 {
     return nullptr;
--- a/gfx/gl/GLContextProviderWGL.cpp
+++ b/gfx/gl/GLContextProviderWGL.cpp
@@ -602,17 +602,17 @@ CreateWindowOffscreenContext()
     nsRefPtr<GLContextWGL> glContext = new GLContextWGL(caps,
                                                         shareContext, true,
                                                         dc, context, win);
 
     return glContext.forget();
 }
 
 already_AddRefed<GLContext>
-GLContextProviderWGL::CreateHeadless(bool requireCompatProfile, bool forceEnabled)
+GLContextProviderWGL::CreateHeadless(CreateContextFlags)
 {
     if (!sWGLLib.EnsureInitialized()) {
         return nullptr;
     }
 
     nsRefPtr<GLContextWGL> glContext;
 
     // Always try to create a pbuffer context first, because we
@@ -637,19 +637,19 @@ GLContextProviderWGL::CreateHeadless(boo
 
     nsRefPtr<GLContext> retGL = glContext.get();
     return retGL.forget();
 }
 
 already_AddRefed<GLContext>
 GLContextProviderWGL::CreateOffscreen(const IntSize& size,
                                       const SurfaceCaps& caps,
-                                      bool requireCompatProfile)
+                                      CreateContextFlags flags)
 {
-    nsRefPtr<GLContext> glContext = CreateHeadless(requireCompatProfile);
+    nsRefPtr<GLContext> glContext = CreateHeadless(flags);
     if (!glContext)
         return nullptr;
 
     if (!glContext->InitOffscreen(size, caps))
         return nullptr;
 
     return glContext.forget();
 }
--- a/gfx/layers/GLImages.cpp
+++ b/gfx/layers/GLImages.cpp
@@ -34,17 +34,17 @@ EGLImageImage::~EGLImageImage()
 }
 
 already_AddRefed<gfx::SourceSurface>
 GLImage::GetAsSourceSurface()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread");
 
   if (!sSnapshotContext) {
-    sSnapshotContext = GLContextProvider::CreateHeadless(false);
+    sSnapshotContext = GLContextProvider::CreateHeadless(CreateContextFlags::NONE);
     if (!sSnapshotContext) {
       NS_WARNING("Failed to create snapshot GLContext");
       return nullptr;
     }
   }
 
   sSnapshotContext->MakeCurrent();
   ScopedTexture scopedTex(sSnapshotContext);
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -121,19 +121,18 @@ CompositorOGL::CreateContext()
 #endif
 
   // Allow to create offscreen GL context for main Layer Manager
   if (!context && PR_GetEnv("MOZ_LAYERS_PREFER_OFFSCREEN")) {
     SurfaceCaps caps = SurfaceCaps::ForRGB();
     caps.preserve = false;
     caps.bpp16 = gfxPlatform::GetPlatform()->GetOffscreenFormat() == gfxImageFormat::RGB16_565;
 
-    bool requireCompatProfile = true;
     context = GLContextProvider::CreateOffscreen(mSurfaceSize,
-                                                 caps, requireCompatProfile);
+                                                 caps, CreateContextFlags::REQUIRE_COMPAT_PROFILE);
   }
 
   if (!context) {
     context = gl::GLContextProvider::CreateForWindow(mWidget);
   }
 
   if (!context) {
     NS_WARNING("Failed to create CompositorOGL context");
--- a/gfx/tests/gtest/TestCompositor.cpp
+++ b/gfx/tests/gtest/TestCompositor.cpp
@@ -40,17 +40,18 @@ public:
   NS_IMETHOD              GetBounds(IntRect &aRect) override { return GetClientBounds(aRect); }
 
   void* GetNativeData(uint32_t aDataType) override {
     if (aDataType == NS_NATIVE_OPENGL_CONTEXT) {
       mozilla::gl::SurfaceCaps caps = mozilla::gl::SurfaceCaps::ForRGB();
       caps.preserve = false;
       caps.bpp16 = false;
       nsRefPtr<GLContext> context = GLContextProvider::CreateOffscreen(
-        IntSize(gCompWidth, gCompHeight), caps, true);
+        IntSize(gCompWidth, gCompHeight), caps,
+        CreateContextFlags::REQUIRE_COMPAT_PROFILE);
       return context.forget().take();
     }
     return nullptr;
   }
 
   NS_IMETHOD              Create(nsIWidget *aParent,
                                  nsNativeWidget aNativeParent,
                                  const IntRect &aRect,
--- a/gfx/tests/gtest/TestGfxPrefs.cpp
+++ b/gfx/tests/gtest/TestGfxPrefs.cpp
@@ -20,17 +20,17 @@ TEST(GfxPrefs, Singleton) {
   ASSERT_TRUE(gfxPrefs::SingletonExists());
 }
 
 TEST(GfxPrefs, LiveValues) {
   gfxPrefs::GetSingleton();
   ASSERT_TRUE(gfxPrefs::SingletonExists());
 
   // Live boolean, default false
-  ASSERT_FALSE(gfxPrefs::CanvasAzureAccelerated());
+  ASSERT_FALSE(gfxPrefs::LayersDumpTexture());
 
   // Live int32_t, default 23456
   ASSERT_TRUE(gfxPrefs::LayerScopePort() == 23456);
 
   // Live uint32_t, default 2
   ASSERT_TRUE(gfxPrefs::MSAALevel() == 2);
 }
 
@@ -61,21 +61,21 @@ TEST(GfxPrefs, Set) {
   // Once boolean, default false
   ASSERT_FALSE(gfxPrefs::LayersDump());
   gfxPrefs::SetLayersDump(true);
   ASSERT_TRUE(gfxPrefs::LayersDump());
   gfxPrefs::SetLayersDump(false);
   ASSERT_FALSE(gfxPrefs::LayersDump());
 
   // Live boolean, default false
-  ASSERT_FALSE(gfxPrefs::CanvasAzureAccelerated());
-  gfxPrefs::SetCanvasAzureAccelerated(true);
-  ASSERT_TRUE(gfxPrefs::CanvasAzureAccelerated());
-  gfxPrefs::SetCanvasAzureAccelerated(false);
-  ASSERT_FALSE(gfxPrefs::CanvasAzureAccelerated());
+  ASSERT_FALSE(gfxPrefs::LayersDumpTexture());
+  gfxPrefs::SetLayersDumpTexture(true);
+  ASSERT_TRUE(gfxPrefs::LayersDumpTexture());
+  gfxPrefs::SetLayersDumpTexture(false);
+  ASSERT_FALSE(gfxPrefs::LayersDumpTexture());
 
   // Once float, default -1
   ASSERT_TRUE(gfxPrefs::APZMaxVelocity() == -1.0f);
   gfxPrefs::SetAPZMaxVelocity(1.75f);
   ASSERT_TRUE(gfxPrefs::APZMaxVelocity() == 1.75f);
   gfxPrefs::SetAPZMaxVelocity(-1.0f);
   ASSERT_TRUE(gfxPrefs::APZMaxVelocity() == -1.0f);
 }
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -117,16 +117,17 @@ namespace layers {
 void InitGralloc();
 #endif
 void ShutdownTileCache();
 } // namespace layers
 } // namespace mozilla
 
 using namespace mozilla;
 using namespace mozilla::layers;
+using namespace mozilla::gl;
 
 gfxPlatform *gPlatform = nullptr;
 static bool gEverInitialized = false;
 
 static Mutex* gGfxPlatformPrefsLock = nullptr;
 
 // These two may point to the same profile
 static qcms_profile *gCMSOutputProfile = nullptr;
@@ -489,17 +490,17 @@ gfxPlatform::Init()
     gPlatform = new gfxQtPlatform;
 #elif defined(ANDROID)
     gPlatform = new gfxAndroidPlatform;
 #else
     #error "No gfxPlatform implementation available"
 #endif
 
 #ifdef MOZ_GL_DEBUG
-    mozilla::gl::GLContext::StaticInit();
+    GLContext::StaticInit();
 #endif
 
     InitLayersAccelerationPrefs();
     InitLayersIPC();
 
     nsresult rv;
 
     bool usePlatformFontList = true;
@@ -537,21 +538,21 @@ gfxPlatform::Init()
 
     /* Create and register our CMS Override observer. */
     gPlatform->mSRGBOverrideObserver = new SRGBOverrideObserver();
     Preferences::AddWeakObserver(gPlatform->mSRGBOverrideObserver, GFX_PREF_CMS_FORCE_SRGB);
 
     gPlatform->mFontPrefsObserver = new FontPrefsObserver();
     Preferences::AddStrongObservers(gPlatform->mFontPrefsObserver, kObservedPrefs);
 
-    mozilla::gl::GLContext::PlatformStartup();
+    GLContext::PlatformStartup();
 
 #ifdef MOZ_WIDGET_ANDROID
     // Texture pool init
-    mozilla::gl::TexturePoolOGL::Init();
+    TexturePoolOGL::Init();
 #endif
 
 #ifdef MOZ_WIDGET_GONK
     mozilla::layers::InitGralloc();
 #endif
 
     Preferences::RegisterCallbackAndCall(RecordingPrefChanged, "gfx.2d.recording", nullptr);
 
@@ -621,30 +622,30 @@ gfxPlatform::Shutdown()
 
         gPlatform->mMemoryPressureObserver = nullptr;
         gPlatform->mSkiaGlue = nullptr;
         gPlatform->mVsyncSource = nullptr;
     }
 
 #ifdef MOZ_WIDGET_ANDROID
     // Shut down the texture pool
-    mozilla::gl::TexturePoolOGL::Shutdown();
+    TexturePoolOGL::Shutdown();
 #endif
 
     // Shut down the default GL context provider.
-    mozilla::gl::GLContextProvider::Shutdown();
+    GLContextProvider::Shutdown();
 
 #if defined(XP_WIN)
     // The above shutdown calls operate on the available context providers on
     // most platforms.  Windows is a "special snowflake", though, and has three
     // context providers available, so we have to shut all of them down.
     // We should only support the default GL provider on Windows; then, this
     // could go away. Unfortunately, we currently support WGL (the default) for
     // WebGL on Optimus.
-    mozilla::gl::GLContextProviderEGL::Shutdown();
+    GLContextProviderEGL::Shutdown();
 #endif
 
     // This is a bit iffy - we're assuming that we were the ones that set the
     // log forwarder in the Factory, so that it's our responsibility to 
     // delete it.
     delete mozilla::gfx::Factory::GetLogForwarder();
     mozilla::gfx::Factory::SetLogForwarder(nullptr);
 
@@ -1073,34 +1074,34 @@ gfxPlatform::InitializeSkiaCacheLimits()
     printf_stderr("Determined SkiaGL cache limits: Size %i, Items: %i\n", cacheSizeLimit, cacheItemLimit);
   #endif
 
     mSkiaGlue->GetGrContext()->setResourceCacheLimits(cacheItemLimit, cacheSizeLimit);
 #endif
   }
 }
 
-mozilla::gl::SkiaGLGlue*
+SkiaGLGlue*
 gfxPlatform::GetSkiaGLGlue()
 {
 #ifdef USE_SKIA_GPU
   if (!mSkiaGlue) {
     /* Dummy context. We always draw into a FBO.
      *
      * FIXME: This should be stored in TLS or something, since there needs to be one for each thread using it. As it
      * stands, this only works on the main thread.
      */
-    bool requireCompatProfile = true;
-    nsRefPtr<mozilla::gl::GLContext> glContext;
-    glContext = mozilla::gl::GLContextProvider::CreateHeadless(requireCompatProfile);
+    nsRefPtr<GLContext> glContext;
+    glContext = GLContextProvider::CreateHeadless(CreateContextFlags::REQUIRE_COMPAT_PROFILE |
+                                                  CreateContextFlags::ALLOW_OFFLINE_RENDERER);
     if (!glContext) {
       printf_stderr("Failed to create GLContext for SkiaGL!\n");
       return nullptr;
     }
-    mSkiaGlue = new mozilla::gl::SkiaGLGlue(glContext);
+    mSkiaGlue = new SkiaGLGlue(glContext);
     MOZ_ASSERT(mSkiaGlue->GetGrContext(), "No GrContext");
     InitializeSkiaCacheLimits();
   }
 #endif
 
   return mSkiaGlue;
 }
 
--- a/layout/reftests/css-gradients/reftest.list
+++ b/layout/reftests/css-gradients/reftest.list
@@ -1,14 +1,14 @@
-fuzzy-if(!contentSameGfxBackendAsCanvas,4,88500) fuzzy-if(azureSkiaGL,3,89700) fuzzy-if(azureQuartz,1,34792) == linear-1a.html linear-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,4,88500) fuzzy-if(azureSkiaGL,3,89700) fuzzy-if(azureQuartz,1,34792) == linear-1b.html linear-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,4,88500) fuzzy-if(azureSkiaGL,4,89700) fuzzy-if(azureQuartz,1,34792) == linear-1a.html linear-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,4,88500) fuzzy-if(azureSkiaGL,4,89700) fuzzy-if(azureQuartz,1,34792) == linear-1b.html linear-1-ref.html
 fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fuzzy-if(azureSkiaGL,2,89997) fuzzy-if(azureQuartz,1,11469) == linear-keywords-1a.html linear-keywords-1-ref.html
 fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fuzzy-if(azureSkiaGL,2,89997) fuzzy-if(azureQuartz,1,11985) == linear-keywords-1b.html linear-keywords-1-ref.html
 fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fuzzy-if(azureQuartz,1,10230) == linear-percent.html linear-percent-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,4,92400) fuzzy-if(azureSkiaGL,2,143400) fuzzy-if(azureQuartz,1,27827) fuzzy-if(Android&&AndroidVersion>=15,4,93000) == linear-mix.html linear-mix-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,4,92400) fuzzy-if(azureSkiaGL,3,143400) fuzzy-if(azureQuartz,1,27827) fuzzy-if(Android&&AndroidVersion>=15,4,93000) == linear-mix.html linear-mix-ref.html
 == linear-diagonal-1a.html linear-diagonal-1-ref.html
 == linear-diagonal-1b.html linear-diagonal-1-ref.html
 == linear-diagonal-1c.html linear-diagonal-1-ref.html
 == linear-diagonal-2a.html linear-diagonal-2-ref.html
 == linear-diagonal-2b.html linear-diagonal-2-ref.html
 == linear-diagonal-2c.html linear-diagonal-2-ref.html
 == linear-diagonal-3a.html linear-diagonal-3-ref.html
 == linear-diagonal-3b.html linear-diagonal-3-ref.html
@@ -40,37 +40,37 @@ fails-if(d2d) == linear-repeat-1g.html l
 == linear-rotated-1.html linear-rotated-1-ref.html
 == linear-size-1a.html linear-size-1-ref.html
 == linear-stops-1a.html linear-stops-1-ref.html
 == linear-stops-1b.html linear-stops-1-ref.html
 == linear-stops-1c.html linear-stops-1-ref.html
 == linear-stops-1d.html linear-stops-1-ref.html
 == linear-stops-1e.html linear-stops-1-ref.html
 == linear-stops-1f.html linear-stops-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,3,88500) fuzzy-if(azureSkiaGL,2,89700) fuzzy-if(azureQuartz,1,22367) == linear-vertical-1a.html linear-vertical-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,3,88500) fuzzy-if(azureSkiaGL,2,89700) fuzzy-if(azureQuartz,1,22367) == linear-vertical-1b.html linear-vertical-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,3,88500) fuzzy-if(azureSkiaGL,2,89700) fuzzy-if(azureQuartz,2,26777) == linear-vertical-1c.html linear-vertical-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,3,88500) fuzzy-if(azureSkiaGL,2,89700) fuzzy-if(azureQuartz,2,26777) == linear-vertical-1d.html linear-vertical-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,3,88500) fuzzy-if(azureSkiaGL,2,89700) fuzzy-if(azureQuartz,1,22367) == linear-vertical-1e.html linear-vertical-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,3,88500) fuzzy-if(azureSkiaGL,3,89700) fuzzy-if(azureQuartz,1,22367) == linear-vertical-1a.html linear-vertical-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,3,88500) fuzzy-if(azureSkiaGL,3,89700) fuzzy-if(azureQuartz,1,22367) == linear-vertical-1b.html linear-vertical-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,3,88500) fuzzy-if(azureSkiaGL,3,89700) fuzzy-if(azureQuartz,2,26777) == linear-vertical-1c.html linear-vertical-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,3,88500) fuzzy-if(azureSkiaGL,3,89700) fuzzy-if(azureQuartz,2,26777) == linear-vertical-1d.html linear-vertical-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,3,88500) fuzzy-if(azureSkiaGL,3,89700) fuzzy-if(azureQuartz,1,22367) == linear-vertical-1e.html linear-vertical-1-ref.html
 == linear-vertical-subpixel-1.html linear-vertical-subpixel-1-ref.html
 == linear-viewport.html linear-viewport-ref.html
 fails-if(OSX==1010) fuzzy-if(Android,4,248) == linear-zero-length-1a.html linear-zero-length-1-ref.html
 fails-if(OSX==1010) fuzzy-if(Android,4,248) == linear-zero-length-1b.html linear-zero-length-1-ref.html
 fails-if(OSX==1010) fuzzy-if(Android,4,248) == linear-zero-length-1c.html linear-zero-length-1-ref.html
 == nostops.html about:blank
 == onestop.html about:blank
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,5884) fuzzy-if(cocoaWidget,9,87824) fuzzy-if(azureSkiaGL,2,88024) random-if(d2d) == radial-1a.html radial-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,5884) fuzzy-if(cocoaWidget,9,87824) fuzzy-if(azureSkiaGL,2,88024) random-if(d2d) == radial-1b.html radial-1-ref.html
-fuzzy-if(!contentSameGfxBackendAsCanvas,1,5884) fuzzy-if(cocoaWidget,9,87824) fuzzy-if(azureSkiaGL,2,88024) random-if(d2d) == radial-1c.html radial-1-ref.html
-fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,2,90000) == radial-2a.html radial-2-ref.html
-fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,2,90000) == radial-2b.html radial-2-ref.html
-fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,2,90000) == radial-2c.html radial-2-ref.html
-fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,2,90000) == radial-2d.html radial-2-ref.html
-fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,2,90000) == radial-2e.html radial-2-ref.html
-fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,2,90000) == radial-2f.html radial-2-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,5884) fuzzy-if(cocoaWidget,9,87824) fuzzy-if(azureSkiaGL,6,88024) random-if(d2d) == radial-1a.html radial-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,5884) fuzzy-if(cocoaWidget,9,87824) fuzzy-if(azureSkiaGL,6,88024) random-if(d2d) == radial-1b.html radial-1-ref.html
+fuzzy-if(!contentSameGfxBackendAsCanvas,1,5884) fuzzy-if(cocoaWidget,9,87824) fuzzy-if(azureSkiaGL,6,88024) random-if(d2d) == radial-1c.html radial-1-ref.html
+fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,4,90000) == radial-2a.html radial-2-ref.html
+fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,4,90000) == radial-2b.html radial-2-ref.html
+fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,4,90000) == radial-2c.html radial-2-ref.html
+fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,4,90000) == radial-2d.html radial-2-ref.html
+fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,4,90000) == radial-2e.html radial-2-ref.html
+fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,4,90000) == radial-2f.html radial-2-ref.html
 == radial-position-1a.html radial-position-1-ref.html
 fuzzy-if(cocoaWidget,1,28) fuzzy-if(winWidget,1,18) == radial-position-1b.html radial-position-1-ref.html
 fuzzy-if(cocoaWidget,4,22317) fuzzy-if(Android,8,771) == radial-shape-closest-corner-1a.html radial-shape-closest-corner-1-ref.html
 fuzzy(1,238) fuzzy-if(cocoaWidget,4,22608) fuzzy-if(/^Windows\x20NT\x206\./.test(http.oscpu)&&d2d,1,336) fuzzy-if(Android,8,787) == radial-shape-closest-corner-1b.html radial-shape-closest-corner-1-ref.html
 fuzzy-if(azureQuartz,2,41171) fuzzy-if(Android,8,771) == radial-shape-closest-corner-1c.html radial-shape-closest-corner-1-ref.html
 fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,17,3880) == radial-shape-closest-side-1a.html radial-shape-closest-side-1-ref.html
 fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,17,3880) == radial-shape-closest-side-1b.html radial-shape-closest-side-1-ref.html
 fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,17,3880) == radial-shape-closest-side-1c.html radial-shape-closest-side-1-ref.html
--- a/mobile/android/base/fxa/FirefoxAccounts.java
+++ b/mobile/android/base/fxa/FirefoxAccounts.java
@@ -124,17 +124,17 @@ public class FirefoxAccounts {
             return;
           }
 
           // There is a small race window here: if the user creates a new Firefox account
           // between our checks, this could erroneously report that no Firefox accounts
           // exist.
           final AndroidFxAccount fxAccount =
               AccountPickler.unpickle(context, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
-          accounts[0] = fxAccount.getAndroidAccount();
+          accounts[0] = fxAccount != null ? fxAccount.getAndroidAccount() : null;
         } finally {
           latch.countDown();
         }
       }
     });
 
     try {
       latch.await(); // Wait for the background thread to return.
--- a/mobile/android/base/fxa/authenticator/AccountPickler.java
+++ b/mobile/android/base/fxa/authenticator/AccountPickler.java
@@ -75,22 +75,23 @@ public class AccountPickler {
 
   // Deprecated, but maintained for migration purposes.
   public static final String KEY_IS_SYNCING_ENABLED = "isSyncingEnabled";
 
   public static final String KEY_BUNDLE = "bundle";
 
   /**
    * Remove Firefox account persisted to disk.
+   * This operation is synchronized to avoid race condition while deleting the account.
    *
    * @param context Android context.
    * @param filename name of persisted pickle file; must not contain path separators.
    * @return <code>true</code> if given pickle existed and was successfully deleted.
    */
-  public static boolean deletePickle(final Context context, final String filename) {
+  public synchronized static boolean deletePickle(final Context context, final String filename) {
     return context.deleteFile(filename);
   }
 
   public static ExtendedJSONObject toJSON(final AndroidFxAccount account, final long now) {
     final ExtendedJSONObject o = new ExtendedJSONObject();
     o.put(KEY_PICKLE_VERSION, PICKLE_VERSION);
     o.put(KEY_PICKLE_TIMESTAMP, now);
 
@@ -117,21 +118,22 @@ public class AccountPickler {
     }
     o.put(KEY_BUNDLE, bundle);
 
     return o;
   }
 
   /**
    * Persist Firefox account to disk as a JSON object.
+   * This operation is synchronized to avoid race condition while deleting the account.
    *
-   * @param AndroidFxAccount the account to persist to disk
+   * @param account the AndroidFxAccount to persist to disk
    * @param filename name of file to persist to; must not contain path separators.
    */
-  public static void pickle(final AndroidFxAccount account, final String filename) {
+  public synchronized static void pickle(final AndroidFxAccount account, final String filename) {
     final ExtendedJSONObject o = toJSON(account, System.currentTimeMillis());
     writeToDisk(account.context, filename, o);
   }
 
   private static void writeToDisk(final Context context, final String filename,
       final ExtendedJSONObject pickle) {
     try {
       final FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
@@ -150,24 +152,25 @@ public class AccountPickler {
     } catch (Exception e) {
       Logger.warn(LOG_TAG, "Caught exception persisting account settings to " + filename +
           "; ignoring.", e);
     }
   }
 
   /**
    * Create Android account from saved JSON object. Assumes that an account does not exist.
+   * This operation is synchronized to avoid race condition while deleting the account.
    *
    * @param context
    *          Android context.
    * @param filename
    *          name of file to read from; must not contain path separators.
    * @return created Android account, or null on error.
    */
-  public static AndroidFxAccount unpickle(final Context context, final String filename) {
+  public synchronized static AndroidFxAccount unpickle(final Context context, final String filename) {
     final String jsonString = Utils.readFile(context, filename);
     if (jsonString == null) {
       Logger.info(LOG_TAG, "Pickle file '" + filename + "' not found; aborting.");
       return null;
     }
 
     ExtendedJSONObject json = null;
     try {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -683,17 +683,17 @@ pref("gfx.font_rendering.opentype_svg.en
 // e.g., pref("gfx.canvas.azure.backends", "direct2d,skia,cairo");
 pref("gfx.canvas.azure.backends", "direct2d1.1,direct2d,skia,cairo");
 pref("gfx.content.azure.backends", "direct2d1.1,direct2d,cairo");
 #else
 #ifdef XP_MACOSX
 pref("gfx.content.azure.backends", "cg");
 pref("gfx.canvas.azure.backends", "skia");
 // Accelerated cg canvas where available (10.7+)
-pref("gfx.canvas.azure.accelerated", false);
+pref("gfx.canvas.azure.accelerated", true);
 #else
 pref("gfx.canvas.azure.backends", "cairo");
 pref("gfx.content.azure.backends", "cairo");
 #endif
 #endif
 
 #ifdef MOZ_WIDGET_GTK2
 pref("gfx.content.azure.backends", "cairo");
@@ -4424,17 +4424,17 @@ pref("dom.sms.maxReadAheadEntries", 0);
 // WebContacts
 pref("dom.mozContacts.enabled", false);
 
 // WebAlarms
 pref("dom.mozAlarms.enabled", false);
 
 // Push
 
-#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+#if !defined(MOZ_B2G) && !defined(ANDROID)
 // Desktop prefs
 #ifdef RELEASE_BUILD
 pref("dom.push.enabled", false);
 #else
 pref("dom.push.enabled", true);
 #endif
 #else
 // Mobile prefs
@@ -4873,17 +4873,24 @@ pref("dom.presentation.tcp_server.debug"
 
 #ifdef XP_MACOSX
 // Use raw ICU instead of CoreServices API in Unicode collation
 pref("intl.collation.mac.use_icu", true);
 
 // Enable NSTextInput protocol for use with IMEs that have not
 // been updated to use the NSTextInputClient protocol.
 pref("intl.ime.nstextinput.enable", false);
+
+#if !defined(RELEASE_BUILD) || defined(DEBUG)
+// In non-release builds we crash by default on insecure text input (when a
+// password editor has focus but secure event input isn't enabled).  The
+// following pref, when turned on, disables this behavior.  See bug 1188425.
+pref("intl.allow-insecure-text-input", false);
 #endif
+#endif // XP_MACOSX
 
 // Enable meta-viewport support in remote APZ-enabled frames.
 pref("dom.meta-viewport.enabled", false);
 
 // MozSettings debugging prefs for each component
 pref("dom.mozSettings.SettingsDB.debug.enabled", false);
 pref("dom.mozSettings.SettingsManager.debug.enabled", false);
 pref("dom.mozSettings.SettingsRequestManager.debug.enabled", false);
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -1466,16 +1466,17 @@ var WalkerActor = protocol.ActorClass({
    * Watch the given document node for mutations using the DOM observer
    * API.
    */
   _watchDocument: function(actor) {
     let node = actor.rawNode;
     // Create the observer on the node's actor.  The node will make sure
     // the observer is cleaned up when the actor is released.
     actor.observer = new actor.rawNode.defaultView.MutationObserver(this.onMutations);
+    actor.observer.mergeAttributeRecords = true;
     actor.observer.observe(node, {
       attributes: true,
       characterData: true,
       childList: true,
       subtree: true
     });
   },
 
@@ -2731,39 +2732,17 @@ var WalkerActor = protocol.ActorClass({
       for (let node of this._orphaned) {
         // Release the orphaned node.  Nodes or children that have been
         // retained will be moved to this._retainedOrphans.
         this.releaseNode(node);
       }
       this._orphaned = new Set();
     }
 
-
-    // Clear out any duplicate attribute mutations before sending them over
-    // the protocol.  Keep only the most recent change for each attribute.
-    let targetMap = {};
-    let filtered = pending.reverse().filter(mutation => {
-      if (mutation.type === "attributes") {
-        if (!targetMap[mutation.target]) {
-          targetMap[mutation.target] = {};
-        }
-        let attributesForTarget = targetMap[mutation.target];
-
-        if (attributesForTarget[mutation.attributeName]) {
-          // Since the array was reversed, if we've seen this attribute already
-          // then this one is a duplicate and can be skipped.
-          return false;
-        }
-
-        attributesForTarget[mutation.attributeName] = true;
-      }
-      return true;
-    }).reverse();
-
-    return filtered;
+    return pending;
   }, {
     request: {
       cleanup: Option(0)
     },
     response: {
       mutations: RetVal("array:dommutation")
     }
   }),
--- a/toolkit/mozapps/installer/packager.mk
+++ b/toolkit/mozapps/installer/packager.mk
@@ -147,22 +147,29 @@ ifdef INSTALL_SDK # Here comes the hard 
 	$(RM) -f $(DESTDIR)$(sdkdir)/lib $(DESTDIR)$(sdkdir)/bin $(DESTDIR)$(sdkdir)/include $(DESTDIR)$(sdkdir)/include $(DESTDIR)$(sdkdir)/sdk/idl $(DESTDIR)$(sdkdir)/idl
 	ln -s $(sdkdir)/sdk/lib $(DESTDIR)$(sdkdir)/lib
 	ln -s $(installdir) $(DESTDIR)$(sdkdir)/bin
 	ln -s $(includedir) $(DESTDIR)$(sdkdir)/include
 	ln -s $(idldir) $(DESTDIR)$(sdkdir)/idl
 endif # INSTALL_SDK
 
 make-sdk:
+ifndef SDK_UNIFY
 	$(MAKE) stage-package UNIVERSAL_BINARY= STAGE_SDK=1 MOZ_PKG_DIR=sdk-stage
+endif
 	@echo 'Packaging SDK...'
 	$(RM) -rf $(DIST)/$(MOZ_APP_NAME)-sdk
 	$(NSINSTALL) -D $(DIST)/$(MOZ_APP_NAME)-sdk/bin
+ifdef SDK_UNIFY
+	(cd $(UNIFY_DIST)/sdk-stage && $(TAR) $(TAR_CREATE_FLAGS) - .) | \
+	  (cd $(DIST)/$(MOZ_APP_NAME)-sdk/bin && tar -xf -)
+else
 	(cd $(DIST)/sdk-stage && $(TAR) $(TAR_CREATE_FLAGS) - .) | \
 	  (cd $(DIST)/$(MOZ_APP_NAME)-sdk/bin && tar -xf -)
+endif
 	$(NSINSTALL) -D $(DIST)/$(MOZ_APP_NAME)-sdk/host/bin
 	(cd $(DIST)/host/bin && $(TAR) $(TAR_CREATE_FLAGS) - .) | \
 	  (cd $(DIST)/$(MOZ_APP_NAME)-sdk/host/bin && tar -xf -)
 	$(NSINSTALL) -D $(DIST)/$(MOZ_APP_NAME)-sdk/sdk
 	find $(DIST)/sdk -name '*.pyc' | xargs rm -f
 	(cd $(DIST)/sdk && $(TAR) $(TAR_CREATE_FLAGS) - .) | \
 	  (cd $(DIST)/$(MOZ_APP_NAME)-sdk/sdk && tar -xf -)
 	$(NSINSTALL) -D $(DIST)/$(MOZ_APP_NAME)-sdk/include
@@ -175,16 +182,21 @@ make-sdk:
 # sdk/lib is the same as sdk/sdk/lib
 	(cd $(DIST)/sdk/lib && $(TAR) $(TAR_CREATE_FLAGS) - .) | \
 	  (cd $(DIST)/$(MOZ_APP_NAME)-sdk/lib && tar -xf -)
 	$(NSINSTALL) -D $(DIST)/$(SDK_PATH)
 ifndef PKG_SKIP_STRIP
 	USE_ELF_HACK= $(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/strip.py $(DIST)/$(MOZ_APP_NAME)-sdk
 endif
 	cd $(DIST) && $(MAKE_SDK)
+ifdef UNIFY_DIST
+ifndef SDK_UNIFY
+	$(MAKE) -C $(UNIFY_DIST)/.. sdk SDK_UNIFY=1
+endif
+endif
 
 checksum:
 	mkdir -p `dirname $(CHECKSUM_FILE)`
 	@$(PYTHON) $(MOZILLA_DIR)/build/checksums.py \
 		-o $(CHECKSUM_FILE) \
 		$(CHECKSUM_ALGORITHM_PARAM) \
 		-s $(call QUOTED_WILDCARD,$(DIST)) \
 		$(UPLOAD_FILES)
--- a/toolkit/mozapps/installer/upload-files.mk
+++ b/toolkit/mozapps/installer/upload-files.mk
@@ -753,16 +753,23 @@ UPLOAD_FILES += \
   $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(SYMBOL_FULL_ARCHIVE_BASENAME).zip)
 endif
 
 ifdef MOZ_CODE_COVERAGE
 UPLOAD_FILES += \
   $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(CODE_COVERAGE_ARCHIVE_BASENAME).zip)
 endif
 
+ifdef UNIFY_DIST
+UNIFY_ARCH := $(notdir $(patsubst %/,%,$(dir $(UNIFY_DIST))))
+UPLOAD_FILES += \
+  $(wildcard $(UNIFY_DIST)/$(SDK_PATH)$(PKG_BASENAME)-$(UNIFY_ARCH).sdk$(SDK_SUFFIX)) \
+  $(wildcard $(UNIFY_DIST)/$(SDK_PATH)$(PKG_BASENAME)-$(UNIFY_ARCH).sdk$(SDK_SUFFIX).asc)
+endif
+
 SIGN_CHECKSUM_CMD=
 ifdef MOZ_SIGN_CMD
 # If we're signing with gpg, we'll have a bunch of extra detached signatures to
 # upload. We also want to sign our checksums file
 SIGN_CHECKSUM_CMD=$(MOZ_SIGN_CMD) -f gpg $(CHECKSUM_FILE)
 
 CHECKSUM_FILES += $(CHECKSUM_FILE).asc
 UPLOAD_FILES += $(call QUOTED_WILDCARD,$(DIST)/$(COMPLETE_MAR).asc)
--- a/widget/android/GfxInfo.cpp
+++ b/widget/android/GfxInfo.cpp
@@ -70,18 +70,17 @@ public:
   }
 
   void EnsureInitialized() {
     if (mReady) {
       return;
     }
 
     nsRefPtr<gl::GLContext> gl;
-    bool requireCompatProfile = true;
-    gl = gl::GLContextProvider::CreateHeadless(requireCompatProfile);
+    gl = gl::GLContextProvider::CreateHeadless(gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE);
 
     if (!gl) {
       // Setting mReady to true here means that we won't retry. Everything will
       // remain blacklisted forever. Ideally, we would like to update that once
       // any GLContext is successfully created, like the compositor's GLContext.
       mReady = true;
       return;
     }
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -5437,17 +5437,18 @@ static int32_t RoundUp(double aDouble)
   // otherwise why is the OS sending us a key down event?  But it's just
   // possible we're in Gecko's hidden window, so we check first.
   NSWindow *viewWindow = [self window];
   if (viewWindow && [viewWindow isKeyWindow]) {
     [viewWindow orderWindow:NSWindowAbove relativeTo:0];
   }
 
 #if !defined(RELEASE_BUILD) || defined(DEBUG)
-  if (mGeckoChild && mTextInputHandler && mTextInputHandler->IsFocused()) {
+  if (!Preferences::GetBool("intl.allow-insecure-text-input", false) &&
+      mGeckoChild && mTextInputHandler && mTextInputHandler->IsFocused()) {
 #ifdef MOZ_CRASHREPORTER
     NSWindow* window = [self window];
     NSString* info = [NSString stringWithFormat:@"\nview [%@], window [%@], window is key %i, is fullscreen %i, app is active %i",
                       self, window, [window isKeyWindow], ([window styleMask] & (1 << 14)) != 0,
                       [NSApp isActive]];
     nsAutoCString additionalInfo([info UTF8String]);
 #endif
     if (mGeckoChild->GetInputContext().IsPasswordEditor() &&
--- a/widget/gtk/nsDeviceContextSpecG.cpp
+++ b/widget/gtk/nsDeviceContextSpecG.cpp
@@ -269,38 +269,45 @@ gboolean nsDeviceContextSpecGTK::Printer
   // Find the printer whose name matches the one inside the settings.
   nsXPIDLString printerName;
   nsresult rv =
     spec->mPrintSettings->GetPrinterName(getter_Copies(printerName));
   if (NS_SUCCEEDED(rv) && printerName) {
     NS_ConvertUTF16toUTF8 requestedName(printerName);
     const char* currentName = gtk_printer_get_name(aPrinter);
     if (requestedName.Equals(currentName)) {
-      nsDeviceContextSpecGTK::StartPrintJob(spec, aPrinter);
+      spec->mPrintSettings->SetGtkPrinter(aPrinter);
+
+      // Bug 1145916 - attempting to kick off a print job for this printer
+      // during this tick of the event loop will result in the printer backend
+      // misunderstanding what the capabilities of the printer are due to a
+      // GTK bug (https://bugzilla.gnome.org/show_bug.cgi?id=753041). We
+      // sidestep this by deferring the print to the next tick.
+      nsCOMPtr<nsIRunnable> event =
+        NS_NewRunnableMethod(spec, &nsDeviceContextSpecGTK::StartPrintJob);
+      NS_DispatchToCurrentThread(event);
       return TRUE;
     }
   }
 
   // We haven't found it yet - keep searching...
   return FALSE;
 }
 
-/* static */
-void nsDeviceContextSpecGTK::StartPrintJob(nsDeviceContextSpecGTK* spec,
-                                           GtkPrinter* printer) {
-  GtkPrintJob* job = gtk_print_job_new(spec->mTitle.get(),
-                                       printer,
-                                       spec->mGtkPrintSettings,
-                                       spec->mGtkPageSetup);
+void nsDeviceContextSpecGTK::StartPrintJob() {
+  GtkPrintJob* job = gtk_print_job_new(mTitle.get(),
+                                       mPrintSettings->GetGtkPrinter(),
+                                       mGtkPrintSettings,
+                                       mGtkPageSetup);
 
-  if (!gtk_print_job_set_source_file(job, spec->mSpoolName.get(), nullptr))
+  if (!gtk_print_job_set_source_file(job, mSpoolName.get(), nullptr))
     return;
 
-  NS_ADDREF(spec->mSpoolFile.get());
-  gtk_print_job_send(job, print_callback, spec->mSpoolFile, ns_release_macro);
+  NS_ADDREF(mSpoolFile.get());
+  gtk_print_job_send(job, print_callback, mSpoolFile, ns_release_macro);
 }
 
 void
 nsDeviceContextSpecGTK::EnumeratePrinters()
 {
   gtk_enumerate_printers(&nsDeviceContextSpecGTK::PrinterEnumerator, this,
                          nullptr, TRUE);
 }
@@ -323,17 +330,17 @@ NS_IMETHODIMP nsDeviceContextSpecGTK::En
     // In the multi-process case, we proxy the print settings dialog over to
     // the parent process, and only get the name of the printer back on the
     // content process side. In that case, we need to enumerate the printers
     // on the content side, and find a printer with a matching name.
 
     GtkPrinter* printer = mPrintSettings->GetGtkPrinter();
     if (printer) {
       // We have a printer, so we can print right away.
-      nsDeviceContextSpecGTK::StartPrintJob(this, printer);
+      StartPrintJob();
     } else {
       // We don't have a printer. We have to enumerate the printers and find
       // one with a matching name.
       nsCOMPtr<nsIRunnable> event =
         NS_NewRunnableMethod(this, &nsDeviceContextSpecGTK::EnumeratePrinters);
       NS_DispatchToCurrentThread(event);
     }
   } else {
--- a/widget/gtk/nsDeviceContextSpecG.h
+++ b/widget/gtk/nsDeviceContextSpecG.h
@@ -51,19 +51,18 @@ protected:
   GtkPageSetup*     mGtkPageSetup;
 
   nsCString         mSpoolName;
   nsCOMPtr<nsIFile> mSpoolFile;
   nsCString         mTitle;
 
 private:
   void EnumeratePrinters();
+  void StartPrintJob();
   static gboolean PrinterEnumerator(GtkPrinter *aPrinter, gpointer aData);
-  static void StartPrintJob(nsDeviceContextSpecGTK *spec,
-                            GtkPrinter *printer);
 };
 
 //-------------------------------------------------------------------------
 // Printer Enumerator
 //-------------------------------------------------------------------------
 class nsPrinterEnumeratorGTK final : public nsIPrinterEnumerator
 {
   ~nsPrinterEnumeratorGTK() {}
--- a/xpcom/tests/TestTimers.cpp
+++ b/xpcom/tests/TestTimers.cpp
@@ -13,16 +13,21 @@
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "prinrval.h"
 #include "prmon.h"
 #include "prthread.h"
 #include "mozilla/Attributes.h"
 
 #include "mozilla/ReentrantMonitor.h"
+
+#include <list>
+#include <vector>
+#include <stdio.h>
+
 using namespace mozilla;
 
 typedef nsresult(*TestFuncPtr)();
 
 class AutoTestThread
 {
 public:
   AutoTestThread() {
@@ -50,17 +55,17 @@ private:
   nsCOMPtr<nsIThread> mThread;
 };
 
 class AutoCreateAndDestroyReentrantMonitor
 {
 public:
   AutoCreateAndDestroyReentrantMonitor() {
     mReentrantMonitor = new ReentrantMonitor("TestTimers::AutoMon");
-    NS_ASSERTION(mReentrantMonitor, "Out of memory!");
+    MOZ_ASSERT(mReentrantMonitor, "Out of memory!");
   }
 
   ~AutoCreateAndDestroyReentrantMonitor() {
     delete mReentrantMonitor;
   }
 
   operator ReentrantMonitor* () {
     return mReentrantMonitor;
@@ -74,22 +79,22 @@ class TimerCallback final : public nsITi
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   TimerCallback(nsIThread** aThreadPtr, ReentrantMonitor* aReentrantMonitor)
   : mThreadPtr(aThreadPtr), mReentrantMonitor(aReentrantMonitor) { }
 
   NS_IMETHOD Notify(nsITimer* aTimer) override {
-    NS_ASSERTION(mThreadPtr, "Callback was not supposed to be called!");
+    MOZ_ASSERT(mThreadPtr, "Callback was not supposed to be called!");
     nsCOMPtr<nsIThread> current(do_GetCurrentThread());
 
     ReentrantMonitorAutoEnter mon(*mReentrantMonitor);
 
-    NS_ASSERTION(!*mThreadPtr, "Timer called back more than once!");
+    MOZ_ASSERT(!*mThreadPtr, "Timer called back more than once!");
     *mThreadPtr = current;
 
     mon.Notify();
 
     return NS_OK;
   }
 private:
   ~TimerCallback() {}
@@ -119,18 +124,17 @@ TestTargetedTimers()
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsIThread* notifiedThread = nullptr;
 
   nsCOMPtr<nsITimerCallback> callback =
     new TimerCallback(&notifiedThread, newMon);
   NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
 
-  rv = timer->InitWithCallback(callback, PR_MillisecondsToInterval(2000),
-                               nsITimer::TYPE_ONE_SHOT);
+  rv = timer->InitWithCallback(callback, 2000, nsITimer::TYPE_ONE_SHOT);
   NS_ENSURE_SUCCESS(rv, rv);
 
   ReentrantMonitorAutoEnter mon(*newMon);
   while (!notifiedThread) {
     mon.Wait();
   }
   NS_ENSURE_TRUE(notifiedThread == testThread, NS_ERROR_FAILURE);
 
@@ -152,35 +156,334 @@ TestTimerWithStoppedTarget()
   rv = timer->SetTarget(target);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If this is called, we'll assert
   nsCOMPtr<nsITimerCallback> callback =
     new TimerCallback(nullptr, nullptr);
   NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
 
-  rv = timer->InitWithCallback(callback, PR_MillisecondsToInterval(100),
-                               nsITimer::TYPE_ONE_SHOT);
+  rv = timer->InitWithCallback(callback, 100, nsITimer::TYPE_ONE_SHOT);
   NS_ENSURE_SUCCESS(rv, rv);
 
   testThread->Shutdown();
 
   PR_Sleep(400);
 
   return NS_OK;
 }
 
+#define FUZZ_MAX_TIMEOUT 9
+class FuzzTestThreadState final : public nsITimerCallback {
+  public:
+    NS_DECL_THREADSAFE_ISUPPORTS
+
+    explicit FuzzTestThreadState(nsIThread* thread) :
+      mThread(thread),
+      mStopped(false)
+    {}
+
+    class StartRunnable final : public nsRunnable {
+      public:
+        explicit StartRunnable(FuzzTestThreadState* threadState) :
+          mThreadState(threadState)
+        {}
+
+        NS_IMETHOD Run() override
+        {
+          mThreadState->ScheduleOrCancelTimers();
+          return NS_OK;
+        }
+
+      private:
+        nsRefPtr<FuzzTestThreadState> mThreadState;
+    };
+
+    void Start()
+    {
+      nsCOMPtr<nsIRunnable> runnable = new StartRunnable(this);
+      nsresult rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+      if (NS_FAILED(rv)) {
+        MOZ_ASSERT(false, "Failed to dispatch StartRunnable.");
+      }
+    }
+
+    void Stop()
+    {
+      mStopped = true;
+    }
+
+    NS_IMETHOD Notify(nsITimer* aTimer) override
+    {
+      bool onCorrectThread;
+      nsresult rv = mThread->IsOnCurrentThread(&onCorrectThread);
+      MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to perform thread check.");
+      MOZ_ASSERT(onCorrectThread, "Notify invoked on wrong thread.");
+
+      uint32_t delay;
+      rv = aTimer->GetDelay(&delay);
+      if (NS_FAILED(rv)) {
+        MOZ_ASSERT(false, "GetDelay failed.");
+        return rv;
+      }
+
+      if (delay > FUZZ_MAX_TIMEOUT) {
+        MOZ_ASSERT(false, "Delay was an invalid value for this test.");
+        return NS_ERROR_FAILURE;
+      }
+
+      uint32_t type;
+      rv = aTimer->GetType(&type);
+      MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to get timer type.");
+      MOZ_ASSERT(type <= nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP);
+
+      if (type == nsITimer::TYPE_ONE_SHOT) {
+        if (mOneShotTimersByDelay[delay].empty()) {
+          MOZ_ASSERT(false, "Unexpected one-shot timer.");
+          return NS_ERROR_FAILURE;
+        }
+
+        if (mOneShotTimersByDelay[delay].front().get() != aTimer) {
+          MOZ_ASSERT(false,
+                     "One-shot timers for a given duration have been reordered.");
+          return NS_ERROR_FAILURE;
+        }
+
+        mOneShotTimersByDelay[delay].pop_front();
+        --mTimersOutstanding;
+      } else if (mStopped) {
+        CancelRepeatingTimer(aTimer);
+      }
+
+      ScheduleOrCancelTimers();
+      RescheduleSomeTimers();
+      return NS_OK;
+    }
+
+    bool HasTimersOutstanding() const
+    {
+      return !!mTimersOutstanding;
+    }
+
+  private:
+    ~FuzzTestThreadState()
+    {
+      for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) {
+        MOZ_ASSERT(mOneShotTimersByDelay[i].empty(),
+                   "Timers remain at end of test.");
+      }
+    }
+
+    uint32_t GetRandomType() const
+    {
+      return rand() % (nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP + 1);
+    }
+
+    size_t CountOneShotTimers() const
+    {
+      size_t count = 0;
+      for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) {
+        count += mOneShotTimersByDelay[i].size();
+      }
+      return count;
+    }
+
+    void ScheduleOrCancelTimers()
+    {
+      if (mStopped) {
+        return;
+      }
+
+      const size_t numTimersDesired = (rand() % 100) + 100;
+      MOZ_ASSERT(numTimersDesired >= 100);
+      MOZ_ASSERT(numTimersDesired < 200);
+      int adjustment = numTimersDesired - mTimersOutstanding;
+
+      while (adjustment > 0) {
+        CreateRandomTimer();
+        --adjustment;
+      }
+
+      while (adjustment < 0) {
+        CancelRandomTimer();
+        ++adjustment;
+      }
+
+      MOZ_ASSERT(numTimersDesired == mTimersOutstanding);
+    }
+
+    void RescheduleSomeTimers()
+    {
+      if (mStopped) {
+        return;
+      }
+
+      static const size_t kNumRescheduled = 40;
+
+      // Reschedule some timers with a Cancel first.
+      for (size_t i = 0; i < kNumRescheduled; ++i) {
+        InitRandomTimer(CancelRandomTimer().get());
+      }
+      // Reschedule some timers without a Cancel first.
+      for (size_t i = 0; i < kNumRescheduled; ++i) {
+        InitRandomTimer(RemoveRandomTimer().get());
+      }
+    }
+
+    void CreateRandomTimer()
+    {
+      nsresult rv;
+      nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+      if (NS_FAILED(rv)) {
+        MOZ_ASSERT(false, "Failed to create timer.");
+        return;
+      }
+
+      rv = timer->SetTarget(static_cast<nsIEventTarget*>(mThread.get()));
+      if (NS_FAILED(rv)) {
+        MOZ_ASSERT(false, "Failed to set target.");
+        return;
+      }
+
+      InitRandomTimer(timer.get());
+    }
+
+    nsCOMPtr<nsITimer> CancelRandomTimer()
+    {
+      nsCOMPtr<nsITimer> timer(RemoveRandomTimer());
+      timer->Cancel();
+      return timer;
+    }
+
+    nsCOMPtr<nsITimer> RemoveRandomTimer()
+    {
+      MOZ_ASSERT(mTimersOutstanding);
+
+      if ((GetRandomType() == nsITimer::TYPE_ONE_SHOT && CountOneShotTimers())
+          || mRepeatingTimers.empty()) {
+        uint32_t delayToRemove = rand() % (FUZZ_MAX_TIMEOUT + 1);
+        while (mOneShotTimersByDelay[delayToRemove].empty()) {
+          // ++delayToRemove mod FUZZ_MAX_TIMEOUT + 1
+          delayToRemove = (delayToRemove + 1) % (FUZZ_MAX_TIMEOUT + 1);
+        }
+
+        uint32_t indexToRemove =
+          rand() % mOneShotTimersByDelay[delayToRemove].size();
+
+        for (auto it = mOneShotTimersByDelay[delayToRemove].begin();
+             it != mOneShotTimersByDelay[delayToRemove].end();
+             ++it) {
+          if (indexToRemove) {
+            --indexToRemove;
+            continue;
+          }
+
+          nsCOMPtr<nsITimer> removed = *it;
+          mOneShotTimersByDelay[delayToRemove].erase(it);
+          --mTimersOutstanding;
+          return removed;
+        }
+      } else {
+        size_t indexToRemove = rand() % mRepeatingTimers.size();
+        nsCOMPtr<nsITimer> removed(mRepeatingTimers[indexToRemove]);
+        mRepeatingTimers.erase(mRepeatingTimers.begin() + indexToRemove);
+        --mTimersOutstanding;
+        return removed;
+      }
+
+      MOZ_ASSERT_UNREACHABLE("Unable to remove a timer");
+      return nullptr;
+    }
+
+    void InitRandomTimer(nsITimer* aTimer)
+    {
+      // Between 0 and FUZZ_MAX_TIMEOUT
+      uint32_t delay = rand() % (FUZZ_MAX_TIMEOUT + 1);
+      uint32_t type = GetRandomType();
+      nsresult rv = aTimer->InitWithCallback(this, delay, type);
+      if (NS_FAILED(rv)) {
+        MOZ_ASSERT(false, "Failed to set timer.");
+        return;
+      }
+
+      if (type == nsITimer::TYPE_ONE_SHOT) {
+        mOneShotTimersByDelay[delay].push_back(aTimer);
+      } else {
+        mRepeatingTimers.push_back(aTimer);
+      }
+      ++mTimersOutstanding;
+    }
+
+    void CancelRepeatingTimer(nsITimer* aTimer)
+    {
+      for (auto it = mRepeatingTimers.begin();
+           it != mRepeatingTimers.end();
+           ++it) {
+        if (it->get() == aTimer) {
+          mRepeatingTimers.erase(it);
+          aTimer->Cancel();
+          --mTimersOutstanding;
+          return;
+        }
+      }
+    }
+
+    nsCOMPtr<nsIThread> mThread;
+    // Scheduled timers, indexed by delay between 0-9 ms, in lists
+    // with most recently scheduled last.
+    std::list<nsCOMPtr<nsITimer>> mOneShotTimersByDelay[FUZZ_MAX_TIMEOUT + 1];
+    std::vector<nsCOMPtr<nsITimer>> mRepeatingTimers;
+    Atomic<bool> mStopped;
+    Atomic<size_t> mTimersOutstanding;
+};
+
+NS_IMPL_ISUPPORTS(FuzzTestThreadState, nsITimerCallback)
+
+nsresult
+FuzzTestTimers()
+{
+  static const size_t kNumThreads(10);
+  AutoTestThread threads[kNumThreads];
+  nsRefPtr<FuzzTestThreadState> threadStates[kNumThreads];
+
+  for (size_t i = 0; i < kNumThreads; ++i) {
+    threadStates[i] = new FuzzTestThreadState(&*threads[i]);
+    threadStates[i]->Start();
+  }
+
+  PR_Sleep(PR_MillisecondsToInterval(20000));
+
+  for (size_t i = 0; i < kNumThreads; ++i) {
+    threadStates[i]->Stop();
+  }
+
+  // Wait at most 10 seconds for all outstanding timers to pop
+  PRIntervalTime start = PR_IntervalNow();
+  for (auto& threadState : threadStates) {
+    while (threadState->HasTimersOutstanding()) {
+      if (PR_IntervalToMilliseconds(PR_IntervalNow() - start) > 10000) {
+        MOZ_ASSERT(false, "Timed out waiting for all timers to pop");
+        return NS_ERROR_FAILURE;
+      }
+      PR_Sleep(PR_MillisecondsToInterval(10));
+    }
+  }
+
+  return NS_OK;
+}
+
 int main(int argc, char** argv)
 {
   ScopedXPCOM xpcom("TestTimers");
   NS_ENSURE_FALSE(xpcom.failed(), 1);
 
   static TestFuncPtr testsToRun[] = {
     TestTargetedTimers,
-    TestTimerWithStoppedTarget
+    TestTimerWithStoppedTarget,
+    FuzzTestTimers
   };
   static uint32_t testCount = sizeof(testsToRun) / sizeof(testsToRun[0]);
 
   for (uint32_t i = 0; i < testCount; i++) {
     nsresult rv = testsToRun[i]();
     NS_ENSURE_SUCCESS(rv, 1);
   }
 
--- a/xpcom/threads/TimerThread.cpp
+++ b/xpcom/threads/TimerThread.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsTimerImpl.h"
 #include "TimerThread.h"
 
 #include "nsThreadUtils.h"
+#include "plarena.h"
 #include "pratom.h"
 
 #include "nsIObserverService.h"
 #include "nsIServiceManager.h"
 #include "mozilla/Services.h"
 #include "mozilla/ChaosMode.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/BinarySearch.h"
@@ -75,30 +76,226 @@ TimerObserverRunnable::Run()
     observerService->AddObserver(mObserver, "suspend_process_notification", false);
     observerService->AddObserver(mObserver, "resume_process_notification", false);
   }
   return NS_OK;
 }
 
 } // namespace
 
+namespace {
+
+// TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents.
+// It's needed to avoid contention over the default allocator lock when
+// firing timer events (see bug 733277).  The thread-safety is required because
+// nsTimerEvent objects are allocated on the timer thread, and freed on another
+// thread.  Because TimerEventAllocator has its own lock, contention over that
+// lock is limited to the allocation and deallocation of nsTimerEvent objects.
+//
+// Because this allocator is layered over PLArenaPool, it never shrinks -- even
+// "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list
+// for later recycling.  So the amount of memory consumed will always be equal
+// to the high-water mark consumption.  But nsTimerEvents are small and it's
+// unusual to have more than a few hundred of them, so this shouldn't be a
+// problem in practice.
+
+class TimerEventAllocator
+{
+private:
+  struct FreeEntry
+  {
+    FreeEntry* mNext;
+  };
+
+  PLArenaPool mPool;
+  FreeEntry* mFirstFree;
+  mozilla::Monitor mMonitor;
+
+public:
+  TimerEventAllocator()
+    : mFirstFree(nullptr)
+    , mMonitor("TimerEventAllocator")
+  {
+    PL_InitArenaPool(&mPool, "TimerEventPool", 4096, /* align = */ 0);
+  }
+
+  ~TimerEventAllocator()
+  {
+    PL_FinishArenaPool(&mPool);
+  }
+
+  void* Alloc(size_t aSize);
+  void Free(void* aPtr);
+};
+
+} // namespace
+
+class nsTimerEvent : public nsRunnable
+{
+public:
+  NS_IMETHOD Run();
+
+  nsTimerEvent()
+    : mTimer()
+    , mGeneration(0)
+  {
+    MOZ_COUNT_CTOR(nsTimerEvent);
+
+    // Note: We override operator new for this class, and the override is
+    // fallible!
+    sAllocatorUsers++;
+  }
+
+  TimeStamp mInitTime;
+
+  static void Init();
+  static void Shutdown();
+  static void DeleteAllocatorIfNeeded();
+
+  static void* operator new(size_t aSize) CPP_THROW_NEW
+  {
+    return sAllocator->Alloc(aSize);
+  }
+  void operator delete(void* aPtr)
+  {
+    sAllocator->Free(aPtr);
+    DeleteAllocatorIfNeeded();
+  }
+
+  already_AddRefed<nsTimerImpl> ForgetTimer()
+  {
+    return mTimer.forget();
+  }
+
+  void SetTimer(already_AddRefed<nsTimerImpl> aTimer)
+  {
+    mTimer = aTimer;
+    mGeneration = mTimer->GetGeneration();
+  }
+
+private:
+  ~nsTimerEvent()
+  {
+    MOZ_COUNT_DTOR(nsTimerEvent);
+
+    MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0,
+               "This will result in us attempting to deallocate the nsTimerEvent allocator twice");
+    sAllocatorUsers--;
+  }
+
+  nsRefPtr<nsTimerImpl> mTimer;
+  int32_t      mGeneration;
+
+  static TimerEventAllocator* sAllocator;
+  static Atomic<int32_t> sAllocatorUsers;
+  static bool sCanDeleteAllocator;
+};
+
+TimerEventAllocator* nsTimerEvent::sAllocator = nullptr;
+Atomic<int32_t> nsTimerEvent::sAllocatorUsers;
+bool nsTimerEvent::sCanDeleteAllocator = false;
+
+namespace {
+
+void*
+TimerEventAllocator::Alloc(size_t aSize)
+{
+  MOZ_ASSERT(aSize == sizeof(nsTimerEvent));
+
+  mozilla::MonitorAutoLock lock(mMonitor);
+
+  void* p;
+  if (mFirstFree) {
+    p = mFirstFree;
+    mFirstFree = mFirstFree->mNext;
+  } else {
+    PL_ARENA_ALLOCATE(p, &mPool, aSize);
+    if (!p) {
+      return nullptr;
+    }
+  }
+
+  return p;
+}
+
+void
+TimerEventAllocator::Free(void* aPtr)
+{
+  mozilla::MonitorAutoLock lock(mMonitor);
+
+  FreeEntry* entry = reinterpret_cast<FreeEntry*>(aPtr);
+
+  entry->mNext = mFirstFree;
+  mFirstFree = entry;
+}
+
+} // namespace
+
+void
+nsTimerEvent::Init()
+{
+  sAllocator = new TimerEventAllocator();
+}
+
+void
+nsTimerEvent::Shutdown()
+{
+  sCanDeleteAllocator = true;
+  DeleteAllocatorIfNeeded();
+}
+
+void
+nsTimerEvent::DeleteAllocatorIfNeeded()
+{
+  if (sCanDeleteAllocator && sAllocatorUsers == 0) {
+    delete sAllocator;
+    sAllocator = nullptr;
+  }
+}
+
+NS_IMETHODIMP
+nsTimerEvent::Run()
+{
+  if (mGeneration != mTimer->GetGeneration()) {
+    return NS_OK;
+  }
+
+  if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
+    TimeStamp now = TimeStamp::Now();
+    MOZ_LOG(GetTimerLog(), LogLevel::Debug,
+           ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n",
+            this, (now - mInitTime).ToMilliseconds()));
+  }
+
+  mTimer->Fire();
+  // Since nsTimerImpl is not thread-safe, we should release |mTimer|
+  // here in the target thread to avoid race condition. Otherwise,
+  // ~nsTimerEvent() which calls nsTimerImpl::Release() could run in the
+  // timer thread and result in race condition.
+  mTimer = nullptr;
+
+  return NS_OK;
+}
+
 nsresult
 TimerThread::Init()
 {
   MOZ_LOG(GetTimerLog(), LogLevel::Debug,
          ("TimerThread::Init [%d]\n", mInitialized));
 
   if (mInitialized) {
     if (!mThread) {
       return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
   }
 
+  nsTimerEvent::Init();
+
   if (mInitInProgress.exchange(true) == false) {
     // We hold on to mThread to keep the thread alive.
     nsresult rv = NS_NewThread(getter_AddRefs(mThread), this);
     if (NS_FAILED(rv)) {
       mThread = nullptr;
     } else {
       nsRefPtr<TimerObserverRunnable> r = new TimerObserverRunnable(this);
       if (NS_IsMainThread()) {
@@ -163,16 +360,18 @@ TimerThread::Shutdown()
   for (uint32_t i = 0; i < timersCount; i++) {
     nsTimerImpl* timer = timers[i];
     timer->ReleaseCallback();
     ReleaseTimerInternal(timer);
   }
 
   mThread->Shutdown();    // wait for the thread to die
 
+  nsTimerEvent::Shutdown();
+
   MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown end\n"));
   return NS_OK;
 }
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
@@ -260,25 +459,20 @@ TimerThread::Run()
           nsRefPtr<nsTimerImpl> timerRef(timer);
           RemoveTimerInternal(timer);
           timer = nullptr;
 
           MOZ_LOG(GetTimerLog(), LogLevel::Debug,
                  ("Timer thread woke up %fms from when it was supposed to\n",
                   fabs((now - timerRef->mTimeout).ToMilliseconds())));
 
-          {
-            // We release mMonitor around the Fire call to avoid deadlock.
-            MonitorAutoUnlock unlock(mMonitor);
-
-            // We are going to let the call to PostTimerEvent here handle the
-            // release of the timer so that we don't end up releasing the timer
-            // on the TimerThread instead of on the thread it targets.
-            timerRef = nsTimerImpl::PostTimerEvent(timerRef.forget());
-          }
+          // We are going to let the call to PostTimerEvent here handle the
+          // release of the timer so that we don't end up releasing the timer
+          // on the TimerThread instead of on the thread it targets.
+          timerRef = PostTimerEvent(timerRef.forget());
 
           if (timerRef) {
             // We got our reference back due to an error.
             // Unhook the nsRefPtr, and release manually so we can get the
             // refcount.
             nsrefcnt rc = timerRef.forget().take()->Release();
             (void)rc;
 
@@ -482,16 +676,85 @@ TimerThread::ReleaseTimerInternal(nsTime
     // copied to a local array before releasing in shutdown
     mMonitor.AssertCurrentThreadOwns();
   }
   // Order is crucial here -- see nsTimerImpl::Release.
   aTimer->mArmed = false;
   NS_RELEASE(aTimer);
 }
 
+already_AddRefed<nsTimerImpl>
+TimerThread::PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef)
+{
+  mMonitor.AssertCurrentThreadOwns();
+
+  nsRefPtr<nsTimerImpl> timer(aTimerRef);
+  if (!timer->mEventTarget) {
+    NS_ERROR("Attempt to post timer event to NULL event target");
+    return timer.forget();
+  }
+
+  // XXX we may want to reuse this nsTimerEvent in the case of repeating timers.
+
+  // Since we already addref'd 'timer', we don't need to addref here.
+  // We will release either in ~nsTimerEvent(), or pass the reference back to
+  // the caller. We need to copy the generation number from this timer into the
+  // event, so we can avoid firing a timer that was re-initialized after being
+  // canceled.
+
+  nsRefPtr<nsTimerEvent> event = new nsTimerEvent;
+  if (!event) {
+    return timer.forget();
+  }
+
+  if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
+    event->mInitTime = TimeStamp::Now();
+  }
+
+  // If this is a repeating precise timer, we need to calculate the time for
+  // the next timer to fire before we make the callback.
+  if (timer->IsRepeatingPrecisely()) {
+    timer->SetDelayInternal(timer->mDelay);
+
+    // But only re-arm REPEATING_PRECISE timers.
+    if (timer->mType == nsTimerImpl::TYPE_REPEATING_PRECISE) {
+      if (AddTimerInternal(timer) == -1) {
+        return timer.forget();
+      }
+    }
+  }
+
+#ifdef MOZ_TASK_TRACER
+  // During the dispatch of TimerEvent, we overwrite the current TraceInfo
+  // partially with the info saved in timer earlier, and restore it back by
+  // AutoSaveCurTraceInfo.
+  AutoSaveCurTraceInfo saveCurTraceInfo;
+  (timer->GetTracedTask()).SetTLSTraceInfo();
+#endif
+
+  nsIEventTarget* target = timer->mEventTarget;
+  event->SetTimer(timer.forget());
+
+  nsresult rv;
+  {
+    // We release mMonitor around the Dispatch because if this timer is targeted
+    // at the TimerThread we'll deadlock.
+    MonitorAutoUnlock unlock(mMonitor);
+    rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
+  }
+
+  if (NS_FAILED(rv)) {
+    timer = event->ForgetTimer();
+    RemoveTimerInternal(timer);
+    return timer.forget();
+  }
+
+  return nullptr;
+}
+
 void
 TimerThread::DoBeforeSleep()
 {
   // Mainthread
   MonitorAutoLock lock(mMonitor);
   mLastTimerEventLoopRun = TimeStamp::Now();
   mSleeping = true;
 }
--- a/xpcom/threads/TimerThread.h
+++ b/xpcom/threads/TimerThread.h
@@ -63,16 +63,18 @@ private:
 
   // These two internal helper methods must be called while mMonitor is held.
   // AddTimerInternal returns the position where the timer was added in the
   // list, or -1 if it failed.
   int32_t AddTimerInternal(nsTimerImpl* aTimer);
   bool    RemoveTimerInternal(nsTimerImpl* aTimer);
   void    ReleaseTimerInternal(nsTimerImpl* aTimer);
 
+  already_AddRefed<nsTimerImpl> PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef);
+
   nsCOMPtr<nsIThread> mThread;
   Monitor mMonitor;
 
   bool mShutdown;
   bool mWaiting;
   bool mNotified;
   bool mSleeping;
   TimeStamp mLastTimerEventLoopRun;
--- a/xpcom/threads/nsTimerImpl.cpp
+++ b/xpcom/threads/nsTimerImpl.cpp
@@ -4,17 +4,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsTimerImpl.h"
 #include "TimerThread.h"
 #include "nsAutoPtr.h"
 #include "nsThreadManager.h"
 #include "nsThreadUtils.h"
-#include "plarena.h"
 #include "pratom.h"
 #include "GeckoProfiler.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Logging.h"
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 #ifdef MOZ_TASK_TRACER
@@ -61,165 +60,16 @@ myNS_MeanAndStdDev(double n, double sumO
     }
     // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this:
     stdDev = var != 0.0 ? sqrt(var) : 0.0;
   }
   *meanResult = mean;
   *stdDevResult = stdDev;
 }
 
-namespace {
-
-// TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents.
-// It's needed to avoid contention over the default allocator lock when
-// firing timer events (see bug 733277).  The thread-safety is required because
-// nsTimerEvent objects are allocated on the timer thread, and freed on another
-// thread.  Because TimerEventAllocator has its own lock, contention over that
-// lock is limited to the allocation and deallocation of nsTimerEvent objects.
-//
-// Because this allocator is layered over PLArenaPool, it never shrinks -- even
-// "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list
-// for later recycling.  So the amount of memory consumed will always be equal
-// to the high-water mark consumption.  But nsTimerEvents are small and it's
-// unusual to have more than a few hundred of them, so this shouldn't be a
-// problem in practice.
-
-class TimerEventAllocator
-{
-private:
-  struct FreeEntry
-  {
-    FreeEntry* mNext;
-  };
-
-  PLArenaPool mPool;
-  FreeEntry* mFirstFree;
-  mozilla::Monitor mMonitor;
-
-public:
-  TimerEventAllocator()
-    : mFirstFree(nullptr)
-    , mMonitor("TimerEventAllocator")
-  {
-    PL_InitArenaPool(&mPool, "TimerEventPool", 4096, /* align = */ 0);
-  }
-
-  ~TimerEventAllocator()
-  {
-    PL_FinishArenaPool(&mPool);
-  }
-
-  void* Alloc(size_t aSize);
-  void Free(void* aPtr);
-};
-
-} // namespace
-
-class nsTimerEvent : public nsRunnable
-{
-public:
-  NS_IMETHOD Run();
-
-  nsTimerEvent()
-    : mTimer()
-    , mGeneration(0)
-  {
-    MOZ_COUNT_CTOR(nsTimerEvent);
-
-    MOZ_ASSERT(gThread->IsOnTimerThread(),
-               "nsTimer must always be allocated on the timer thread");
-
-    sAllocatorUsers++;
-  }
-
-  TimeStamp mInitTime;
-
-  static void Init();
-  static void Shutdown();
-  static void DeleteAllocatorIfNeeded();
-
-  static void* operator new(size_t aSize) CPP_THROW_NEW
-  {
-    return sAllocator->Alloc(aSize);
-  }
-  void operator delete(void* aPtr)
-  {
-    sAllocator->Free(aPtr);
-    DeleteAllocatorIfNeeded();
-  }
-
-  already_AddRefed<nsTimerImpl> ForgetTimer()
-  {
-    return mTimer.forget();
-  }
-
-  void SetTimer(already_AddRefed<nsTimerImpl> aTimer)
-  {
-    mTimer = aTimer;
-    mGeneration = mTimer->GetGeneration();
-  }
-
-private:
-  ~nsTimerEvent()
-  {
-    MOZ_COUNT_DTOR(nsTimerEvent);
-
-    MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0,
-               "This will result in us attempting to deallocate the nsTimerEvent allocator twice");
-    sAllocatorUsers--;
-  }
-
-  nsRefPtr<nsTimerImpl> mTimer;
-  int32_t      mGeneration;
-
-  static TimerEventAllocator* sAllocator;
-  static Atomic<int32_t> sAllocatorUsers;
-  static bool sCanDeleteAllocator;
-};
-
-TimerEventAllocator* nsTimerEvent::sAllocator = nullptr;
-Atomic<int32_t> nsTimerEvent::sAllocatorUsers;
-bool nsTimerEvent::sCanDeleteAllocator = false;
-
-namespace {
-
-void*
-TimerEventAllocator::Alloc(size_t aSize)
-{
-  MOZ_ASSERT(aSize == sizeof(nsTimerEvent));
-
-  mozilla::MonitorAutoLock lock(mMonitor);
-
-  void* p;
-  if (mFirstFree) {
-    p = mFirstFree;
-    mFirstFree = mFirstFree->mNext;
-  } else {
-    PL_ARENA_ALLOCATE(p, &mPool, aSize);
-    if (!p) {
-      return nullptr;
-    }
-  }
-
-  return p;
-}
-
-void
-TimerEventAllocator::Free(void* aPtr)
-{
-  mozilla::MonitorAutoLock lock(mMonitor);
-
-  FreeEntry* entry = reinterpret_cast<FreeEntry*>(aPtr);
-
-  entry->mNext = mFirstFree;
-  mFirstFree = entry;
-}
-
-} // namespace
-
 NS_IMPL_QUERY_INTERFACE(nsTimerImpl, nsITimer)
 NS_IMPL_ADDREF(nsTimerImpl)
 
 NS_IMETHODIMP_(MozExternalRefCountType)
 nsTimerImpl::Release(void)
 {
   nsrefcnt count;
 
@@ -297,18 +147,16 @@ nsTimerImpl::~nsTimerImpl()
 }
 
 //static
 nsresult
 nsTimerImpl::Startup()
 {
   nsresult rv;
 
-  nsTimerEvent::Init();
-
   gThread = new TimerThread();
 
   NS_ADDREF(gThread);
   rv = gThread->InitLocks();
 
   if (NS_FAILED(rv)) {
     NS_RELEASE(gThread);
   }
@@ -331,18 +179,16 @@ nsTimerImpl::Shutdown()
   }
 
   if (!gThread) {
     return;
   }
 
   gThread->Shutdown();
   NS_RELEASE(gThread);
-
-  nsTimerEvent::Shutdown();
 }
 
 
 nsresult
 nsTimerImpl::InitCommon(uint32_t aType, uint32_t aDelay)
 {
   nsresult rv;
 
@@ -354,33 +200,17 @@ nsTimerImpl::InitCommon(uint32_t aType, 
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   rv = gThread->Init();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  /**
-   * In case of re-Init, both with and without a preceding Cancel, clear the
-   * mCanceled flag and assign a new mGeneration.  But first, remove any armed
-   * timer from the timer thread's list.
-   *
-   * If we are racing with the timer thread to remove this timer and we lose,
-   * the RemoveTimer call made here will fail to find this timer in the timer
-   * thread's list, and will return false harmlessly.  We test mArmed here to
-   * avoid the small overhead in RemoveTimer of locking the timer thread and
-   * checking its list for this timer.  It's safe to test mArmed even though
-   * it might be cleared on another thread in the next cycle (or even already
-   * be cleared by another CPU whose store hasn't reached our CPU's cache),
-   * because RemoveTimer is idempotent.
-   */
-  if (mArmed) {
-    gThread->RemoveTimer(this);
-  }
+  gThread->RemoveTimer(this);
   mCanceled = false;
   mTimeout = TimeStamp();
   mGeneration = gGenerator++;
 
   mType = (uint8_t)aType;
   SetDelayInternal(aDelay);
 
   return gThread->AddTimer(this);
@@ -657,127 +487,16 @@ nsTimerImpl::Fire()
     // already happened.
     if (gThread) {
       gThread->AddTimer(this);
     }
   }
 }
 
 void
-nsTimerEvent::Init()
-{
-  sAllocator = new TimerEventAllocator();
-}
-
-void
-nsTimerEvent::Shutdown()
-{
-  sCanDeleteAllocator = true;
-  DeleteAllocatorIfNeeded();
-}
-
-void
-nsTimerEvent::DeleteAllocatorIfNeeded()
-{
-  if (sCanDeleteAllocator && sAllocatorUsers == 0) {
-    delete sAllocator;
-    sAllocator = nullptr;
-  }
-}
-
-NS_IMETHODIMP
-nsTimerEvent::Run()
-{
-  if (mGeneration != mTimer->GetGeneration()) {
-    return NS_OK;
-  }
-
-  if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
-    TimeStamp now = TimeStamp::Now();
-    MOZ_LOG(GetTimerLog(), LogLevel::Debug,
-           ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n",
-            this, (now - mInitTime).ToMilliseconds()));
-  }
-
-  mTimer->Fire();
-  // Since nsTimerImpl is not thread-safe, we should release |mTimer|
-  // here in the target thread to avoid race condition. Otherwise,
-  // ~nsTimerEvent() which calls nsTimerImpl::Release() could run in the
-  // timer thread and result in race condition.
-  mTimer = nullptr;
-
-  return NS_OK;
-}
-
-already_AddRefed<nsTimerImpl>
-nsTimerImpl::PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef)
-{
-  nsRefPtr<nsTimerImpl> timer(aTimerRef);
-  if (!timer->mEventTarget) {
-    NS_ERROR("Attempt to post timer event to NULL event target");
-    return timer.forget();
-  }
-
-  // XXX we may want to reuse this nsTimerEvent in the case of repeating timers.
-
-  // Since TimerThread addref'd 'timer' for us, we don't need to addref here.
-  // We will release either in ~nsTimerEvent(), or pass the reference back to
-  // the caller. We need to copy the generation number from this timer into the
-  // event, so we can avoid firing a timer that was re-initialized after being
-  // canceled.
-
-  // Note: We override operator new for this class, and the override is
-  // fallible!
-  nsRefPtr<nsTimerEvent> event = new nsTimerEvent;
-  if (!event) {
-    return timer.forget();
-  }
-
-  if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
-    event->mInitTime = TimeStamp::Now();
-  }
-
-  // If this is a repeating precise timer, we need to calculate the time for
-  // the next timer to fire before we make the callback.
-  if (timer->IsRepeatingPrecisely()) {
-    timer->SetDelayInternal(timer->mDelay);
-
-    // But only re-arm REPEATING_PRECISE timers.
-    if (gThread && timer->mType == TYPE_REPEATING_PRECISE) {
-      nsresult rv = gThread->AddTimer(timer);
-      if (NS_FAILED(rv)) {
-        return timer.forget();
-      }
-    }
-  }
-
-#ifdef MOZ_TASK_TRACER
-  // During the dispatch of TimerEvent, we overwrite the current TraceInfo
-  // partially with the info saved in timer earlier, and restore it back by
-  // AutoSaveCurTraceInfo.
-  AutoSaveCurTraceInfo saveCurTraceInfo;
-  (timer->GetTracedTask()).SetTLSTraceInfo();
-#endif
-
-  nsIEventTarget* target = timer->mEventTarget;
-  event->SetTimer(timer.forget());
-
-  nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
-  if (NS_FAILED(rv)) {
-    timer = event->ForgetTimer();
-    if (gThread) {
-      gThread->RemoveTimer(timer);
-    }
-    return timer.forget();
-  }
-
-  return nullptr;
-}
-
-void
 nsTimerImpl::SetDelayInternal(uint32_t aDelay)
 {
   TimeDuration delayInterval = TimeDuration::FromMilliseconds(aDelay);
 
   mDelay = aDelay;
 
   TimeStamp now = TimeStamp::Now();
   if (mTimeout.IsNull() || mType != TYPE_REPEATING_PRECISE) {
--- a/xpcom/threads/nsTimerImpl.h
+++ b/xpcom/threads/nsTimerImpl.h
@@ -37,18 +37,18 @@ public:
   typedef mozilla::TimeStamp TimeStamp;
 
   nsTimerImpl();
 
   static nsresult Startup();
   static void Shutdown();
 
   friend class TimerThread;
+  friend class nsTimerEvent;
   friend struct TimerAdditionComparator;
-  friend class nsTimerEvent;
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSITIMER
 
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
 
 private:
   void SetDelayInternal(uint32_t aDelay);