Merge m-c to b2g-inbound on a CLOSED TREE
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 21 Oct 2015 15:37:25 +0200
changeset 303987 03549a07785542490752c20e358e2f55f364833b
parent 303986 87f16074faab82bbcb82674e130575db208dd6a9 (current diff)
parent 303840 473aefe5bd85842eeb142e0cde8e2cd21edbf40b (diff)
child 303988 e376b24d15c57b1ae935a9d4ca08cccfce25ef35
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone44.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 on a CLOSED TREE
devtools/client/inspector/test/browser_inspector_menu-05-other.js
testing/web-platform/meta/XMLHttpRequest/send-content-type-string.htm.ini
testing/web-platform/meta/XMLHttpRequest/send-entity-body-document.htm.ini
testing/web-platform/mozilla/meta/service-workers/service-worker/sync-xhr-doesnt-deadlock.https.html.ini
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/sync-xhr-doesnt-deadlock-iframe.html
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/sync-xhr-doesnt-deadlock.data
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/sync-xhr-doesnt-deadlock.js
testing/web-platform/mozilla/tests/service-workers/service-worker/sync-xhr-doesnt-deadlock.https.html
xpcom/glue/Logging.h
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -653,22 +653,25 @@ AccessibleWrap::get_accFocus(
   //              and does not contain a child that has the keyboard focus.
   // VT_I4:       lVal is CHILDID_SELF. The object itself has the keyboard focus.
   // VT_I4:       lVal contains the child ID of the child element with the keyboard focus.
   // VT_DISPATCH: pdispVal member is the address of the IDispatch interface
   //              for the child object with the keyboard focus.
   if (IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
-  // TODO make this work with proxies.
-  if (IsProxy())
-    return E_NOTIMPL;
+  // Return the current IAccessible child that has focus
+  Accessible* focusedAccessible;
+  if (IsProxy()) {
+    ProxyAccessible* proxy = Proxy()->FocusedChild();
+    focusedAccessible = proxy ? WrapperFor(proxy) : nullptr;
+  } else {
+    focusedAccessible = FocusedChild();
+  }
 
-  // Return the current IAccessible child that has focus
-  Accessible* focusedAccessible = FocusedChild();
   if (focusedAccessible == this) {
     pvarChild->vt = VT_I4;
     pvarChild->lVal = CHILDID_SELF;
   }
   else if (focusedAccessible) {
     pvarChild->vt = VT_DISPATCH;
     pvarChild->pdispVal = NativeAccessible(focusedAccessible);
   }
@@ -825,17 +828,27 @@ AccessibleWrap::get_accSelection(VARIANT
     return CO_E_OBJNOTCONNECTED;
 
   // TODO make this work with proxies.
   if (IsProxy())
     return E_NOTIMPL;
 
   if (IsSelect()) {
     nsAutoTArray<Accessible*, 10> selectedItems;
-    SelectedItems(&selectedItems);
+    if (IsProxy()) {
+      nsTArray<ProxyAccessible*> proxies;
+      Proxy()->SelectedItems(&proxies);
+
+      uint32_t selectedCount = proxies.Length();
+      for (uint32_t i = 0; i < selectedCount; i++) {
+        selectedItems.AppendElement(WrapperFor(proxies[i]));
+      }
+    } else {
+      SelectedItems(&selectedItems);
+    }
 
     // 1) Create and initialize the enumeration
     RefPtr<AccessibleEnumerator> pEnum = new AccessibleEnumerator(selectedItems);
     pvarChildren->vt = VT_UNKNOWN;    // this must be VT_UNKNOWN for an IEnumVARIANT
     NS_ADDREF(pvarChildren->punkVal = pEnum);
   }
   return S_OK;
 
@@ -859,22 +872,23 @@ AccessibleWrap::get_accDefaultAction(
 
   Accessible* xpAccessible = GetXPAccessibleFor(varChild);
   if (!xpAccessible)
     return E_INVALIDARG;
 
   if (xpAccessible->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
-  // TODO make this work with proxies.
-  if (xpAccessible->IsProxy())
-    return E_NOTIMPL;
+  nsAutoString defaultAction;
+  if (xpAccessible->IsProxy()) {
+    xpAccessible->Proxy()->ActionNameAt(0, defaultAction);
+  } else {
+    xpAccessible->ActionNameAt(0, defaultAction);
+  }
 
-  nsAutoString defaultAction;
-  xpAccessible->ActionNameAt(0, defaultAction);
   *pszDefaultAction = ::SysAllocStringLen(defaultAction.get(),
                                           defaultAction.Length());
   return *pszDefaultAction ? S_OK : E_OUTOFMEMORY;
 
   A11Y_TRYBLOCK_END
 }
 
 STDMETHODIMP
@@ -890,33 +904,52 @@ AccessibleWrap::accSelect(
   // currently only handle focus and selection
   Accessible* xpAccessible = GetXPAccessibleFor(varChild);
   if (!xpAccessible)
     return E_INVALIDARG;
 
   if (xpAccessible->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
-  // TODO make this work with proxies.
-  if (xpAccessible->IsProxy())
-    return E_NOTIMPL;
+  if (flagsSelect & SELFLAG_TAKEFOCUS) {
+    if (xpAccessible->IsProxy()) {
+      xpAccessible->Proxy()->TakeFocus();
+    } else {
+      xpAccessible->TakeFocus();
+    }
 
-  if (flagsSelect & (SELFLAG_TAKEFOCUS|SELFLAG_TAKESELECTION|SELFLAG_REMOVESELECTION))
-  {
-    if (flagsSelect & SELFLAG_TAKEFOCUS)
-      xpAccessible->TakeFocus();
+    return S_OK;
+  }
+
+  if (flagsSelect & SELFLAG_TAKESELECTION) {
+    if (xpAccessible->IsProxy()) {
+      xpAccessible->Proxy()->TakeSelection();
+    } else {
+      xpAccessible->TakeSelection();
+    }
 
-    if (flagsSelect & SELFLAG_TAKESELECTION)
-      xpAccessible->TakeSelection();
+    return S_OK;
+  }
+
+  if (flagsSelect & SELFLAG_ADDSELECTION) {
+    if (xpAccessible->IsProxy()) {
+      xpAccessible->Proxy()->SetSelected(true);
+    } else {
+      xpAccessible->SetSelected(true);
+    }
 
-    if (flagsSelect & SELFLAG_ADDSELECTION)
-      xpAccessible->SetSelected(true);
+    return S_OK;
+  }
 
-    if (flagsSelect & SELFLAG_REMOVESELECTION)
+  if (flagsSelect & SELFLAG_REMOVESELECTION) {
+    if (xpAccessible->IsProxy()) {
+      xpAccessible->Proxy()->SetSelected(false);
+    } else {
       xpAccessible->SetSelected(false);
+    }
 
     return S_OK;
   }
 
   return E_FAIL;
 
   A11Y_TRYBLOCK_END
 }
@@ -1058,21 +1091,25 @@ AccessibleWrap::accHitTest(
   if (!pvarChild)
     return E_INVALIDARG;
 
   VariantInit(pvarChild);
 
   if (IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
-  // TODO make this work with proxies.
-  if (IsProxy())
-    return E_NOTIMPL;
-
-  Accessible* accessible = ChildAtPoint(xLeft, yTop, eDirectChild);
+  Accessible* accessible = nullptr;
+  if (IsProxy()) {
+    ProxyAccessible* proxy = Proxy()->ChildAtPoint(xLeft, yTop, eDirectChild);
+    if (proxy) {
+      accessible = WrapperFor(proxy);
+    }
+  } else {
+    accessible = ChildAtPoint(xLeft, yTop, eDirectChild);
+  }
 
   // if we got a child
   if (accessible) {
     // if the child is us
     if (accessible == this) {
       pvarChild->vt = VT_I4;
       pvarChild->lVal = CHILDID_SELF;
     } else { // its not create an Accessible for it.
@@ -1102,17 +1139,17 @@ AccessibleWrap::accDoDefaultAction(
   if (!xpAccessible)
     return E_INVALIDARG;
 
   if (xpAccessible->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
   // TODO make this work with proxies.
   if (xpAccessible->IsProxy())
-    return E_NOTIMPL;
+    return xpAccessible->Proxy()->DoAction(0) ? S_OK : E_INVALIDARG;
 
   return xpAccessible->DoAction(0) ? S_OK : E_INVALIDARG;
 
   A11Y_TRYBLOCK_END
 }
 
 STDMETHODIMP
 AccessibleWrap::put_accName(
--- a/addon-sdk/source/CONTRIBUTING.md
+++ b/addon-sdk/source/CONTRIBUTING.md
@@ -32,17 +32,16 @@ There is a list of [good first bugs here
 
 ## Reviewers
 
 All changes need a review by someone on the Jetpack review crew:
 
 - [@mossop]
 - [@gozala]
 - [@ZER0]
-- [@erikvold]
 - [@jsantell]
 - [@zombie]
 
 For review of Mozilla platform usage and best practices, ask [@autonome],
 [@0c0w3], or [@mossop] to find the domain expert.
 
 For API and developer ergonomics review, ask [@gozala].
 
@@ -56,11 +55,10 @@ For API and developer ergonomics review,
 [coding style guide]:https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide
 [Add-on SDK repo]:https://github.com/mozilla/addon-sdk
 [GitHub]:https://github.com/
 [good first bugs]:https://bugzilla.mozilla.org/buglist.cgi?list_id=7345714&columnlist=bug_severity%2Cpriority%2Cassigned_to%2Cbug_status%2Ctarget_milestone%2Cresolution%2Cshort_desc%2Cchangeddate&query_based_on=jetpack-good-1st-bugs&status_whiteboard_type=allwordssubstr&query_format=advanced&status_whiteboard=[good%20first%20bug]&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=VERIFIED&product=Add-on%20SDK&known_name=jetpack-good-1st-bugs
 
 [@mossop]:https://github.com/mossop/
 [@gozala]:https://github.com/Gozala/
 [@ZER0]:https://github.com/ZER0/
-[@erikvold]:https://github.com/erikvold/
 [@jsantell]:https://github.com/jsantell
 [@zombie]:https://github.com/zombie
--- a/addon-sdk/source/bin/node-scripts/words.txt
+++ b/addon-sdk/source/bin/node-scripts/words.txt
@@ -1,12 +1,11 @@
 addon-sdk
 github
 stackoverflow
 bugzilla
 irc
-erikvold
 jsantell
 mossop
 gozala
 zer0
 autonome
 0c0w3
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1441,21 +1441,29 @@ pref("security.onecrl.maximum_staleness_
 // Override the Gecko-default value of false for Firefox.
 pref("plain_text.wrap_long_lines", true);
 
 // If this turns true, Moz*Gesture events are not called stopPropagation()
 // before content.
 pref("dom.debug.propagate_gesture_events_through_content", false);
 
 // The request URL of the GeoLocation backend.
+#ifdef RELEASE_BUILD
+pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_API_KEY%");
+#else
 pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
+#endif
 
 #ifdef XP_MACOSX
+#ifdef RELEASE_BUILD
+pref("geo.provider.use_corelocation", false);
+#else
 pref("geo.provider.use_corelocation", true);
 #endif
+#endif
 
 #ifdef XP_WIN
 pref("geo.provider.ms-windows-location", false);
 #endif
 
 // Necko IPC security checks only needed for app isolation for cookies/cache/etc:
 // currently irrelevant for desktop e10s
 pref("network.disable.ipc.security", true);
--- a/browser/base/content/test/alerts/browser.ini
+++ b/browser/base/content/test/alerts/browser.ini
@@ -1,9 +1,10 @@
 [DEFAULT]
 support-files =
   file_dom_notifications.html
 
+[browser_notification_do_not_disturb.js]
 [browser_notification_open_settings.js]
 [browser_notification_remove_permission.js]
 skip-if = e10s
 [browser_notification_tab_switching.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1100662 - content access causing uncaught exception - Error: cannot ipc non-cpow object at chrome://mochitests/content/browser/browser/base/content/test/general/browser_notification_tab_switching.js:32 (or in RemoteAddonsChild.jsm)
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_do_not_disturb.js
@@ -0,0 +1,91 @@
+"use strict";
+
+var tab;
+var notification;
+var notification2;
+var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+
+const ALERT_SERVICE = Cc["@mozilla.org/alerts-service;1"]
+                        .getService(Ci.nsIAlertsService)
+                        .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+
+function test () {
+  waitForExplicitFinish();
+
+  try {
+    // Only run the test if the do-not-disturb
+    // interface has been implemented.
+    ALERT_SERVICE.manualDoNotDisturb;
+    ok(true, "Alert service implements do-not-disturb interface");
+  } catch (e) {
+    ok(true, "Alert service doesn't implement do-not-disturb interface, exiting test");
+    finish();
+    return;
+  }
+
+  let pm = Services.perms;
+  registerCleanupFunction(function() {
+    ALERT_SERVICE.manualDoNotDisturb = false;
+    pm.remove(makeURI(notificationURL), "desktop-notification");
+    gBrowser.removeTab(tab);
+    window.restore();
+  });
+
+  pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+  // Make sure that do-not-disturb is not enabled.
+  ok(!ALERT_SERVICE.manualDoNotDisturb, "Alert service should not be disabled when test starts");
+  ALERT_SERVICE.manualDoNotDisturb = false;
+
+  tab = gBrowser.addTab(notificationURL);
+  gBrowser.selectedTab = tab;
+  tab.linkedBrowser.addEventListener("load", onLoad, true);
+}
+
+function onLoad() {
+  tab.linkedBrowser.removeEventListener("load", onLoad, true);
+  let win = tab.linkedBrowser.contentWindow.wrappedJSObject;
+  notification = win.showNotification2();
+  notification.addEventListener("show", onAlertShowing);
+}
+
+function onAlertShowing() {
+  info("Notification alert showing");
+  notification.removeEventListener("show", onAlertShowing);
+
+  let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+  if (!alertWindow) {
+    ok(true, "Notifications don't use XUL windows on all platforms.");
+    notification.close();
+    finish();
+    return;
+  }
+  let doNotDisturbMenuItem = alertWindow.document.getElementById("doNotDisturbMenuItem");
+  is(doNotDisturbMenuItem.localName, "menuitem", "menuitem found");
+  alertWindow.addEventListener("beforeunload", onAlertClosing);
+  doNotDisturbMenuItem.click();
+  info("Clicked on do-not-disturb menuitem")
+}
+
+function onAlertClosing(event) {
+  event.target.removeEventListener("beforeunload", onAlertClosing);
+
+  ok(ALERT_SERVICE.manualDoNotDisturb, "Alert service should be disabled after clicking menuitem");
+  let win = tab.linkedBrowser.contentWindow.wrappedJSObject;
+  notification2 = win.showNotification2();
+  notification2.addEventListener("show", onAlert2Showing);
+
+  // The notification should not appear, but there is
+  // no way from the client-side to know that it was
+  // blocked, except for waiting some time and realizing
+  // that the "onshow" event never fired.
+  setTimeout(function() {
+    notification2.removeEventListener("show", onAlert2Showing);
+    finish();
+  }, 2000);
+}
+
+function onAlert2Showing() {
+  ok(false, "the second alert should not have been shown");
+  notification2.close();
+}
--- a/browser/components/preferences/in-content/content.js
+++ b/browser/components/preferences/in-content/content.js
@@ -1,12 +1,25 @@
 /* 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/. */
 
+XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function () {
+  try {
+    let alertsService = Cc["@mozilla.org/alerts-service;1"]
+                          .getService(Ci.nsIAlertsService)
+                          .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+    // This will throw if manualDoNotDisturb isn't implemented.
+    alertsService.manualDoNotDisturb;
+    return alertsService;
+  } catch (ex) {
+    return undefined;
+  }
+});
+
 var gContentPane = {
   init: function ()
   {
     function setEventListener(aId, aEventType, aCallback)
     {
       document.getElementById(aId)
               .addEventListener(aEventType, aCallback.bind(gContentPane));
     }
@@ -25,32 +38,46 @@ var gContentPane = {
       row.removeAttribute("hidden");
       // Showing attribution only for Bing Translator.
       Components.utils.import("resource:///modules/translation/Translation.jsm");
       if (Translation.translationEngine == "bing") {
         document.getElementById("bingAttribution").removeAttribute("hidden");
       }
     }
 
+    let doNotDisturbAlertsEnabled = false;
+    if (AlertsServiceDND) {
+      let notificationsDoNotDisturbRow =
+        document.getElementById("notificationsDoNotDisturbRow");
+      notificationsDoNotDisturbRow.removeAttribute("hidden");
+      if (AlertsServiceDND.manualDoNotDisturb) {
+        let notificationsDoNotDisturb =
+          document.getElementById("notificationsDoNotDisturb");
+        notificationsDoNotDisturb.setAttribute("checked", true);
+      }
+    }
+
     setEventListener("font.language.group", "change",
       gContentPane._rebuildFonts);
     setEventListener("notificationsPolicyButton", "command",
       gContentPane.showNotificationExceptions);
     setEventListener("popupPolicyButton", "command",
       gContentPane.showPopupExceptions);
     setEventListener("advancedFonts", "command",
       gContentPane.configureFonts);
     setEventListener("colors", "command",
       gContentPane.configureColors);
     setEventListener("chooseLanguage", "command",
       gContentPane.showLanguages);
     setEventListener("translationAttributionImage", "click",
       gContentPane.openTranslationProviderAttribution);
     setEventListener("translateButton", "command",
       gContentPane.showTranslationExceptions);
+    setEventListener("notificationsDoNotDisturb", "command",
+      gContentPane.toggleDoNotDisturbNotifications);
 
     let drmInfoURL =
       Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
     document.getElementById("playDRMContentLink").setAttribute("href", drmInfoURL);
     let emeUIEnabled = Services.prefs.getBoolPref("browser.eme.ui.enabled");
     // Force-disable/hide on WinXP:
     if (navigator.platform.toLowerCase().startsWith("win")) {
       emeUIEnabled = emeUIEnabled && parseFloat(Services.sysinfo.get("version")) >= 6;
@@ -248,10 +275,15 @@ var gContentPane = {
   {
     gSubDialog.open("chrome://browser/content/preferences/translation.xul");
   },
 
   openTranslationProviderAttribution: function ()
   {
     Components.utils.import("resource:///modules/translation/Translation.jsm");
     Translation.openProviderAttribution();
-  }
+  },
+
+  toggleDoNotDisturbNotifications: function (event)
+  {
+    AlertsServiceDND.manualDoNotDisturb = event.target.checked;
+  },
 };
--- a/browser/components/preferences/in-content/content.xul
+++ b/browser/components/preferences/in-content/content.xul
@@ -72,16 +72,25 @@
         <vbox align="start">
           <label id="notificationsPolicy">&notificationsPolicyDesc.label;</label>
         </vbox>
         <hbox pack="end">
           <button id="notificationsPolicyButton" label="&notificationsPolicyButton.label;"
                   accesskey="&notificationsPolicyButton.accesskey;"/>
         </hbox>
       </row>
+      <row id="notificationsDoNotDisturbRow" hidden="true">
+        <vbox align="start">
+          <checkbox id="notificationsDoNotDisturb" label="&notificationsDoNotDisturb.label;"
+                    accesskey="&notificationsDoNotDisturb.accesskey;"/>
+          <label id="notificationsDoNotDisturbDetails"
+                 class="indent"
+                 value="&notificationsDoNotDisturbDetails.value;"/>
+        </vbox>
+      </row>
     </rows>
   </grid>
 </groupbox>
 
 <groupbox id="miscGroup" data-category="paneContent" hidden="true">
   <caption><label>&popups.label;</label></caption>
   <grid id="contentGrid">
     <columns>
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -15,16 +15,17 @@ support-files =
 [browser_change_app_handler.js]
 skip-if = os != "win" # This test tests the windows-specific app selection dialog, so can't run on non-Windows
 [browser_chunk_permissions.js]
 [browser_connection.js]
 [browser_connection_bug388287.js]
 [browser_cookies_exceptions.js]
 [browser_healthreport.js]
 skip-if = !healthreport || (os == 'linux' && debug)
+[browser_notifications_do_not_disturb.js]
 [browser_permissions.js]
 [browser_proxy_backup.js]
 [browser_privacypane_1.js]
 [browser_privacypane_3.js]
 [browser_privacypane_4.js]
 [browser_privacypane_5.js]
 [browser_privacypane_8.js]
 skip-if = e10s # Bug ?????? -  "leaked until shutdown [nsGlobalWindow #99 about:preferences]"
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_notifications_do_not_disturb.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+registerCleanupFunction(function() {
+  while (gBrowser.tabs[1])
+    gBrowser.removeTab(gBrowser.tabs[1]);
+});
+
+add_task(function() {
+  let prefs = yield openPreferencesViaOpenPreferencesAPI("paneContent", undefined, {leaveOpen: true});
+  is(prefs.selectedPane, "paneContent", "Content pane was selected");
+
+  let doc = gBrowser.contentDocument;
+  let notificationsDoNotDisturbRow = doc.getElementById("notificationsDoNotDisturbRow");
+  if (notificationsDoNotDisturbRow.hidden) {
+    todo(false, "Do not disturb is not available on this platform");
+    return;
+  }
+
+  let alertService;
+  try {
+    alertService = Cc["@mozilla.org/alerts-service;1"]
+                     .getService(Ci.nsIAlertsService)
+                     .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+  } catch (ex) {
+    ok(true, "Do not disturb is not available on this platform: " + ex.message);
+    return;
+  }
+
+  let checkbox = doc.getElementById("notificationsDoNotDisturb");
+  ok(!checkbox.checked, "Checkbox should not be checked by default");
+  ok(!alertService.manualDoNotDisturb, "Do not disturb should be off by default");
+
+  let checkboxChanged = waitForEvent(checkbox, "command")
+  checkbox.click();
+  yield checkboxChanged;
+  ok(alertService.manualDoNotDisturb, "Do not disturb should be enabled when checked");
+
+  checkboxChanged = waitForEvent(checkbox, "command")
+  checkbox.click();
+  yield checkboxChanged;
+  ok(!alertService.manualDoNotDisturb, "Do not disturb should be disabled when unchecked");
+});
--- a/browser/locales/en-US/chrome/browser/devtools/inspector.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/inspector.dtd
@@ -74,16 +74,42 @@
 <!ENTITY inspectorScrollNodeIntoView.label       "Scroll Into View">
 <!ENTITY inspectorScrollNodeIntoView.accesskey   "S">
 
 <!-- LOCALIZATION NOTE (inspectorHTMLDelete.label): This is the label shown in
      the inspector contextual-menu for the item that lets users delete the
      current node -->
 <!ENTITY inspectorHTMLDelete.label          "Delete Node">
 <!ENTITY inspectorHTMLDelete.accesskey      "D">
+<!-- LOCALIZATION NOTE (inspectorAttributeSubmenu.label): This is the label
+     shown in the inspector contextual-menu for the sub-menu of the other
+     attribute items, which allow to:
+     - add new attribute
+     - edit attribute
+     - remove attribute -->
+<!ENTITY inspectorAttributeSubmenu.label      "Attribute">
+<!ENTITY inspectorAttributeSubmenu.accesskey  "A">
+
+<!-- LOCALIZATION NOTE (inspectorAddAttribute.label): This is the label shown in
+     the inspector contextual-menu for the item that lets users add attribute
+     to current node -->
+<!ENTITY inspectorAddAttribute.label        "Add Attribute">
+<!ENTITY inspectorAddAttribute.accesskey    "A">
+
+<!-- LOCALIZATION NOTE (inspectorEditAttribute.label): This is the label shown in
+     the inspector contextual-menu for the item that lets users edit attribute
+     for current node -->
+<!ENTITY inspectorEditAttribute.label        "Edit Attribute">
+<!ENTITY inspectorEditAttribute.accesskey    "E">
+
+<!-- LOCALIZATION NOTE (inspectorRemoveAttribute.label): This is the label shown in
+     the inspector contextual-menu for the item that lets users delete attribute
+     from current node -->
+<!ENTITY inspectorRemoveAttribute.label        "Remove Attribute">
+<!ENTITY inspectorRemoveAttribute.accesskey    "R">
 
 <!ENTITY inspector.selectButton.tooltip     "Select element with mouse">
 
 <!-- LOCALIZATION NOTE (inspectorSearchHTML.label2): This is the label shown as
      the placeholder in inspector search box -->
 <!ENTITY inspectorSearchHTML.label2          "Search with CSS Selectors">
 <!ENTITY inspectorSearchHTML.key            "F">
 
--- a/browser/locales/en-US/chrome/browser/devtools/inspector.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/inspector.properties
@@ -99,8 +99,20 @@ inspector.menu.openUrlInNewTab.label=Ope
 inspector.menu.copyUrlToClipboard.label=Copy Link Address
 
 # LOCALIZATION NOTE (inspector.menu.selectElement.label): This is the label of a
 # menu item in the inspector contextual-menu that appears when the user right-
 # clicks on the attribute of a node in the inspector that is the ID of another
 # element in the DOM (like with <label for="input-id">), and that allows to
 # select that element in the inspector.
 inspector.menu.selectElement.label=Select Element #%S
+
+# LOCALIZATION NOTE (inspector.menu.editAttribute.label): This is the label of a
+# sub-menu "Attribute" in the inspector contextual-menu that appears
+# when the user right-clicks on the node in the inspector, and that allows
+# to edit an attribute on this node.
+inspector.menu.editAttribute.label=Edit Attribute %S
+
+# LOCALIZATION NOTE (inspector.menu.removeAttribute.label): This is the label of a
+# sub-menu "Attribute" in the inspector contextual-menu that appears
+# when the user right-clicks on the attribute of a node in the inspector,
+# and that allows to remove this attribute.
+inspector.menu.removeAttribute.label=Remove Attribute %S
--- a/browser/locales/en-US/chrome/browser/preferences/content.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/content.dtd
@@ -6,16 +6,19 @@
 
 <!ENTITY  blockPopups.label           "Block pop-up windows">
 <!ENTITY  blockPopups.accesskey       "B">
 
 <!ENTITY  notificationsPolicy.label            "Notifications">
 <!ENTITY  notificationsPolicyDesc.label        "Choose which sites are allowed to show notifications">
 <!ENTITY  notificationsPolicyButton.accesskey  "h">
 <!ENTITY  notificationsPolicyButton.label      "Choose…">
+<!ENTITY  notificationsDoNotDisturb.label      "Do not disturb me">
+<!ENTITY  notificationsDoNotDisturb.accesskey  "n">
+<!ENTITY  notificationsDoNotDisturbDetails.value "No notification will be shown until you restart &brandShortName;">
 
 <!ENTITY  popupExceptions.label       "Exceptions…">
 <!ENTITY  popupExceptions.accesskey   "E">
 
 <!ENTITY  fontsAndColors.label        "Fonts &amp; Colors">
 
 <!ENTITY  defaultFont.label           "Default font:">
 <!ENTITY  defaultFont.accesskey       "D">
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -266,17 +266,19 @@ description > html|a {
   /* !important is needed to override the rules defined in common.css */
   -moz-margin-start: 20px !important;
   /* center the links */
   margin-top: 8px;
   margin-bottom: 8px;
 }
 
 .indent {
-  -moz-margin-start: 33px;
+  /* !important needed to override -moz-margin-start:0 !important; rule
+     define in common.css for labels */
+  -moz-margin-start: 33px !important;
 }
 
 .text-link {
   margin-bottom: 0;
 }
 
 #showUpdateHistory {
   -moz-margin-start: 0;
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -72,16 +72,17 @@ const LAYOUT_CHANGE_TIMER = 250;
  */
 function InspectorPanel(iframeWindow, toolbox) {
   this._toolbox = toolbox;
   this._target = toolbox._target;
   this.panelDoc = iframeWindow.document;
   this.panelWin = iframeWindow;
   this.panelWin.inspector = this;
 
+  this.nodeMenuTriggerInfo = null;
   this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
   this._target.on("will-navigate", this._onBeforeNavigate);
 
   EventEmitter.decorate(this);
 }
 
 exports.InspectorPanel = InspectorPanel;
 
@@ -642,19 +643,24 @@ InspectorPanel.prototype = {
       if (content && content.trim().length > 0) {
         return content;
       }
     }
     return null;
   },
 
   /**
-   * Disable the delete item if needed. Update the pseudo classes.
+   * Update, enable, disable, hide, show any menu item depending on the current
+   * element.
    */
-  _setupNodeMenu: function() {
+  _setupNodeMenu: function(event) {
+    let markupContainer = this.markup.getContainer(this.selection.nodeFront);
+    this.nodeMenuTriggerInfo =
+      markupContainer.editor.getInfoAtNode(event.target.triggerNode);
+
     let isSelectionElement = this.selection.isElementNode() &&
                              !this.selection.isPseudoElementNode();
     let isEditableElement = isSelectionElement &&
                             !this.selection.isAnonymousNode();
     let isDuplicatableElement = isSelectionElement &&
                                 !this.selection.isAnonymousNode() &&
                                 !this.selection.isRoot();
     let isScreenshotable = isSelectionElement &&
@@ -691,19 +697,18 @@ InspectorPanel.prototype = {
     let copyOuterHTML = this.panelDoc.getElementById("node-menu-copyouter");
     let scrollIntoView = this.panelDoc.getElementById("node-menu-scrollnodeintoview");
     let expandAll = this.panelDoc.getElementById("node-menu-expand");
     let collapse = this.panelDoc.getElementById("node-menu-collapse");
 
     expandAll.setAttribute("disabled", "true");
     collapse.setAttribute("disabled", "true");
 
-    let markUpContainer = this.markup.importNode(this.selection.nodeFront, false);
-    if (this.selection.isNode() && markUpContainer.hasChildren) {
-      if (markUpContainer.expanded) {
+    if (this.selection.isNode() && markupContainer.hasChildren) {
+      if (markupContainer.expanded) {
         collapse.removeAttribute("disabled");
       }
       expandAll.removeAttribute("disabled");
     }
 
     this._target.actorHasMethod("domwalker", "duplicateNode").then(value => {
       duplicateNode.hidden = !value;
     });
@@ -779,22 +784,62 @@ InspectorPanel.prototype = {
       pasteAfter.disabled = true;
       pasteFirstChild.disabled = true;
       pasteLastChild.disabled = true;
     }
 
     // Enable the "copy image data-uri" item if the selection is previewable
     // which essentially checks if it's an image or canvas tag
     let copyImageData = this.panelDoc.getElementById("node-menu-copyimagedatauri");
-    let markupContainer = this.markup.getContainer(this.selection.nodeFront);
     if (isSelectionElement && markupContainer && markupContainer.isPreviewable()) {
       copyImageData.removeAttribute("disabled");
     } else {
       copyImageData.setAttribute("disabled", "true");
     }
+
+    // Enable / disable "Add Attribute", "Edit Attribute"
+    // and "Remove Attribute" items
+    this._setupAttributeMenu(isEditableElement);
+  },
+
+  _setupAttributeMenu: function(isEditableElement) {
+    let addAttribute = this.panelDoc.getElementById("node-menu-add-attribute");
+    let editAttribute = this.panelDoc.getElementById("node-menu-edit-attribute");
+    let removeAttribute = this.panelDoc.getElementById("node-menu-remove-attribute");
+    let nodeInfo = this.nodeMenuTriggerInfo;
+
+    // Enable "Add Attribute" for all editable elements
+    if (isEditableElement) {
+      addAttribute.removeAttribute("disabled");
+    } else {
+      addAttribute.setAttribute("disabled", "true");
+    }
+
+    // Enable "Edit Attribute" and "Remove Attribute" only on attribute click
+    if (isEditableElement && nodeInfo && nodeInfo.type === "attribute") {
+      editAttribute.removeAttribute("disabled");
+      editAttribute.setAttribute("label",
+        strings.formatStringFromName(
+          "inspector.menu.editAttribute.label", [`"${nodeInfo.name}"`], 1));
+
+      removeAttribute.removeAttribute("disabled");
+      removeAttribute.setAttribute("label",
+        strings.formatStringFromName(
+          "inspector.menu.removeAttribute.label", [`"${nodeInfo.name}"`], 1));
+    } else {
+      editAttribute.setAttribute("disabled", "true");
+      editAttribute.setAttribute("label",
+        strings.formatStringFromName(
+          "inspector.menu.editAttribute.label", [''], 1));
+
+      removeAttribute.setAttribute("disabled", "true");
+      removeAttribute.setAttribute("label",
+        strings.formatStringFromName(
+          "inspector.menu.removeAttribute.label", [''], 1));
+    }
   },
 
   _resetNodeMenu: function() {
     // Remove any extra items
     while (this.lastNodemenuItem.nextSibling) {
       let toDelete = this.lastNodemenuItem.nextSibling;
       toDelete.parentNode.removeChild(toDelete);
     }
@@ -1219,16 +1264,43 @@ InspectorPanel.prototype = {
     if (this.markup) {
       this.markup.deleteNode(this.selection.nodeFront);
     } else {
       // remove the node from content
       this.walker.removeNode(this.selection.nodeFront);
     }
   },
 
+  /**
+   * Add attribute to node.
+   * Used for node context menu and shouldn't be called directly.
+   */
+  onAddAttribute: function() {
+    let container = this.markup.getContainer(this.selection.nodeFront);
+    container.addAttribute();
+  },
+
+  /**
+   * Edit attribute for node.
+   * Used for node context menu and shouldn't be called directly.
+   */
+  onEditAttribute: function() {
+    let container = this.markup.getContainer(this.selection.nodeFront);
+    container.editAttribute(this.nodeMenuTriggerInfo.name);
+  },
+
+  /**
+   * Remove attribute from node.
+   * Used for node context menu and shouldn't be called directly.
+   */
+  onRemoveAttribute: function() {
+    let container = this.markup.getContainer(this.selection.nodeFront);
+    container.removeAttribute(this.nodeMenuTriggerInfo.name);
+  },
+
   expandNode: function() {
     this.markup.expandAll(this.selection.nodeFront);
   },
 
   collapseNode: function() {
     this.markup.collapseNode(this.selection.nodeFront);
   },
 
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -105,16 +105,33 @@
         oncommand="inspector.screenshotNode()" />
       <menuitem id="node-menu-duplicatenode"
         label="&inspectorDuplicateNode.label;"
         oncommand="inspector.duplicateNode()"/>
       <menuitem id="node-menu-delete"
         label="&inspectorHTMLDelete.label;"
         accesskey="&inspectorHTMLDelete.accesskey;"
         oncommand="inspector.deleteNode()"/>
+      <menu label="&inspectorAttributeSubmenu.label;"
+        accesskey="&inspectorAttributeSubmenu.accesskey;">
+        <menupopup>
+          <menuitem id="node-menu-add-attribute"
+            label="&inspectorAddAttribute.label;"
+            accesskey="&inspectorAddAttribute.accesskey;"
+            oncommand="inspector.onAddAttribute()"/>
+          <menuitem id="node-menu-edit-attribute"
+            label="&inspectorEditAttribute.label;"
+            accesskey="&inspectorEditAttribute.accesskey;"
+            oncommand="inspector.onEditAttribute()"/>
+          <menuitem id="node-menu-remove-attribute"
+            label="&inspectorRemoveAttribute.label;"
+            accesskey="&inspectorRemoveAttribute.accesskey;"
+            oncommand="inspector.onRemoveAttribute()"/>
+        </menupopup>
+      </menu>
       <menuseparator id="node-menu-link-separator"/>
       <menuitem id="node-menu-link-follow"
         oncommand="inspector.onFollowLink()"/>
       <menuitem id="node-menu-link-copy"
         oncommand="inspector.onCopyLink()"/>
       <menuseparator/>
       <menuitem id="node-menu-pseudo-hover"
         label=":hover" type="checkbox"
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -81,17 +81,18 @@ skip-if = e10s # GCLI isn't e10s compati
 [browser_inspector_inspect-object-element.js]
 [browser_inspector_invalidate.js]
 [browser_inspector_keyboard-shortcuts-copy-outerhtml.js]
 [browser_inspector_keyboard-shortcuts.js]
 [browser_inspector_menu-01-sensitivity.js]
 [browser_inspector_menu-02-copy-items.js]
 [browser_inspector_menu-03-paste-items.js]
 [browser_inspector_menu-04-use-in-console.js]
-[browser_inspector_menu-05-other.js]
+[browser_inspector_menu-05-attribute-items.js]
+[browser_inspector_menu-06-other.js]
 [browser_inspector_navigation.js]
 [browser_inspector_pane-toggle-01.js]
 [browser_inspector_pane-toggle-02.js]
 [browser_inspector_pane-toggle-03.js]
 [browser_inspector_picker-stop-on-destroy.js]
 [browser_inspector_picker-stop-on-tool-change.js]
 [browser_inspector_pseudoclass-lock.js]
 [browser_inspector_pseudoclass-menu.js]
--- a/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js
@@ -27,22 +27,34 @@ const ALL_MENU_ITEMS = [
   "node-menu-copyouter",
   "node-menu-copyuniqueselector",
   "node-menu-copyimagedatauri",
   "node-menu-delete",
   "node-menu-pseudo-hover",
   "node-menu-pseudo-active",
   "node-menu-pseudo-focus",
   "node-menu-scrollnodeintoview",
-  "node-menu-screenshotnode"
+  "node-menu-screenshotnode",
+  "node-menu-add-attribute",
+  "node-menu-edit-attribute",
+  "node-menu-remove-attribute"
 ].concat(PASTE_MENU_ITEMS, ACTIVE_ON_DOCTYPE_ITEMS);
 
 const INACTIVE_ON_DOCTYPE_ITEMS =
   ALL_MENU_ITEMS.filter(item => ACTIVE_ON_DOCTYPE_ITEMS.indexOf(item) === -1);
 
+/**
+ * Test cases, each item of this array may define the following properties:
+ *   desc: string that will be logged
+ *   selector: selector of the node to be selected
+ *   disabled: items that should have disabled state
+ *   clipboardData: clipboard content
+ *   clipboardDataType: clipboard content type
+ *   attributeTrigger: attribute that will be used as context menu trigger
+ */
 const TEST_CASES = [
   {
     desc: "doctype node with empty clipboard",
     selector: null,
     disabled: INACTIVE_ON_DOCTYPE_ITEMS,
   },
   {
     desc: "doctype node with html on clipboard",
@@ -50,138 +62,183 @@ const TEST_CASES = [
     clipboardDataType: "html",
     selector: null,
     disabled: INACTIVE_ON_DOCTYPE_ITEMS,
   },
   {
     desc: "element node HTML on the clipboard",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
-    disabled: ["node-menu-copyimagedatauri"],
+    disabled: [
+      "node-menu-copyimagedatauri",
+      "node-menu-edit-attribute",
+      "node-menu-remove-attribute"
+    ],
     selector: "#sensitivity",
   },
   {
     desc: "<html> element",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     selector: "html",
     disabled: [
       "node-menu-copyimagedatauri",
       "node-menu-pastebefore",
       "node-menu-pasteafter",
       "node-menu-pastefirstchild",
       "node-menu-pastelastchild",
+      "node-menu-edit-attribute",
+      "node-menu-remove-attribute"
     ],
   },
   {
     desc: "<body> with HTML on clipboard",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     selector: "body",
     disabled: [
       "node-menu-copyimagedatauri",
       "node-menu-pastebefore",
       "node-menu-pasteafter",
+      "node-menu-edit-attribute",
+      "node-menu-remove-attribute"
     ]
   },
   {
     desc: "<img> with HTML on clipboard",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     selector: "img",
-    disabled: []
+    disabled: [
+      "node-menu-edit-attribute",
+      "node-menu-remove-attribute"
+    ]
   },
   {
     desc: "<head> with HTML on clipboard",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     selector: "head",
     disabled: [
       "node-menu-copyimagedatauri",
       "node-menu-pastebefore",
       "node-menu-pasteafter",
       "node-menu-screenshotnode",
+      "node-menu-edit-attribute",
+      "node-menu-remove-attribute"
     ],
   },
   {
     desc: "<head> with no html on clipboard",
     selector: "head",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
       "node-menu-screenshotnode",
+      "node-menu-edit-attribute",
+      "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> with text on clipboard",
     clipboardData: "some text",
     clipboardDataType: undefined,
     selector: "#paste-area",
-    disabled: ["node-menu-copyimagedatauri"],
+    disabled: [
+      "node-menu-copyimagedatauri",
+      "node-menu-edit-attribute",
+      "node-menu-remove-attribute"
+    ]
   },
   {
     desc: "<element> with base64 encoded image data uri on clipboard",
     clipboardData:
       "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" +
       "AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
     clipboardDataType: undefined,
     selector: "#paste-area",
-    disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
+    disabled: PASTE_MENU_ITEMS.concat([
+      "node-menu-copyimagedatauri",
+      "node-menu-edit-attribute",
+      "node-menu-remove-attribute"
+    ]),
   },
   {
     desc: "<element> with empty string on clipboard",
     clipboardData: "",
     clipboardDataType: undefined,
     selector: "#paste-area",
-    disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
+    disabled: PASTE_MENU_ITEMS.concat([
+      "node-menu-copyimagedatauri",
+      "node-menu-edit-attribute",
+      "node-menu-remove-attribute"
+    ]),
   },
   {
     desc: "<element> with whitespace only on clipboard",
     clipboardData: " \n\n\t\n\n  \n",
     clipboardDataType: undefined,
     selector: "#paste-area",
-    disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
+    disabled: PASTE_MENU_ITEMS.concat([
+      "node-menu-copyimagedatauri",
+      "node-menu-edit-attribute",
+      "node-menu-remove-attribute"
+    ]),
   },
   {
     desc: "<element> that isn't visible on the page, empty clipboard",
     selector: "#hiddenElement",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
       "node-menu-screenshotnode",
+      "node-menu-edit-attribute",
+      "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> nested in another hidden element, empty clipboard",
     selector: "#nestedHiddenElement",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
       "node-menu-screenshotnode",
+      "node-menu-edit-attribute",
+      "node-menu-remove-attribute"
     ]),
+  },
+  {
+    desc: "<element> with context menu triggered on attribute, empty clipboard",
+    selector: "#attributes",
+    disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
+    attributeTrigger: "data-edit"
   }
 ];
 
 var clipboard = require("sdk/clipboard");
 registerCleanupFunction(() => {
   clipboard = null;
 });
 
 add_task(function *() {
   let { inspector } = yield openInspectorForURL(TEST_URL);
   for (let test of TEST_CASES) {
-    let { desc, disabled, selector } = test;
+    let { desc, disabled, selector, attributeTrigger } = test;
 
     info(`Test ${desc}`);
     setupClipboard(test.clipboardData, test.clipboardDataType);
 
     let front = yield getNodeFrontForSelector(selector, inspector);
 
     info("Selecting the specified node.");
     yield selectNode(front, inspector);
 
     info("Simulating context menu click on the selected node container.");
-    contextMenuClick(getContainerForNodeFront(front, inspector).tagLine);
+    let nodeFrontContainer = getContainerForNodeFront(front, inspector);
+    let contextMenuTrigger = attributeTrigger
+      ? nodeFrontContainer.tagLine.querySelector(`[data-attr="${attributeTrigger}"]`)
+      : nodeFrontContainer.tagLine;
+    contextMenuClick(contextMenuTrigger);
 
     for (let menuitem of ALL_MENU_ITEMS) {
       let elt = inspector.panelDoc.getElementById(menuitem);
       let shouldBeDisabled = disabled.indexOf(menuitem) !== -1;
       let isDisabled = elt.hasAttribute("disabled");
 
       is(isDisabled, shouldBeDisabled,
         `#${menuitem} should be ${shouldBeDisabled ? "disabled" : "enabled"} `);
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js
@@ -0,0 +1,75 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that attribute items work in the context menu
+
+const TEST_URL = TEST_URL_ROOT + "doc_inspector_menu.html";
+
+add_task(function* () {
+  let { inspector, toolbox, testActor } = yield openInspectorForURL(TEST_URL);
+  yield selectNode("#attributes", inspector);
+
+  yield testAddAttribute();
+  yield testEditAttribute();
+  yield testRemoveAttribute();
+
+  function* testAddAttribute() {
+    info("Testing 'Add Attribute' menu item");
+    let addAttribute = getMenuItem("node-menu-add-attribute");
+
+    info("Triggering 'Add Attribute' and waiting for mutation to occur");
+    dispatchCommandEvent(addAttribute);
+    EventUtils.synthesizeKey('class="u-hidden"', {});
+    let onMutation = inspector.once("markupmutation");
+    EventUtils.synthesizeKey('VK_RETURN', {});
+    yield onMutation;
+
+    let hasAttribute = testActor.hasNode("#attributes.u-hidden");
+    ok(hasAttribute, "attribute was successfully added");
+  }
+
+  function* testEditAttribute() {
+    info("Testing 'Edit Attribute' menu item");
+    let editAttribute = getMenuItem("node-menu-edit-attribute");
+
+    info("Triggering 'Edit Attribute' and waiting for mutation to occur");
+    inspector.nodeMenuTriggerInfo = {
+      type: "attribute",
+      name: "data-edit"
+    };
+    dispatchCommandEvent(editAttribute);
+    EventUtils.synthesizeKey("data-edit='edited'", {});
+    let onMutation = inspector.once("markupmutation");
+    EventUtils.synthesizeKey('VK_RETURN', {});
+    yield onMutation;
+
+    let isAttributeChanged =
+      yield testActor.hasNode("#attributes[data-edit='edited']");
+    ok(isAttributeChanged, "attribute was successfully edited");
+  }
+
+  function* testRemoveAttribute() {
+    info("Testing 'Remove Attribute' menu item");
+    let removeAttribute = getMenuItem("node-menu-remove-attribute");
+
+    info("Triggering 'Remove Attribute' and waiting for mutation to occur");
+    inspector.nodeMenuTriggerInfo = {
+      type: "attribute",
+      name: "data-remove"
+    };
+    let onMutation = inspector.once("markupmutation");
+    dispatchCommandEvent(removeAttribute);
+    yield onMutation;
+
+    let hasAttribute = yield testActor.hasNode("#attributes[data-remove]")
+    ok(!hasAttribute, "attribute was successfully removed");
+  }
+
+  function getMenuItem(id) {
+    let attribute = inspector.panelDoc.getElementById(id);
+    ok(attribute, "Menu item '" + id + "' found");
+    return attribute;
+  }
+});
rename from devtools/client/inspector/test/browser_inspector_menu-05-other.js
rename to devtools/client/inspector/test/browser_inspector_menu-06-other.js
--- a/devtools/client/inspector/test/doc_inspector_menu.html
+++ b/devtools/client/inspector/test/doc_inspector_menu.html
@@ -18,11 +18,12 @@
       <p class="duplicate">This will be duplicated</p>
       <p id="delete">This has to be deleted</p>
       <img id="copyimage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==" />
       <div id="hiddenElement" style="display: none;">
         <p id="nestedHiddenElement">Visible element nested inside a non-visible element</p>
       </div>
       <p id="console-var">Paragraph for testing console variables</p>
       <p id="console-var-multi">Paragraph for testing multiple console variables</p>
+      <p id="attributes" data-edit="original" data-remove="thing">Attributes are going to be changed here</p>
     </div>
   </body>
 </html>
--- a/devtools/client/markupview/markup-view.js
+++ b/devtools/client/markupview/markup-view.js
@@ -2300,16 +2300,46 @@ MarkupElementContainer.prototype = Herit
   setSingleTextChild: function(singleTextChild) {
     this.singleTextChild = singleTextChild;
     this.editor.updateTextEditor();
   },
 
   clearSingleTextChild: function() {
     this.singleTextChild = undefined;
     this.editor.updateTextEditor();
+  },
+
+  /**
+   * Trigger new attribute field for input.
+   */
+  addAttribute: function() {
+    this.editor.newAttr.editMode();
+  },
+
+  /**
+   * Trigger attribute field for editing.
+   */
+  editAttribute: function(attrName) {
+    this.editor.attrElements.get(attrName).editMode();
+  },
+
+  /**
+   * Remove attribute from container.
+   * This is an undoable action.
+   */
+  removeAttribute: function(attrName) {
+    let doMods = this.editor._startModifyingAttributes();
+    let undoMods = this.editor._startModifyingAttributes();
+    this.editor._saveAttribute(attrName, undoMods);
+    doMods.removeAttribute(attrName);
+    this.undo.do(() => {
+      doMods.apply();
+    }, () => {
+      undoMods.apply();
+    });
   }
 });
 
 /**
  * Dummy container node used for the root document element.
  */
 function RootContainer(aMarkupView, aNode) {
   this.doc = aMarkupView.doc;
@@ -2356,16 +2386,23 @@ function GenericEditor(aContainer, aNode
   } else {
     this.tag.textContent = aNode.nodeName;
   }
 }
 
 GenericEditor.prototype = {
   destroy: function() {
     this.elt.remove();
+  },
+
+  /**
+   * Stub method for consistency with ElementEditor.
+   */
+  getInfoAtNode: function() {
+    return null;
   }
 };
 
 /**
  * Creates a simple text editor node, used for TEXT and COMMENT
  * nodes.
  *
  * @param MarkupContainer aContainer The container owning this editor.
@@ -2438,17 +2475,24 @@ TextEditor.prototype = {
         if (this.selected) {
           this.value.textContent = str;
           this.markup.emit("text-expand")
         }
       }).then(null, console.error);
     }
   },
 
-  destroy: function() {}
+  destroy: function() {},
+
+  /**
+   * Stub method for consistency with ElementEditor.
+   */
+  getInfoAtNode: function() {
+    return null;
+  }
 };
 
 /**
  * Creates an editor for an Element node.
  *
  * @param MarkupContainer aContainer The container owning this editor.
  * @param Element aNode The node being edited.
  */
@@ -2492,28 +2536,24 @@ function ElementEditor(aContainer, aNode
     stopOnReturn: true,
     contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED,
     popup: this.markup.popup,
     done: (aVal, aCommit) => {
       if (!aCommit) {
         return;
       }
 
-      try {
-        let doMods = this._startModifyingAttributes();
-        let undoMods = this._startModifyingAttributes();
-        this._applyAttributes(aVal, null, doMods, undoMods);
-        this.container.undo.do(() => {
-          doMods.apply();
-        }, function() {
-          undoMods.apply();
-        });
-      } catch(x) {
-        console.error(x);
-      }
+      let doMods = this._startModifyingAttributes();
+      let undoMods = this._startModifyingAttributes();
+      this._applyAttributes(aVal, null, doMods, undoMods);
+      this.container.undo.do(() => {
+        doMods.apply();
+      }, function() {
+        undoMods.apply();
+      });
     }
   });
 
   let tagName = this.node.nodeName.toLowerCase();
   this.tag.textContent = tagName;
   this.closeTag.textContent = tagName;
   this.eventNode.style.display = this.node.hasEventListeners ? "inline-block" : "none";
 
@@ -2535,16 +2575,45 @@ ElementEditor.prototype = {
     }
 
     flashElementOn(this.getAttributeElement(attrName));
 
     this.animationTimers[attrName] = setTimeout(() => {
       flashElementOff(this.getAttributeElement(attrName));
     }, this.markup.CONTAINER_FLASHING_DURATION);
   },
+  /**
+   * Returns information about node in the editor.
+   *
+   * @param {DOMNode} node
+   *        The node to get information from.
+   *
+   * @return {Object}
+   *         An object literal with the following information:
+   *         {type: "attribute", name: "rel", value: "index", el: node}
+   */
+  getInfoAtNode: function(node) {
+    if (!node) {
+      return null;
+    }
+
+    let type = null;
+    let name = null;
+    let value = null;
+
+    // Attribute
+    let attribute = node.closest('.attreditor');
+    if (attribute) {
+      type = "attribute";
+      name = attribute.querySelector('.attr-name').textContent;
+      value = attribute.querySelector('.attr-value').textContent;
+    }
+
+    return {type, name, value, el: node};
+  },
 
   /**
    * Update the state of the editor from the node.
    */
   update: function() {
     let nodeAttributes = this.node.attributes || [];
 
     // Keep the data model in sync with attributes on the node.
@@ -2692,29 +2761,25 @@ ElementEditor.prototype = {
         }
 
         let doMods = this._startModifyingAttributes();
         let undoMods = this._startModifyingAttributes();
 
         // Remove the attribute stored in this editor and re-add any attributes
         // parsed out of the input element. Restore original attribute if
         // parsing fails.
-        try {
-          this.refocusOnEdit(aAttr.name, attr, direction);
-          this._saveAttribute(aAttr.name, undoMods);
-          doMods.removeAttribute(aAttr.name);
-          this._applyAttributes(aVal, attr, doMods, undoMods);
-          this.container.undo.do(() => {
-            doMods.apply();
-          }, () => {
-            undoMods.apply();
-          });
-        } catch(ex) {
-          console.error(ex);
-        }
+        this.refocusOnEdit(aAttr.name, attr, direction);
+        this._saveAttribute(aAttr.name, undoMods);
+        doMods.removeAttribute(aAttr.name);
+        this._applyAttributes(aVal, attr, doMods, undoMods);
+        this.container.undo.do(() => {
+          doMods.apply();
+        }, () => {
+          undoMods.apply();
+        });
       }
     });
 
     // Figure out where we should place the attribute.
     let before = aBefore;
     if (aAttr.name == "id") {
       before = this.attrList.firstChild;
     } else if (aAttr.name == "class") {
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -2057,502 +2057,16 @@ FragmentOrElement::GetChildArray(uint32_
 }
 
 int32_t
 FragmentOrElement::IndexOf(const nsINode* aPossibleChild) const
 {
   return mAttrsAndChildren.IndexOfChild(aPossibleChild);
 }
 
-// Try to keep the size of StringBuilder close to a jemalloc bucket size.
-#define STRING_BUFFER_UNITS 1020
-
-namespace {
-
-// We put StringBuilder in the anonymous namespace to prevent anything outside
-// this file from accidentally being linked against it.
-
-class StringBuilder
-{
-private:
-  class Unit
-  {
-  public:
-    Unit() : mAtom(nullptr), mType(eUnknown), mLength(0)
-    {
-      MOZ_COUNT_CTOR(StringBuilder::Unit);
-    }
-    ~Unit()
-    {
-      if (mType == eString || mType == eStringWithEncode) {
-        delete mString;
-      }
-      MOZ_COUNT_DTOR(StringBuilder::Unit);
-    }
-
-    enum Type
-    {
-      eUnknown,
-      eAtom,
-      eString,
-      eStringWithEncode,
-      eLiteral,
-      eTextFragment,
-      eTextFragmentWithEncode,
-    };
-
-    union
-    {
-      nsIAtom*              mAtom;
-      const char*           mLiteral;
-      nsAutoString*         mString;
-      const nsTextFragment* mTextFragment;
-    };
-    Type     mType;
-    uint32_t mLength;
-  };
-public:
-  StringBuilder() : mLast(this), mLength(0)
-  {
-    MOZ_COUNT_CTOR(StringBuilder);
-  }
-
-  ~StringBuilder()
-  {
-    MOZ_COUNT_DTOR(StringBuilder);
-  }
-
-  void Append(nsIAtom* aAtom)
-  {
-    Unit* u = AddUnit();
-    u->mAtom = aAtom;
-    u->mType = Unit::eAtom;
-    uint32_t len = aAtom->GetLength();
-    u->mLength = len;
-    mLength += len;
-  }
-
-  template<int N>
-  void Append(const char (&aLiteral)[N])
-  {
-    Unit* u = AddUnit();
-    u->mLiteral = aLiteral;
-    u->mType = Unit::eLiteral;
-    uint32_t len = N - 1;
-    u->mLength = len;
-    mLength += len;
-  }
-
-  template<int N>
-  void Append(char (&aLiteral)[N])
-  {
-    Unit* u = AddUnit();
-    u->mLiteral = aLiteral;
-    u->mType = Unit::eLiteral;
-    uint32_t len = N - 1;
-    u->mLength = len;
-    mLength += len;
-  }
-
-  void Append(const nsAString& aString)
-  {
-    Unit* u = AddUnit();
-    u->mString = new nsAutoString(aString);
-    u->mType = Unit::eString;
-    uint32_t len = aString.Length();
-    u->mLength = len;
-    mLength += len;
-  }
-
-  void Append(nsAutoString* aString)
-  {
-    Unit* u = AddUnit();
-    u->mString = aString;
-    u->mType = Unit::eString;
-    uint32_t len = aString->Length();
-    u->mLength = len;
-    mLength += len;
-  }
-
-  void AppendWithAttrEncode(nsAutoString* aString, uint32_t aLen)
-  {
-    Unit* u = AddUnit();
-    u->mString = aString;
-    u->mType = Unit::eStringWithEncode;
-    u->mLength = aLen;
-    mLength += aLen;
-  }
-
-  void Append(const nsTextFragment* aTextFragment)
-  {
-    Unit* u = AddUnit();
-    u->mTextFragment = aTextFragment;
-    u->mType = Unit::eTextFragment;
-    uint32_t len = aTextFragment->GetLength();
-    u->mLength = len;
-    mLength += len;
-  }
-
-  void AppendWithEncode(const nsTextFragment* aTextFragment, uint32_t aLen)
-  {
-    Unit* u = AddUnit();
-    u->mTextFragment = aTextFragment;
-    u->mType = Unit::eTextFragmentWithEncode;
-    u->mLength = aLen;
-    mLength += aLen;
-  }
-
-  bool ToString(nsAString& aOut)
-  {
-    if (!aOut.SetCapacity(mLength, fallible)) {
-      return false;
-    }
-
-    for (StringBuilder* current = this; current; current = current->mNext) {
-      uint32_t len = current->mUnits.Length();
-      for (uint32_t i = 0; i < len; ++i) {
-        Unit& u = current->mUnits[i];
-        switch (u.mType) {
-          case Unit::eAtom:
-            aOut.Append(nsDependentAtomString(u.mAtom));
-            break;
-          case Unit::eString:
-            aOut.Append(*(u.mString));
-            break;
-          case Unit::eStringWithEncode:
-            EncodeAttrString(*(u.mString), aOut);
-            break;
-          case Unit::eLiteral:
-            aOut.AppendASCII(u.mLiteral, u.mLength);
-            break;
-          case Unit::eTextFragment:
-            u.mTextFragment->AppendTo(aOut);
-            break;
-          case Unit::eTextFragmentWithEncode:
-            EncodeTextFragment(u.mTextFragment, aOut);
-            break;
-          default:
-            MOZ_CRASH("Unknown unit type?");
-        }
-      }
-    }
-    return true;
-  }
-private:
-  Unit* AddUnit()
-  {
-    if (mLast->mUnits.Length() == STRING_BUFFER_UNITS) {
-      new StringBuilder(this);
-    }
-    return mLast->mUnits.AppendElement();
-  }
-
-  explicit StringBuilder(StringBuilder* aFirst)
-  : mLast(nullptr), mLength(0)
-  {
-    MOZ_COUNT_CTOR(StringBuilder);
-    aFirst->mLast->mNext = this;
-    aFirst->mLast = this;
-  }
-
-  void EncodeAttrString(const nsAutoString& aValue, nsAString& aOut)
-  {
-    const char16_t* c = aValue.BeginReading();
-    const char16_t* end = aValue.EndReading();
-    while (c < end) {
-      switch (*c) {
-      case '"':
-        aOut.AppendLiteral("&quot;");
-        break;
-      case '&':
-        aOut.AppendLiteral("&amp;");
-        break;
-      case 0x00A0:
-        aOut.AppendLiteral("&nbsp;");
-        break;
-      default:
-        aOut.Append(*c);
-        break;
-      }
-      ++c;
-    }
-  }
-
-  void EncodeTextFragment(const nsTextFragment* aValue, nsAString& aOut)
-  {
-    uint32_t len = aValue->GetLength();
-    if (aValue->Is2b()) {
-      const char16_t* data = aValue->Get2b();
-      for (uint32_t i = 0; i < len; ++i) {
-        const char16_t c = data[i];
-        switch (c) {
-          case '<':
-            aOut.AppendLiteral("&lt;");
-            break;
-          case '>':
-            aOut.AppendLiteral("&gt;");
-            break;
-          case '&':
-            aOut.AppendLiteral("&amp;");
-            break;
-          case 0x00A0:
-            aOut.AppendLiteral("&nbsp;");
-            break;
-          default:
-            aOut.Append(c);
-            break;
-        }
-      }
-    } else {
-      const char* data = aValue->Get1b();
-      for (uint32_t i = 0; i < len; ++i) {
-        const unsigned char c = data[i];
-        switch (c) {
-          case '<':
-            aOut.AppendLiteral("&lt;");
-            break;
-          case '>':
-            aOut.AppendLiteral("&gt;");
-            break;
-          case '&':
-            aOut.AppendLiteral("&amp;");
-            break;
-          case 0x00A0:
-            aOut.AppendLiteral("&nbsp;");
-            break;
-          default:
-            aOut.Append(c);
-            break;
-        }
-      }
-    }
-  }
-
-  nsAutoTArray<Unit, STRING_BUFFER_UNITS> mUnits;
-  nsAutoPtr<StringBuilder>                mNext;
-  StringBuilder*                          mLast;
-  // mLength is used only in the first StringBuilder object in the linked list.
-  uint32_t                                mLength;
-};
-
-} // namespace
-
-static void
-AppendEncodedCharacters(const nsTextFragment* aText, StringBuilder& aBuilder)
-{
-  uint32_t extraSpaceNeeded = 0;
-  uint32_t len = aText->GetLength();
-  if (aText->Is2b()) {
-    const char16_t* data = aText->Get2b();
-    for (uint32_t i = 0; i < len; ++i) {
-      const char16_t c = data[i];
-      switch (c) {
-        case '<':
-          extraSpaceNeeded += ArrayLength("&lt;") - 2;
-          break;
-        case '>':
-          extraSpaceNeeded += ArrayLength("&gt;") - 2;
-          break;
-        case '&':
-          extraSpaceNeeded += ArrayLength("&amp;") - 2;
-          break;
-        case 0x00A0:
-          extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
-          break;
-        default:
-          break;
-      }
-    }
-  } else {
-    const char* data = aText->Get1b();
-    for (uint32_t i = 0; i < len; ++i) {
-      const unsigned char c = data[i];
-      switch (c) {
-        case '<':
-          extraSpaceNeeded += ArrayLength("&lt;") - 2;
-          break;
-        case '>':
-          extraSpaceNeeded += ArrayLength("&gt;") - 2;
-          break;
-        case '&':
-          extraSpaceNeeded += ArrayLength("&amp;") - 2;
-          break;
-        case 0x00A0:
-          extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
-          break;
-        default:
-          break;
-      }
-    }
-  }
-
-  if (extraSpaceNeeded) {
-    aBuilder.AppendWithEncode(aText, len + extraSpaceNeeded);
-  } else {
-    aBuilder.Append(aText);
-  }
-}
-
-static void
-AppendEncodedAttributeValue(nsAutoString* aValue, StringBuilder& aBuilder)
-{
-  const char16_t* c = aValue->BeginReading();
-  const char16_t* end = aValue->EndReading();
-
-  uint32_t extraSpaceNeeded = 0;
-  while (c < end) {
-    switch (*c) {
-      case '"':
-        extraSpaceNeeded += ArrayLength("&quot;") - 2;
-        break;
-      case '&':
-        extraSpaceNeeded += ArrayLength("&amp;") - 2;
-        break;
-      case 0x00A0:
-        extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
-        break;
-      default:
-        break;
-    }
-    ++c;
-  }
-
-  if (extraSpaceNeeded) {
-    aBuilder.AppendWithAttrEncode(aValue, aValue->Length() + extraSpaceNeeded);
-  } else {
-    aBuilder.Append(aValue);
-  }
-}
-
-static void
-StartElement(Element* aContent, StringBuilder& aBuilder)
-{
-  nsIAtom* localName = aContent->NodeInfo()->NameAtom();
-  int32_t tagNS = aContent->GetNameSpaceID();
-
-  aBuilder.Append("<");
-  if (aContent->IsHTMLElement() || aContent->IsSVGElement() ||
-      aContent->IsMathMLElement()) {
-    aBuilder.Append(localName);
-  } else {
-    aBuilder.Append(aContent->NodeName());
-  }
-
-  int32_t count = aContent->GetAttrCount();
-  for (int32_t i = count; i > 0;) {
-    --i;
-    const nsAttrName* name = aContent->GetAttrNameAt(i);
-    int32_t attNs = name->NamespaceID();
-    nsIAtom* attName = name->LocalName();
-
-    // Filter out any attribute starting with [-|_]moz
-    nsDependentAtomString attrNameStr(attName);
-    if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) ||
-        StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) {
-      continue;
-    }
-
-    nsAutoString* attValue = new nsAutoString();
-    aContent->GetAttr(attNs, attName, *attValue);
-
-    // Filter out special case of <br type="_moz*"> used by the editor.
-    // Bug 16988.  Yuck.
-    if (localName == nsGkAtoms::br && tagNS == kNameSpaceID_XHTML &&
-        attName == nsGkAtoms::type && attNs == kNameSpaceID_None &&
-        StringBeginsWith(*attValue, NS_LITERAL_STRING("_moz"))) {
-      delete attValue;
-      continue;
-    }
-
-    aBuilder.Append(" ");
-
-    if (MOZ_LIKELY(attNs == kNameSpaceID_None) ||
-        (attNs == kNameSpaceID_XMLNS &&
-         attName == nsGkAtoms::xmlns)) {
-      // Nothing else required
-    } else if (attNs == kNameSpaceID_XML) {
-      aBuilder.Append("xml:");
-    } else if (attNs == kNameSpaceID_XMLNS) {
-      aBuilder.Append("xmlns:");
-    } else if (attNs == kNameSpaceID_XLink) {
-      aBuilder.Append("xlink:");
-    } else {
-      nsIAtom* prefix = name->GetPrefix();
-      if (prefix) {
-        aBuilder.Append(prefix);
-        aBuilder.Append(":");
-      }
-    }
-
-    aBuilder.Append(attName);
-    aBuilder.Append("=\"");
-    AppendEncodedAttributeValue(attValue, aBuilder);
-    aBuilder.Append("\"");
-  }
-
-  aBuilder.Append(">");
-
-  /*
-  // Per HTML spec we should append one \n if the first child of
-  // pre/textarea/listing is a textnode and starts with a \n.
-  // But because browsers haven't traditionally had that behavior,
-  // we're not changing our behavior either - yet.
-  if (aContent->IsHTMLElement()) {
-    if (localName == nsGkAtoms::pre || localName == nsGkAtoms::textarea ||
-        localName == nsGkAtoms::listing) {
-      nsIContent* fc = aContent->GetFirstChild();
-      if (fc &&
-          (fc->NodeType() == nsIDOMNode::TEXT_NODE ||
-           fc->NodeType() == nsIDOMNode::CDATA_SECTION_NODE)) {
-        const nsTextFragment* text = fc->GetText();
-        if (text && text->GetLength() && text->CharAt(0) == char16_t('\n')) {
-          aBuilder.Append("\n");
-        }
-      }
-    }
-  }*/
-}
-
-static inline bool
-ShouldEscape(nsIContent* aParent)
-{
-  if (!aParent || !aParent->IsHTMLElement()) {
-    return true;
-  }
-
-  static const nsIAtom* nonEscapingElements[] = {
-    nsGkAtoms::style, nsGkAtoms::script, nsGkAtoms::xmp,
-    nsGkAtoms::iframe, nsGkAtoms::noembed, nsGkAtoms::noframes,
-    nsGkAtoms::plaintext,
-    // Per the current spec noscript should be escaped in case
-    // scripts are disabled or if document doesn't have
-    // browsing context. However the latter seems to be a spec bug
-    // and Gecko hasn't traditionally done the former.
-    nsGkAtoms::noscript
-  };
-  static mozilla::BloomFilter<12, nsIAtom> sFilter;
-  static bool sInitialized = false;
-  if (!sInitialized) {
-    sInitialized = true;
-    for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) {
-      sFilter.add(nonEscapingElements[i]);
-    }
-  }
-
-  nsIAtom* tag = aParent->NodeInfo()->NameAtom();
-  if (sFilter.mightContain(tag)) {
-    for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) {
-      if (tag == nonEscapingElements[i]) {
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
 static inline bool
 IsVoidTag(nsIAtom* aTag)
 {
   static const nsIAtom* voidElements[] = {
     nsGkAtoms::area, nsGkAtoms::base, nsGkAtoms::basefont,
     nsGkAtoms::bgsound, nsGkAtoms::br, nsGkAtoms::col,
     nsGkAtoms::embed, nsGkAtoms::frame,
     nsGkAtoms::hr, nsGkAtoms::img, nsGkAtoms::input,
@@ -2575,146 +2089,31 @@ IsVoidTag(nsIAtom* aTag)
       if (aTag == voidElements[i]) {
         return true;
       }
     }
   }
   return false;
 }
 
-static inline bool
-IsVoidTag(Element* aElement)
-{
-  if (!aElement->IsHTMLElement()) {
-    return false;
-  }
-  return IsVoidTag(aElement->NodeInfo()->NameAtom());
-}
-
 /* static */
 bool
 FragmentOrElement::IsHTMLVoid(nsIAtom* aLocalName)
 {
   return aLocalName && IsVoidTag(aLocalName);
 }
 
-static bool
-Serialize(FragmentOrElement* aRoot, bool aDescendentsOnly, nsAString& aOut)
-{
-  nsINode* current = aDescendentsOnly ?
-    nsNodeUtils::GetFirstChildOfTemplateOrNode(aRoot) : aRoot;
-
-  if (!current) {
-    return true;
-  }
-
-  StringBuilder builder;
-  nsIContent* next;
-  while (true) {
-    bool isVoid = false;
-    switch (current->NodeType()) {
-      case nsIDOMNode::ELEMENT_NODE: {
-        Element* elem = current->AsElement();
-        StartElement(elem, builder);
-        isVoid = IsVoidTag(elem);
-        if (!isVoid &&
-            (next = nsNodeUtils::GetFirstChildOfTemplateOrNode(current))) {
-          current = next;
-          continue;
-        }
-        break;
-      }
-
-      case nsIDOMNode::TEXT_NODE:
-      case nsIDOMNode::CDATA_SECTION_NODE: {
-        const nsTextFragment* text = static_cast<nsIContent*>(current)->GetText();
-        nsIContent* parent = current->GetParent();
-        if (ShouldEscape(parent)) {
-          AppendEncodedCharacters(text, builder);
-        } else {
-          builder.Append(text);
-        }
-        break;
-      }
-
-      case nsIDOMNode::COMMENT_NODE: {
-        builder.Append("<!--");
-        builder.Append(static_cast<nsIContent*>(current)->GetText());
-        builder.Append("-->");
-        break;
-      }
-
-      case nsIDOMNode::DOCUMENT_TYPE_NODE: {
-        builder.Append("<!DOCTYPE ");
-        builder.Append(current->NodeName());
-        builder.Append(">");
-        break;
-      }
-
-      case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: {
-        builder.Append("<?");
-        builder.Append(current->NodeName());
-        builder.Append(" ");
-        builder.Append(static_cast<nsIContent*>(current)->GetText());
-        builder.Append(">");
-        break;
-      }
-    }
-
-    while (true) {
-      if (!isVoid && current->NodeType() == nsIDOMNode::ELEMENT_NODE) {
-        builder.Append("</");
-        nsIContent* elem = static_cast<nsIContent*>(current);
-        if (elem->IsHTMLElement() || elem->IsSVGElement() ||
-            elem->IsMathMLElement()) {
-          builder.Append(elem->NodeInfo()->NameAtom());
-        } else {
-          builder.Append(current->NodeName());
-        }
-        builder.Append(">");
-      }
-      isVoid = false;
-
-      if (current == aRoot) {
-        return builder.ToString(aOut);
-      }
-
-      if ((next = current->GetNextSibling())) {
-        current = next;
-        break;
-      }
-
-      current = current->GetParentNode();
-
-      // Handle template element. If the parent is a template's content,
-      // then adjust the parent to be the template element.
-      if (current != aRoot &&
-          current->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
-        DocumentFragment* frag = static_cast<DocumentFragment*>(current);
-        nsIContent* fragHost = frag->GetHost();
-        if (fragHost && nsNodeUtils::IsTemplateElement(fragHost)) {
-          current = fragHost;
-        }
-      }
-
-      if (aDescendentsOnly && current == aRoot) {
-        return builder.ToString(aOut);
-      }
-    }
-  }
-}
-
 void
 FragmentOrElement::GetMarkup(bool aIncludeSelf, nsAString& aMarkup)
 {
   aMarkup.Truncate();
 
   nsIDocument* doc = OwnerDoc();
   if (IsInHTMLDocument()) {
-    Serialize(this, !aIncludeSelf, aMarkup);
+    nsContentUtils::SerializeNodeToMarkup(this, !aIncludeSelf, aMarkup);
     return;
   }
 
   nsAutoString contentType;
   doc->GetContentType(contentType);
   bool tryToCacheEncoder = !aIncludeSelf;
 
   nsCOMPtr<nsIDocumentEncoder> docEncoder = doc->GetCachedEncoder();
--- a/dom/base/nsContentIterator.cpp
+++ b/dom/base/nsContentIterator.cpp
@@ -43,21 +43,31 @@ static bool
 NodeIsInTraversalRange(nsINode* aNode, bool aIsPreMode,
                        nsINode* aStartNode, int32_t aStartOffset,
                        nsINode* aEndNode, int32_t aEndOffset)
 {
   if (!aStartNode || !aEndNode || !aNode) {
     return false;
   }
 
-  // If a chardata node contains an end point of the traversal range, it is
+  // If a leaf node contains an end point of the traversal range, it is
   // always in the traversal range.
-  if (aNode->IsNodeOfType(nsINode::eDATA_NODE) &&
-      (aNode == aStartNode || aNode == aEndNode)) {
-    return true;
+  if (aNode == aStartNode || aNode == aEndNode) {
+    if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
+      return true; // text node or something
+    }
+    if (!aNode->HasChildren()) {
+      MOZ_ASSERT(aNode != aStartNode || !aStartOffset,
+        "aStartNode doesn't have children and not a data node, "
+        "aStartOffset should be 0");
+      MOZ_ASSERT(aNode != aEndNode || !aEndOffset,
+        "aStartNode doesn't have children and not a data node, "
+        "aStartOffset should be 0");
+      return true;
+    }
   }
 
   nsINode* parent = aNode->GetParentNode();
   if (!parent) {
     return false;
   }
 
   int32_t indx = parent->IndexOf(aNode);
@@ -336,22 +346,24 @@ nsContentIterator::Init(nsIDOMRange* aDO
     // XXXbz no children might also just mean no children.  So I'm not
     // sure what that comment above is talking about.
 
     if (mPre) {
       // XXX: In the future, if start offset is after the last
       //      character in the cdata node, should we set mFirst to
       //      the next sibling?
 
-      if (!startIsData) {
+      // If the node has no child, the child may be <br> or something.
+      // So, we shouldn't skip the empty node if the start offset is 0.
+      // In other words, if the offset is 1, the node should be ignored.
+      if (!startIsData && startIndx) {
         mFirst = GetNextSibling(startNode);
 
         // Does mFirst node really intersect the range?  The range could be
         // 'degenerate', i.e., not collapsed but still contain no content.
-
         if (mFirst && !NodeIsInTraversalRange(mFirst, mPre, startNode,
                                               startIndx, endNode, endIndx)) {
           mFirst = nullptr;
         }
       } else {
         mFirst = startNode->AsContent();
       }
     } else {
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -192,16 +192,17 @@
 #include "nsXULPopupManager.h"
 #include "xpcprivate.h" // nsXPConnect
 #include "HTMLSplitOnSpacesTokenizer.h"
 #include "nsContentTypeParser.h"
 #include "nsICookiePermission.h"
 #include "mozIThirdPartyUtil.h"
 #include "nsICookieService.h"
 #include "mozilla/EnumSet.h"
+#include "mozilla/BloomFilter.h"
 
 #include "nsIBidiKeyboard.h"
 
 #if defined(XP_WIN)
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
 #endif
 
@@ -8254,8 +8255,614 @@ nsContentUtils::InternalStorageAllowedFo
       // for non-cookie storage types, this may change.
 
       return StorageAccess::eDeny;
     }
   }
 
   return access;
 }
+
+namespace {
+
+// We put StringBuilder in the anonymous namespace to prevent anything outside
+// this file from accidentally being linked against it.
+
+class StringBuilder
+{
+private:
+  // Try to keep the size of StringBuilder close to a jemalloc bucket size.
+  static const uint32_t STRING_BUFFER_UNITS = 1020;
+  class Unit
+  {
+  public:
+    Unit() : mAtom(nullptr), mType(eUnknown), mLength(0)
+    {
+      MOZ_COUNT_CTOR(StringBuilder::Unit);
+    }
+    ~Unit()
+    {
+      if (mType == eString || mType == eStringWithEncode) {
+        delete mString;
+      }
+      MOZ_COUNT_DTOR(StringBuilder::Unit);
+    }
+
+    enum Type
+    {
+      eUnknown,
+      eAtom,
+      eString,
+      eStringWithEncode,
+      eLiteral,
+      eTextFragment,
+      eTextFragmentWithEncode,
+    };
+
+    union
+    {
+      nsIAtom*              mAtom;
+      const char*           mLiteral;
+      nsAutoString*         mString;
+      const nsTextFragment* mTextFragment;
+    };
+    Type     mType;
+    uint32_t mLength;
+  };
+public:
+  StringBuilder() : mLast(this), mLength(0)
+  {
+    MOZ_COUNT_CTOR(StringBuilder);
+  }
+
+  ~StringBuilder()
+  {
+    MOZ_COUNT_DTOR(StringBuilder);
+  }
+
+  void Append(nsIAtom* aAtom)
+  {
+    Unit* u = AddUnit();
+    u->mAtom = aAtom;
+    u->mType = Unit::eAtom;
+    uint32_t len = aAtom->GetLength();
+    u->mLength = len;
+    mLength += len;
+  }
+
+  template<int N>
+  void Append(const char (&aLiteral)[N])
+  {
+    Unit* u = AddUnit();
+    u->mLiteral = aLiteral;
+    u->mType = Unit::eLiteral;
+    uint32_t len = N - 1;
+    u->mLength = len;
+    mLength += len;
+  }
+
+  template<int N>
+  void Append(char (&aLiteral)[N])
+  {
+    Unit* u = AddUnit();
+    u->mLiteral = aLiteral;
+    u->mType = Unit::eLiteral;
+    uint32_t len = N - 1;
+    u->mLength = len;
+    mLength += len;
+  }
+
+  void Append(const nsAString& aString)
+  {
+    Unit* u = AddUnit();
+    u->mString = new nsAutoString(aString);
+    u->mType = Unit::eString;
+    uint32_t len = aString.Length();
+    u->mLength = len;
+    mLength += len;
+  }
+
+  void Append(nsAutoString* aString)
+  {
+    Unit* u = AddUnit();
+    u->mString = aString;
+    u->mType = Unit::eString;
+    uint32_t len = aString->Length();
+    u->mLength = len;
+    mLength += len;
+  }
+
+  void AppendWithAttrEncode(nsAutoString* aString, uint32_t aLen)
+  {
+    Unit* u = AddUnit();
+    u->mString = aString;
+    u->mType = Unit::eStringWithEncode;
+    u->mLength = aLen;
+    mLength += aLen;
+  }
+
+  void Append(const nsTextFragment* aTextFragment)
+  {
+    Unit* u = AddUnit();
+    u->mTextFragment = aTextFragment;
+    u->mType = Unit::eTextFragment;
+    uint32_t len = aTextFragment->GetLength();
+    u->mLength = len;
+    mLength += len;
+  }
+
+  void AppendWithEncode(const nsTextFragment* aTextFragment, uint32_t aLen)
+  {
+    Unit* u = AddUnit();
+    u->mTextFragment = aTextFragment;
+    u->mType = Unit::eTextFragmentWithEncode;
+    u->mLength = aLen;
+    mLength += aLen;
+  }
+
+  bool ToString(nsAString& aOut)
+  {
+    if (!aOut.SetCapacity(mLength, fallible)) {
+      return false;
+    }
+
+    for (StringBuilder* current = this; current; current = current->mNext) {
+      uint32_t len = current->mUnits.Length();
+      for (uint32_t i = 0; i < len; ++i) {
+        Unit& u = current->mUnits[i];
+        switch (u.mType) {
+          case Unit::eAtom:
+            aOut.Append(nsDependentAtomString(u.mAtom));
+            break;
+          case Unit::eString:
+            aOut.Append(*(u.mString));
+            break;
+          case Unit::eStringWithEncode:
+            EncodeAttrString(*(u.mString), aOut);
+            break;
+          case Unit::eLiteral:
+            aOut.AppendASCII(u.mLiteral, u.mLength);
+            break;
+          case Unit::eTextFragment:
+            u.mTextFragment->AppendTo(aOut);
+            break;
+          case Unit::eTextFragmentWithEncode:
+            EncodeTextFragment(u.mTextFragment, aOut);
+            break;
+          default:
+            MOZ_CRASH("Unknown unit type?");
+        }
+      }
+    }
+    return true;
+  }
+private:
+  Unit* AddUnit()
+  {
+    if (mLast->mUnits.Length() == STRING_BUFFER_UNITS) {
+      new StringBuilder(this);
+    }
+    return mLast->mUnits.AppendElement();
+  }
+
+  explicit StringBuilder(StringBuilder* aFirst)
+  : mLast(nullptr), mLength(0)
+  {
+    MOZ_COUNT_CTOR(StringBuilder);
+    aFirst->mLast->mNext = this;
+    aFirst->mLast = this;
+  }
+
+  void EncodeAttrString(const nsAutoString& aValue, nsAString& aOut)
+  {
+    const char16_t* c = aValue.BeginReading();
+    const char16_t* end = aValue.EndReading();
+    while (c < end) {
+      switch (*c) {
+      case '"':
+        aOut.AppendLiteral("&quot;");
+        break;
+      case '&':
+        aOut.AppendLiteral("&amp;");
+        break;
+      case 0x00A0:
+        aOut.AppendLiteral("&nbsp;");
+        break;
+      default:
+        aOut.Append(*c);
+        break;
+      }
+      ++c;
+    }
+  }
+
+  void EncodeTextFragment(const nsTextFragment* aValue, nsAString& aOut)
+  {
+    uint32_t len = aValue->GetLength();
+    if (aValue->Is2b()) {
+      const char16_t* data = aValue->Get2b();
+      for (uint32_t i = 0; i < len; ++i) {
+        const char16_t c = data[i];
+        switch (c) {
+          case '<':
+            aOut.AppendLiteral("&lt;");
+            break;
+          case '>':
+            aOut.AppendLiteral("&gt;");
+            break;
+          case '&':
+            aOut.AppendLiteral("&amp;");
+            break;
+          case 0x00A0:
+            aOut.AppendLiteral("&nbsp;");
+            break;
+          default:
+            aOut.Append(c);
+            break;
+        }
+      }
+    } else {
+      const char* data = aValue->Get1b();
+      for (uint32_t i = 0; i < len; ++i) {
+        const unsigned char c = data[i];
+        switch (c) {
+          case '<':
+            aOut.AppendLiteral("&lt;");
+            break;
+          case '>':
+            aOut.AppendLiteral("&gt;");
+            break;
+          case '&':
+            aOut.AppendLiteral("&amp;");
+            break;
+          case 0x00A0:
+            aOut.AppendLiteral("&nbsp;");
+            break;
+          default:
+            aOut.Append(c);
+            break;
+        }
+      }
+    }
+  }
+
+  nsAutoTArray<Unit, STRING_BUFFER_UNITS> mUnits;
+  nsAutoPtr<StringBuilder>                mNext;
+  StringBuilder*                          mLast;
+  // mLength is used only in the first StringBuilder object in the linked list.
+  uint32_t                                mLength;
+};
+
+} // namespace
+
+static void
+AppendEncodedCharacters(const nsTextFragment* aText, StringBuilder& aBuilder)
+{
+  uint32_t extraSpaceNeeded = 0;
+  uint32_t len = aText->GetLength();
+  if (aText->Is2b()) {
+    const char16_t* data = aText->Get2b();
+    for (uint32_t i = 0; i < len; ++i) {
+      const char16_t c = data[i];
+      switch (c) {
+        case '<':
+          extraSpaceNeeded += ArrayLength("&lt;") - 2;
+          break;
+        case '>':
+          extraSpaceNeeded += ArrayLength("&gt;") - 2;
+          break;
+        case '&':
+          extraSpaceNeeded += ArrayLength("&amp;") - 2;
+          break;
+        case 0x00A0:
+          extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
+          break;
+        default:
+          break;
+      }
+    }
+  } else {
+    const char* data = aText->Get1b();
+    for (uint32_t i = 0; i < len; ++i) {
+      const unsigned char c = data[i];
+      switch (c) {
+        case '<':
+          extraSpaceNeeded += ArrayLength("&lt;") - 2;
+          break;
+        case '>':
+          extraSpaceNeeded += ArrayLength("&gt;") - 2;
+          break;
+        case '&':
+          extraSpaceNeeded += ArrayLength("&amp;") - 2;
+          break;
+        case 0x00A0:
+          extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  if (extraSpaceNeeded) {
+    aBuilder.AppendWithEncode(aText, len + extraSpaceNeeded);
+  } else {
+    aBuilder.Append(aText);
+  }
+}
+
+static void
+AppendEncodedAttributeValue(nsAutoString* aValue, StringBuilder& aBuilder)
+{
+  const char16_t* c = aValue->BeginReading();
+  const char16_t* end = aValue->EndReading();
+
+  uint32_t extraSpaceNeeded = 0;
+  while (c < end) {
+    switch (*c) {
+      case '"':
+        extraSpaceNeeded += ArrayLength("&quot;") - 2;
+        break;
+      case '&':
+        extraSpaceNeeded += ArrayLength("&amp;") - 2;
+        break;
+      case 0x00A0:
+        extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
+        break;
+      default:
+        break;
+    }
+    ++c;
+  }
+
+  if (extraSpaceNeeded) {
+    aBuilder.AppendWithAttrEncode(aValue, aValue->Length() + extraSpaceNeeded);
+  } else {
+    aBuilder.Append(aValue);
+  }
+}
+
+static void
+StartElement(Element* aContent, StringBuilder& aBuilder)
+{
+  nsIAtom* localName = aContent->NodeInfo()->NameAtom();
+  int32_t tagNS = aContent->GetNameSpaceID();
+
+  aBuilder.Append("<");
+  if (aContent->IsHTMLElement() || aContent->IsSVGElement() ||
+      aContent->IsMathMLElement()) {
+    aBuilder.Append(localName);
+  } else {
+    aBuilder.Append(aContent->NodeName());
+  }
+
+  int32_t count = aContent->GetAttrCount();
+  for (int32_t i = count; i > 0;) {
+    --i;
+    const nsAttrName* name = aContent->GetAttrNameAt(i);
+    int32_t attNs = name->NamespaceID();
+    nsIAtom* attName = name->LocalName();
+
+    // Filter out any attribute starting with [-|_]moz
+    nsDependentAtomString attrNameStr(attName);
+    if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) ||
+        StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) {
+      continue;
+    }
+
+    nsAutoString* attValue = new nsAutoString();
+    aContent->GetAttr(attNs, attName, *attValue);
+
+    // Filter out special case of <br type="_moz*"> used by the editor.
+    // Bug 16988.  Yuck.
+    if (localName == nsGkAtoms::br && tagNS == kNameSpaceID_XHTML &&
+        attName == nsGkAtoms::type && attNs == kNameSpaceID_None &&
+        StringBeginsWith(*attValue, NS_LITERAL_STRING("_moz"))) {
+      delete attValue;
+      continue;
+    }
+
+    aBuilder.Append(" ");
+
+    if (MOZ_LIKELY(attNs == kNameSpaceID_None) ||
+        (attNs == kNameSpaceID_XMLNS &&
+         attName == nsGkAtoms::xmlns)) {
+      // Nothing else required
+    } else if (attNs == kNameSpaceID_XML) {
+      aBuilder.Append("xml:");
+    } else if (attNs == kNameSpaceID_XMLNS) {
+      aBuilder.Append("xmlns:");
+    } else if (attNs == kNameSpaceID_XLink) {
+      aBuilder.Append("xlink:");
+    } else {
+      nsIAtom* prefix = name->GetPrefix();
+      if (prefix) {
+        aBuilder.Append(prefix);
+        aBuilder.Append(":");
+      }
+    }
+
+    aBuilder.Append(attName);
+    aBuilder.Append("=\"");
+    AppendEncodedAttributeValue(attValue, aBuilder);
+    aBuilder.Append("\"");
+  }
+
+  aBuilder.Append(">");
+
+  /*
+  // Per HTML spec we should append one \n if the first child of
+  // pre/textarea/listing is a textnode and starts with a \n.
+  // But because browsers haven't traditionally had that behavior,
+  // we're not changing our behavior either - yet.
+  if (aContent->IsHTMLElement()) {
+    if (localName == nsGkAtoms::pre || localName == nsGkAtoms::textarea ||
+        localName == nsGkAtoms::listing) {
+      nsIContent* fc = aContent->GetFirstChild();
+      if (fc &&
+          (fc->NodeType() == nsIDOMNode::TEXT_NODE ||
+           fc->NodeType() == nsIDOMNode::CDATA_SECTION_NODE)) {
+        const nsTextFragment* text = fc->GetText();
+        if (text && text->GetLength() && text->CharAt(0) == char16_t('\n')) {
+          aBuilder.Append("\n");
+        }
+      }
+    }
+  }*/
+}
+
+static inline bool
+ShouldEscape(nsIContent* aParent)
+{
+  if (!aParent || !aParent->IsHTMLElement()) {
+    return true;
+  }
+
+  static const nsIAtom* nonEscapingElements[] = {
+    nsGkAtoms::style, nsGkAtoms::script, nsGkAtoms::xmp,
+    nsGkAtoms::iframe, nsGkAtoms::noembed, nsGkAtoms::noframes,
+    nsGkAtoms::plaintext,
+    // Per the current spec noscript should be escaped in case
+    // scripts are disabled or if document doesn't have
+    // browsing context. However the latter seems to be a spec bug
+    // and Gecko hasn't traditionally done the former.
+    nsGkAtoms::noscript
+  };
+  static mozilla::BloomFilter<12, nsIAtom> sFilter;
+  static bool sInitialized = false;
+  if (!sInitialized) {
+    sInitialized = true;
+    for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) {
+      sFilter.add(nonEscapingElements[i]);
+    }
+  }
+
+  nsIAtom* tag = aParent->NodeInfo()->NameAtom();
+  if (sFilter.mightContain(tag)) {
+    for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) {
+      if (tag == nonEscapingElements[i]) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+static inline bool
+IsVoidTag(Element* aElement)
+{
+  if (!aElement->IsHTMLElement()) {
+    return false;
+  }
+  return FragmentOrElement::IsHTMLVoid(aElement->NodeInfo()->NameAtom());
+}
+
+bool
+nsContentUtils::SerializeNodeToMarkup(nsINode* aRoot,
+                                      bool aDescendentsOnly,
+                                      nsAString& aOut)
+{
+  // If you pass in a DOCUMENT_NODE, you must pass aDescendentsOnly as true
+  MOZ_ASSERT(aDescendentsOnly ||
+             aRoot->NodeType() != nsIDOMNode::DOCUMENT_NODE);
+
+  nsINode* current = aDescendentsOnly ?
+    nsNodeUtils::GetFirstChildOfTemplateOrNode(aRoot) : aRoot;
+
+  if (!current) {
+    return true;
+  }
+
+  StringBuilder builder;
+  nsIContent* next;
+  while (true) {
+    bool isVoid = false;
+    switch (current->NodeType()) {
+      case nsIDOMNode::ELEMENT_NODE: {
+        Element* elem = current->AsElement();
+        StartElement(elem, builder);
+        isVoid = IsVoidTag(elem);
+        if (!isVoid &&
+            (next = nsNodeUtils::GetFirstChildOfTemplateOrNode(current))) {
+          current = next;
+          continue;
+        }
+        break;
+      }
+
+      case nsIDOMNode::TEXT_NODE:
+      case nsIDOMNode::CDATA_SECTION_NODE: {
+        const nsTextFragment* text = static_cast<nsIContent*>(current)->GetText();
+        nsIContent* parent = current->GetParent();
+        if (ShouldEscape(parent)) {
+          AppendEncodedCharacters(text, builder);
+        } else {
+          builder.Append(text);
+        }
+        break;
+      }
+
+      case nsIDOMNode::COMMENT_NODE: {
+        builder.Append("<!--");
+        builder.Append(static_cast<nsIContent*>(current)->GetText());
+        builder.Append("-->");
+        break;
+      }
+
+      case nsIDOMNode::DOCUMENT_TYPE_NODE: {
+        builder.Append("<!DOCTYPE ");
+        builder.Append(current->NodeName());
+        builder.Append(">");
+        break;
+      }
+
+      case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: {
+        builder.Append("<?");
+        builder.Append(current->NodeName());
+        builder.Append(" ");
+        builder.Append(static_cast<nsIContent*>(current)->GetText());
+        builder.Append(">");
+        break;
+      }
+    }
+
+    while (true) {
+      if (!isVoid && current->NodeType() == nsIDOMNode::ELEMENT_NODE) {
+        builder.Append("</");
+        nsIContent* elem = static_cast<nsIContent*>(current);
+        if (elem->IsHTMLElement() || elem->IsSVGElement() ||
+            elem->IsMathMLElement()) {
+          builder.Append(elem->NodeInfo()->NameAtom());
+        } else {
+          builder.Append(current->NodeName());
+        }
+        builder.Append(">");
+      }
+      isVoid = false;
+
+      if (current == aRoot) {
+        return builder.ToString(aOut);
+      }
+
+      if ((next = current->GetNextSibling())) {
+        current = next;
+        break;
+      }
+
+      current = current->GetParentNode();
+
+      // Handle template element. If the parent is a template's content,
+      // then adjust the parent to be the template element.
+      if (current != aRoot &&
+          current->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
+        DocumentFragment* frag = static_cast<DocumentFragment*>(current);
+        nsIContent* fragHost = frag->GetHost();
+        if (fragHost && nsNodeUtils::IsTemplateElement(fragHost)) {
+          current = fragHost;
+        }
+      }
+
+      if (aDescendentsOnly && current == aRoot) {
+        return builder.ToString(aOut);
+      }
+    }
+  }
+}
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2556,16 +2556,23 @@ public:
 
   /*
    * Checks if storage for the given principal is permitted by the user's
    * preferences. The caller is assumed to not be a third-party iframe.
    * (if that is possible, the caller should use StorageAllowedForWindow)
    */
   static StorageAccess StorageAllowedForPrincipal(nsIPrincipal* aPrincipal);
 
+  /*
+   * Serializes a HTML nsINode into its markup representation.
+   */
+  static bool SerializeNodeToMarkup(nsINode* aRoot,
+                                    bool aDescendentsOnly,
+                                    nsAString& aOut);
+
 private:
   static bool InitializeEventTable();
 
   static nsresult EnsureStringBundle(PropertiesFile aFile);
 
   static bool CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
                                 nsIPrincipal* aPrincipal);
 
--- a/dom/base/nsXMLHttpRequest.cpp
+++ b/dom/base/nsXMLHttpRequest.cpp
@@ -2306,37 +2306,55 @@ nsXMLHttpRequest::ChangeStateToDone()
   }
 }
 
 static nsresult
 GetRequestBody(nsIDOMDocument* aDoc, nsIInputStream** aResult,
                uint64_t* aContentLength, nsACString& aContentType,
                nsACString& aCharset)
 {
-  aContentType.AssignLiteral("application/xml");
   nsCOMPtr<nsIDocument> doc(do_QueryInterface(aDoc));
   NS_ENSURE_STATE(doc);
   aCharset.AssignLiteral("UTF-8");
 
   nsresult rv;
-  nsCOMPtr<nsIDOMSerializer> serializer =
-    do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   nsCOMPtr<nsIStorageStream> storStream;
   rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIOutputStream> output;
   rv = storStream->GetOutputStream(0, getter_AddRefs(output));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Make sure to use the encoding we'll send
-  rv = serializer->SerializeToStream(aDoc, output, aCharset);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (doc->IsHTMLDocument()) {
+    aContentType.AssignLiteral("text/html");
+
+    nsString serialized;
+    if (!nsContentUtils::SerializeNodeToMarkup(doc, true, serialized)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+    NS_ConvertUTF16toUTF8 utf8Serialized(serialized);
+
+    uint32_t written;
+    rv = output->Write(utf8Serialized.get(), utf8Serialized.Length(), &written);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    MOZ_ASSERT(written == utf8Serialized.Length());
+  } else {
+    aContentType.AssignLiteral("application/xml");
+
+    nsCOMPtr<nsIDOMSerializer> serializer =
+      do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Make sure to use the encoding we'll send
+    rv = serializer->SerializeToStream(aDoc, output, aCharset);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+  }
 
   output->Close();
 
   uint32_t length;
   rv = storStream->GetLength(&length);
   NS_ENSURE_SUCCESS(rv, rv);
   *aContentLength = length;
 
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -4843,16 +4843,19 @@ HTMLInputElement::IsFilesAndDirectoriesS
 
 void
 HTMLInputElement::ChooseDirectory(ErrorResult& aRv)
 {
   if (mType != NS_FORM_INPUT_FILE) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
+  // Script can call this method directly, so even though we don't show the
+  // "Pick Folder..." button on platforms that don't have a directory picker
+  // we have to redirect to the file picker here.
   InitFilePicker(
 #if defined(ANDROID) || defined(MOZ_B2G)
                  // No native directory picker - redirect to plain file picker
                  FILE_PICKER_FILE
 #else
                  FILE_PICKER_DIRECTORY
 #endif
                  );
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -108,18 +108,16 @@ public:
       sUniqueInstance = nullptr;
     }
   }
 };
 
 StaticRefPtr<MediaMemoryTracker> MediaMemoryTracker::sUniqueInstance;
 
 #if defined(PR_LOGGING)
-PRLogModuleInfo* gStateWatchingLog;
-PRLogModuleInfo* gMozPromiseLog;
 PRLogModuleInfo* gMediaTimerLog;
 PRLogModuleInfo* gMediaSampleLog;
 #endif
 
 void
 MediaDecoder::InitStatics()
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -814,19 +814,18 @@ WebMTrackDemuxer::Seek(media::TimeUnit a
 
   media::TimeUnit seekTime = aTime;
   mSamples.Reset();
   mParent->SeekInternal(aTime);
   mParent->GetNextPacket(mType, &mSamples);
 
   // Check what time we actually seeked to.
   if (mSamples.GetSize() > 0) {
-    RefPtr<MediaRawData> sample(mSamples.PopFront());
+    const RefPtr<MediaRawData>& sample = mSamples.First();
     seekTime = media::TimeUnit::FromMicroseconds(sample->mTime);
-    mSamples.PushFront(sample);
   }
   SetNextKeyFrameTime();
 
   return SeekPromise::CreateAndResolve(seekTime, __func__);
 }
 
 RefPtr<MediaRawData>
 WebMTrackDemuxer::NextSample()
@@ -871,50 +870,50 @@ WebMTrackDemuxer::SetNextKeyFrameTime()
     return;
   }
 
   int64_t frameTime = -1;
 
   mNextKeyframeTime.reset();
 
   MediaRawDataQueue skipSamplesQueue;
-  RefPtr<MediaRawData> sample;
   bool foundKeyframe = false;
   while (!foundKeyframe && mSamples.GetSize()) {
-    sample = mSamples.PopFront();
+    RefPtr<MediaRawData> sample = mSamples.PopFront();
     if (sample->mKeyframe) {
       frameTime = sample->mTime;
       foundKeyframe = true;
     }
-    skipSamplesQueue.Push(sample);
+    skipSamplesQueue.Push(sample.forget());
   }
   Maybe<int64_t> startTime;
   if (skipSamplesQueue.GetSize()) {
-    sample = skipSamplesQueue.PopFront();
+    const RefPtr<MediaRawData>& sample = skipSamplesQueue.First();
     startTime.emplace(sample->mTimecode);
-    skipSamplesQueue.PushFront(sample);
   }
   // Demux and buffer frames until we find a keyframe.
+  RefPtr<MediaRawData> sample;
   while (!foundKeyframe && (sample = NextSample())) {
     if (sample->mKeyframe) {
       frameTime = sample->mTime;
       foundKeyframe = true;
     }
-    skipSamplesQueue.Push(sample);
+    int64_t sampleTimecode = sample->mTimecode;
+    skipSamplesQueue.Push(sample.forget());
     if (!startTime) {
-      startTime.emplace(sample->mTimecode);
+      startTime.emplace(sampleTimecode);
     } else if (!foundKeyframe &&
-               sample->mTimecode > startTime.ref() + MAX_LOOK_AHEAD) {
+               sampleTimecode > startTime.ref() + MAX_LOOK_AHEAD) {
       WEBM_DEBUG("Couldn't find keyframe in a reasonable time, aborting");
       break;
     }
   }
   // We may have demuxed more than intended, so ensure that all frames are kept
   // in the right order.
-  mSamples.PushFront(skipSamplesQueue);
+  mSamples.PushFront(Move(skipSamplesQueue));
 
   if (frameTime != -1) {
     mNextKeyframeTime.emplace(media::TimeUnit::FromMicroseconds(frameTime));
     WEBM_DEBUG("Next Keyframe %f (%u queued %.02fs)",
                mNextKeyframeTime.value().ToSeconds(),
                uint32_t(mSamples.GetSize()),
                media::TimeUnit::FromMicroseconds(mSamples.Last()->mTimecode - mSamples.First()->mTimecode).ToSeconds());
   } else {
@@ -967,17 +966,17 @@ WebMTrackDemuxer::SkipToNextRandomAccess
   RefPtr<MediaRawData> sample;
 
   WEBM_DEBUG("TimeThreshold: %f", aTimeThreshold.ToSeconds());
   while (!found && (sample = NextSample())) {
     parsed++;
     if (sample->mKeyframe && sample->mTime >= aTimeThreshold.ToMicroseconds()) {
       found = true;
       mSamples.Reset();
-      mSamples.PushFront(sample);
+      mSamples.PushFront(sample.forget());
     }
   }
   SetNextKeyFrameTime();
   if (found) {
     WEBM_DEBUG("next sample: %f (parsed: %d)",
                media::TimeUnit::FromMicroseconds(sample->mTime).ToSeconds(),
                parsed);
     return SkipAccessPointPromise::CreateAndResolve(parsed, __func__);
--- a/dom/media/webm/WebMDemuxer.h
+++ b/dom/media/webm/WebMDemuxer.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(WebMDemuxer_h_)
 #define WebMDemuxer_h_
 
 #include "nsTArray.h"
 #include "MediaDataDemuxer.h"
 #include "NesteggPacketHolder.h"
+#include "mozilla/Move.h"
 
 typedef struct nestegg nestegg;
 
 namespace mozilla {
 
 class WebMBufferedState;
 
 // Queue for holding MediaRawData samples
@@ -22,26 +23,32 @@ class MediaRawDataQueue {
   uint32_t GetSize() {
     return mQueue.size();
   }
 
   void Push(MediaRawData* aItem) {
     mQueue.push_back(aItem);
   }
 
-  void Push(const MediaRawDataQueue& aOther) {
-    mQueue.insert(mQueue.end(), aOther.mQueue.begin(), aOther.mQueue.end());
+  void Push(already_AddRefed<MediaRawData>&& aItem) {
+    mQueue.push_back(Move(aItem));
   }
 
   void PushFront(MediaRawData* aItem) {
     mQueue.push_front(aItem);
   }
 
-  void PushFront(const MediaRawDataQueue& aOther) {
-    mQueue.insert(mQueue.begin(), aOther.mQueue.begin(), aOther.mQueue.end());
+  void PushFront(already_AddRefed<MediaRawData>&& aItem) {
+    mQueue.push_front(Move(aItem));
+  }
+
+  void PushFront(MediaRawDataQueue&& aOther) {
+    while (!aOther.mQueue.empty()) {
+      Push(aOther.PopFront());
+    }
   }
 
   already_AddRefed<MediaRawData> PopFront() {
     RefPtr<MediaRawData> result = mQueue.front().forget();
     mQueue.pop_front();
     return result.forget();
   }
 
--- a/dom/webidl/XMLHttpRequest.webidl
+++ b/dom/webidl/XMLHttpRequest.webidl
@@ -49,17 +49,17 @@ dictionary MozXMLHttpRequestParameters
 };
 
 [Constructor(optional MozXMLHttpRequestParameters params),
  // There are apparently callers, specifically CoffeeScript, who do
  // things like this:
  //   c = new(window.ActiveXObject || XMLHttpRequest)("Microsoft.XMLHTTP")
  // To handle that, we need a constructor that takes a string.
  Constructor(DOMString ignored),
- Exposed=(Window,Worker)]
+ Exposed=(Window,DedicatedWorker,SharedWorker)]
 interface XMLHttpRequest : XMLHttpRequestEventTarget {
   // event handler
   attribute EventHandler onreadystatechange;
 
   // states
   const unsigned short UNSENT = 0;
   const unsigned short OPENED = 1;
   const unsigned short HEADERS_RECEIVED = 2;
--- a/dom/webidl/XMLHttpRequestEventTarget.webidl
+++ b/dom/webidl/XMLHttpRequestEventTarget.webidl
@@ -5,17 +5,17 @@
  *
  * The origin of this IDL file is
  * www.w3.org/TR/2012/WD-XMLHttpRequest-20120117/
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
-[Exposed=(Window,Worker)]
+[Exposed=(Window,DedicatedWorker,SharedWorker)]
 interface XMLHttpRequestEventTarget : EventTarget {
   // event handlers
   [SetterThrows=Workers, GetterThrows=Workers]
   attribute EventHandler onloadstart;
 
   [SetterThrows=Workers, GetterThrows=Workers]
   attribute EventHandler onprogress;
 
--- a/dom/webidl/XMLHttpRequestUpload.webidl
+++ b/dom/webidl/XMLHttpRequestUpload.webidl
@@ -5,12 +5,12 @@
  *
  * The origin of this IDL file is
  * www.w3.org/TR/2012/WD-XMLHttpRequest-20120117/
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
-[Exposed=(Window,Worker)]
+[Exposed=(Window,DedicatedWorker,SharedWorker)]
 interface XMLHttpRequestUpload : XMLHttpRequestEventTarget {
 
 };
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -191,22 +191,16 @@ var interfaceNamesInGlobalScope =
     "ServiceWorkerGlobalScope",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ServiceWorkerRegistration",
 // 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!
     "TextEncoder",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "XMLHttpRequest",
-// IMPORTANT: Do not change this list without review from a DOM peer!
-    "XMLHttpRequestEventTarget",
-// IMPORTANT: Do not change this list without review from a DOM peer!
-    "XMLHttpRequestUpload",
-// IMPORTANT: Do not change this list without review from a DOM peer!
     "URL",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "URLSearchParams",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WebSocket",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WindowClient",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/ipc/glue/Neutering.h
+++ b/ipc/glue/Neutering.h
@@ -52,13 +52,28 @@ public:
   DeneuteredWindowRegion(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
   ~DeneuteredWindowRegion();
 
 private:
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
   bool mReneuter;
 };
 
+class MOZ_RAII SuppressedNeuteringRegion
+{
+public:
+  SuppressedNeuteringRegion(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
+  ~SuppressedNeuteringRegion();
+
+  static inline bool IsNeuteringSuppressed() { return sSuppressNeutering; }
+
+private:
+  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+  bool mReenable;
+
+  static bool sSuppressNeutering;
+};
+
 } // namespace ipc
 } // namespace mozilla
 
 #endif // mozilla_ipc_Neutering_h
 
--- a/ipc/glue/WindowsMessageLoop.cpp
+++ b/ipc/glue/WindowsMessageLoop.cpp
@@ -10,16 +10,17 @@
 #include "WindowsMessageLoop.h"
 #include "Neutering.h"
 #include "MessageChannel.h"
 
 #include "nsAutoPtr.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsIXULAppInfo.h"
+#include "nsWindowsDllInterceptor.h"
 #include "WinUtils.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/PaintTracker.h"
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 using namespace mozilla::ipc::windows;
@@ -431,16 +432,103 @@ ProcessOrDeferMessage(HWND hwnd,
     NS_ASSERTION(gDeferredMessages, "Out of memory!");
   }
 
   // Save for later. The array takes ownership of |deferred|.
   gDeferredMessages->AppendElement(deferred);
   return res;
 }
 
+/*
+ * It is bad to subclass a window when neutering is active because you'll end
+ * up subclassing the *neutered* window procedure instead of the real window
+ * procedure. Since CreateWindow* fires WM_CREATE (and could thus trigger
+ * neutering), we intercept these calls and suppress neutering for the duration
+ * of the call. This ensures that any subsequent subclassing replaces the
+ * correct window procedure.
+ */
+WindowsDllInterceptor sUser32Interceptor;
+typedef HWND (WINAPI *CreateWindowExWPtr)(DWORD,LPCWSTR,LPCWSTR,DWORD,int,int,int,int,HWND,HMENU,HINSTANCE,LPVOID);
+typedef HWND (WINAPI *CreateWindowExAPtr)(DWORD,LPCSTR,LPCSTR,DWORD,int,int,int,int,HWND,HMENU,HINSTANCE,LPVOID);
+typedef HWND (WINAPI *CreateWindowWPtr)(LPCWSTR,LPCWSTR,DWORD,int,int,int,int,HWND,HMENU,HINSTANCE,LPVOID);
+typedef HWND (WINAPI *CreateWindowAPtr)(LPCSTR,LPCSTR,DWORD,int,int,int,int,HWND,HMENU,HINSTANCE,LPVOID);
+
+CreateWindowExWPtr sCreateWindowExWStub = nullptr;
+CreateWindowExAPtr sCreateWindowExAStub = nullptr;
+CreateWindowWPtr sCreateWindowWStub = nullptr;
+CreateWindowAPtr sCreateWindowAStub = nullptr;
+
+HWND WINAPI
+CreateWindowExWHook(DWORD aExStyle, LPCWSTR aClassName, LPCWSTR aWindowName,
+                    DWORD aStyle, int aX, int aY, int aWidth, int aHeight,
+                    HWND aParent, HMENU aMenu, HINSTANCE aInstance,
+                    LPVOID aParam)
+{
+  SuppressedNeuteringRegion doNotNeuterThisWindowYet;
+  return sCreateWindowExWStub(aExStyle, aClassName, aWindowName, aStyle, aX, aY,
+                              aWidth, aHeight, aParent, aMenu, aInstance, aParam);
+}
+
+HWND WINAPI
+CreateWindowExAHook(DWORD aExStyle, LPCSTR aClassName, LPCSTR aWindowName,
+                    DWORD aStyle, int aX, int aY, int aWidth, int aHeight,
+                    HWND aParent, HMENU aMenu, HINSTANCE aInstance,
+                    LPVOID aParam)
+{
+  SuppressedNeuteringRegion doNotNeuterThisWindowYet;
+  return sCreateWindowExAStub(aExStyle, aClassName, aWindowName, aStyle, aX, aY,
+                              aWidth, aHeight, aParent, aMenu, aInstance, aParam);
+}
+
+HWND WINAPI
+CreateWindowWHook(LPCWSTR aClassName, LPCWSTR aWindowName, DWORD aStyle, int aX,
+                  int aY, int aWidth, int aHeight, HWND aParent, HMENU aMenu,
+                  HINSTANCE aInstance, LPVOID aParam)
+{
+  SuppressedNeuteringRegion doNotNeuterThisWindowYet;
+  return sCreateWindowWStub(aClassName, aWindowName, aStyle, aX, aY, aWidth,
+                            aHeight, aParent, aMenu, aInstance, aParam);
+}
+
+HWND WINAPI
+CreateWindowAHook(LPCSTR aClassName, LPCSTR aWindowName, DWORD aStyle, int aX,
+                  int aY, int aWidth, int aHeight, HWND aParent, HMENU aMenu,
+                  HINSTANCE aInstance, LPVOID aParam)
+{
+  SuppressedNeuteringRegion doNotNeuterThisWindowYet;
+  return sCreateWindowAStub(aClassName, aWindowName, aStyle, aX, aY, aWidth,
+                            aHeight, aParent, aMenu, aInstance, aParam);
+}
+
+void
+InitCreateWindowHook()
+{
+  sUser32Interceptor.Init("user32.dll");
+  if (!sCreateWindowExWStub) {
+    sUser32Interceptor.AddHook("CreateWindowExW",
+                               reinterpret_cast<intptr_t>(CreateWindowExWHook),
+                               (void**) &sCreateWindowExWStub);
+  }
+  if (!sCreateWindowExAStub) {
+    sUser32Interceptor.AddHook("CreateWindowExA",
+                               reinterpret_cast<intptr_t>(CreateWindowExAHook),
+                               (void**) &sCreateWindowExAStub);
+  }
+  if (!sCreateWindowWStub) {
+    sUser32Interceptor.AddHook("CreateWindowW",
+                               reinterpret_cast<intptr_t>(CreateWindowWHook),
+                               (void**) &sCreateWindowWStub);
+  }
+  if (!sCreateWindowAStub) {
+    sUser32Interceptor.AddHook("CreateWindowA",
+                               reinterpret_cast<intptr_t>(CreateWindowAHook),
+                               (void**) &sCreateWindowAStub);
+  }
+}
+
 } // namespace
 
 // We need the pointer value of this in PluginInstanceChild.
 LRESULT CALLBACK
 NeuteredWindowProc(HWND hwnd,
                    UINT uMsg,
                    WPARAM wParam,
                    LPARAM lParam)
@@ -605,17 +693,19 @@ CallWindowProcedureHook(int nCode,
                         WPARAM wParam,
                         LPARAM lParam)
 {
   if (nCode >= 0) {
     NS_ASSERTION(gNeuteredWindows, "This should never be null!");
 
     HWND hWnd = reinterpret_cast<CWPSTRUCT*>(lParam)->hwnd;
 
-    if (!gNeuteredWindows->Contains(hWnd) && NeuterWindowProcedure(hWnd)) {
+    if (!gNeuteredWindows->Contains(hWnd) &&
+        !SuppressedNeuteringRegion::IsNeuteringSuppressed() &&
+        NeuterWindowProcedure(hWnd)) {
       if (!gNeuteredWindows->AppendElement(hWnd)) {
         NS_ERROR("Out of memory!");
         RestoreWindowProcedure(hWnd);
       }
     }
   }
   return CallNextHookEx(nullptr, nCode, wParam, lParam);
 }
@@ -705,16 +795,18 @@ InitUIThread()
                                     NULL, &WinEventHook, GetCurrentProcessId(),
                                     gUIThreadId, WINEVENT_OUTOFCONTEXT);
 
     // We need to execute this after setting the hook in case the OLE window
     // already existed.
     gCOMWindow = FindCOMWindow();
   }
   MOZ_ASSERT(gWinEventHook);
+
+  InitCreateWindowHook();
 }
 
 } // namespace windows
 } // namespace ipc
 } // namespace mozilla
 
 // See SpinInternalEventLoop below
 MessageChannel::SyncStackFrame::SyncStackFrame(MessageChannel* channel, bool interrupt)
@@ -938,16 +1030,36 @@ DeneuteredWindowRegion::DeneuteredWindow
 
 DeneuteredWindowRegion::~DeneuteredWindowRegion()
 {
   if (mReneuter) {
     StartNeutering();
   }
 }
 
+SuppressedNeuteringRegion::SuppressedNeuteringRegion(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
+  : mReenable(::gUIThreadId == ::GetCurrentThreadId() && ::gWindowHook)
+{
+  MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+  if (mReenable) {
+    MOZ_ASSERT(!sSuppressNeutering);
+    sSuppressNeutering = true;
+  }
+}
+
+SuppressedNeuteringRegion::~SuppressedNeuteringRegion()
+{
+  if (mReenable) {
+    MOZ_ASSERT(sSuppressNeutering);
+    sSuppressNeutering = false;
+  }
+}
+
+bool SuppressedNeuteringRegion::sSuppressNeutering = false;
+
 bool
 MessageChannel::WaitForSyncNotify(bool aHandleWindowsMessages)
 {
   mMonitor->AssertCurrentThreadOwns();
 
   MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!");
 
   // Use a blocking wait if this channel does not require
@@ -992,16 +1104,18 @@ MessageChannel::WaitForSyncNotify(bool a
     InitTimeoutData(&timeoutData, mTimeoutMs);
 
     // We only do this to ensure that we won't get stuck in
     // MsgWaitForMultipleObjects below.
     timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr);
     NS_ASSERTION(timerId, "SetTimer failed!");
   }
 
+  NeuteredWindowRegion neuteredRgn(true);
+
   {
     while (1) {
       MSG msg = { 0 };
       // Don't get wrapped up in here if the child connection dies.
       {
         MonitorAutoLock lock(*mMonitor);
         if (!Connected()) {
           break;
--- a/js/src/asmjs/AsmJSCompile.cpp
+++ b/js/src/asmjs/AsmJSCompile.cpp
@@ -118,16 +118,21 @@ class ModuleCompiler
     bool getOrCreateFunctionEntry(uint32_t funcIndex, Label** label)
     {
         return compileResults_->getOrCreateFunctionEntry(funcIndex, label);
     }
 
     bool finishGeneratingFunction(AsmFunction& func, CodeGenerator& codegen,
                                   const AsmJSFunctionLabels& labels)
     {
+        // If we have hit OOM then invariants which we assert below may not
+        // hold, so abort now.
+        if (masm().oom())
+            return false;
+
         // Code range
         unsigned line = func.lineno();
         unsigned column = func.column();
         PropertyName* funcName = func.name();
         if (!compileResults_->addCodeRange(AsmJSModule::FunctionCodeRange(funcName, line, labels)))
             return false;
 
         // Script counts
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -636,21 +636,18 @@ ArrayMetaTypeDescr::create(JSContext* cx
     return obj;
 }
 
 bool
 ArrayMetaTypeDescr::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    if (!args.isConstructing()) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
-                             JSMSG_NOT_FUNCTION, "ArrayType");
+    if (!ThrowIfNotConstructing(cx, args, "ArrayType"))
         return false;
-    }
 
     RootedObject arrayTypeGlobal(cx, &args.callee());
 
     // Expect two arguments. The first is a type object, the second is a length.
     if (args.length() < 2) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
                              "ArrayType", "1", "");
         return false;
@@ -993,21 +990,18 @@ StructMetaTypeDescr::create(JSContext* c
     return descr;
 }
 
 bool
 StructMetaTypeDescr::construct(JSContext* cx, unsigned int argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    if (!args.isConstructing()) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
-                             JSMSG_NOT_FUNCTION, "StructType");
+    if (!ThrowIfNotConstructing(cx, args, "StructType"))
         return false;
-    }
 
     if (args.length() >= 1 && args[0].isObject()) {
         RootedObject metaTypeDescr(cx, &args.callee());
         RootedObject fields(cx, &args[0].toObject());
         RootedObject obj(cx, create(cx, metaTypeDescr, fields));
         if (!obj)
             return false;
         args.rval().setObject(*obj);
--- a/js/src/builtin/WeakSetObject.cpp
+++ b/js/src/builtin/WeakSetObject.cpp
@@ -80,20 +80,18 @@ WeakSetObject::construct(JSContext* cx, 
 {
     Rooted<WeakSetObject*> obj(cx, WeakSetObject::create(cx));
     if (!obj)
         return false;
 
     // Based on our "Set" implementation instead of the more general ES6 steps.
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    if (!args.isConstructing()) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "WeakSet");
+    if (!ThrowIfNotConstructing(cx, args, "WeakSet"))
         return false;
-    }
 
     if (!args.get(0).isNullOrUndefined()) {
         RootedObject map(cx, &obj->getReservedSlot(WEAKSET_MAP_SLOT).toObject());
 
         RootedValue adderVal(cx);
         if (!GetProperty(cx, obj, obj, cx->names().add, &adderVal))
             return false;
 
--- a/js/src/jit-test/lib/bytecode-cache.js
+++ b/js/src/jit-test/lib/bytecode-cache.js
@@ -4,17 +4,17 @@ function evalWithCache(code, ctx) {
   ctx = Object.create(ctx, {
     fileName: { value: "evalWithCacheCode.js" },
     lineNumber: { value: 0 }
   });
   code = code instanceof Object ? code : cacheEntry(code);
 
   // We create a new global ...
   if (!("global" in ctx))
-    ctx.global = newGlobal();
+    ctx.global = newGlobal({ cloneSingletons: true });
 
   if (!("isRunOnce" in ctx))
     ctx.isRunOnce = true;
 
   // Fetch the verification function from the evaluation context.  This function
   // is used to assert the state of the script/function after each run of the
   // evaluate function.
   var checkAfter = ctx.checkAfter || function(ctx) {};
--- a/js/src/jit-test/tests/basic/bug1057571.js
+++ b/js/src/jit-test/tests/basic/bug1057571.js
@@ -3,12 +3,13 @@ test = (function () {
   function f() {
     [1,2,3,4,5];
   };
   return "var obj = { x : 2 };" + f.toSource() + "; f()";
 })();
 evalWithCache(test, {});
 function evalWithCache(code, ctx) {
   code = cacheEntry(code);
+  ctx.global = newGlobal({ cloneSingletons: true });
   ctx.isRunOnce = true;
   var res1 = evaluate(code, Object.create(ctx, {saveBytecode: { value: true } }));
   var res2 = evaluate(code, Object.create(ctx, {loadBytecode: { value: true }, saveBytecode: { value: true } }));
 }
--- a/js/src/jit-test/tests/basic/bug1061534.js
+++ b/js/src/jit-test/tests/basic/bug1061534.js
@@ -1,14 +1,15 @@
 
 test = (function () {
   function f() {};
   return "var obj = { x : 2 };" + f.toSource() + (4);
 })();
 evalWithCache(test, {});
 function evalWithCache(code, ctx) {
   code = cacheEntry(code);
+  ctx.global = newGlobal({ cloneSingletons: true });
   var res1 = evaluate(code, Object.create(ctx, {saveBytecode: { value: true } }));
 }
 if (typeof assertThrowsInstanceOf === 'undefined') {
     var assertThrowsInstanceOf = function assertThrowsInstanceOf(f, ctor, msg) {};
 }
 evaluate('evaluate(\'assertThrowsInstanceOf(function () {}, ["jak", "ms"]);\', { noScriptRval : true, isRunOnce: true  })');
--- a/js/src/jit-test/tests/gc/bug-1108007.js
+++ b/js/src/jit-test/tests/gc/bug-1108007.js
@@ -1,16 +1,16 @@
 // |jit-test| --no-threads; --no-ion; --no-baseline
 if (!("gczeal" in this))
     quit();
 gczeal(2);
 (function() {
     evaluate(cacheEntry((function() {
         return "".toSource()
-    })()), Object.create({}, {
+    })()), Object.create({ global: newGlobal({ cloneSingletons: true }) }, {
         saveBytecode: {
             value: true
         }
     }))
 })();
 [[0], [0], [0], [0], [0], [0], [0], [0], [0], [0],
  [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], 
  [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/xdr/bug1108603.js
@@ -0,0 +1,9 @@
+var caught = false;
+try {
+  evaluate(cacheEntry(""), {saveBytecode: {value: true}, global: this});
+  [[0]];
+} catch (err) {
+  caught = true;
+  assertEq(err.message, "compartment cannot save singleton anymore.");
+}
+assertEq(caught, true);
\ No newline at end of file
--- a/js/src/jit-test/tests/xdr/lazy.js
+++ b/js/src/jit-test/tests/xdr/lazy.js
@@ -145,17 +145,17 @@ test = `
   function f() { return true; };
   var canBeLazy = isRelazifiableFunction(f) || isLazyFunction(f);
   relazifyFunctions();
   assertEq(isLazyFunction(f), canBeLazy);
   f()`
 evalWithCache(test, { assertEqBytecode: true, assertEqResult: true });
 
 // And more of the same, in a slightly different way
-var g1 = newGlobal();
+var g1 = newGlobal({ cloneSingletons: true });
 var g2 = newGlobal();
 var res = "function f(){}";
 var code = cacheEntry(res + "; f();");
 evaluate(code, {global:g1, compileAndGo: true, saveBytecode: {value: true}});
 evaluate(code, {global:g2, loadBytecode: true});
 gc();
 assertEq(g2.f.toString(), res);
 
--- a/js/src/jit/arm/Assembler-arm.cpp
+++ b/js/src/jit/arm/Assembler-arm.cpp
@@ -2174,17 +2174,17 @@ Assembler::as_BranchPool(uint32_t value,
     php.phd.init(0, c, PoolHintData::PoolBranch, pc);
     BufferOffset ret = allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value, pe,
                                   /* markAsBranch = */ true, /* loadToPC = */ true);
     // If this label is already bound, then immediately replace the stub load
     // with a correct branch.
     if (label->bound()) {
         BufferOffset dest(label);
         as_b(dest.diffB<BOffImm>(ret), c, ret);
-    } else {
+    } else if (!oom()) {
         label->use(ret.getOffset());
     }
 #ifdef JS_DISASM_ARM
     if (documentation)
         spewTarget(documentation);
 #endif
     return ret;
 }
@@ -2374,47 +2374,52 @@ Assembler::as_b(BOffImm off, Condition c
 {
     BufferOffset ret = writeBranchInst(((int)c) | OpB | off.encode(), documentation);
     return ret;
 }
 
 BufferOffset
 Assembler::as_b(Label* l, Condition c)
 {
-    if (m_buffer.oom()) {
-        BufferOffset ret;
-        return ret;
-    }
-
     if (l->bound()) {
         // Note only one instruction is emitted here, the NOP is overwritten.
         BufferOffset ret = allocBranchInst();
+        if (oom())
+            return BufferOffset();
+
         as_b(BufferOffset(l).diffB<BOffImm>(ret), c, ret);
 #ifdef JS_DISASM_ARM
         spewBranch(m_buffer.getInstOrNull(ret), l);
 #endif
         return ret;
     }
 
+    if (oom())
+        return BufferOffset();
+
     int32_t old;
     BufferOffset ret;
     if (l->used()) {
         old = l->offset();
         // This will currently throw an assertion if we couldn't actually
         // encode the offset of the branch.
         if (!BOffImm::IsInRange(old)) {
             m_buffer.fail_bail();
             return ret;
         }
         ret = as_b(BOffImm(old), c, l);
     } else {
         old = LabelBase::INVALID_OFFSET;
         BOffImm inv;
         ret = as_b(inv, c, l);
     }
+
+    if (oom())
+        return BufferOffset();
+
     DebugOnly<int32_t> check = l->use(ret.getOffset());
     MOZ_ASSERT(check == old);
     return ret;
 }
 
 BufferOffset
 Assembler::as_b(BOffImm off, Condition c, BufferOffset inst)
 {
@@ -2441,31 +2446,32 @@ BufferOffset
 Assembler::as_bl(BOffImm off, Condition c, Label* documentation)
 {
     return writeBranchInst(((int)c) | OpBl | off.encode(), documentation);
 }
 
 BufferOffset
 Assembler::as_bl(Label* l, Condition c)
 {
-    if (m_buffer.oom()) {
-        BufferOffset ret;
-        return ret;
-    }
-
     if (l->bound()) {
         // Note only one instruction is emitted here, the NOP is overwritten.
         BufferOffset ret = allocBranchInst();
+        if (oom())
+            return BufferOffset();
+
         as_bl(BufferOffset(l).diffB<BOffImm>(ret), c, ret);
 #ifdef JS_DISASM_ARM
         spewBranch(m_buffer.getInstOrNull(ret), l);
 #endif
         return ret;
     }
 
+    if (oom())
+        return BufferOffset();
+
     int32_t old;
     BufferOffset ret;
     // See if the list was empty :(
     if (l->used()) {
         // This will currently throw an assertion if we couldn't actually encode
         // the offset of the branch.
         old = l->offset();
         if (!BOffImm::IsInRange(old)) {
@@ -2473,16 +2479,20 @@ Assembler::as_bl(Label* l, Condition c)
             return ret;
         }
         ret = as_bl(BOffImm(old), c, l);
     } else {
         old = LabelBase::INVALID_OFFSET;
         BOffImm inv;
         ret = as_bl(inv, c, l);
     }
+
+    if (oom())
+        return BufferOffset();
+
     DebugOnly<int32_t> check = l->use(ret.getOffset());
     MOZ_ASSERT(check == old);
     return ret;
 }
 
 BufferOffset
 Assembler::as_bl(BOffImm off, Condition c, BufferOffset inst)
 {
--- a/js/src/jit/shared/IonAssemblerBuffer.h
+++ b/js/src/jit/shared/IonAssemblerBuffer.h
@@ -147,18 +147,23 @@ class AssemblerBuffer
             fail_oom();
             return nullptr;
         }
         return new (tmp) Slice;
     }
 
     bool ensureSpace(int size) {
         // Space can exist in the most recent Slice.
-        if (tail && tail->length() + size <= tail->Capacity())
+        if (tail && tail->length() + size <= tail->Capacity()) {
+            // Simulate allocation failure even when we don't need a new slice.
+            if (js::oom::ShouldFailWithOOM())
+                return fail_oom();
+
             return true;
+        }
 
         // Otherwise, a new Slice must be added.
         Slice* slice = newSlice(lifoAlloc_);
         if (slice == nullptr)
             return fail_oom();
 
         // If this is the first Slice in the buffer, add to head position.
         if (!head) {
--- a/js/src/jit/shared/IonAssemblerBufferWithConstantPools.h
+++ b/js/src/jit/shared/IonAssemblerBufferWithConstantPools.h
@@ -699,16 +699,18 @@ struct AssemblerBufferWithConstantPools 
         // Should not be placing a pool in a no-pool region, check.
         MOZ_ASSERT(!canNotPlacePool_);
 
         // Dump the pool with a guard branch around the pool.
         BufferOffset branch = this->nextOffset();
         // Mark and emit the guard branch.
         markNextAsBranch();
         this->putBytes(guardSize_ * InstSize, nullptr);
+        if (this->oom())
+            return;
         BufferOffset afterPool = this->nextOffset();
         Asm::WritePoolGuard(branch, this->getInst(branch), afterPool);
 
         // Perforate the buffer which finishes the current slice and allocates a
         // new slice. This is necessary because Pools are always placed after
         // the end of a slice.
         BufferSlice* perforatedSlice = getTail();
         BufferOffset perforation = this->nextOffset();
@@ -859,17 +861,17 @@ struct AssemblerBufferWithConstantPools 
         if (pool_.checkFull(poolOffset)) {
             // Alignment would cause a pool dump, so dump the pool now.
             JitSpew(JitSpew_Pools, "[%d] Alignment of %d at %d caused a spill.", id, alignment,
                     sizeExcludingCurrentPool());
             finishPool();
         }
 
         inhibitNops_ = true;
-        while (sizeExcludingCurrentPool() & (alignment - 1))
+        while ((sizeExcludingCurrentPool() & (alignment - 1)) && !this->oom())
             putInt(alignFillInst_);
         inhibitNops_ = false;
     }
 
   private:
     void patchBranch(Inst* i, unsigned curpool, BufferOffset branch) {
         const Inst* ci = i;
         ptrdiff_t offset = Asm::GetBranchOffset(ci);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -72,18 +72,17 @@ MSG_DEF(JSMSG_INVALID_MAP_ITERABLE,    1
 MSG_DEF(JSMSG_NESTING_GENERATOR,       0, JSEXN_TYPEERR, "already executing generator")
 MSG_DEF(JSMSG_INCOMPATIBLE_METHOD,     3, JSEXN_TYPEERR, "{0} {1} called on incompatible {2}")
 MSG_DEF(JSMSG_OBJECT_WATCH_DEPRECATED, 0, JSEXN_NONE, "Object.prototype.watch and unwatch are very slow, non-standard, and deprecated; use a getter/setter instead")
 MSG_DEF(JSMSG_TYPE_ERR_BAD_ARGS,       0, JSEXN_TYPEERR, "invalid arguments")
 MSG_DEF(JSMSG_BAD_SURROGATE_CHAR,      1, JSEXN_TYPEERR, "bad surrogate character {0}")
 MSG_DEF(JSMSG_UTF8_CHAR_TOO_LARGE,     1, JSEXN_TYPEERR, "UTF-8 character {0} too large")
 MSG_DEF(JSMSG_MALFORMED_UTF8_CHAR,     1, JSEXN_TYPEERR, "malformed UTF-8 character sequence at offset {0}")
 MSG_DEF(JSMSG_WRONG_CONSTRUCTOR,       1, JSEXN_TYPEERR, "wrong constructor called for {0}")
-MSG_DEF(JSMSG_BUILTIN_CTOR_NO_NEW,     1, JSEXN_NONE, "calling a builtin {0} constructor without new is deprecated and will be forbidden in ES6")
-MSG_DEF(JSMSG_BUILTIN_CTOR_NO_NEW_FATAL, 1, JSEXN_TYPEERR, "calling a builtin {0} constructor without new is forbidden")
+MSG_DEF(JSMSG_BUILTIN_CTOR_NO_NEW,     1, JSEXN_TYPEERR, "calling a builtin {0} constructor without new is forbidden")
 MSG_DEF(JSMSG_PROTO_SETTING_SLOW,      0, JSEXN_NONE, "mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create")
 MSG_DEF(JSMSG_BAD_GENERATOR_YIELD,     1, JSEXN_TYPEERR, "yield from closing generator {0}")
 MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE,      0, JSEXN_TYPEERR, "reduce of empty array with no initial value")
 MSG_DEF(JSMSG_UNEXPECTED_TYPE,         2, JSEXN_TYPEERR, "{0} is {1}")
 MSG_DEF(JSMSG_MISSING_FUN_ARG,         2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")
 MSG_DEF(JSMSG_NOT_NONNULL_OBJECT,      1, JSEXN_TYPEERR, "{0} is not a non-null object")
 MSG_DEF(JSMSG_SET_NON_OBJECT_RECEIVER, 1, JSEXN_TYPEERR, "can't assign to properties of {0}: not an object")
 MSG_DEF(JSMSG_INVALID_DESCRIPTOR,      0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified")
--- a/js/src/proxy/ScriptedDirectProxyHandler.cpp
+++ b/js/src/proxy/ScriptedDirectProxyHandler.cpp
@@ -1237,20 +1237,18 @@ NewScriptedProxy(JSContext* cx, CallArgs
     return true;
 }
 
 bool
 js::proxy(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    if (!args.isConstructing()) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "Proxy");
+    if (!ThrowIfNotConstructing(cx, args, "Proxy"))
         return false;
-    }
 
     return NewScriptedProxy(cx, args, "Proxy");
 }
 
 static bool
 RevokeProxy(JSContext* cx, unsigned argc, Value* vp)
 {
     CallReceiver rec = CallReceiverFromVp(vp);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1240,22 +1240,24 @@ Evaluate(JSContext* cx, unsigned argc, V
         if (saveFrameChain && !asfc.save())
             return false;
 
         JSAutoCompartment ac(cx, global);
         RootedScript script(cx);
 
         {
             if (saveBytecode) {
-                if (!JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates()) {
+                if (!JS::CompartmentOptionsRef(cx).cloneSingletons()) {
                     JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
                                          JSSMSG_CACHE_SINGLETON_FAILED);
                     return false;
                 }
-                JS::CompartmentOptionsRef(cx).setCloneSingletons(true);
+
+                // cloneSingletons implies that singletons are used as template objects.
+                MOZ_ASSERT(JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates());
             }
 
             if (loadBytecode) {
                 script = JS_DecodeScript(cx, loadBuffer, loadLength);
             } else {
                 mozilla::Range<const char16_t> chars = codeChars.twoByteRange();
                 (void) JS::Compile(cx, options, chars.start().get(), chars.length(), &script);
             }
@@ -3800,16 +3802,21 @@ NewGlobal(JSContext* cx, unsigned argc, 
         if (v.isObject())
             options.setSameZoneAs(UncheckedUnwrap(&v.toObject()));
 
         if (!JS_GetProperty(cx, opts, "invisibleToDebugger", &v))
             return false;
         if (v.isBoolean())
             options.setInvisibleToDebugger(v.toBoolean());
 
+        if (!JS_GetProperty(cx, opts, "cloneSingletons", &v))
+            return false;
+        if (v.isBoolean())
+            options.setCloneSingletons(v.toBoolean());
+
         if (!JS_GetProperty(cx, opts, "principal", &v))
             return false;
         if (!v.isUndefined()) {
             uint32_t bits;
             if (!ToUint32(cx, v, &bits))
                 return false;
             principals = cx->new_<ShellPrincipals>(bits);
             if (!principals)
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -584,14 +584,14 @@ LookupPropertyInline(ExclusiveContext* c
 }
 
 inline bool
 ThrowIfNotConstructing(JSContext *cx, const CallArgs &args, const char *builtinName)
 {
     if (args.isConstructing())
         return true;
     return JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, GetErrorMessage, nullptr,
-                                        JSMSG_BUILTIN_CTOR_NO_NEW_FATAL, builtinName);
+                                        JSMSG_BUILTIN_CTOR_NO_NEW, builtinName);
 }
 
 } // namespace js
 
 #endif /* vm_NativeObject_inl_h */
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -24,21 +24,21 @@ namespace js {
  * versions.  If deserialization fails, the data should be invalidated if
  * possible.
  *
  * When you change this, run make_opcode_doc.py and copy the new output into
  * this wiki page:
  *
  *  https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
  */
-static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 313;
+static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 314;
 static const uint32_t XDR_BYTECODE_VERSION =
     uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
 
-static_assert(JSErr_Limit == 419,
+static_assert(JSErr_Limit == 418,
               "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
               "removed MSG_DEFs from js.msg, you should increment "
               "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "
               "expected JSErr_Limit value.");
 
 class XDRBuffer {
   public:
     explicit XDRBuffer(JSContext* cx)
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -1249,16 +1249,18 @@ XRE_XPCShellMain(int argc, char** argv, 
     nsresult rv;
 
     gErrFile = stderr;
     gOutFile = stdout;
     gInFile = stdin;
 
     NS_LogInit();
 
+    mozilla::LogModule::Init();
+
     // A initializer to initialize histogram collection
     // used by telemetry.
     UniquePtr<base::StatisticsRecorder> telStats =
        MakeUnique<base::StatisticsRecorder>();
 
     if (PR_GetEnv("MOZ_CHAOSMODE")) {
         ChaosFeature feature = ChaosFeature::Any;
         long featureInt = strtol(PR_GetEnv("MOZ_CHAOSMODE"), nullptr, 16);
--- a/layout/forms/nsFileControlFrame.cpp
+++ b/layout/forms/nsFileControlFrame.cpp
@@ -122,19 +122,24 @@ MakeAnonButton(nsIDocument* aDoc, const 
   return button.forget();
 }
 
 nsresult
 nsFileControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
 {
   nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
 
+#if defined(ANDROID) || defined(MOZ_B2G)
+   bool isDirPicker = false;
+#else
   nsIContent* content = GetContent();
-  bool isDirPicker = Preferences::GetBool("dom.input.dirpicker", false) &&
-                     content && content->HasAttr(kNameSpaceID_None, nsGkAtoms::directory);
+  bool isDirPicker =
+    Preferences::GetBool("dom.input.dirpicker", false) &&
+    content && content->HasAttr(kNameSpaceID_None, nsGkAtoms::directory);
+#endif
 
   RefPtr<HTMLInputElement> fileContent = HTMLInputElement::FromContentOrNull(mContent);
 
   // The access key is transferred to the "Choose files..." button only. In
   // effect that access key allows access to the control via that button, then
   // the user can tab between the two buttons.
   nsAutoString accessKey;
   fileContent->GetAccessKey(accessKey);
--- a/media/webrtc/signaling/signaling.gyp
+++ b/media/webrtc/signaling/signaling.gyp
@@ -67,17 +67,16 @@
         './src/common',
         './src/common/browser_logging',
         './src/common/time_profiling',
         './src/media',
         './src/media-conduit',
         './src/mediapipeline',
         './src/peerconnection',
         './src/sdp/sipcc',
-        '../../../xpcom/base',
         '../../../dom/base',
         '../../../dom/media',
         '../../../media/mtransport',
         '../trunk',
         '../../libyuv/include',
         '../../mtransport/third_party/nrappkit/src/util/libekr',
       ],
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp
@@ -18,17 +18,17 @@
 #include <mozilla/Types.h>
 #endif
 
 #include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID
 #include "nsServiceManagerUtils.h" // do_GetService
 #include "nsIObserverService.h"
 #include "nsIObserver.h"
 #include "mozilla/Services.h"
-#include "StaticPtr.h"
+#include "mozilla/StaticPtr.h"
 
 #include "gmp-video-decode.h" // GMP_API_VIDEO_DECODER
 #include "gmp-video-encode.h" // GMP_API_VIDEO_ENCODER
 
 static const char* logTag = "PeerConnectionCtx";
 
 namespace mozilla {
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h
@@ -7,17 +7,17 @@
 
 #include <string>
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
 #include "WebrtcGlobalChild.h"
 #endif
 
 #include "mozilla/Attributes.h"
-#include "StaticPtr.h"
+#include "mozilla/StaticPtr.h"
 #include "PeerConnectionImpl.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "nsIRunnable.h"
 
 namespace mozilla {
 class PeerConnectionCtxShutdown;
 
 namespace dom {
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -204,17 +204,16 @@ public class BrowserApp extends GeckoApp
 
     private static final int GECKO_TOOLS_MENU = -1;
     private static final int ADDON_MENU_OFFSET = 1000;
     public static final String TAB_HISTORY_FRAGMENT_TAG = "tabHistoryFragment";
 
     private static class MenuItemInfo {
         public int id;
         public String label;
-        public String icon;
         public boolean checkable;
         public boolean checked;
         public boolean enabled = true;
         public boolean visible = true;
         public int parent;
         public boolean added;   // So we can re-add after a locale change.
     }
 
@@ -1758,17 +1757,16 @@ public class BrowserApp extends GeckoApp
             final Intent intent = new Intent(Intent.ACTION_VIEW);
             intent.setData(Uri.parse("market://details?id=" + getPackageName()));
             startActivity(intent);
 
         } else if ("Menu:Add".equals(event)) {
             final MenuItemInfo info = new MenuItemInfo();
             info.label = message.getString("name");
             info.id = message.getInt("id") + ADDON_MENU_OFFSET;
-            info.icon = message.optString("icon", null);
             info.checked = message.optBoolean("checked", false);
             info.enabled = message.optBoolean("enabled", true);
             info.visible = message.optBoolean("visible", true);
             info.checkable = message.optBoolean("checkable", false);
             final int parent = message.optInt("parent", 0);
             info.parent = parent <= 0 ? parent : parent + ADDON_MENU_OFFSET;
             final MenuItemInfo menuItemInfo = info;
             ThreadUtils.postToUiThread(new Runnable() {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -2312,19 +2312,19 @@ var NativeWindow = {
     _callbacks: [],
     _menuId: 1,
     toolsMenuID: -1,
     add: function() {
       let options;
       if (arguments.length == 1) {
         options = arguments[0];
       } else if (arguments.length == 3) {
+          Log.w("Browser", "This menu addon API has been deprecated. Instead, use the options object API.");
           options = {
             name: arguments[0],
-            icon: arguments[1],
             callback: arguments[2]
           };
       } else {
          throw "Incorrect number of parameters";
       }
 
       options.type = "Menu:Add";
       options.id = this._menuId;
@@ -5837,95 +5837,124 @@ var HealthReportStatusListener = {
           },
         });
       }.bind(this));
   },
 };
 
 var XPInstallObserver = {
   init: function() {
+    Services.obs.addObserver(this, "addon-install-origin-blocked", false);
+    Services.obs.addObserver(this, "addon-install-disabled", false);
     Services.obs.addObserver(this, "addon-install-blocked", false);
     Services.obs.addObserver(this, "addon-install-started", false);
     Services.obs.addObserver(this, "xpi-signature-changed", false);
     Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
 
     AddonManager.addInstallListener(this);
   },
 
   observe: function(aSubject, aTopic, aData) {
+    let installInfo = aSubject.QueryInterface(Ci.amIWebInstallInfo);
+    let tab = BrowserApp.getTabForBrowser(installInfo.browser);
+    let strings = Strings.browser;
+
+    let host = null;
+    if (installInfo.originatingURI) {
+      host = installInfo.originatingURI.host;
+    }
+
+    let brandShortName = Strings.brand.GetStringFromName("brandShortName");
+
     switch (aTopic) {
       case "addon-install-started":
-        NativeWindow.toast.show(Strings.browser.GetStringFromName("alertAddonsDownloading"), "short");
+        NativeWindow.toast.show(strings.GetStringFromName("alertAddonsDownloading"), "short");
         break;
-      case "addon-install-blocked": {
-        let installInfo = aSubject.QueryInterface(Ci.amIWebInstallInfo);
-        let tab = BrowserApp.getTabForBrowser(installInfo.browser);
+      case "addon-install-disabled": {
         if (!tab)
           return;
 
-        let host = null;
-        if (installInfo.originatingURI) {
-          host = installInfo.originatingURI.host;
-        }
-
-        let brandShortName = Strings.brand.GetStringFromName("brandShortName");
-        let notificationName, buttons, message;
-        let strings = Strings.browser;
         let enabled = true;
         try {
           enabled = Services.prefs.getBoolPref("xpinstall.enabled");
-        }
-        catch (e) {}
-
+        } catch (e) {}
+
+        let buttons, message, callback;
         if (!enabled) {
-          notificationName = "xpinstall-disabled";
-          if (Services.prefs.prefIsLocked("xpinstall.enabled")) {
-            message = strings.GetStringFromName("xpinstallDisabledMessageLocked");
-            buttons = [];
-          } else {
-            message = strings.formatStringFromName("xpinstallDisabledMessage2", [brandShortName, host], 2);
-            buttons = [{
-              label: strings.GetStringFromName("xpinstallDisabledButton"),
-              callback: function editPrefs() {
-                Services.prefs.setBoolPref("xpinstall.enabled", true);
-                return false;
-              }
-            }];
+          message = strings.GetStringFromName("xpinstallDisabledMessageLocked");
+          buttons = [strings.GetStringFromName("unsignedAddonsDisabled.dismiss")];
+          callback: (data) => {};
+        } else {
+          message = strings.formatStringFromName("xpinstallDisabledMessage2", [brandShortName, host], 2);
+          buttons = [
+              strings.GetStringFromName("xpinstallDisabledButton"),
+              strings.GetStringFromName("unsignedAddonsDisabled.dismiss")
+          ];
+          callback: (data) => {
+            if (data.button === 1) {
+              Services.prefs.setBoolPref("xpinstall.enabled", true)
+            }
+          };
+        }
+
+        new Prompt({
+          title: Strings.browser.GetStringFromName("addonError.titleError"),
+          message: message,
+          buttons: buttons
+        }).show(callback);
+        break;
+      }
+      case "addon-install-blocked": {
+        if (!tab)
+          return;
+
+        let message;
+        if (host) {
+          // We have a host which asked for the install.
+          message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2);
+        } else {
+          // Without a host we address the add-on as the initiator of the install.
+          let addon = null;
+          if (installInfo.installs.length > 0) {
+            addon = installInfo.installs[0].name;
           }
-        } else {
-          notificationName = "xpinstall";
-          if (host) {
-            // We have a host which asked for the install.
-            message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2);
+          if (addon) {
+            // We have an addon name, show the regular message.
+            message = strings.formatStringFromName("xpinstallPromptWarningLocal", [brandShortName, addon], 2);
           } else {
-            // Without a host we address the add-on as the initiator of the install.
-            let addon = null;
-            if (installInfo.installs.length > 0) {
-              addon = installInfo.installs[0].name;
-            }
-            if (addon) {
-              // We have an addon name, show the regular message.
-              message = strings.formatStringFromName("xpinstallPromptWarningLocal", [brandShortName, addon], 2);
-            } else {
-              // We don't have an addon name, show an alternative message.
-              message = strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1);
-            }
+            // We don't have an addon name, show an alternative message.
+            message = strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1);
           }
-
-          buttons = [{
-            label: strings.GetStringFromName("xpinstallPromptAllowButton"),
-            callback: function() {
-              // Kick off the install
-              installInfo.install();
-              return false;
-            },
-            positive: true
-          }];
         }
-        NativeWindow.doorhanger.show(message, aTopic, buttons, tab.id);
+
+        let buttons = [
+            strings.GetStringFromName("xpinstallPromptAllowButton"),
+            strings.GetStringFromName("unsignedAddonsDisabled.dismiss")
+        ];
+        new Prompt({
+          title: Strings.browser.GetStringFromName("addonError.titleBlocked"),
+          message: message,
+          buttons: buttons
+        }).show((data) => {
+          if (data.button === 0) {
+            // Kick off the install
+            installInfo.install();
+          }
+        });
+        break;
+      }
+      case "addon-install-origin-blocked": {
+        if (!tab)
+          return;
+
+        new Prompt({
+          title: Strings.browser.GetStringFromName("addonError.titleBlocked"),
+          message: strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1),
+          buttons: [strings.GetStringFromName("unsignedAddonsDisabled.dismiss")]
+        }).show((data) => {});
         break;
       }
       case "xpi-signature-changed": {
         if (JSON.parse(aData).disabled.length) {
           this._notifyUnsignedAddonsDisabled();
         }
         break;
       }
new file mode 100644
--- /dev/null
+++ b/netwerk/base/ARefBase.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* 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/. */
+
+#ifndef mozilla_net_ARefBase_h
+#define mozilla_net_ARefBase_h
+
+namespace mozilla { namespace net {
+
+// This is an abstract class that can be pointed to by either
+// nsCOMPtr or nsRefPtr. nsHttpConnectionMgr uses it for generic
+// objects that need to be reference counted - similiar to nsISupports
+// but it may or may not be xpcom.
+
+class ARefBase
+{
+public:
+  ARefBase() {}
+  virtual ~ARefBase() {}
+
+  NS_IMETHOD_ (MozExternalRefCountType) AddRef() = 0;
+  NS_IMETHOD_ (MozExternalRefCountType) Release() = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
--- a/netwerk/base/EventTokenBucket.cpp
+++ b/netwerk/base/EventTokenBucket.cpp
@@ -85,17 +85,16 @@ EventTokenBucket::EventTokenBucket(uint3
   , mPaused(false)
   , mStopped(false)
   , mTimerArmed(false)
 #ifdef XP_WIN
   , mFineGrainTimerInUse(false)
   , mFineGrainResetTimerArmed(false)
 #endif
 {
-  MOZ_COUNT_CTOR(EventTokenBucket);
   mLastUpdate = TimeStamp::Now();
 
   MOZ_ASSERT(NS_IsMainThread());
 
   nsresult rv;
   nsCOMPtr<nsIEventTarget> sts;
   nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
   if (NS_SUCCEEDED(rv))
@@ -107,17 +106,16 @@ EventTokenBucket::EventTokenBucket(uint3
   SetRate(eventsPerSecond, burstSize);
 }
 
 EventTokenBucket::~EventTokenBucket()
 {
   SOCKET_LOG(("EventTokenBucket::dtor %p events=%d\n",
               this, mEvents.GetSize()));
 
-  MOZ_COUNT_DTOR(EventTokenBucket);
   if (mTimer && mTimerArmed)
     mTimer->Cancel();
 
 #ifdef XP_WIN
   NormalTimers();
   if (mFineGrainResetTimerArmed) {
     mFineGrainResetTimerArmed = false;
     mFineGrainResetTimer->Cancel();
--- a/netwerk/base/EventTokenBucket.h
+++ b/netwerk/base/EventTokenBucket.h
@@ -2,16 +2,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 #ifndef NetEventTokenBucket_h__
 #define NetEventTokenBucket_h__
 
+#include "ARefBase.h"
 #include "nsCOMPtr.h"
 #include "nsDeque.h"
 #include "nsITimer.h"
 
 #include "mozilla/TimeStamp.h"
 
 class nsICancelable;
 
@@ -54,28 +55,26 @@ namespace net {
   + An event is submitted to the token bucket asynchronously through SubmitEvent().
     The OnTokenBucketAdmitted() method of the submitted event is used as a callback
     when the event is ready to run. A cancelable event is returned to the SubmitEvent() caller
     for use in the case they do not wish to wait for the callback.
 */
 
 class EventTokenBucket;
 
-class ATokenBucketEvent 
+class ATokenBucketEvent
 {
 public:
   virtual void OnTokenBucketAdmitted() = 0;
 };
 
 class TokenBucketCancelable;
 
-class EventTokenBucket : public nsITimerCallback
+class EventTokenBucket : public nsITimerCallback, public ARefBase
 {
-  virtual ~EventTokenBucket();
-
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSITIMERCALLBACK
 
   // This should be constructed on the main thread
   EventTokenBucket(uint32_t eventsPerSecond, uint32_t burstSize);
 
   // These public methods are all meant to be called from the socket thread
@@ -88,16 +87,18 @@ public:
   void Pause();
   void UnPause();
   void Stop() { mStopped = true; }
 
   // The returned cancelable event can only be canceled from the socket thread
   nsresult SubmitEvent(ATokenBucketEvent *event, nsICancelable **cancelable);
 
 private:
+  virtual ~EventTokenBucket();
+
   friend class RunNotifyEvent;
   friend class SetTimerEvent;
 
   bool TryImmediateDispatch(TokenBucketCancelable *event);
   void SetRate(uint32_t eventsPerSecond, uint32_t burstSize);
 
   void DispatchEvents();
   void UpdateTimer();
--- a/netwerk/protocol/http/ConnectionDiagnostics.cpp
+++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp
@@ -23,17 +23,17 @@ namespace net {
 
 void
 nsHttpConnectionMgr::PrintDiagnostics()
 {
   PostEvent(&nsHttpConnectionMgr::OnMsgPrintDiagnostics, 0, nullptr);
 }
 
 void
-nsHttpConnectionMgr::OnMsgPrintDiagnostics(int32_t, void *)
+nsHttpConnectionMgr::OnMsgPrintDiagnostics(int32_t, ARefBase *)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
   nsCOMPtr<nsIConsoleService> consoleService =
     do_GetService(NS_CONSOLESERVICE_CONTRACTID);
   if (!consoleService)
     return;
 
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -9,16 +9,17 @@
 #include "nsHttpConnectionInfo.h"
 #include "nsAHttpTransaction.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsProxyRelease.h"
 #include "prinrval.h"
 #include "TunnelUtils.h"
 #include "mozilla/Mutex.h"
+#include "ARefBase.h"
 
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsITimer.h"
 
 class nsISocketTransport;
 class nsISSLSocketControl;
@@ -38,16 +39,17 @@ class ASpdySession;
 
 class nsHttpConnection final : public nsAHttpSegmentReader
                              , public nsAHttpSegmentWriter
                              , public nsIInputStreamCallback
                              , public nsIOutputStreamCallback
                              , public nsITransportEventSink
                              , public nsIInterfaceRequestor
                              , public NudgeTunnelCallback
+                             , public ARefBase
 {
     virtual ~nsHttpConnection();
 
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSAHTTPSEGMENTREADER
     NS_DECL_NSAHTTPSEGMENTWRITER
     NS_DECL_NSIINPUTSTREAMCALLBACK
--- a/netwerk/protocol/http/nsHttpConnectionInfo.h
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -7,16 +7,17 @@
 #ifndef nsHttpConnectionInfo_h__
 #define nsHttpConnectionInfo_h__
 
 #include "nsHttp.h"
 #include "nsProxyInfo.h"
 #include "nsCOMPtr.h"
 #include "nsStringFwd.h"
 #include "mozilla/Logging.h"
+#include "ARefBase.h"
 
 extern PRLogModuleInfo *gHttpLog;
 
 //-----------------------------------------------------------------------------
 // nsHttpConnectionInfo - holds the properties of a connection
 //-----------------------------------------------------------------------------
 
 // http:// uris through a proxy will all share the same CI, because they can
@@ -25,17 +26,17 @@ extern PRLogModuleInfo *gHttpLog;
 // however, https:// uris tunnel through the proxy so they will have different
 // CIs - the CI reflects both the proxy and the origin.
 // however, proxy conenctions made with http/2 (or spdy) can tunnel to the origin
 // and multiplex non tunneled transactions at the same time, so they have a
 // special wildcard CI that accepts all origins through that proxy.
 
 namespace mozilla { namespace net {
 
-class nsHttpConnectionInfo
+class nsHttpConnectionInfo: public ARefBase
 {
 public:
     nsHttpConnectionInfo(const nsACString &originHost,
                          int32_t originPort,
                          const nsACString &npnToken,
                          const nsACString &username,
                          nsProxyInfo *proxyInfo,
                          bool endToEndSSL = false);
@@ -157,16 +158,16 @@ private:
     nsCString              mUsername;
     nsCOMPtr<nsProxyInfo>  mProxyInfo;
     bool                   mUsingHttpProxy;
     bool                   mUsingHttpsProxy;
     bool                   mEndToEndSSL;
     bool                   mUsingConnect;  // if will use CONNECT with http proxy
     nsCString              mNPNToken;
 
-// for nsRefPtr
+// for RefPtr
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpConnectionInfo)
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // nsHttpConnectionInfo_h__
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -142,65 +142,105 @@ nsHttpConnectionMgr::Init(uint16_t maxCo
         mMaxOptimisticPipelinedRequests = maxOptimisticPipelinedRequests;
 
         mIsShuttingDown = false;
     }
 
     return EnsureSocketThreadTarget();
 }
 
+class BoolWrapper : public ARefBase
+{
+public:
+    BoolWrapper() : mBool(false) {}
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper)
+
+public: // intentional!
+    bool mBool;
+
+private:
+    virtual ~BoolWrapper() {}
+};
+
 nsresult
 nsHttpConnectionMgr::Shutdown()
 {
     LOG(("nsHttpConnectionMgr::Shutdown\n"));
 
-    bool shutdown = false;
+    RefPtr<BoolWrapper> shutdownWrapper = new BoolWrapper();
     {
         ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
         // do nothing if already shutdown
         if (!mSocketThreadTarget)
             return NS_OK;
 
         nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgShutdown,
-                                0, &shutdown);
+                                0, shutdownWrapper);
 
         // release our reference to the STS to prevent further events
         // from being posted.  this is how we indicate that we are
         // shutting down.
         mIsShuttingDown = true;
         mSocketThreadTarget = 0;
 
         if (NS_FAILED(rv)) {
             NS_WARNING("unable to post SHUTDOWN message");
             return rv;
         }
     }
 
     // wait for shutdown event to complete
-    while (!shutdown)
+    while (!shutdownWrapper->mBool) {
         NS_ProcessNextEvent(NS_GetCurrentThread());
+    }
 
     return NS_OK;
 }
 
+class ConnEvent : public nsRunnable
+{
+public:
+    ConnEvent(nsHttpConnectionMgr *mgr,
+              nsConnEventHandler handler, int32_t iparam, ARefBase *vparam)
+        : mMgr(mgr)
+        , mHandler(handler)
+        , mIParam(iparam)
+        , mVParam(vparam) {}
+
+    NS_IMETHOD Run()
+    {
+        (mMgr->*mHandler)(mIParam, mVParam);
+        return NS_OK;
+    }
+
+private:
+    virtual ~ConnEvent() {}
+
+    RefPtr<nsHttpConnectionMgr>  mMgr;
+    nsConnEventHandler           mHandler;
+    int32_t                      mIParam;
+    RefPtr<ARefBase>             mVParam;
+};
+
 nsresult
-nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler, int32_t iparam, void *vparam)
+nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler,
+                               int32_t iparam, ARefBase *vparam)
 {
     EnsureSocketThreadTarget();
 
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
     nsresult rv;
     if (!mSocketThreadTarget) {
         NS_WARNING("cannot post event if not initialized");
         rv = NS_ERROR_NOT_INITIALIZED;
     }
     else {
-        nsCOMPtr<nsIRunnable> event = new nsConnEvent(this, handler, iparam, vparam);
+        nsCOMPtr<nsIRunnable> event = new ConnEvent(this, handler, iparam, vparam);
         rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL);
     }
     return rv;
 }
 
 void
 nsHttpConnectionMgr::PruneDeadConnectionsAfter(uint32_t timeInSeconds)
 {
@@ -288,47 +328,32 @@ nsHttpConnectionMgr::Observe(nsISupports
 
 
 //-----------------------------------------------------------------------------
 
 nsresult
 nsHttpConnectionMgr::AddTransaction(nsHttpTransaction *trans, int32_t priority)
 {
     LOG(("nsHttpConnectionMgr::AddTransaction [trans=%p %d]\n", trans, priority));
-
-    NS_ADDREF(trans);
-    nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority, trans);
-    if (NS_FAILED(rv))
-        NS_RELEASE(trans);
-    return rv;
+    return PostEvent(&nsHttpConnectionMgr::OnMsgNewTransaction, priority, trans);
 }
 
 nsresult
 nsHttpConnectionMgr::RescheduleTransaction(nsHttpTransaction *trans, int32_t priority)
 {
     LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans, priority));
-
-    NS_ADDREF(trans);
-    nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, trans);
-    if (NS_FAILED(rv))
-        NS_RELEASE(trans);
-    return rv;
+    return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, trans);
 }
 
 nsresult
 nsHttpConnectionMgr::CancelTransaction(nsHttpTransaction *trans, nsresult reason)
 {
     LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%x]\n", trans, reason));
-
-    NS_ADDREF(trans);
-    nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
-                            static_cast<int32_t>(reason), trans);
-    if (NS_FAILED(rv))
-        NS_RELEASE(trans);
-    return rv;
+    return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
+                     static_cast<int32_t>(reason), trans);
 }
 
 nsresult
 nsHttpConnectionMgr::PruneDeadConnections()
 {
     return PostEvent(&nsHttpConnectionMgr::OnMsgPruneDeadConnections);
 }
 
@@ -345,61 +370,44 @@ nsHttpConnectionMgr::PruneNoTraffic()
 
 nsresult
 nsHttpConnectionMgr::VerifyTraffic()
 {
     LOG(("nsHttpConnectionMgr::VerifyTraffic\n"));
     return PostEvent(&nsHttpConnectionMgr::OnMsgVerifyTraffic);
 }
 
-
 nsresult
 nsHttpConnectionMgr::DoShiftReloadConnectionCleanup(nsHttpConnectionInfo *aCI)
 {
-    RefPtr<nsHttpConnectionInfo> connInfo(aCI);
-
-    nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup,
-                            0, connInfo);
-    if (NS_SUCCEEDED(rv))
-        unused << connInfo.forget();
-    return rv;
+    return PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup,
+                     0, aCI);
 }
 
-class SpeculativeConnectArgs
+class SpeculativeConnectArgs : public ARefBase
 {
-    virtual ~SpeculativeConnectArgs() {}
-
 public:
     SpeculativeConnectArgs() { mOverridesOK = false; }
-
-    // Added manually so we can use nsRefPtr without inheriting from
-    // nsISupports
-    NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
-    NS_IMETHOD_(MozExternalRefCountType) Release(void);
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs)
 
 public: // intentional!
     RefPtr<NullHttpTransaction> mTrans;
 
     bool mOverridesOK;
     uint32_t mParallelSpeculativeConnectLimit;
     bool mIgnoreIdle;
     bool mIgnorePossibleSpdyConnections;
     bool mIsFromPredictor;
     bool mAllow1918;
 
-    // As above, added manually so we can use nsRefPtr without inheriting from
-    // nsISupports
-protected:
-    ThreadSafeAutoRefCnt mRefCnt;
+private:
+    virtual ~SpeculativeConnectArgs() {}
     NS_DECL_OWNINGTHREAD
 };
 
-NS_IMPL_ADDREF(SpeculativeConnectArgs)
-NS_IMPL_RELEASE(SpeculativeConnectArgs)
-
 nsresult
 nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci,
                                         nsIInterfaceRequestor *callbacks,
                                         uint32_t caps,
                                         NullHttpTransaction *nullTransaction)
 {
     MOZ_ASSERT(NS_IsMainThread(), "nsHttpConnectionMgr::SpeculativeConnect called off main thread!");
 
@@ -439,118 +447,99 @@ nsHttpConnectionMgr::SpeculativeConnect(
             &args->mParallelSpeculativeConnectLimit);
         overrider->GetIgnoreIdle(&args->mIgnoreIdle);
         overrider->GetIgnorePossibleSpdyConnections(
             &args->mIgnorePossibleSpdyConnections);
         overrider->GetIsFromPredictor(&args->mIsFromPredictor);
         overrider->GetAllow1918(&args->mAllow1918);
     }
 
-    nsresult rv =
-        PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
-    if (NS_SUCCEEDED(rv))
-        unused << args.forget();
-    return rv;
+    return PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
 }
 
 nsresult
 nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target)
 {
     EnsureSocketThreadTarget();
 
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     NS_IF_ADDREF(*target = mSocketThreadTarget);
     return NS_OK;
 }
 
 nsresult
 nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn)
 {
     LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%p]\n", conn));
-
-    NS_ADDREF(conn);
-    nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgReclaimConnection, 0, conn);
-    if (NS_FAILED(rv))
-        NS_RELEASE(conn);
-    return rv;
+    return PostEvent(&nsHttpConnectionMgr::OnMsgReclaimConnection, 0, conn);
 }
 
 // A structure used to marshall 2 pointers across the various necessary
 // threads to complete an HTTP upgrade.
-class nsCompleteUpgradeData
+class nsCompleteUpgradeData : public ARefBase
 {
 public:
-nsCompleteUpgradeData(nsAHttpConnection *aConn,
-                      nsIHttpUpgradeListener *aListener)
-    : mConn(aConn), mUpgradeListener(aListener) {}
+    nsCompleteUpgradeData(nsAHttpConnection *aConn,
+                          nsIHttpUpgradeListener *aListener)
+        : mConn(aConn)
+        , mUpgradeListener(aListener) { }
+
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsCompleteUpgradeData)
 
     RefPtr<nsAHttpConnection> mConn;
     nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
+private:
+    virtual ~nsCompleteUpgradeData() { }
 };
 
 nsresult
 nsHttpConnectionMgr::CompleteUpgrade(nsAHttpConnection *aConn,
                                      nsIHttpUpgradeListener *aUpgradeListener)
 {
-    nsCompleteUpgradeData *data =
+    RefPtr<nsCompleteUpgradeData> data =
         new nsCompleteUpgradeData(aConn, aUpgradeListener);
-    nsresult rv;
-    rv = PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data);
-    if (NS_FAILED(rv))
-        delete data;
-    return rv;
+    return PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data);
 }
 
 nsresult
 nsHttpConnectionMgr::UpdateParam(nsParamName name, uint16_t value)
 {
     uint32_t param = (uint32_t(name) << 16) | uint32_t(value);
-    return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam, 0,
-                     (void *)(uintptr_t) param);
+    return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam,
+                     static_cast<int32_t>(param), nullptr);
 }
 
 nsresult
 nsHttpConnectionMgr::ProcessPendingQ(nsHttpConnectionInfo *ci)
 {
     LOG(("nsHttpConnectionMgr::ProcessPendingQ [ci=%s]\n", ci->HashKey().get()));
-
-    NS_ADDREF(ci);
-    nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
-    if (NS_FAILED(rv))
-        NS_RELEASE(ci);
-    return rv;
+    return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
 }
 
 nsresult
 nsHttpConnectionMgr::ProcessPendingQ()
 {
     LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
     return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr);
 }
 
 void
-nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t, void *param)
+nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket(int32_t, ARefBase *param)
 {
-    RefPtr<EventTokenBucket> tokenBucket =
-        dont_AddRef(static_cast<EventTokenBucket *>(param));
+    EventTokenBucket *tokenBucket = static_cast<EventTokenBucket *>(param);
     gHttpHandler->SetRequestTokenBucket(tokenBucket);
 }
 
 nsresult
 nsHttpConnectionMgr::UpdateRequestTokenBucket(EventTokenBucket *aBucket)
 {
-    RefPtr<EventTokenBucket> bucket(aBucket);
-
     // Call From main thread when a new EventTokenBucket has been made in order
     // to post the new value to the socket thread.
-    nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket,
-                            0, bucket);
-    if (NS_SUCCEEDED(rv))
-        unused << bucket.forget();
-    return rv;
+    return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateRequestTokenBucket,
+                     0, aBucket);
 }
 
 PLDHashOperator
 nsHttpConnectionMgr::RemoveDeadConnections(const nsACString &key,
                 nsAutoPtr<nsConnectionEntry> &ent,
                 void *aArg)
 {
     if (ent->mIdleConns.Length()   == 0 &&
@@ -1191,62 +1180,57 @@ nsHttpConnectionMgr::SupportsPipelining(
     nsConnectionEntry *ent = mCT.Get(ci->HashKey());
     if (ent)
         return ent->SupportsPipelining();
     return false;
 }
 
 // nsHttpPipelineFeedback used to hold references across events
 
-class nsHttpPipelineFeedback
+class nsHttpPipelineFeedback : public ARefBase
 {
 public:
     nsHttpPipelineFeedback(nsHttpConnectionInfo *ci,
                            nsHttpConnectionMgr::PipelineFeedbackInfoType info,
                            nsHttpConnection *conn, uint32_t data)
         : mConnInfo(ci)
         , mConn(conn)
         , mInfo(info)
         , mData(data)
         {
         }
 
-    ~nsHttpPipelineFeedback()
-    {
-    }
 
     RefPtr<nsHttpConnectionInfo> mConnInfo;
     RefPtr<nsHttpConnection> mConn;
     nsHttpConnectionMgr::PipelineFeedbackInfoType mInfo;
     uint32_t mData;
+private:
+    ~nsHttpPipelineFeedback() {}
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpPipelineFeedback)
 };
 
 void
 nsHttpConnectionMgr::PipelineFeedbackInfo(nsHttpConnectionInfo *ci,
                                           PipelineFeedbackInfoType info,
                                           nsHttpConnection *conn,
                                           uint32_t data)
 {
     if (!ci)
         return;
 
     // Post this to the socket thread if we are not running there already
     if (PR_GetCurrentThread() != gSocketThread) {
-        nsHttpPipelineFeedback *fb = new nsHttpPipelineFeedback(ci, info,
-                                                                conn, data);
-
-        nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessFeedback,
-                                0, fb);
-        if (NS_FAILED(rv))
-            delete fb;
+        RefPtr<nsHttpPipelineFeedback> fb =
+            new nsHttpPipelineFeedback(ci, info, conn, data);
+        PostEvent(&nsHttpConnectionMgr::OnMsgProcessFeedback, 0, fb);
         return;
     }
 
     nsConnectionEntry *ent = mCT.Get(ci->HashKey());
-
     if (ent)
         ent->OnPipelineFeedbackInfo(info, conn, data);
 }
 
 void
 nsHttpConnectionMgr::ReportFailedToProcess(nsIURI *uri)
 {
     MOZ_ASSERT(uri);
@@ -1919,16 +1903,54 @@ nsHttpConnectionMgr::DispatchTransaction
         else
             AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP,
                 trans->GetPendingTime(), TimeStamp::Now());
         trans->SetPendingTime(false);
     }
     return rv;
 }
 
+//-----------------------------------------------------------------------------
+// ConnectionHandle
+//
+// thin wrapper around a real connection, used to keep track of references
+// to the connection to determine when the connection may be reused.  the
+// transaction (or pipeline) owns a reference to this handle.  this extra
+// layer of indirection greatly simplifies consumer code, avoiding the
+// need for consumer code to know when to give the connection back to the
+// connection manager.
+//
+class ConnectionHandle : public nsAHttpConnection
+{
+public:
+    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_DECL_NSAHTTPCONNECTION(mConn)
+
+    explicit ConnectionHandle(nsHttpConnection *conn) { NS_ADDREF(mConn = conn); }
+    nsHttpConnection *mConn;
+
+private:
+    virtual ~ConnectionHandle();
+};
+
+nsAHttpConnection *
+nsHttpConnectionMgr::MakeConnectionHandle(nsHttpConnection *aWrapped)
+{
+    return new ConnectionHandle(aWrapped);
+}
+
+ConnectionHandle::~ConnectionHandle()
+{
+    if (mConn) {
+        gHttpHandler->ReclaimConnection(mConn);
+        NS_RELEASE(mConn);
+    }
+}
+
+NS_IMPL_ISUPPORTS0(ConnectionHandle)
 
 // Use this method for dispatching nsAHttpTransction's. It can only safely be
 // used upon first use of a connection when NPN has not negotiated SPDY vs
 // HTTP/1 yet as multiplexing onto an existing SPDY session requires a
 // concrete nsHttpTransaction
 nsresult
 nsHttpConnectionMgr::DispatchAbstractTransaction(nsConnectionEntry *ent,
                                                  nsAHttpTransaction *aTrans,
@@ -1958,17 +1980,17 @@ nsHttpConnectionMgr::DispatchAbstractTra
             return rv;
         transaction = pipeline;
     }
     else {
         LOG(("   not using pipeline datastructure due to class solo.\n"));
         transaction = aTrans;
     }
 
-    RefPtr<nsConnectionHandle> handle = new nsConnectionHandle(conn);
+    RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
 
     // give the transaction the indirect reference to the connection.
     transaction->SetConnection(handle);
 
     rv = conn->Activate(transaction, caps, priority);
     if (NS_FAILED(rv)) {
         LOG(("  conn->Activate failed [rv=%x]\n", rv));
         ent->mActiveConns.RemoveElement(conn);
@@ -2248,17 +2270,17 @@ nsHttpConnectionMgr::ProcessSpdyPendingQ
                                            void *closure)
 {
     nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
     self->ProcessSpdyPendingQ(ent);
     return PL_DHASH_NEXT;
 }
 
 void
-nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, void *)
+nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase *)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
     mCT.Enumerate(ProcessSpdyPendingQCB, this);
 }
 
 nsHttpConnection *
 nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent)
@@ -2289,17 +2311,17 @@ nsHttpConnectionMgr::GetSpdyPreferredCon
     }
 
     return conn;
 }
 
 //-----------------------------------------------------------------------------
 
 void
-nsHttpConnectionMgr::OnMsgShutdown(int32_t, void *param)
+nsHttpConnectionMgr::OnMsgShutdown(int32_t, ARefBase *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
 
     mCT.Enumerate(ShutdownPassCB, this);
 
     if (mTimeoutTick) {
         mTimeoutTick->Cancel();
@@ -2312,76 +2334,72 @@ nsHttpConnectionMgr::OnMsgShutdown(int32
     }
     if (mTrafficTimer) {
       mTrafficTimer->Cancel();
       mTrafficTimer = nullptr;
     }
 
     // signal shutdown complete
     nsCOMPtr<nsIRunnable> runnable =
-        new nsConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm,
-                        0, param);
+        new ConnEvent(this, &nsHttpConnectionMgr::OnMsgShutdownConfirm,
+                      0, param);
     NS_DispatchToMainThread(runnable);
 }
 
 void
-nsHttpConnectionMgr::OnMsgShutdownConfirm(int32_t priority, void *param)
+nsHttpConnectionMgr::OnMsgShutdownConfirm(int32_t priority, ARefBase *param)
 {
     MOZ_ASSERT(NS_IsMainThread());
     LOG(("nsHttpConnectionMgr::OnMsgShutdownConfirm\n"));
 
-    bool *shutdown = static_cast<bool*>(param);
-    *shutdown = true;
+    BoolWrapper *shutdown = static_cast<BoolWrapper *>(param);
+    shutdown->mBool = true;
 }
 
 void
-nsHttpConnectionMgr::OnMsgNewTransaction(int32_t priority, void *param)
+nsHttpConnectionMgr::OnMsgNewTransaction(int32_t priority, ARefBase *param)
 {
     LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param));
 
-    nsHttpTransaction *trans = (nsHttpTransaction *) param;
+    nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
     trans->SetPriority(priority);
     nsresult rv = ProcessNewTransaction(trans);
     if (NS_FAILED(rv))
         trans->Close(rv); // for whatever its worth
-    NS_RELEASE(trans);
 }
 
 void
-nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority, void *param)
+nsHttpConnectionMgr::OnMsgReschedTransaction(int32_t priority, ARefBase *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param));
 
-    nsHttpTransaction *trans = (nsHttpTransaction *) param;
+    nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
     trans->SetPriority(priority);
 
     nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(),
                                                    nullptr, trans);
 
     if (ent) {
         int32_t index = ent->mPendingQ.IndexOf(trans);
         if (index >= 0) {
             ent->mPendingQ.RemoveElementAt(index);
             InsertTransactionSorted(ent->mPendingQ, trans);
         }
     }
-
-    NS_RELEASE(trans);
 }
 
 void
-nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, void *param)
+nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, ARefBase *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
 
     nsresult closeCode = static_cast<nsresult>(reason);
-    RefPtr<nsHttpTransaction> trans =
-        dont_AddRef(static_cast<nsHttpTransaction *>(param));
+    nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
 
     //
     // if the transaction owns a connection and the transaction is not done,
     // then ask the connection to close the transaction.  otherwise, close the
     // transaction directly (removing it from the pending queue first).
     //
     RefPtr<nsAHttpConnection> conn(trans->Connection());
     if (conn && !trans->IsDone()) {
@@ -2389,17 +2407,17 @@ nsHttpConnectionMgr::OnMsgCancelTransact
     } else {
         nsConnectionEntry *ent =
             LookupConnectionEntry(trans->ConnectionInfo(), nullptr, trans);
 
         if (ent) {
             int32_t index = ent->mPendingQ.IndexOf(trans);
             if (index >= 0) {
                 LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
-                     " found in pending queue\n", trans.get()));
+                     " found in pending queue\n", trans));
                 ent->mPendingQ.RemoveElementAt(index);
                 nsHttpTransaction *temp = trans;
                 NS_RELEASE(temp); // b/c NS_RELEASE nulls its argument!
             }
 
             // Abandon all half-open sockets belonging to the given transaction.
             for (uint32_t index = 0;
                  index < ent->mHalfOpens.Length();
@@ -2424,28 +2442,28 @@ nsHttpConnectionMgr::OnMsgCancelTransact
         for (uint32_t index = 0;
              ent && (index < ent->mActiveConns.Length());
              ++index) {
             nsHttpConnection *activeConn = ent->mActiveConns[index];
             nsAHttpTransaction *liveTransaction = activeConn->Transaction();
             if (liveTransaction && liveTransaction->IsNullTransaction()) {
                 LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p] "
                      "also canceling Null Transaction %p on conn %p\n",
-                     trans.get(), liveTransaction, activeConn));
+                     trans, liveTransaction, activeConn));
                 activeConn->CloseTransaction(liveTransaction, closeCode);
             }
         }
     }
 }
 
 void
-nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, void *param)
+nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, ARefBase *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
-    nsHttpConnectionInfo *ci = (nsHttpConnectionInfo *) param;
+    nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
 
     if (!ci) {
         LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n"));
         // Try and dispatch everything
         mCT.Enumerate(ProcessAllTransactionsCB, this);
         return;
     }
 
@@ -2454,40 +2472,32 @@ nsHttpConnectionMgr::OnMsgProcessPending
 
     // start by processing the queue identified by the given connection info.
     nsConnectionEntry *ent = mCT.Get(ci->HashKey());
     if (!(ent && ProcessPendingQForEntry(ent, false))) {
         // if we reach here, it means that we couldn't dispatch a transaction
         // for the specified connection info.  walk the connection table...
         mCT.Enumerate(ProcessOneTransactionCB, this);
     }
-
-    NS_RELEASE(ci);
 }
 
 nsresult
-nsHttpConnectionMgr::CancelTransactions(nsHttpConnectionInfo *aCI, nsresult code)
+nsHttpConnectionMgr::CancelTransactions(nsHttpConnectionInfo *ci, nsresult code)
 {
-    RefPtr<nsHttpConnectionInfo> ci(aCI);
     LOG(("nsHttpConnectionMgr::CancelTransactions %s\n",ci->HashKey().get()));
 
     int32_t intReason = static_cast<int32_t>(code);
-    nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransactions, intReason, ci);
-    if (NS_SUCCEEDED(rv)) {
-        unused << ci.forget();
-    }
-    return rv;
+    return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransactions, intReason, ci);
 }
 
 void
-nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code, void *param)
+nsHttpConnectionMgr::OnMsgCancelTransactions(int32_t code, ARefBase *param)
 {
     nsresult reason = static_cast<nsresult>(code);
-    RefPtr<nsHttpConnectionInfo> ci =
-        dont_AddRef(static_cast<nsHttpConnectionInfo *>(param));
+    nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
     nsConnectionEntry *ent = mCT.Get(ci->HashKey());
     LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n",
          ci->HashKey().get(), ent));
     if (!ent) {
         return;
     }
 
     RefPtr<nsHttpTransaction> trans;
@@ -2497,44 +2507,44 @@ nsHttpConnectionMgr::OnMsgCancelTransact
              ci->HashKey().get(), ent, trans.get()));
         ent->mPendingQ.RemoveElementAt(i);
         trans->Close(reason);
         trans = nullptr;
     }
 }
 
 void
-nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, void *)
+nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, ARefBase *)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
 
     // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
     mTimeOfNextWakeUp = UINT64_MAX;
 
     // check canreuse() for all idle connections plus any active connections on
     // connection entries that are using spdy.
     if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled()))
         mCT.Enumerate(PruneDeadConnectionsCB, this);
 }
 
 void
-nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, void *)
+nsHttpConnectionMgr::OnMsgPruneNoTraffic(int32_t, ARefBase *)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgPruneNoTraffic\n"));
 
     // Prune connections without traffic
     mCT.Enumerate(PruneNoTrafficCB, this);
 
     mPruningNoTraffic = false; // not pruning anymore
 }
 
 void
-nsHttpConnectionMgr::OnMsgVerifyTraffic(int32_t, void *)
+nsHttpConnectionMgr::OnMsgVerifyTraffic(int32_t, ARefBase *)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgVerifyTraffic\n"));
 
     if (mPruningNoTraffic) {
       // Called in the time gap when the timeout to prune notraffic
       // connections has triggered but the pruning hasn't happened yet.
       return;
@@ -2556,36 +2566,35 @@ nsHttpConnectionMgr::OnMsgVerifyTraffic(
         mTrafficTimer->Init(this, gHttpHandler->NetworkChangedTimeout(),
                             nsITimer::TYPE_ONE_SHOT);
     } else {
         NS_WARNING("failed to create timer for VerifyTraffic!");
     }
 }
 
 void
-nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t, void *param)
+nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t, ARefBase *param)
 {
     LOG(("nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup\n"));
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
-    RefPtr<nsHttpConnectionInfo> ci =
-        dont_AddRef(static_cast<nsHttpConnectionInfo *>(param));
+    nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
 
     mCT.Enumerate(ClosePersistentConnectionsCB, this);
     if (ci)
         ResetIPFamilyPreference(ci);
 }
 
 void
-nsHttpConnectionMgr::OnMsgReclaimConnection(int32_t, void *param)
+nsHttpConnectionMgr::OnMsgReclaimConnection(int32_t, ARefBase *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [conn=%p]\n", param));
 
-    nsHttpConnection *conn = (nsHttpConnection *) param;
+    nsHttpConnection *conn = static_cast<nsHttpConnection *>(param);
 
     //
     // 1) remove the connection from the active list
     // 2) if keep-alive, add connection to idle list
     // 3) post event to process the pending transaction queue
     //
 
     nsConnectionEntry *ent = LookupConnectionEntry(conn->ConnectionInfo(),
@@ -2596,18 +2605,17 @@ nsHttpConnectionMgr::OnMsgReclaimConnect
         // future transactions. HTTP/2 tunnels work like this.
         ent = GetOrCreateConnectionEntry(conn->ConnectionInfo(), true);
         LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection conn %p "
              "forced new hash entry %s\n",
              conn, conn->ConnectionInfo()->HashKey().get()));
     }
 
     MOZ_ASSERT(ent);
-    nsHttpConnectionInfo *ci = nullptr;
-    NS_ADDREF(ci = ent->mConnInfo);
+    RefPtr<nsHttpConnectionInfo> ci(ent->mConnInfo);
 
     // If the connection is in the active list, remove that entry
     // and the reference held by the mActiveConns list.
     // This is never the final reference on conn as the event context
     // is also holding one that is released at the end of this function.
 
     if (conn->EverUsedSpdy()) {
         // Spdy connections aren't reused in the traditional HTTP way in
@@ -2663,25 +2671,24 @@ nsHttpConnectionMgr::OnMsgReclaimConnect
         uint32_t timeToLive = conn->TimeToLive();
         if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp)
             PruneDeadConnectionsAfter(timeToLive);
     } else {
         LOG(("  connection cannot be reused; closing connection\n"));
         conn->Close(NS_ERROR_ABORT);
     }
 
-    OnMsgProcessPendingQ(0, ci); // releases |ci|
-    NS_RELEASE(conn);
+    OnMsgProcessPendingQ(0, ci);
 }
 
 void
-nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, void *param)
+nsHttpConnectionMgr::OnMsgCompleteUpgrade(int32_t, ARefBase *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
-    nsCompleteUpgradeData *data = (nsCompleteUpgradeData *) param;
+    nsCompleteUpgradeData *data = static_cast<nsCompleteUpgradeData *>(param);
     LOG(("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
          "this=%p conn=%p listener=%p\n", this, data->mConn.get(),
          data->mUpgradeListener.get()));
 
     nsCOMPtr<nsISocketTransport> socketTransport;
     nsCOMPtr<nsIAsyncInputStream> socketIn;
     nsCOMPtr<nsIAsyncOutputStream> socketOut;
 
@@ -2689,24 +2696,24 @@ nsHttpConnectionMgr::OnMsgCompleteUpgrad
     rv = data->mConn->TakeTransport(getter_AddRefs(socketTransport),
                                     getter_AddRefs(socketIn),
                                     getter_AddRefs(socketOut));
 
     if (NS_SUCCEEDED(rv))
         data->mUpgradeListener->OnTransportAvailable(socketTransport,
                                                      socketIn,
                                                      socketOut);
-    delete data;
 }
 
 void
-nsHttpConnectionMgr::OnMsgUpdateParam(int32_t, void *param)
+nsHttpConnectionMgr::OnMsgUpdateParam(int32_t inParam, ARefBase *)
 {
-    uint16_t name  = (NS_PTR_TO_INT32(param) & 0xFFFF0000) >> 16;
-    uint16_t value =  NS_PTR_TO_INT32(param) & 0x0000FFFF;
+    uint32_t param = static_cast<uint32_t>(inParam);
+    uint16_t name  = ((param) & 0xFFFF0000) >> 16;
+    uint16_t value =  param & 0x0000FFFF;
 
     switch (name) {
     case MAX_CONNECTIONS:
         mMaxConns = value;
         break;
     case MAX_PERSISTENT_CONNECTIONS_PER_HOST:
         mMaxPersistConnsPerHost = value;
         break;
@@ -2730,23 +2737,21 @@ nsHttpConnectionMgr::OnMsgUpdateParam(in
 // nsHttpConnectionMgr::nsConnectionEntry
 nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
 {
     MOZ_COUNT_DTOR(nsConnectionEntry);
     gHttpHandler->ConnMgr()->RemovePreferredHash(this);
 }
 
 void
-nsHttpConnectionMgr::OnMsgProcessFeedback(int32_t, void *param)
+nsHttpConnectionMgr::OnMsgProcessFeedback(int32_t, ARefBase *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
-    nsHttpPipelineFeedback *fb = (nsHttpPipelineFeedback *)param;
-
+    nsHttpPipelineFeedback *fb = static_cast<nsHttpPipelineFeedback *>(param);
     PipelineFeedbackInfo(fb->mConnInfo, fb->mInfo, fb->mConn, fb->mData);
-    delete fb;
 }
 
 // Read Timeout Tick handlers
 
 void
 nsHttpConnectionMgr::ActivateTimeoutTick()
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@@ -2849,29 +2854,16 @@ nsHttpConnectionMgr::TimeoutTickCB(const
         }
     }
     if (ent->mHalfOpens.Length()) {
         self->mTimeoutTickNext = 1;
     }
     return PL_DHASH_NEXT;
 }
 
-//-----------------------------------------------------------------------------
-// nsHttpConnectionMgr::nsConnectionHandle
-
-nsHttpConnectionMgr::nsConnectionHandle::~nsConnectionHandle()
-{
-    if (mConn) {
-        gHttpHandler->ReclaimConnection(mConn);
-        NS_RELEASE(mConn);
-    }
-}
-
-NS_IMPL_ISUPPORTS0(nsHttpConnectionMgr::nsConnectionHandle)
-
 // GetOrCreateConnectionEntry finds a ent for a particular CI for use in
 // dispatching a transaction according to these rules
 // 1] use an ent that matches the ci that can be dispatched immediately
 // 2] otherwise use an ent of wildcard(ci) than can be dispatched immediately
 // 3] otherwise create an ent that matches ci and make new conn on it
 
 nsHttpConnectionMgr::nsConnectionEntry *
 nsHttpConnectionMgr::GetOrCreateConnectionEntry(nsHttpConnectionInfo *specificCI,
@@ -2902,46 +2894,44 @@ nsHttpConnectionMgr::GetOrCreateConnecti
         RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
         specificEnt = new nsConnectionEntry(clone);
         mCT.Put(clone->HashKey(), specificEnt);
     }
     return specificEnt;
 }
 
 nsresult
-nsHttpConnectionMgr::nsConnectionHandle::OnHeadersAvailable(nsAHttpTransaction *trans,
-                                                            nsHttpRequestHead *req,
-                                                            nsHttpResponseHead *resp,
-                                                            bool *reset)
+ConnectionHandle::OnHeadersAvailable(nsAHttpTransaction *trans,
+                                     nsHttpRequestHead *req,
+                                     nsHttpResponseHead *resp,
+                                     bool *reset)
 {
     return mConn->OnHeadersAvailable(trans, req, resp, reset);
 }
 
 void
-nsHttpConnectionMgr::nsConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
+ConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
 {
     mConn->CloseTransaction(trans, reason);
 }
 
 nsresult
-nsHttpConnectionMgr::
-nsConnectionHandle::TakeTransport(nsISocketTransport  **aTransport,
-                                  nsIAsyncInputStream **aInputStream,
-                                  nsIAsyncOutputStream **aOutputStream)
+ConnectionHandle::TakeTransport(nsISocketTransport  **aTransport,
+                                nsIAsyncInputStream **aInputStream,
+                                nsIAsyncOutputStream **aOutputStream)
 {
     return mConn->TakeTransport(aTransport, aInputStream, aOutputStream);
 }
 
 void
-nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, void *param)
+nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
-    RefPtr<SpeculativeConnectArgs> args =
-        dont_AddRef(static_cast<SpeculativeConnectArgs *>(param));
+    SpeculativeConnectArgs *args = static_cast<SpeculativeConnectArgs *>(param);
 
     LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n",
          args->mTrans->ConnectionInfo()->HashKey().get()));
 
     nsConnectionEntry *ent =
         GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo(), false);
 
     // If spdy has previously made a preferred entry for this host via
@@ -2975,35 +2965,35 @@ nsHttpConnectionMgr::OnMsgSpeculativeCon
         CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true, isFromPredictor, allow1918);
     }
     else {
         LOG(("  Transport not created due to existing connection count\n"));
     }
 }
 
 bool
-nsHttpConnectionMgr::nsConnectionHandle::IsPersistent()
+ConnectionHandle::IsPersistent()
 {
     return mConn->IsPersistent();
 }
 
 bool
-nsHttpConnectionMgr::nsConnectionHandle::IsReused()
+ConnectionHandle::IsReused()
 {
     return mConn->IsReused();
 }
 
 void
-nsHttpConnectionMgr::nsConnectionHandle::DontReuse()
+ConnectionHandle::DontReuse()
 {
     mConn->DontReuse();
 }
 
 nsresult
-nsHttpConnectionMgr::nsConnectionHandle::PushBack(const char *buf, uint32_t bufLen)
+ConnectionHandle::PushBack(const char *buf, uint32_t bufLen)
 {
     return mConn->PushBack(buf, bufLen);
 }
 
 
 //////////////////////// nsHalfOpenSocket
 
 NS_IMPL_ISUPPORTS(nsHttpConnectionMgr::nsHalfOpenSocket,
@@ -3446,20 +3436,17 @@ nsHalfOpenSocket::OnOutputStreamReady(ns
             gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
             conn->Classify(nsAHttpTransaction::CLASS_SOLO);
             rv = gHttpHandler->ConnMgr()->
                 DispatchAbstractTransaction(mEnt, trans, mCaps, conn, 0);
         } else {
             // otherwise just put this in the persistent connection pool
             LOG(("nsHalfOpenSocket::OnOutputStreamReady no transaction match "
                  "returning conn %p to pool\n", conn.get()));
-            RefPtr<nsHttpConnection> copy(conn);
-            // forget() to effectively addref because onmsg*() will drop a ref
-            gHttpHandler->ConnMgr()->OnMsgReclaimConnection(
-                0, conn.forget().take());
+            gHttpHandler->ConnMgr()->OnMsgReclaimConnection(0, conn);
         }
     }
 
     return rv;
 }
 
 // method for nsITransportEventSink
 NS_IMETHODIMP
@@ -3564,41 +3551,41 @@ nsHttpConnectionMgr::nsHalfOpenSocket::G
         if (callbacks)
             return callbacks->GetInterface(iid, result);
     }
     return NS_ERROR_NO_INTERFACE;
 }
 
 
 nsHttpConnection *
-nsHttpConnectionMgr::nsConnectionHandle::TakeHttpConnection()
+ConnectionHandle::TakeHttpConnection()
 {
     // return our connection object to the caller and clear it internally
     // do not drop our reference - the caller now owns it.
 
     MOZ_ASSERT(mConn);
     nsHttpConnection *conn = mConn;
     mConn = nullptr;
     return conn;
 }
 
 uint32_t
-nsHttpConnectionMgr::nsConnectionHandle::CancelPipeline(nsresult reason)
+ConnectionHandle::CancelPipeline(nsresult reason)
 {
     // no pipeline to cancel
     return 0;
 }
 
 nsAHttpTransaction::Classifier
-nsHttpConnectionMgr::nsConnectionHandle::Classification()
+ConnectionHandle::Classification()
 {
     if (mConn)
         return mConn->Classification();
 
-    LOG(("nsConnectionHandle::Classification this=%p "
+    LOG(("ConnectionHandle::Classification this=%p "
          "has null mConn using CLASS_SOLO default", this));
     return nsAHttpTransaction::CLASS_SOLO;
 }
 
 // nsConnectionEntry
 
 nsHttpConnectionMgr::
 nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -12,30 +12,35 @@
 #include "nsThreadUtils.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsAutoPtr.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Attributes.h"
 #include "AlternateServices.h"
+#include "ARefBase.h"
 
 #include "nsIObserver.h"
 #include "nsITimer.h"
 
 class nsIHttpUpgradeListener;
 
 namespace mozilla {
 namespace net {
 class EventTokenBucket;
 class NullHttpTransaction;
 struct HttpRetParams;
 
 //-----------------------------------------------------------------------------
 
+// message handlers have this signature
+class nsHttpConnectionMgr;
+typedef void (nsHttpConnectionMgr:: *nsConnEventHandler)(int32_t, ARefBase *);
+
 class nsHttpConnectionMgr final : public nsIObserver
                                 , public AltSvcCache
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIOBSERVER
 
     // parameter names
@@ -384,42 +389,19 @@ private:
         bool mPreferIPv6 : 1;
 
         // Set the IP family preference flags according the connected family
         void RecordIPFamilyPreference(uint16_t family);
         // Resets all flags to their default values
         void ResetIPFamilyPreference();
     };
 
-    // nsConnectionHandle
-    //
-    // thin wrapper around a real connection, used to keep track of references
-    // to the connection to determine when the connection may be reused.  the
-    // transaction (or pipeline) owns a reference to this handle.  this extra
-    // layer of indirection greatly simplifies consumer code, avoiding the
-    // need for consumer code to know when to give the connection back to the
-    // connection manager.
-    //
-    class nsConnectionHandle : public nsAHttpConnection
-    {
-        virtual ~nsConnectionHandle();
+public:
+    static nsAHttpConnection *MakeConnectionHandle(nsHttpConnection *aWrapped);
 
-    public:
-        NS_DECL_THREADSAFE_ISUPPORTS
-        NS_DECL_NSAHTTPCONNECTION(mConn)
-
-        explicit nsConnectionHandle(nsHttpConnection *conn) { NS_ADDREF(mConn = conn); }
-
-        nsHttpConnection *mConn;
-    };
-public:
-    static nsAHttpConnection *MakeConnectionHandle(nsHttpConnection *aWrapped)
-    {
-        return new nsConnectionHandle(aWrapped);
-    }
 private:
 
     // nsHalfOpenSocket is used to hold the state of an opening TCP socket
     // while we wait for it to establish and bind it to a connection
 
     class nsHalfOpenSocket final : public nsIOutputStreamCallback,
                                    public nsITransportEventSink,
                                    public nsIInterfaceRequestor,
@@ -517,17 +499,17 @@ private:
 
     // connection limits
     uint16_t mMaxConns;
     uint16_t mMaxPersistConnsPerHost;
     uint16_t mMaxPersistConnsPerProxy;
     uint16_t mMaxRequestDelay; // in seconds
     uint16_t mMaxPipelinedRequests;
     uint16_t mMaxOptimisticPipelinedRequests;
-    bool mIsShuttingDown;
+    Atomic<bool, mozilla::Relaxed> mIsShuttingDown;
 
     //-------------------------------------------------------------------------
     // NOTE: these members are only accessed on the socket transport thread
     //-------------------------------------------------------------------------
 
     static PLDHashOperator ProcessOneTransactionCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
     static PLDHashOperator ProcessAllTransactionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
 
@@ -589,82 +571,40 @@ private:
                                              nsHttpConnection *conn,
                                              nsHttpTransaction *trans);
 
     void               ProcessSpdyPendingQ(nsConnectionEntry *ent);
     static PLDHashOperator ProcessSpdyPendingQCB(
         const nsACString &key, nsAutoPtr<nsConnectionEntry> &ent,
         void *closure);
 
-    // message handlers have this signature
-    typedef void (nsHttpConnectionMgr:: *nsConnEventHandler)(int32_t, void *);
-
-    // nsConnEvent
-    //
-    // subclass of nsRunnable used to marshall events to the socket transport
-    // thread.  this class is used to implement PostEvent.
-    //
-    class nsConnEvent;
-    friend class nsConnEvent;
-    class nsConnEvent : public nsRunnable
-    {
-    public:
-        nsConnEvent(nsHttpConnectionMgr *mgr,
-                    nsConnEventHandler handler,
-                    int32_t iparam,
-                    void *vparam)
-            : mMgr(mgr)
-            , mHandler(handler)
-            , mIParam(iparam)
-            , mVParam(vparam)
-        {
-            NS_ADDREF(mMgr);
-        }
-
-        NS_IMETHOD Run()
-        {
-            (mMgr->*mHandler)(mIParam, mVParam);
-            return NS_OK;
-        }
-
-    private:
-        virtual ~nsConnEvent()
-        {
-            NS_RELEASE(mMgr);
-        }
-
-        nsHttpConnectionMgr *mMgr;
-        nsConnEventHandler   mHandler;
-        int32_t              mIParam;
-        void                *mVParam;
-    };
-
+    // used to marshall events to the socket transport thread.
     nsresult PostEvent(nsConnEventHandler  handler,
                        int32_t             iparam = 0,
-                       void               *vparam = nullptr);
+                       ARefBase            *vparam = nullptr);
 
     // message handlers
-    void OnMsgShutdown             (int32_t, void *);
-    void OnMsgShutdownConfirm      (int32_t, void *);
-    void OnMsgNewTransaction       (int32_t, void *);
-    void OnMsgReschedTransaction   (int32_t, void *);
-    void OnMsgCancelTransaction    (int32_t, void *);
-    void OnMsgCancelTransactions   (int32_t, void *);
-    void OnMsgProcessPendingQ      (int32_t, void *);
-    void OnMsgPruneDeadConnections (int32_t, void *);
-    void OnMsgSpeculativeConnect   (int32_t, void *);
-    void OnMsgReclaimConnection    (int32_t, void *);
-    void OnMsgCompleteUpgrade      (int32_t, void *);
-    void OnMsgUpdateParam          (int32_t, void *);
-    void OnMsgDoShiftReloadConnectionCleanup (int32_t, void *);
-    void OnMsgProcessFeedback      (int32_t, void *);
-    void OnMsgProcessAllSpdyPendingQ (int32_t, void *);
-    void OnMsgUpdateRequestTokenBucket (int32_t, void *);
-    void OnMsgVerifyTraffic (int32_t, void *);
-    void OnMsgPruneNoTraffic (int32_t, void *);
+    void OnMsgShutdown             (int32_t, ARefBase *);
+    void OnMsgShutdownConfirm      (int32_t, ARefBase *);
+    void OnMsgNewTransaction       (int32_t, ARefBase *);
+    void OnMsgReschedTransaction   (int32_t, ARefBase *);
+    void OnMsgCancelTransaction    (int32_t, ARefBase *);
+    void OnMsgCancelTransactions   (int32_t, ARefBase *);
+    void OnMsgProcessPendingQ      (int32_t, ARefBase *);
+    void OnMsgPruneDeadConnections (int32_t, ARefBase *);
+    void OnMsgSpeculativeConnect   (int32_t, ARefBase *);
+    void OnMsgReclaimConnection    (int32_t, ARefBase *);
+    void OnMsgCompleteUpgrade      (int32_t, ARefBase *);
+    void OnMsgUpdateParam          (int32_t, ARefBase *);
+    void OnMsgDoShiftReloadConnectionCleanup (int32_t, ARefBase *);
+    void OnMsgProcessFeedback      (int32_t, ARefBase *);
+    void OnMsgProcessAllSpdyPendingQ (int32_t, ARefBase *);
+    void OnMsgUpdateRequestTokenBucket (int32_t, ARefBase *);
+    void OnMsgVerifyTraffic (int32_t, ARefBase *);
+    void OnMsgPruneNoTraffic (int32_t, ARefBase *);
 
     // Total number of active connections in all of the ConnectionEntry objects
     // that are accessed from mCT connection table.
     uint16_t mNumActiveConns;
     // Total number of idle connections in all of the ConnectionEntry objects
     // that are accessed from mCT connection table.
     uint16_t mNumIdleConns;
     // Total number of spdy connections which are a subset of the active conns
@@ -706,17 +646,17 @@ private:
 
     // Read Timeout Tick handlers
     void TimeoutTick();
     static PLDHashOperator TimeoutTickCB(const nsACString &key,
                                          nsAutoPtr<nsConnectionEntry> &ent,
                                          void *closure);
 
     // For diagnostics
-    void OnMsgPrintDiagnostics(int32_t, void *);
+    void OnMsgPrintDiagnostics(int32_t, ARefBase *);
     static PLDHashOperator PrintDiagnosticsCB(const nsACString &key,
                                               nsAutoPtr<nsConnectionEntry> &ent,
                                               void *closure);
     nsCString mLogData;
 };
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -11,16 +11,17 @@
 #include "nsAHttpConnection.h"
 #include "EventTokenBucket.h"
 #include "nsCOMPtr.h"
 #include "nsThreadUtils.h"
 #include "nsIInterfaceRequestor.h"
 #include "TimingStruct.h"
 #include "Http2Push.h"
 #include "mozilla/net/DNS.h"
+#include "ARefBase.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsINetworkInterface.h"
 #include "nsProxyRelease.h"
 #endif
 
 //-----------------------------------------------------------------------------
 
@@ -40,16 +41,17 @@ class nsHttpResponseHead;
 // nsHttpTransaction represents a single HTTP transaction.  It is thread-safe,
 // intended to run on the socket thread.
 //-----------------------------------------------------------------------------
 
 class nsHttpTransaction final : public nsAHttpTransaction
                               , public ATokenBucketEvent
                               , public nsIInputStreamCallback
                               , public nsIOutputStreamCallback
+                              , public ARefBase
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSAHTTPTRANSACTION
     NS_DECL_NSIINPUTSTREAMCALLBACK
     NS_DECL_NSIOUTPUTSTREAMCALLBACK
 
     nsHttpTransaction();
--- a/testing/marionette/client/marionette/__init__.py
+++ b/testing/marionette/client/marionette/__init__.py
@@ -1,28 +1,36 @@
 # 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/.
 
 
 __version__ = '1.0.0'
 
-from .marionette_test import MarionetteTestCase, MarionetteJSTestCase, CommonTestCase, expectedFailure, skip, SkipTest
+from .marionette_test import (
+    CommonTestCase,
+    expectedFailure,
+    MarionetteJSTestCase,
+    MarionetteTestCase,
+    skip,
+    SkipTest,
+    skip_unless_protocol,
+)
 from .runner import (
-        B2GTestCaseMixin,
-        B2GTestResultMixin,
-        BaseMarionetteArguments,
-        BaseMarionetteTestRunner,
-        BrowserMobProxyTestCaseMixin,
-        EnduranceArguments,
-        EnduranceTestCaseMixin,
-        HTMLReportingArguments,
-        HTMLReportingTestResultMixin,
-        HTMLReportingTestRunnerMixin,
-        Marionette,
-        MarionetteTest,
-        MarionetteTestResult,
-        MarionetteTextTestRunner,
-        MemoryEnduranceTestCaseMixin,
-        TestManifest,
-        TestResult,
-        TestResultCollection
+    B2GTestCaseMixin,
+    B2GTestResultMixin,
+    BaseMarionetteArguments,
+    BaseMarionetteTestRunner,
+    BrowserMobProxyTestCaseMixin,
+    EnduranceArguments,
+    EnduranceTestCaseMixin,
+    HTMLReportingArguments,
+    HTMLReportingTestResultMixin,
+    HTMLReportingTestRunnerMixin,
+    Marionette,
+    MarionetteTest,
+    MarionetteTestResult,
+    MarionetteTextTestRunner,
+    MemoryEnduranceTestCaseMixin,
+    TestManifest,
+    TestResult,
+    TestResultCollection,
 )
--- a/testing/marionette/client/marionette/marionette_test.py
+++ b/testing/marionette/client/marionette/marionette_test.py
@@ -51,19 +51,17 @@ class _ExpectedFailure(Exception):
 
 class _UnexpectedSuccess(Exception):
     """
     The test was supposed to fail, but it didn't!
     """
     pass
 
 def skip(reason):
-    """
-    Unconditionally skip a test.
-    """
+    """Unconditionally skip a test."""
     def decorator(test_item):
         if not isinstance(test_item, (type, types.ClassType)):
             @functools.wraps(test_item)
             def skip_wrapper(*args, **kwargs):
                 raise SkipTest(reason)
             test_item = skip_wrapper
 
         test_item.__unittest_skip__ = True
@@ -76,22 +74,28 @@ def expectedFailure(func):
     def wrapper(*args, **kwargs):
         try:
             func(*args, **kwargs)
         except Exception:
             raise _ExpectedFailure(sys.exc_info())
         raise _UnexpectedSuccess
     return wrapper
 
+def skip_if_desktop(target):
+    def wrapper(self, *args, **kwargs):
+        if self.marionette.session_capabilities.get('b2g') is None:
+            raise SkipTest('skipping due to desktop')
+        return target(self, *args, **kwargs)
+    return wrapper
+
 def skip_if_b2g(target):
     def wrapper(self, *args, **kwargs):
         if self.marionette.session_capabilities.get('b2g') == True:
             raise SkipTest('skipping due to b2g')
         return target(self, *args, **kwargs)
-
     return wrapper
 
 def skip_if_e10s(target):
     def wrapper(self, *args, **kwargs):
         with self.marionette.using_context('chrome'):
             multi_process_browser = self.marionette.execute_script("""
             try {
               return Services.appinfo.browserTabsRemoteAutostart;
@@ -99,16 +103,29 @@ def skip_if_e10s(target):
               return false;
             }""")
 
         if multi_process_browser:
             raise SkipTest('skipping due to e10s')
         return target(self, *args, **kwargs)
     return wrapper
 
+def skip_unless_protocol(predicate):
+    """Given a predicate passed the current protocol level, skip the
+    test if the predicate does not match."""
+    def decorator(test_item):
+        @functools.wraps(test_item)
+        def skip_wrapper(self):
+            level = self.marionette.client.protocol
+            if not predicate(level):
+                raise SkipTest('skipping because protocol level is %s' % level)
+            return self
+        return skip_wrapper
+    return decorator
+
 def parameterized(func_suffix, *args, **kwargs):
     """
     A decorator that can generate methods given a base method and some data.
 
     **func_suffix** is used as a suffix for the new created method and must be
     unique given a base method. if **func_suffix** countains characters that
     are not allowed in normal python function name, these characters will be
     replaced with "_".
--- a/testing/marionette/client/marionette/tests/unit/test_emulator.py
+++ b/testing/marionette/client/marionette/tests/unit/test_emulator.py
@@ -1,60 +1,60 @@
 # 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/.
 
-from marionette import MarionetteTestCase
-from marionette_driver.errors import MarionetteException
+from unittest import skip
+
+from marionette.marionette_test import MarionetteTestCase, skip_if_desktop, skip_unless_protocol
+from marionette_driver.errors import MarionetteException, JavascriptException
 
 
 class TestEmulatorContent(MarionetteTestCase):
-
+    @skip_if_desktop
     def test_emulator_cmd(self):
         self.marionette.set_script_timeout(10000)
         expected = ["<build>",
                     "OK"]
         result = self.marionette.execute_async_script("""
         runEmulatorCmd("avd name", marionetteScriptFinished)
         """);
         self.assertEqual(result, expected)
 
+    @skip_if_desktop
     def test_emulator_shell(self):
         self.marionette.set_script_timeout(10000)
         expected = ["Hello World!"]
         result = self.marionette.execute_async_script("""
         runEmulatorShell(["echo", "Hello World!"], marionetteScriptFinished)
         """);
         self.assertEqual(result, expected)
 
+    @skip_if_desktop
     def test_emulator_order(self):
         self.marionette.set_script_timeout(10000)
         self.assertRaises(MarionetteException,
                           self.marionette.execute_async_script,
         """runEmulatorCmd("gsm status", function(result) {});
            marionetteScriptFinished(true);
         """);
 
 
 class TestEmulatorChrome(TestEmulatorContent):
-
     def setUp(self):
         super(TestEmulatorChrome, self).setUp()
         self.marionette.set_context("chrome")
 
 
 class TestEmulatorScreen(MarionetteTestCase):
-
-    def setUp(self):
-        MarionetteTestCase.setUp(self)
-
+    @skip_if_desktop
+    def test_emulator_orientation(self):
         self.screen = self.marionette.emulator.screen
         self.screen.initialize()
 
-    def test_emulator_orientation(self):
         self.assertEqual(self.screen.orientation, self.screen.SO_PORTRAIT_PRIMARY,
                          'Orientation has been correctly initialized.')
 
         self.screen.orientation = self.screen.SO_PORTRAIT_SECONDARY
         self.assertEqual(self.screen.orientation, self.screen.SO_PORTRAIT_SECONDARY,
                          'Orientation has been set to portrait-secondary')
 
         self.screen.orientation = self.screen.SO_LANDSCAPE_PRIMARY
@@ -63,8 +63,75 @@ class TestEmulatorScreen(MarionetteTestC
 
         self.screen.orientation = self.screen.SO_LANDSCAPE_SECONDARY
         self.assertEqual(self.screen.orientation, self.screen.SO_LANDSCAPE_SECONDARY,
                          'Orientation has been set to landscape-secondary')
 
         self.screen.orientation = self.screen.SO_PORTRAIT_PRIMARY
         self.assertEqual(self.screen.orientation, self.screen.SO_PORTRAIT_PRIMARY,
                          'Orientation has been set to portrait-primary')
+
+
+class TestEmulatorCallbacks(MarionetteTestCase):
+    def setUp(self):
+        MarionetteTestCase.setUp(self)
+        self.original_emulator_cmd = self.marionette._emulator_cmd
+        self.original_emulator_shell = self.marionette._emulator_shell
+        self.marionette._emulator_cmd = self.mock_emulator_cmd
+        self.marionette._emulator_shell = self.mock_emulator_shell
+
+    def tearDown(self):
+        self.marionette._emulator_cmd = self.original_emulator_cmd
+        self.marionette._emulator_shell = self.original_emulator_shell
+
+    def mock_emulator_cmd(self, *args):
+        return self.marionette._send_emulator_result("cmd response")
+
+    def mock_emulator_shell(self, *args):
+        return self.marionette._send_emulator_result("shell response")
+
+    def _execute_emulator(self, action, args):
+        script = "%s(%s, function(res) { marionetteScriptFinished(res); })" % (action, args)
+        return self.marionette.execute_async_script(script)
+
+    def emulator_cmd(self, cmd):
+        return self._execute_emulator("runEmulatorCmd", escape(cmd))
+
+    def emulator_shell(self, *args):
+        js_args = ", ".join(map(escape, args))
+        js_args = "[%s]" % js_args
+        return self._execute_emulator("runEmulatorShell", js_args)
+
+    def test_emulator_cmd_content(self):
+        with self.marionette.using_context("content"):
+            res = self.emulator_cmd("yo")
+            self.assertEqual("cmd response", res)
+
+    def test_emulator_shell_content(self):
+        with self.marionette.using_context("content"):
+            res = self.emulator_shell("first", "second")
+            self.assertEqual("shell response", res)
+
+    @skip_unless_protocol(lambda level: level >= 3)
+    def test_emulator_result_error_content(self):
+        with self.marionette.using_context("content"):
+            with self.assertRaisesRegexp(JavascriptException, "TypeError"):
+                self.marionette.execute_async_script("runEmulatorCmd()")
+
+    def test_emulator_cmd_chrome(self):
+        with self.marionette.using_context("chrome"):
+            res = self.emulator_cmd("yo")
+            self.assertEqual("cmd response", res)
+
+    def test_emulator_shell_chrome(self):
+        with self.marionette.using_context("chrome"):
+            res = self.emulator_shell("first", "second")
+            self.assertEqual("shell response", res)
+
+    @skip_unless_protocol(lambda level: level >= 3)
+    def test_emulator_result_error_chrome(self):
+        with self.marionette.using_context("chrome"):
+            with self.assertRaisesRegexp(JavascriptException, "TypeError"):
+                self.marionette.execute_async_script("runEmulatorCmd()")
+
+
+def escape(word):
+    return "'%s'" % word
--- a/testing/marionette/client/marionette/tests/unit/test_switch_frame.py
+++ b/testing/marionette/client/marionette/tests/unit/test_switch_frame.py
@@ -1,13 +1,14 @@
 # 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/.
 
 from marionette import MarionetteTestCase
+from marionette_driver.by import By
 from marionette_driver.errors import (JavascriptException,
                                       NoSuchFrameException)
 
 
 class TestSwitchFrame(MarionetteTestCase):
     def test_switch_simple(self):
         start_url = "test_iframe.html"
         verify_title = "Marionette IFrame Test"
@@ -129,8 +130,61 @@ class TestSwitchFrame(MarionetteTestCase
         frame_html = self.marionette.absolute_url("frameset.html")
         self.marionette.navigate(frame_html)
         frame = self.marionette.find_element("name", "third")
         self.marionette.switch_to_frame(frame)
         self.assertEqual("Unique title", self.marionette.title)
         test_html = self.marionette.absolute_url("test.html")
         self.marionette.navigate(test_html)
         self.assertEqual("Marionette Test", self.marionette.title)
+
+    def test_switch_to_parent_frame(self):
+        frame_html = self.marionette.absolute_url("frameset.html")
+        self.marionette.navigate(frame_html)
+        frame = self.marionette.find_element("name", "third")
+        self.marionette.switch_to_frame(frame)
+
+        # If we don't find the following element we aren't on the right page
+        self.marionette.find_element(By.ID, "checky")
+        form_page_title = self.marionette.execute_script("return document.title")
+        self.assertEqual("We Leave From Here", form_page_title)
+
+        self.marionette.switch_to_parent_frame()
+
+        current_page_title = self.marionette.execute_script("return document.title")
+        self.assertEqual("Unique title", current_page_title)
+
+    def test_switch_to_parent_frame_from_default_context_is_a_noop(self):
+        formpage = self.marionette.absolute_url("formPage.html")
+        self.marionette.navigate(formpage)
+
+        self.marionette.switch_to_parent_frame()
+
+        form_page_title = self.marionette.execute_script("return document.title")
+        self.assertEqual("We Leave From Here", form_page_title)
+
+    def test_should_be_able_to_switch_to_parent_from_second_level(self):
+        frame_html = self.marionette.absolute_url("frameset.html")
+        self.marionette.navigate(frame_html)
+        frame = self.marionette.find_element(By.NAME, "fourth")
+        self.marionette.switch_to_frame(frame)
+
+        second_level = self.marionette.find_element(By.NAME, "child1")
+        self.marionette.switch_to_frame(second_level)
+        self.marionette.find_element(By.NAME, "myCheckBox")
+
+        self.marionette.switch_to_parent_frame()
+
+        second_level = self.marionette.find_element(By.NAME, "child1")
+
+    def test_should_be_able_to_switch_to_parent_from_iframe(self):
+        frame_html = self.marionette.absolute_url("test_iframe.html")
+        self.marionette.navigate(frame_html)
+        frame = self.marionette.find_element(By.ID, "test_iframe")
+        self.marionette.switch_to_frame(frame)
+
+        current_page_title = self.marionette.execute_script("return document.title")
+        self.assertEqual("Marionette Test", current_page_title)
+
+        self.marionette.switch_to_parent_frame()
+
+        parent_page_title = self.marionette.execute_script("return document.title")
+        self.assertEqual("Marionette IFrame Test", parent_page_title)
new file mode 100644
--- /dev/null
+++ b/testing/marionette/client/marionette/tests/unit/test_transport.py
@@ -0,0 +1,181 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import json
+from marionette import MarionetteTestCase, skip_unless_protocol
+from marionette_transport import (
+    Command,
+    Proto2Command,
+    Proto2Response,
+    Response
+)
+
+get_current_url = ("getCurrentUrl", None)
+execute_script = ("executeScript", {"script": "return 42"})
+
+
+class TestMessageSequencing(MarionetteTestCase):
+    @property
+    def last_id(self):
+        return self.marionette.client.last_id
+
+    @last_id.setter
+    def last_id(self, new_id):
+        self.marionette.client.last_id = new_id
+
+    def send(self, name, params):
+        self.last_id = self.last_id + 1
+        cmd = Command(self.last_id, name, params)
+        self.marionette.client.send(cmd)
+        return self.last_id
+
+    @skip_unless_protocol(lambda level: level >= 3)
+    def test_discard_older_messages(self):
+        first = self.send(*get_current_url)
+        second = self.send(*execute_script)
+        resp = self.marionette.client.receive()
+        self.assertEqual(second, resp.id)
+
+    @skip_unless_protocol(lambda level: level >= 3)
+    def test_last_id_incremented(self):
+        before = self.last_id
+        self.send(*get_current_url)
+        self.assertGreater(self.last_id, before)
+
+
+class MessageTestCase(MarionetteTestCase):
+    def assert_attr(self, obj, attr):
+        self.assertTrue(hasattr(obj, attr),
+                        "object does not have attribute %s" % attr)
+
+
+class TestCommand(MessageTestCase):
+    def create(self, msgid="msgid", name="name", params="params"):
+        return Command(msgid, name, params)
+
+    def test_initialise(self):
+        cmd = self.create()
+        self.assert_attr(cmd, "id")
+        self.assert_attr(cmd, "name")
+        self.assert_attr(cmd, "params")
+        self.assertEqual("msgid", cmd.id)
+        self.assertEqual("name", cmd.name)
+        self.assertEqual("params", cmd.params)
+
+    def test_stringify(self):
+        cmd = self.create()
+        string = str(cmd)
+        self.assertIn("Command", string)
+        self.assertIn("id=msgid", string)
+        self.assertIn("name=name", string)
+        self.assertIn("params=params", string)
+
+    def test_to_msg(self):
+        cmd = self.create()
+        msg = json.loads(cmd.to_msg())
+        self.assertEquals(msg[0], Command.TYPE)
+        self.assertEquals(msg[1], "msgid")
+        self.assertEquals(msg[2], "name")
+        self.assertEquals(msg[3], "params")
+
+    def test_from_msg(self):
+        msg = [Command.TYPE, "msgid", "name", "params"]
+        payload = json.dumps(msg)
+        cmd = Command.from_msg(payload)
+        self.assertEquals(msg[1], cmd.id)
+        self.assertEquals(msg[2], cmd.name)
+        self.assertEquals(msg[3], cmd.params)
+
+
+class TestResponse(MessageTestCase):
+    def create(self, msgid="msgid", error="error", result="result"):
+        return Response(msgid, error, result)
+
+    def test_initialise(self):
+        resp = self.create()
+        self.assert_attr(resp, "id")
+        self.assert_attr(resp, "error")
+        self.assert_attr(resp, "result")
+        self.assertEqual("msgid", resp.id)
+        self.assertEqual("error", resp.error)
+        self.assertEqual("result", resp.result)
+
+    def test_stringify(self):
+        resp = self.create()
+        string = str(resp)
+        self.assertIn("Response", string)
+        self.assertIn("id=msgid", string)
+        self.assertIn("error=error", string)
+        self.assertIn("result=result", string)
+
+    def test_to_msg(self):
+        resp = self.create()
+        msg = json.loads(resp.to_msg())
+        self.assertEquals(msg[0], Response.TYPE)
+        self.assertEquals(msg[1], "msgid")
+        self.assertEquals(msg[2], "error")
+        self.assertEquals(msg[3], "result")
+
+    def test_from_msg(self):
+        msg = [Response.TYPE, "msgid", "error", "result"]
+        payload = json.dumps(msg)
+        resp = Response.from_msg(payload)
+        self.assertEquals(msg[1], resp.id)
+        self.assertEquals(msg[2], resp.error)
+        self.assertEquals(msg[3], resp.result)
+
+
+class TestProto2Command(MessageTestCase):
+    def create(self, name="name", params="params"):
+        return Proto2Command(name, params)
+
+    def test_initialise(self):
+        cmd = self.create()
+        self.assert_attr(cmd, "id")
+        self.assert_attr(cmd, "name")
+        self.assert_attr(cmd, "params")
+        self.assertEqual(None, cmd.id)
+        self.assertEqual("name", cmd.name)
+        self.assertEqual("params", cmd.params)
+
+    def test_from_data_emulator_cmd(self):
+        data = {"emulator_cmd": "emulator_cmd"}
+        cmd = Proto2Command.from_data(data)
+        self.assertEqual("runEmulatorCmd", cmd.name)
+        self.assertEqual(data, cmd.params)
+
+    def test_from_data_emulator_shell(self):
+        data = {"emulator_shell": "emulator_shell"}
+        cmd = Proto2Command.from_data(data)
+        self.assertEqual("runEmulatorShell", cmd.name)
+        self.assertEqual(data, cmd.params)
+
+    def test_from_data_unknown(self):
+        with self.assertRaises(ValueError):
+            cmd = Proto2Command.from_data({})
+
+
+class TestProto2Response(MessageTestCase):
+    def create(self, error="error", result="result"):
+        return Proto2Response(error, result)
+
+    def test_initialise(self):
+        resp = self.create()
+        self.assert_attr(resp, "id")
+        self.assert_attr(resp, "error")
+        self.assert_attr(resp, "result")
+        self.assertEqual(None, resp.id)
+        self.assertEqual("error", resp.error)
+        self.assertEqual("result", resp.result)
+
+    def test_from_data_error(self):
+        data = {"error": "error"}
+        resp = Proto2Response.from_data(data)
+        self.assertEqual(data, resp.error)
+        self.assertEqual(None, resp.result)
+
+    def test_from_data_result(self):
+        resp = Proto2Response.from_data("result")
+        self.assertEqual(None, resp.error)
+        self.assertEqual("result", resp.result)
--- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini
+++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini
@@ -46,17 +46,16 @@ b2g = false
 [test_text_chrome.py]
 disabled = "Bug 896046"
 
 [test_clearing.py]
 [test_typing.py]
 
 [test_log.py]
 [test_emulator.py]
-browser = false
 qemu = true
 
 [test_about_pages.py]
 b2g = false
 
 [test_execute_async_script.py]
 [test_execute_script.py]
 [test_simpletest_fail.js]
--- a/testing/marionette/client/marionette/www/framesetPage2.html
+++ b/testing/marionette/client/marionette/www/framesetPage2.html
@@ -1,7 +1,7 @@
 <html>
 <head></head>
 <frameset cols="*, *">
-    <frame name="child1" src="page/10"/>
-    <frame name="child2" src="page/11"/>
+    <frame name="child1" src="test.html"/>
+    <frame name="child2" src="test.html"/>
 </frameset>
-</html>
\ No newline at end of file
+</html>
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -1618,16 +1618,23 @@ GeckoDriver.prototype.getActiveFrame = f
 
     case Context.CONTENT:
       resp.body.value = this.currentFrameElement;
       break;
   }
 };
 
 /**
+ *
+ */
+GeckoDriver.prototype.switchToParentFrame = function (cmd, resp) {
+  let res = yield this.listener.switchToParentFrame();
+};
+
+/**
  * Switch to a given frame within the current window.
  *
  * @param {Object} element
  *     A web element reference to the element to switch to.
  * @param {(string|number)} id
  *     If element is not defined, then this holds either the id, name,
  *     or index of the frame to switch to.
  */
@@ -2961,16 +2968,17 @@ GeckoDriver.prototype.commands = {
   "getWindowHandles": GeckoDriver.prototype.getWindowHandles,
   "getChromeWindowHandles": GeckoDriver.prototype.getChromeWindowHandles,
   "getCurrentWindowHandles": GeckoDriver.prototype.getWindowHandles,  // Selenium 2 compat
   "getWindows":  GeckoDriver.prototype.getWindowHandles,  // deprecated
   "getWindowPosition": GeckoDriver.prototype.getWindowPosition,
   "setWindowPosition": GeckoDriver.prototype.setWindowPosition,
   "getActiveFrame": GeckoDriver.prototype.getActiveFrame,
   "switchToFrame": GeckoDriver.prototype.switchToFrame,
+  "switchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
   "switchToWindow": GeckoDriver.prototype.switchToWindow,
   "switchToShadowRoot": GeckoDriver.prototype.switchToShadowRoot,
   "deleteSession": GeckoDriver.prototype.deleteSession,
   "importScript": GeckoDriver.prototype.importScript,
   "clearImportedScripts": GeckoDriver.prototype.clearImportedScripts,
   "getAppCacheStatus": GeckoDriver.prototype.getAppCacheStatus,
   "close": GeckoDriver.prototype.close,
   "closeWindow": GeckoDriver.prototype.close,  // deprecated
--- a/testing/marionette/driver/marionette_driver/marionette.py
+++ b/testing/marionette/driver/marionette_driver/marionette.py
@@ -10,26 +10,27 @@ import socket
 import StringIO
 import traceback
 import warnings
 
 from contextlib import contextmanager
 
 from decorators import do_crash_check
 from keys import Keys
-from marionette_transport import MarionetteTransport
+import marionette_transport as transport
 
 from mozrunner import B2GEmulatorRunner
 
 import geckoinstance
 import errors
 
 WEBELEMENT_KEY = "ELEMENT"
 W3C_WEBELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf"
 
+
 class HTMLElement(object):
     """
     Represents a DOM Element.
     """
 
     def __init__(self, marionette, id):
         self.marionette = marionette
         assert(id is not None)
@@ -618,26 +619,25 @@ class Marionette(object):
             self.runner = B2GEmulatorRunner(b2g_home=homedir,
                                             logdir=logdir,
                                             process_args=process_args)
             self.emulator = self.runner.device
             self.emulator.connect()
             self.port = self.emulator.setup_port_forwarding(remote_port=self.port)
             assert(self.emulator.wait_for_port(self.port)), "Timed out waiting for port!"
 
-        self.client = MarionetteTransport(
-            self.host,
-            self.port,
-            self.socket_timeout)
-
         if emulator:
             if busybox:
                 self.emulator.install_busybox(busybox=busybox)
             self.emulator.wait_for_system_message(self)
 
+        # for callbacks from a protocol level 2 or lower remote,
+        # we store the callback ID so it can be used by _send_emulator_result
+        self.emulator_callback_id = None
+
     def cleanup(self):
         if self.session:
             try:
                 self.delete_session()
             except (errors.MarionetteException, socket.error, IOError):
                 # These exceptions get thrown if the Marionette server
                 # hit an exception/died or the connection died. We can
                 # do no further server-side cleanup in this case.
@@ -662,116 +662,120 @@ class Marionette(object):
             s.bind((host, port))
             return True
         except socket.error:
             return False
         finally:
             s.close()
 
     def wait_for_port(self, timeout=60):
-        return MarionetteTransport.wait_for_port(self.host,
-                                                 self.port,
-                                                 timeout=timeout)
+        return transport.wait_for_port(self.host, self.port, timeout=timeout)
 
     @do_crash_check
-    def _send_message(self, command, body=None, key=None):
-        if not self.session_id and command != "newSession":
+    def _send_message(self, name, params=None, key=None):
+        if not self.session_id and name != "newSession":
             raise errors.MarionetteException("Please start a session")
 
-        message = {"name": command}
-        if body:
-            message["parameters"] = body
+        try:
+            if self.protocol < 3:
+                data = {"name": name}
+                if params:
+                    data["parameters"] = params
+                self.client.send(data)
+                msg = self.client.receive()
 
-        packet = json.dumps(message)
+            else:
+                msg = self.client.request(name, params)
 
-        try:
-            resp = self.client.send(packet)
         except IOError:
             if self.instance and not hasattr(self.instance, 'detached'):
                 # If we've launched the binary we've connected to, wait
                 # for it to shut down.
                 returncode = self.instance.runner.wait(timeout=self.DEFAULT_STARTUP_TIMEOUT)
                 raise IOError("process died with returncode %s" % returncode)
             raise
         except socket.timeout:
             self.session = None
             self.window = None
             self.client.close()
             raise errors.TimeoutException("Connection timed out")
 
-        # Process any emulator commands that are sent from a script
-        # while it's executing
-        if isinstance(resp, dict) and any (k in resp for k in ("emulator_cmd", "emulator_shell")):
-            while True:
-                id = resp.get("id")
-                cmd = resp.get("emulator_cmd")
-                shell = resp.get("emulator_shell")
-                if cmd:
-                    resp = self._emulator_cmd(id, cmd)
-                    continue
-                if shell:
-                    resp = self._emulator_shell(id, shell)
-                    continue
-                break
+        if isinstance(msg, transport.Command):
+            if msg.name == "runEmulatorCmd":
+                self.emulator_callback_id = msg.params.get("id")
+                msg = self._emulator_cmd(msg.params["emulator_cmd"])
+            elif msg.name == "runEmulatorShell":
+                self.emulator_callback_id = msg.params.get("id")
+                msg = self._emulator_shell(msg.params["emulator_shell"])
+            else:
+                raise IOError("Unknown command: %s" % msg)
 
-        if "error" in resp:
-            self._handle_error(resp)
+        res, err = msg.result, msg.error
+        if err:
+            self._handle_error(err)
 
         if key is not None:
-            return self._unwrap_response(resp.get(key))
+            return self._unwrap_response(res.get(key))
         else:
-            return self._unwrap_response(resp)
+            return self._unwrap_response(res)
 
     def _unwrap_response(self, value):
         if isinstance(value, dict) and \
         (WEBELEMENT_KEY in value or W3C_WEBELEMENT_KEY in value):
             if value.get(WEBELEMENT_KEY):
                 return HTMLElement(self, value.get(WEBELEMENT_KEY))
             else:
                 return HTMLElement(self, value.get(W3C_WEBELEMENT_KEY))
         elif isinstance(value, list):
             return list(self._unwrap_response(item) for item in value)
         else:
             return value
 
-    def _emulator_cmd(self, id, cmd):
+    def _emulator_cmd(self, cmd):
         if not self.emulator:
             raise errors.MarionetteException(
                 "No emulator in this test to run command against")
         payload = cmd.encode("ascii")
         result = self.emulator._run_telnet(payload)
-        return self._send_emulator_result(id, result)
+        return self._send_emulator_result(result)
 
-    def _emulator_shell(self, id, args):
+    def _emulator_shell(self, args):
         if not isinstance(args, list) or not self.emulator:
             raise errors.MarionetteException(
                 "No emulator in this test to run shell command against")
         buf = StringIO.StringIO()
         self.emulator.dm.shell(args, buf)
         result = str(buf.getvalue()[0:-1]).rstrip().splitlines()
         buf.close()
-        return self._send_emulator_result(id, result)
+        return self._send_emulator_result(result)
 
-    def _send_emulator_result(self, id, result):
-        return self.client.send(json.dumps({"name": "emulatorCmdResult",
-                                            "id": id,
-                                            "result": result}))
+    def _send_emulator_result(self, result):
+        if self.protocol < 3:
+            body = {"name": "emulatorCmdResult",
+                    "id": self.emulator_callback_id,
+                    "result": result}
+            self.client.send(body)
+            return self.client.receive()
+        else:
+            return self.client.respond(result)
 
-    def _handle_error(self, resp):
+    def _handle_error(self, obj):
         if self.protocol == 1:
-            if "error" not in resp or not isinstance(resp["error"], dict):
+            if "error" not in obj or not isinstance(obj["error"], dict):
                 raise errors.MarionetteException(
-                    "Malformed packet, expected key 'error' to be a dict: %s" % resp)
-            error = resp["error"].get("status")
-            message = resp["error"].get("message")
-            stacktrace = resp["error"].get("stacktrace")
+                    "Malformed packet, expected key 'error' to be a dict: %s" % obj)
+            error = obj["error"].get("status")
+            message = obj["error"].get("message")
+            stacktrace = obj["error"].get("stacktrace")
+
         else:
-            error = resp["error"]
-            message = resp["message"]
-            stacktrace = resp["stacktrace"]
+            error = obj["error"]
+            message = obj["message"]
+            stacktrace = obj["stacktrace"]
+
         raise errors.lookup(error)(message, stacktrace=stacktrace)
 
     def _reset_timeouts(self):
         if self.timeout is not None:
             self.timeouts(self.TIMEOUT_SEARCH, self.timeout)
             self.timeouts(self.TIMEOUT_SCRIPT, self.timeout)
             self.timeouts(self.TIMEOUT_PAGE, self.timeout)
         else:
@@ -1127,16 +1131,20 @@ class Marionette(object):
 
         :returns: A dict of the capabilities offered."""
         if self.instance:
             returncode = self.instance.runner.process_handler.proc.returncode
             if returncode is not None:
                 # We're managing a binary which has terminated, so restart it.
                 self.instance.restart()
 
+        self.client = transport.TcpTransport(
+            self.host,
+            self.port,
+            self.socket_timeout)
         self.protocol, _ = self.client.connect()
         self.wait_for_port(timeout=timeout)
 
         body = {"capabilities": desired_capabilities, "sessionId": session_id}
         resp = self._send_message("newSession", body)
 
         self.session_id = resp["sessionId"]
         self.session = resp["value"] if self.protocol == 1 else resp["capabilities"]
@@ -1303,17 +1311,16 @@ class Marionette(object):
 
         :param context: Context, may be one of the class properties
             `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
 
         Usage example::
 
             marionette.set_context(marionette.CONTEXT_CHROME)
         """
-        assert(context == self.CONTEXT_CHROME or context == self.CONTEXT_CONTENT)
         if context not in [self.CONTEXT_CHROME, self.CONTEXT_CONTENT]:
             raise ValueError("Unknown context: %s" % context)
         self._send_message("setContext", {"value": context})
 
     @contextmanager
     def using_context(self, context):
         """Sets the context that Marionette commands are running in using
         a `with` statement. The state of the context on the server is
@@ -1363,16 +1370,22 @@ class Marionette(object):
         if element:
             return HTMLElement(self, element)
         return None
 
     def switch_to_default_content(self):
         """Switch the current context to page's default content."""
         return self.switch_to_frame()
 
+    def switch_to_parent_frame(self):
+        """
+           Switch to the Parent Frame
+        """
+        self._send_message("switchToParentFrame")
+
     def switch_to_frame(self, frame=None, focus=True):
         """Switch the current context to the specified frame. Subsequent
         commands will operate in the context of the specified frame,
         if applicable.
 
         :param frame: A reference to the frame to switch to.  This can
             be an ``HTMLElement``, an integer index, string name, or an
             ID attribute.  If you call ``switch_to_frame`` without an
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -250,16 +250,17 @@ function startListeners() {
   addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayedFn);
   addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssPropertyFn);
   addMessageListenerId("Marionette:getElementRect", getElementRectFn);
   addMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
   addMessageListenerId("Marionette:isElementSelected", isElementSelectedFn);
   addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   addMessageListenerId("Marionette:clearElement", clearElementFn);
   addMessageListenerId("Marionette:switchToFrame", switchToFrame);
+  addMessageListenerId("Marionette:switchToParentFrame", switchToParentFrame);
   addMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn);
   addMessageListenerId("Marionette:deleteSession", deleteSession);
   addMessageListenerId("Marionette:sleepSession", sleepSession);
   addMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
   addMessageListenerId("Marionette:importScript", importScript);
   addMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
   addMessageListenerId("Marionette:setTestName", setTestName);
   addMessageListenerId("Marionette:takeScreenshot", takeScreenshotFn);
@@ -354,16 +355,17 @@ function deleteSession(msg) {
   removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayedFn);
   removeMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssPropertyFn);
   removeMessageListenerId("Marionette:getElementRect", getElementRectFn);
   removeMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
   removeMessageListenerId("Marionette:isElementSelected", isElementSelectedFn);
   removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   removeMessageListenerId("Marionette:clearElement", clearElementFn);
   removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
+  removeMessageListenerId("Marionette:switchToParentFrame", switchToParentFrame);
   removeMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn);
   removeMessageListenerId("Marionette:deleteSession", deleteSession);
   removeMessageListenerId("Marionette:sleepSession", sleepSession);
   removeMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
   removeMessageListenerId("Marionette:importScript", importScript);
   removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
   removeMessageListenerId("Marionette:setTestName", setTestName);
   removeMessageListenerId("Marionette:takeScreenshot", takeScreenshotFn);
@@ -1675,16 +1677,30 @@ function switchToShadowRoot(id) {
   foundShadowRoot = hostEl.shadowRoot;
   if (!foundShadowRoot) {
     throw new NoSuchElementError('Unable to locate shadow root: ' + id);
   }
   curContainer.shadowRoot = foundShadowRoot;
 }
 
 /**
+ * Switch to the parent frame of the current Frame. If the frame is the top most
+ * is the current frame then no action will happen.
+ */
+ function switchToParentFrame(msg) {
+   let command_id = msg.json.command_id;
+   curContainer.frame = curContainer.frame.parent;
+   let parentElement = elementManager.addToKnownElements(curContainer.frame);
+
+   sendSyncMessage("Marionette:switchedToFrame", { frameValue: parentElement });
+
+   sendOk(msg.json.command_id);
+ }
+
+/**
  * Switch to frame given either the server-assigned element id,
  * its index in window.frames, or the iframe's name or id.
  */
 function switchToFrame(msg) {
   let command_id = msg.json.command_id;
   function checkLoad() {
     let errorRegex = /about:.+(error)|(blocked)\?/;
     if (curContainer.frame.document.readyState == "complete") {
--- a/testing/marionette/transport/marionette_transport/__init__.py
+++ b/testing/marionette/transport/marionette_transport/__init__.py
@@ -1,8 +1,7 @@
 # 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/
 
 __version__ = '0.7.1'
 
-
-from transport import MarionetteTransport
+from transport import *
--- a/testing/marionette/transport/marionette_transport/transport.py
+++ b/testing/marionette/transport/marionette_transport/transport.py
@@ -2,136 +2,309 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import datetime
 import errno
 import json
 import socket
 import time
+import types
+
+
+class Message(object):
+    def __init__(self, msgid):
+        self.id = msgid
+
+    def __eq__(self, other):
+        return self.id == other.id
+
+
+class Command(Message):
+    TYPE = 0
+
+    def __init__(self, msgid, name, params):
+        Message.__init__(self, msgid)
+        self.name = name
+        self.params = params
+
+    def __str__(self):
+        return "<Command id=%s, name=%s, params=%s>" % (self.id, self.name, self.params)
+
+    def to_msg(self):
+        msg = [Command.TYPE, self.id, self.name, self.params]
+        return json.dumps(msg)
+
+    @staticmethod
+    def from_msg(payload):
+        data = json.loads(payload)
+        assert data[0] == Command.TYPE
+        cmd = Command(data[1], data[2], data[3])
+        return cmd
 
 
-class MarionetteTransport(object):
-    """The Marionette socket client.  This speaks the same protocol
-    as the remote debugger inside Gecko, in which messages are always
-    preceded by the message length and a colon, e.g.:
+class Response(Message):
+    TYPE = 1
+
+    def __init__(self, msgid, error, result):
+        Message.__init__(self, msgid)
+        self.error = error
+        self.result = result
+
+    def __str__(self):
+        return "<Response id=%s, error=%s, result=%s>" % (self.id, self.error, self.result)
 
-        20:{"command": "test"}
+    def to_msg(self):
+       msg = [Response.TYPE, self.id, self.error, self.result]
+       return json.dumps(msg)
+
+    @staticmethod
+    def from_msg(payload):
+        data = json.loads(payload)
+        assert data[0] == Response.TYPE
+        return Response(data[1], data[2], data[3])
+
+
+class Proto2Command(Command):
+    """Compatibility shim that marshals messages from a protocol level
+    2 and below remote into ``Command`` objects.
     """
 
+    def __init__(self, name, params):
+        Command.__init__(self, None, name, params)
+
+    @staticmethod
+    def from_data(data):
+        if "emulator_cmd" in data:
+            name = "runEmulatorCmd"
+        elif "emulator_shell" in data:
+            name = "runEmulatorShell"
+        else:
+            raise ValueError
+        return Proto2Command(name, data)
+
+
+class Proto2Response(Response):
+    """Compatibility shim that marshals messages from a protocol level
+    2 and below remote into ``Response`` objects.
+    """
+
+    def __init__(self, error, result):
+        Response.__init__(self, None, error, result)
+
+    @staticmethod
+    def from_data(data):
+        err, res = None, None
+        if "error" in data:
+            err = data
+        else:
+            res = data
+        return Proto2Response(err, res)
+
+
+class TcpTransport(object):
+    """Socket client that communciates with Marionette via TCP.
+
+    It speaks the protocol of the remote debugger in Gecko, in which
+    messages are always preceded by the message length and a colon, e.g.:
+
+        7:MESSAGE
+
+    On top of this protocol it uses a Marionette message format, that
+    depending on the protocol level offered by the remote server, varies.
+    Supported protocol levels are 1 and above.
+    """
     max_packet_length = 4096
     connection_lost_msg = "Connection to Marionette server is lost. Check gecko.log (desktop firefox) or logcat (b2g) for errors."
 
     def __init__(self, addr, port, socket_timeout=360.0):
         self.addr = addr
         self.port = port
         self.socket_timeout = socket_timeout
-        self.sock = None
+
         self.protocol = 1
         self.application_type = None
+        self.last_id = 0
+        self.expected_responses = []
+
+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.sock.settimeout(self.socket_timeout)
 
     def _recv_n_bytes(self, n):
-        """Convenience method for receiving exactly n bytes from self.sock
-        (assuming it's open and connected).
-        """
         data = ""
         while len(data) < n:
             chunk = self.sock.recv(n - len(data))
             if chunk == "":
                 break
             data += chunk
         return data
 
-    def receive(self):
-        """Receive the next complete response from the server, and
-        return it as a JSON structure.  Each response from the server
-        is prepended by len(message) + ":".
+    def _unmarshal(self, packet):
+        msg = None
+
+        # protocol 3 and above
+        if self.protocol >= 3:
+            typ = int(packet[1])
+            if typ == Command.TYPE:
+                msg = Command.from_msg(packet)
+            elif typ == Response.TYPE:
+                msg = Response.from_msg(packet)
+
+        # protocol 2 and below
+        else:
+            data = json.loads(packet)
+
+            # emulator callbacks
+            if isinstance(data, dict) and any(k in data for k in ("emulator_cmd", "emulator_shell")):
+                msg = Proto2Command.from_data(data)
+
+            # everything else
+            else:
+                msg = Proto2Response.from_data(data)
+
+        return msg
+
+    def receive(self, unmarshal=True):
+        """Wait for the next complete response from the remote.
+
+        :param unmarshal: Default is to deserialise the packet and
+            return a ``Message`` type.  Setting this to false will return
+            the raw packet.
         """
-        assert(self.sock)
         now = time.time()
-        response = ''
+        data = ""
         bytes_to_recv = 10
+
         while time.time() - now < self.socket_timeout:
             try:
-                data = self.sock.recv(bytes_to_recv)
-                response += data
+                chunk = self.sock.recv(bytes_to_recv)
+                data += chunk
             except socket.timeout:
                 pass
             else:
-                if not data:
+                if not chunk:
                     raise IOError(self.connection_lost_msg)
-            sep = response.find(':')
+
+            sep = data.find(":")
             if sep > -1:
-                length = response[0:sep]
-                remaining = response[sep + 1:]
+                length = data[0:sep]
+                remaining = data[sep + 1:]
+
                 if len(remaining) == int(length):
-                    return json.loads(remaining)
+                    if unmarshal:
+                        msg = self._unmarshal(remaining)
+                        self.last_id = msg.id
+
+                        if isinstance(msg, Response) and self.protocol >= 3:
+                            if msg not in self.expected_responses:
+                                raise Exception("Received unexpected response: %s" % msg)
+                            else:
+                                self.expected_responses.remove(msg)
+
+                        return msg
+                    else:
+                        return remaining
+
                 bytes_to_recv = int(length) - len(remaining)
-        raise socket.timeout('connection timed out after %d s' % self.socket_timeout)
+
+        raise socket.timeout("connection timed out after %ds" % self.socket_timeout)
 
     def connect(self):
         """Connect to the server and process the hello message we expect
         to receive in response.
 
-        Return a tuple of the protocol level and the application type.
+        Returns a tuple of the protocol level and the application type.
         """
-        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        self.sock.settimeout(self.socket_timeout)
         try:
             self.sock.connect((self.addr, self.port))
         except:
             # Unset self.sock so that the next attempt to send will cause
             # another connection attempt.
             self.sock = None
             raise
+
         self.sock.settimeout(2.0)
 
-        hello = self.receive()
+        # first packet is always a JSON Object
+        # which we can use to tell which protocol level we are at
+        raw = self.receive(unmarshal=False)
+        hello = json.loads(raw)
         self.protocol = hello.get("marionetteProtocol", 1)
         self.application_type = hello.get("applicationType")
 
         return (self.protocol, self.application_type)
 
-    def send(self, data):
-        """Send a message on the socket, prepending it with len(msg) + ":"."""
+    def send(self, obj):
+        """Send message to the remote server.  Allowed input is a
+        ``Message`` instance or a JSON serialisable object.
+        """
         if not self.sock:
             self.connect()
-        data = "%s:%s" % (len(data), data)
 
-        for packet in [data[i:i + self.max_packet_length] for i in
-                       range(0, len(data), self.max_packet_length)]:
+        if isinstance(obj, Message):
+            data = obj.to_msg()
+            self.expected_responses.append(obj)
+        else:
+            data = json.dumps(obj)
+        payload = "%s:%s" % (len(data), data)
+
+        for packet in [payload[i:i + self.max_packet_length] for i in
+                       range(0, len(payload), self.max_packet_length)]:
             try:
                 self.sock.send(packet)
             except IOError as e:
                 if e.errno == errno.EPIPE:
                     raise IOError("%s: %s" % (str(e), self.connection_lost_msg))
                 else:
                     raise e
 
+    def respond(self, obj):
+        """Send a response to a command.  This can be an arbitrary JSON
+        serialisable object or an ``Exception``.
+        """
+        res, err = None, None
+        if isinstance(obj, Exception):
+            err = obj
+        else:
+            res = obj
+        msg = Response(self.last_id, err, res)
+        self.send(msg)
+        return self.receive()
+
+    def request(self, name, params):
+        """Sends a message to the remote server and waits for a response
+        to come back.
+        """
+        self.last_id = self.last_id + 1
+        cmd = Command(self.last_id, name, params)
+        self.send(cmd)
         return self.receive()
 
     def close(self):
         """Close the socket."""
         if self.sock:
             self.sock.close()
+
+    def __del__(self):
+        self.close()
         self.sock = None
 
-    @staticmethod
-    def wait_for_port(host, port, timeout=60):
-        """ Wait for the specified Marionette host/port to be available."""
-        starttime = datetime.datetime.now()
-        poll_interval = 0.1
-        while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
-            sock = None
-            try:
-                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-                sock.connect((host, port))
-                data = sock.recv(16)
+
+def wait_for_port(host, port, timeout=60):
+    """ Wait for the specified host/port to be available."""
+    starttime = datetime.datetime.now()
+    poll_interval = 0.1
+    while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
+        sock = None
+        try:
+            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            sock.connect((host, port))
+            data = sock.recv(16)
+            sock.close()
+            if ':' in data:
+                return True
+        except socket.error:
+            pass
+        finally:
+            if sock:
                 sock.close()
-                if ':' in data:
-                    return True
-            except socket.error:
-                pass
-            finally:
-                if sock:
-                    sock.close()
-            time.sleep(poll_interval)
-        return False
+        time.sleep(poll_interval)
+    return False
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/send-content-type-string.htm.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[send-content-type-string.htm]
-  type: testharness
-  [XMLHttpRequest: send() - Content-Type 1]
-    expected: FAIL
-
-  [XMLHttpRequest: send() - Content-Type 2]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/send-entity-body-document.htm.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[send-entity-body-document.htm]
-  type: testharness
-  [XMLHttpRequest: send() - Document]
-    expected: FAIL
-
-  [XMLHttpRequest: send() - Document 1]
-    expected: FAIL
-
-  [XMLHttpRequest: send() - Document 2]
-    expected: FAIL
-
-  [XMLHttpRequest: send() - Document 3]
-    expected: FAIL
-
-  [XMLHttpRequest: send() - Document 4]
-    expected: FAIL
-
--- a/testing/web-platform/mozilla/meta/MANIFEST.json
+++ b/testing/web-platform/mozilla/meta/MANIFEST.json
@@ -464,22 +464,16 @@
           }
         ],
         "service-workers/service-worker/state.https.html": [
           {
             "path": "service-workers/service-worker/state.https.html",
             "url": "/_mozilla/service-workers/service-worker/state.https.html"
           }
         ],
-        "service-workers/service-worker/sync-xhr-doesnt-deadlock.https.html": [
-          {
-            "path": "service-workers/service-worker/sync-xhr-doesnt-deadlock.https.html",
-            "url": "/_mozilla/service-workers/service-worker/sync-xhr-doesnt-deadlock.https.html"
-          }
-        ],
         "service-workers/service-worker/synced-state.https.html": [
           {
             "path": "service-workers/service-worker/synced-state.https.html",
             "url": "/_mozilla/service-workers/service-worker/synced-state.https.html"
           }
         ],
         "service-workers/service-worker/uncontrolled-page.https.html": [
           {
@@ -523,18 +517,24 @@
             "url": "/_mozilla/service-workers/service-worker/waiting.https.html"
           }
         ],
         "service-workers/service-worker/worker-interception.https.html": [
           {
             "path": "service-workers/service-worker/worker-interception.https.html",
             "url": "/_mozilla/service-workers/service-worker/worker-interception.https.html"
           }
+        ],
+        "service-workers/service-worker/xhr.https.html": [
+          {
+            "path": "service-workers/service-worker/xhr.https.html",
+            "url": "/_mozilla/service-workers/service-worker/xhr.https.html"
+          }
         ]
       }
     },
     "reftest_nodes": {}
   },
   "reftest_nodes": {},
   "rev": null,
   "url_base": "/_mozilla/",
   "version": 2
-}
\ No newline at end of file
+}
deleted file mode 100644
--- a/testing/web-platform/mozilla/meta/service-workers/service-worker/sync-xhr-doesnt-deadlock.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[sync-xhr-doesnt-deadlock.https.html]
-  type: testharness
-  [Verify SyncXHR does not deadlock]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/sync-xhr-doesnt-deadlock-iframe.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: SyncXHR doesn't deadlock iframe</title>
-<script>
-function performSyncXHR() {
-  var url = 'sync-xhr-doesnt-deadlock.data?bustcache=' + Date.now();
-  var syncXhr = new XMLHttpRequest();
-  syncXhr.open("GET", url, false);
-  syncXhr.send();
-  if (syncXhr.responseText != 'hello')
-    throw 'FAIL';
-}
-</script>
deleted file mode 100644
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/sync-xhr-doesnt-deadlock.data
+++ /dev/null
@@ -1,1 +0,0 @@
-hello
\ No newline at end of file
deleted file mode 100644
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/sync-xhr-doesnt-deadlock.js
+++ /dev/null
@@ -1,5 +0,0 @@
-self.onfetch = function(event) {
-  if (event.request.url.indexOf('sync-xhr-doesnt-deadlock.data') == -1)
-    return;
-  event.respondWith(fetch('404resource?bustcache=' + Date.now()));
-};
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/xhr.js
@@ -0,0 +1,6 @@
+self.addEventListener('activate', function(event) {
+    event.waitUntil(clients.claim());
+  });
+self.addEventListener('message', function(event) {
+    event.data.port.postMessage({xhr: !!("XMLHttpRequest" in self)});
+  });
deleted file mode 100644
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/sync-xhr-doesnt-deadlock.https.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: SyncXHR doesn't deadlock</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.sub.js"></script>
-<script>
-
-async_test(function(t) {
-    var url = 'resources/sync-xhr-doesnt-deadlock.js';
-    var scope = 'resources/sync-xhr-doesnt-deadlock-iframe.html';
-
-    service_worker_unregister_and_register(t, url, scope)
-      .then(function(registration) {
-          return wait_for_state(t, registration.installing, 'activated');
-        })
-      .then(function() { return with_iframe(scope); })
-      .then(function(frame) {
-          frame.contentWindow.performSyncXHR();
-          service_worker_unregister_and_done(t, scope);
-        })
-      .catch(unreached_rejection(t));
-  }, 'Verify SyncXHR does not deadlock');
-</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/xhr.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Service Worker: XHR doesn't exist</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<script>
+
+async_test(function(t) {
+    var path = new URL(".", window.location).pathname
+    var url = 'resources/xhr.js';
+    var scope = 'resources/blank.html?xhr';
+    var host_info = get_host_info();
+    var frameURL = host_info['HTTPS_ORIGIN'] + path + scope;
+
+    service_worker_unregister_and_register(t, url, scope)
+      .then(function(registration) {
+          return wait_for_state(t, registration.installing, 'activated');
+        })
+      .then(function() { return with_iframe(frameURL); })
+      .then(function(frame) {
+        return new Promise(function(resolve, reject) {
+          function onMessage(e) {
+            assert_false(e.data.xhr);
+            service_worker_unregister_and_done(t, scope);
+          }
+          var channel = new MessageChannel();
+          channel.port1.onmessage = t.step_func(onMessage);
+          frame.contentWindow.navigator.serviceWorker.controller.postMessage({port: channel.port2}, [channel.port2]);
+        })
+      })
+      .catch(unreached_rejection(t));
+  }, 'Verify XHR does not exist');
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/XMLHttpRequest/resources/empty-div-utf8-html.py
@@ -0,0 +1,5 @@
+def main(request, response):
+    headers = [("Content-type", "text/html;charset=utf-8")]
+    content = "<!DOCTYPE html><div></div>"
+
+    return headers, content
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/XMLHttpRequest/resources/img-utf8-html.py
@@ -0,0 +1,5 @@
+def main(request, response):
+    headers = [("Content-type", "text/html;charset=utf-8")]
+    content = "<img>foo"
+
+    return headers, content
--- a/testing/web-platform/tests/XMLHttpRequest/send-content-type-string.htm
+++ b/testing/web-platform/tests/XMLHttpRequest/send-content-type-string.htm
@@ -15,12 +15,12 @@
           client.open("POST", "resources/content.py", false)
           client.send(data)
           assert_equals(client.getResponseHeader("x-request-content-type"), expected_type)
         })
       }
       request("TEST", "text/plain;charset=UTF-8")
       function init(fr) { request(fr.contentDocument, fr.getAttribute("data-t")) }
     </script>
-    <iframe src='data:text/xml;charset=windows-1252,<%FF/>' onload="init(this)" data-t="application/xml;charset=windows-1252"></iframe>
-    <iframe src='data:text/html;charset=windows-1252,%FF' onload="init(this)" data-t="text/html;charset=windows-1252"></iframe>
+    <iframe src='data:text/xml;charset=windows-1252,<%FF/>' onload="init(this)" data-t="application/xml;charset=UTF-8"></iframe>
+    <iframe src='data:text/html;charset=windows-1252,%FF' onload="init(this)" data-t="text/html;charset=UTF-8"></iframe>
   </body>
 </html>
--- a/testing/web-platform/tests/XMLHttpRequest/send-entity-body-document.htm
+++ b/testing/web-platform/tests/XMLHttpRequest/send-entity-body-document.htm
@@ -11,45 +11,51 @@
   <body>
     <div id="log"></div>
     <script>
      var expectations = [
       { contentType: 'application/xml;charset=UTF-8', responseText : '<\u00FF\/>' },
       { contentType: 'text/html;charset=UTF-8', responseText : '<body>\uFFFD<\/body>' }, /*invalid character code in document turns into FFFD*/
       { contentType: 'text/html;charset=UTF-8', responseText : '<body>\u30C6\u30b9\u30c8<\/body>' } /* correctly serialized Shift-JIS */,
       { contentType: 'text/html;charset=UTF-8', responseText: 'top' }, /* There's some markup included, but it's not really relevant for this test suite, so we do an indexOf() test */
-      { contentType: 'text/html;charset=UTF-8' }
+      { contentType: 'text/html;charset=UTF-8' },
+      { contentType: 'text/html;charset=UTF-8', responseText: '<img>foo' },
+      { contentType: 'text/html;charset=UTF-8', responseText: '<!DOCTYPE html><html><head></head><body><div></div></body></html>' }
      ]
 
 
-      function request(input, isHTML, title) {
+      function request(input, number, title) {
         test(function() {
           var client = new XMLHttpRequest()
           client.open("POST", "resources/content.py?response_charset_label=UTF-8", false)
           client.send(input)
           var exp = expectations[number]
           assert_equals(client.getResponseHeader('X-Request-Content-Type'), exp.contentType, 'document should be serialized and sent as '+exp.contentType+' (TEST#'+number+')')
           // The indexOf() assertation will overlook some stuff, i.e. XML prologues that shouldn't be there (looking at you, Presto).
           // However, arguably these things have little to do with the XHR functionality we're testing.
           if(exp.responseText){ // This test does not want to assert anything about what markup a standalone IMG should be wrapped in. Hence the GIF test lacks a responseText expectation.
-            assert_true(client.responseText.indexOf(exp.responseText) != -1);
+            assert_true(client.responseText.indexOf(exp.responseText) != -1,
+                        JSON.stringify(exp.responseText) + " not in " +
+                        JSON.stringify(client.responseText));
           }
           assert_equals(client.responseXML, null)
         }, title)
       }
-      function init(fr, number, title) { request(fr.contentDocument, number) }
+      function init(fr, number, title) { request(fr.contentDocument, number, title) }
     </script>
     <!--
         This test also tests how documents in various encodings are serialized.
         The below IFRAMEs contain:
           * one XML document parsed from a windows-1252 source - content is <ÿ/>
           * one HTML-document parsed from an invalid UTF-8 source, will contain a basic HTML DOM
              with a U+FFFD replacement character for the invalid char
           * one HTML document parsed from a valid Shift-JIS source
      -->
     <iframe src='resources/win-1252-xml.py' onload="init(this, 0, 'XML document, windows-1252')"></iframe>
     <iframe src='resources/invalid-utf8-html.py' onload="init(this, 1, 'HTML document, invalid UTF-8')"></iframe>
     <iframe src='resources/shift-jis-html.py' onload="init(this, 2, 'HTML document, shift-jis')"></iframe>
     <iframe src='folder.txt' onload="init(this, 3, 'plain text file')"></iframe>
     <iframe src='resources/image.gif' onload="init(this, 4, 'image file')"></iframe>
+    <iframe src='resources/img-utf8-html.py' onload="init(this, 5, 'img tag')"></iframe>
+    <iframe src='resources/empty-div-utf8-html.py' onload="init(this, 6, 'empty div')"></iframe>
 
   </body>
 </html>
--- a/toolkit/components/alerts/resources/content/alert.js
+++ b/toolkit/components/alerts/resources/content/alert.js
@@ -39,62 +39,70 @@ function prefillAlertInfo() {
     case 11: {
       if (window.arguments[10]) {
         let alertBox = document.getElementById("alertBox");
         alertBox.setAttribute("hasOrigin", true);
 
         let hostPort = window.arguments[10];
         const ALERT_BUNDLE = Services.strings.createBundle(
           "chrome://alerts/locale/alert.properties");
+        const BRAND_BUNDLE = Services.strings.createBundle(
+          "chrome://branding/locale/brand.properties");
+        const BRAND_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");
         let label = document.getElementById("alertSourceLabel");
         label.setAttribute("value",
           ALERT_BUNDLE.formatStringFromName("source.label",
                                             [hostPort],
                                             1));
+        let doNotDisturbMenuItem = document.getElementById("doNotDisturbMenuItem");
+        doNotDisturbMenuItem.setAttribute("label",
+          ALERT_BUNDLE.formatStringFromName("doNotDisturb.label",
+                                            [BRAND_NAME],
+                                            1));
         let disableForOrigin = document.getElementById("disableForOriginMenuItem");
         disableForOrigin.setAttribute("label",
           ALERT_BUNDLE.formatStringFromName("webActions.disableForOrigin.label",
                                             [hostPort],
                                             1));
       }
     }
     case 10:
       gAlertListener = window.arguments[9];
     case 9:
       gReplacedWindow = window.arguments[8];
     case 8:
       if (window.arguments[7]) {
-        document.getElementById('alertTitleLabel').setAttribute('lang', window.arguments[7]);
-        document.getElementById('alertTextLabel').setAttribute('lang', window.arguments[7]);
+        document.getElementById("alertTitleLabel").setAttribute("lang", window.arguments[7]);
+        document.getElementById("alertTextLabel").setAttribute("lang", window.arguments[7]);
       }
     case 7:
       if (window.arguments[6]) {
-        document.getElementById('alertNotification').style.direction = window.arguments[6];
+        document.getElementById("alertNotification").style.direction = window.arguments[6];
       }
     case 6:
       gOrigin = window.arguments[5];
     case 5:
       gAlertCookie = window.arguments[4];
     case 4:
       gAlertTextClickable = window.arguments[3];
       if (gAlertTextClickable) {
-        document.getElementById('alertNotification').setAttribute('clickable', true);
-        document.getElementById('alertTextLabel').setAttribute('clickable', true);
+        document.getElementById("alertNotification").setAttribute("clickable", true);
+        document.getElementById("alertTextLabel").setAttribute("clickable", true);
       }
     case 3:
       if (window.arguments[2]) {
-        document.getElementById('alertBox').setAttribute('hasBodyText', true);
-        document.getElementById('alertTextLabel').textContent = window.arguments[2];
+        document.getElementById("alertBox").setAttribute("hasBodyText", true);
+        document.getElementById("alertTextLabel").textContent = window.arguments[2];
       }
     case 2:
-      document.getElementById('alertTitleLabel').textContent = window.arguments[1];
+      document.getElementById("alertTitleLabel").textContent = window.arguments[1];
     case 1:
       if (window.arguments[0]) {
-        document.getElementById('alertBox').setAttribute('hasImage', true);
-        document.getElementById('alertImage').setAttribute('src', window.arguments[0]);
+        document.getElementById("alertBox").setAttribute("hasImage", true);
+        document.getElementById("alertImage").setAttribute("src", window.arguments[0]);
       }
     case 0:
       break;
   }
 }
 
 function onAlertLoad() {
   const ALERT_DURATION_IMMEDIATE = 12000;
@@ -137,17 +145,17 @@ function onAlertLoad() {
   }
 }
 
 function moveWindowToReplace(aReplacedAlert) {
   let heightDelta = window.outerHeight - aReplacedAlert.outerHeight;
 
   // Move windows that come after the replaced alert if the height is different.
   if (heightDelta != 0) {
-    let windows = Services.wm.getEnumerator('alert:alert');
+    let windows = Services.wm.getEnumerator("alert:alert");
     while (windows.hasMoreElements()) {
       let alertWindow = windows.getNext();
       // boolean to determine if the alert window is after the replaced alert.
       let alertIsAfter = gOrigin & NS_ALERT_TOP ?
                          alertWindow.screenY > aReplacedAlert.screenY :
                          aReplacedAlert.screenY > alertWindow.screenY;
       if (alertIsAfter) {
         // The new Y position of the window.
@@ -167,17 +175,17 @@ function moveWindowToReplace(aReplacedAl
 function moveWindowToEnd() {
   // Determine position
   let x = gOrigin & NS_ALERT_LEFT ? screen.availLeft :
           screen.availLeft + screen.availWidth - window.outerWidth;
   let y = gOrigin & NS_ALERT_TOP ? screen.availTop :
           screen.availTop + screen.availHeight - window.outerHeight;
 
   // Position the window at the end of all alerts.
-  let windows = Services.wm.getEnumerator('alert:alert');
+  let windows = Services.wm.getEnumerator("alert:alert");
   while (windows.hasMoreElements()) {
     let alertWindow = windows.getNext();
     if (alertWindow != window) {
       if (gOrigin & NS_ALERT_TOP) {
         y = Math.max(y, alertWindow.screenY + alertWindow.outerHeight);
       } else {
         y = Math.min(y, alertWindow.screenY - window.outerHeight);
       }
@@ -190,17 +198,17 @@ function moveWindowToEnd() {
 
   window.moveTo(x, y);
 }
 
 function onAlertBeforeUnload() {
   if (!gIsReplaced) {
     // Move other alert windows to fill the gap left by closing alert.
     let heightDelta = window.outerHeight + WINDOW_MARGIN;
-    let windows = Services.wm.getEnumerator('alert:alert');
+    let windows = Services.wm.getEnumerator("alert:alert");
     while (windows.hasMoreElements()) {
       let alertWindow = windows.getNext();
       if (alertWindow != window) {
         if (gOrigin & NS_ALERT_TOP) {
           if (alertWindow.screenY > window.screenY) {
             alertWindow.moveTo(alertWindow.screenX, alertWindow.screenY - heightDelta);
           }
         } else {
@@ -226,16 +234,24 @@ function onAlertClick() {
   if (alertBox.getAttribute("animate") == "true") {
     // Closed when the animation ends.
     alertBox.setAttribute("clicked", "true");
   } else {
     window.close();
   }
 }
 
+function doNotDisturb() {
+  const alertService = Cc["@mozilla.org/alerts-service;1"]
+                         .getService(Ci.nsIAlertsService)
+                         .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+  alertService.manualDoNotDisturb = true;
+  onAlertClose();
+}
+
 function disableForOrigin() {
   gAlertListener.observe(null, "alertdisablecallback", gAlertCookie);
   onAlertClose();
 }
 
 function onAlertClose() {
   let alertBox = document.getElementById("alertBox");
   if (alertBox.getAttribute("animate") == "true") {
--- a/toolkit/components/alerts/resources/content/alert.xul
+++ b/toolkit/components/alerts/resources/content/alert.xul
@@ -41,16 +41,19 @@
       <vbox id="alertTextBox" class="alertTextBox">
         <label id="alertTextLabel" class="alertText plain"/>
         <spacer flex="1"/>
         <box id="alertFooter">
           <label id="alertSourceLabel" class="alertSource plain"/>
           <button type="menu" id="alertSettings" tooltiptext="&settings.label;"
                   onclick="event.stopPropagation();">
             <menupopup position="after_end">
+              <menuitem id="doNotDisturbMenuItem"
+                        oncommand="doNotDisturb();"/>
+              <menuseparator/>
               <menuitem id="disableForOriginMenuItem"
                         oncommand="disableForOrigin();"/>
             </menupopup>
           </button>
         </box>
       </vbox>
     </box>
   </vbox>
--- a/toolkit/locales/en-US/chrome/alerts/alert.properties
+++ b/toolkit/locales/en-US/chrome/alerts/alert.properties
@@ -12,8 +12,12 @@ actionButton.label = …
 # with the hostname origin of the notification.
 webActions.disableForOrigin.label = Disable notifications from %S
 
 # LOCALIZATION NOTE(source.label): Used to show the URL of the site that
 # sent the notification (e.g., "via mozilla.org"). "%1$S" is the source host
 # and port.
 source.label=via %1$S
 webActions.settings.label = Notification settings
+
+# LOCALIZATION NOTE(doNotDisturb.label): %S is replaced with the
+# brandShortName of the application.
+doNotDisturb.label = Do not disturb me until I restart %S
--- a/toolkit/modules/SelectContentHelper.jsm
+++ b/toolkit/modules/SelectContentHelper.jsm
@@ -105,16 +105,17 @@ function buildOptionListForChildren(node
       if (textContent == null) {
         textContent = "";
       }
 
       let info = {
         tagName: tagName,
         textContent: textContent,
         disabled: child.disabled,
+        display: child.style.display,
         // We need to do this for every option element as each one can have
         // an individual style set for direction
         textDirection: getComputedDirection(child),
         tooltip: child.title,
         // XXX this uses a highlight color when this is the selected element.
         // We need to suppress such highlighting in the content process to get
         // the option's correct unhighlighted color here.
         // We also need to detect default color vs. custom so that a standard
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -81,16 +81,17 @@ function populateChildren(menulist, opti
 
   for (let option of options) {
     let isOptGroup = (option.tagName == 'OPTGROUP');
     let item = element.ownerDocument.createElement(isOptGroup ? "menucaption" : "menuitem");
 
     item.setAttribute("label", option.textContent);
     item.style.direction = option.textDirection;
     item.style.fontSize = adjustedTextSize;
+    item.style.display = option.display;
     item.setAttribute("tooltiptext", option.tooltip);
 
     element.appendChild(item);
 
     // A disabled optgroup disables all of its child options.
     let isDisabled = isGroupDisabled || option.disabled;
     if (isDisabled) {
       item.setAttribute("disabled", "true");
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -345,16 +345,18 @@ XRE_InitChildProcess(int aArgc,
     if (_fileno(stdin) == -1 || _get_osfhandle(fileno(stdin)) == -1)
         freopen("CONIN$", "r", stdin);
   }
 #endif
 
   // NB: This must be called before profiler_init
   NS_LogInit();
 
+  mozilla::LogModule::Init();
+
   char aLocal;
   profiler_init(&aLocal);
 
   PROFILER_LABEL("Startup", "XRE_InitChildProcess",
     js::ProfileEntry::Category::OTHER);
 
   // Complete 'task_t' exchange for Mac OS X. This structure has the same size
   // regardless of architecture so we don't have any cross-arch issues here.
new file mode 100644
--- /dev/null
+++ b/xpcom/base/Logging.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+
+#include <algorithm>
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPtr.h"
+#include "nsClassHashtable.h"
+
+// NB: Initial amount determined by auditing the codebase for the total amount
+//     of unique module names and padding up to the next power of 2.
+const uint32_t kInitialModuleCount = 256;
+
+namespace mozilla {
+/**
+ * Safely converts an integer into a valid LogLevel.
+ */
+LogLevel
+Clamp(int32_t aLevel)
+{
+  aLevel = std::min(aLevel, static_cast<int32_t>(LogLevel::Verbose));
+  aLevel = std::max(aLevel, static_cast<int32_t>(LogLevel::Disabled));
+  return static_cast<LogLevel>(aLevel);
+}
+
+class LogModuleManager
+{
+public:
+  LogModuleManager()
+    : mModulesLock("logmodules")
+    , mModules(kInitialModuleCount)
+  {
+  }
+
+  ~LogModuleManager()
+  {
+    // NB: mModules owns all of the log modules, they will get destroyed by
+    //     its destructor.
+  }
+
+  LogModule* CreateOrGetModule(const char* aName)
+  {
+    OffTheBooksMutexAutoLock guard(mModulesLock);
+    LogModule* module = nullptr;
+    if (!mModules.Get(aName, &module)) {
+      // Create the PRLogModule, this will read any env vars that set the log
+      // level ahead of time. The module is held internally by NSPR, so it's
+      // okay to drop the pointer when leaving this scope.
+      PRLogModuleInfo* prModule = PR_NewLogModule(aName);
+
+      // NSPR does not impose a restriction on the values that log levels can
+      // be. LogModule uses the LogLevel enum class so we must clamp the value
+      // to a max of Verbose.
+      LogLevel logLevel = Clamp(prModule->level);
+      module = new LogModule(logLevel);
+      mModules.Put(aName, module);
+    }
+
+    return module;
+  }
+
+private:
+  OffTheBooksMutex mModulesLock;
+  nsClassHashtable<nsCharPtrHashKey, LogModule> mModules;
+};
+
+StaticAutoPtr<LogModuleManager> sLogModuleManager;
+
+LogModule*
+LogModule::Get(const char* aName)
+{
+  // This is just a pass through to the LogModuleManager so
+  // that the LogModuleManager implementation can be kept internal.
+  MOZ_ASSERT(sLogModuleManager != nullptr);
+  return sLogModuleManager->CreateOrGetModule(aName);
+}
+
+void
+LogModule::Init()
+{
+  // NB: This method is not threadsafe; it is expected to be called very early
+  //     in startup prior to any other threads being run.
+  if (sLogModuleManager) {
+    // Already initialized.
+    return;
+  }
+
+  // NB: We intentionally do not register for ClearOnShutdown as that happens
+  //     before all logging is complete. And, yes, that means we leak, but
+  //     we're doing that intentionally.
+  sLogModuleManager = new LogModuleManager();
+}
+
+} // namespace mozilla
rename from xpcom/glue/Logging.h
rename to xpcom/base/Logging.h
--- a/xpcom/glue/Logging.h
+++ b/xpcom/base/Logging.h
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_logging_h
 #define mozilla_logging_h
 
 #include "prlog.h"
 
 #include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Likely.h"
 
 // This file is a placeholder for a replacement to the NSPR logging framework
 // that is defined in prlog.h. Currently it is just a pass through, but as
 // work progresses more functionality will be swapped out in favor of
 // mozilla logging implementations.
 
 namespace mozilla {
 
@@ -38,23 +40,109 @@ enum class LogLevel {
   Disabled = 0,
   Error,
   Warning,
   Info,
   Debug,
   Verbose,
 };
 
+class LogModule
+{
+public:
+  /**
+   * Retrieves the module with the given name. If it does not already exist
+   * it will be created.
+   *
+   * @param aName The name of the module.
+   * @return A log module for the given name. This may be shared.
+   */
+#if !defined(MOZILLA_XPCOMRT_API)
+  static LogModule* Get(const char* aName);
+#else
+  // For simplicity, libxpcomrt doesn't supoort logging.
+  static LogModule* Get(const char* aName) { return nullptr; }
+#endif
+
+  static void Init();
+
+  /**
+   * Indicates whether or not the given log level is enabled.
+   */
+  bool ShouldLog(LogLevel aLevel) const { return mLevel >= aLevel; }
+
+  /**
+   * Retrieves the log module's current level.
+   */
+  LogLevel Level() const { return mLevel; }
+
+private:
+  friend class LogModuleManager;
+
+  explicit LogModule(LogLevel aLevel) : mLevel(aLevel) {}
+
+  LogModule(LogModule&) = delete;
+  LogModule& operator=(const LogModule&) = delete;
+
+  Atomic<LogLevel, Relaxed> mLevel;
+};
+
+/**
+ * Helper class that lazy loads the given log module. This is safe to use for
+ * declaring static references to log modules and can be used as a replacement
+ * for accessing a LogModule directly.
+ *
+ * Example usage:
+ *   static LazyLogModule sLayoutLog("layout");
+ *
+ *   void Foo() {
+ *     MOZ_LOG(sLayoutLog, LogLevel::Verbose, ("Entering foo"));
+ *   }
+ */
+class LazyLogModule final
+{
+public:
+  explicit MOZ_CONSTEXPR LazyLogModule(const char* aLogName)
+    : mLogName(aLogName)
+    , mLog(nullptr)
+  {
+  }
+
+  operator LogModule*()
+  {
+    // NB: The use of an atomic makes the reading and assignment of mLog
+    //     thread-safe. There is a small chance that mLog will be set more
+    //     than once, but that's okay as it will be set to the same LogModule
+    //     instance each time. Also note LogModule::Get is thread-safe.
+    LogModule* tmp = mLog;
+    if (MOZ_UNLIKELY(!tmp)) {
+      tmp = LogModule::Get(mLogName);
+      mLog = tmp;
+    }
+
+    return tmp;
+  }
+
+private:
+  const char* const mLogName;
+  Atomic<LogModule*, ReleaseAcquire> mLog;
+};
+
 namespace detail {
 
 inline bool log_test(const PRLogModuleInfo* module, LogLevel level) {
   MOZ_ASSERT(level != LogLevel::Disabled);
   return module && module->level >= static_cast<int>(level);
 }
 
+inline bool log_test(const LogModule* module, LogLevel level) {
+  MOZ_ASSERT(level != LogLevel::Disabled);
+  return module && module->ShouldLog(level);
+}
+
 } // namespace detail
 
 } // namespace mozilla
 
 #define MOZ_LOG_TEST(_module,_level) mozilla::detail::log_test(_module, _level)
 
 #define MOZ_LOG(_module,_level,_args)     \
   PR_BEGIN_MACRO             \
--- a/xpcom/base/moz.build
+++ b/xpcom/base/moz.build
@@ -78,16 +78,17 @@ EXPORTS.mozilla += [
     'CycleCollectedJSRuntime.h',
     'Debug.h',
     'DebuggerOnGCRunnable.h',
     'DeferredFinalize.h',
     'ErrorNames.h',
     'HoldDropJSObjects.h',
     'JSObjectHolder.h',
     'LinuxUtils.h',
+    'Logging.h',
     'nsMemoryInfoDumper.h',
     'OwningNonNull.h',
     'StaticMutex.h',
     'StaticPtr.h',
     'SystemMemoryReporter.h',
 ]
 
 # nsDebugImpl isn't unified because we disable PGO so that NS_ABORT_OOM isn't
@@ -102,16 +103,17 @@ UNIFIED_SOURCES += [
     'ClearOnShutdown.cpp',
     'CycleCollectedJSRuntime.cpp',
     'Debug.cpp',
     'DebuggerOnGCRunnable.cpp',
     'DeferredFinalize.cpp',
     'ErrorNames.cpp',
     'HoldDropJSObjects.cpp',
     'JSObjectHolder.cpp',
+    'Logging.cpp',
     'nsConsoleMessage.cpp',
     'nsConsoleService.cpp',
     'nsCycleCollector.cpp',
     'nsDumpUtils.cpp',
     'nsErrorService.cpp',
     'nsGZFileWriter.cpp',
     'nsInterfaceRequestorAgg.cpp',
     'nsMemoryImpl.cpp',
--- a/xpcom/base/nsDebugImpl.cpp
+++ b/xpcom/base/nsDebugImpl.cpp
@@ -12,17 +12,16 @@
 #include "nsDebugImpl.h"
 #include "nsDebug.h"
 #ifdef MOZ_CRASHREPORTER
 # include "nsExceptionHandler.h"
 #endif
 #include "nsString.h"
 #include "nsXULAppAPI.h"
 #include "prprf.h"
-#include "mozilla/Logging.h"
 #include "nsError.h"
 #include "prerror.h"
 #include "prerr.h"
 #include "prenv.h"
 
 #ifdef ANDROID
 #include <android/log.h>
 #endif
@@ -212,26 +211,16 @@ nsDebugImpl::SetMultiprocessMode(const c
   sMultiprocessDescription = aDesc;
 }
 
 /**
  * Implementation of the nsDebug methods. Note that this code is
  * always compiled in, in case some other module that uses it is
  * compiled with debugging even if this library is not.
  */
-static PRLogModuleInfo* gDebugLog;
-
-static void
-InitLog()
-{
-  if (0 == gDebugLog) {
-    gDebugLog = PR_NewLogModule("nsDebug");
-  }
-}
-
 enum nsAssertBehavior
 {
   NS_ASSERT_UNINITIALIZED,
   NS_ASSERT_WARN,
   NS_ASSERT_SUSPEND,
   NS_ASSERT_STACK,
   NS_ASSERT_TRAP,
   NS_ASSERT_ABORT,
@@ -312,36 +301,30 @@ StuffFixedBuffer(void* aClosure, const c
 
   return aLen;
 }
 
 EXPORT_XPCOM_API(void)
 NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr,
               const char* aFile, int32_t aLine)
 {
-  InitLog();
-
   FixedBuffer buf;
-  mozilla::LogLevel ll = LogLevel::Warning;
   const char* sevString = "WARNING";
 
   switch (aSeverity) {
     case NS_DEBUG_ASSERTION:
       sevString = "###!!! ASSERTION";
-      ll = LogLevel::Error;
       break;
 
     case NS_DEBUG_BREAK:
       sevString = "###!!! BREAK";
-      ll = LogLevel::Error;
       break;
 
     case NS_DEBUG_ABORT:
       sevString = "###!!! ABORT";
-      ll = LogLevel::Error;
       break;
 
     default:
       aSeverity = NS_DEBUG_WARNING;
   };
 
 #  define PrintToBuffer(...) PR_sxprintf(StuffFixedBuffer, &buf, __VA_ARGS__)
 
@@ -366,23 +349,19 @@ NS_DebugBreak(uint32_t aSeverity, const 
     PrintToBuffer("file %s, ", aFile);
   }
   if (aLine != -1) {
     PrintToBuffer("line %d", aLine);
   }
 
 #  undef PrintToBuffer
 
-  // Write out the message to the debug log
-  MOZ_LOG(gDebugLog, ll, ("%s", buf.buffer));
-  PR_LogFlush();
-
   // errors on platforms without a debugdlg ring a bell on stderr
 #if !defined(XP_WIN)
-  if (ll != LogLevel::Warning) {
+  if (aSeverity != NS_DEBUG_WARNING) {
     fprintf(stderr, "\07");
   }
 #endif
 
 #ifdef ANDROID
   __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", buf.buffer);
 #endif
 
--- a/xpcom/build/XPCOMInit.cpp
+++ b/xpcom/build/XPCOMInit.cpp
@@ -493,16 +493,18 @@ NS_InitXPCOM2(nsIServiceManager** aResul
   }
 
   sInitialized = true;
 
   mozPoisonValueInit();
 
   NS_LogInit();
 
+  mozilla::LogModule::Init();
+
   JS_SetCurrentEmbedderTimeFunction(TimeSinceProcessCreation);
 
   char aLocal;
   profiler_init(&aLocal);
   nsresult rv = NS_OK;
 
   // We are not shutting down
   gXPCOMShuttingDown = false;
--- a/xpcom/build/perfprobe.cpp
+++ b/xpcom/build/perfprobe.cpp
@@ -13,26 +13,18 @@
 
 #include "perfprobe.h"
 #include "nsAutoPtr.h"
 
 namespace mozilla {
 namespace probes {
 
 #if defined(MOZ_LOGGING)
-static PRLogModuleInfo*
-GetProbeLog()
-{
-  static PRLogModuleInfo* sLog;
-  if (!sLog) {
-    sLog = PR_NewLogModule("SysProbe");
-  }
-  return sLog;
-}
-#define LOG(x)  MOZ_LOG(GetProbeLog(), mozilla::LogLevel::Debug, x)
+static LazyLogModule sProbeLog("SysProbe");
+#define LOG(x) MOZ_LOG(sProbeLog, mozilla::LogLevel::Debug, x)
 #else
 #define LOG(x)
 #endif
 
 // Utility function
 GUID
 CID_to_GUID(const nsCID& aCID)
 {
--- a/xpcom/components/nsComponentManager.cpp
+++ b/xpcom/components/nsComponentManager.cpp
@@ -76,17 +76,17 @@
 #include <new>     // for placement new
 
 #include "mozilla/Omnijar.h"
 
 #include "mozilla/Logging.h"
 
 using namespace mozilla;
 
-PRLogModuleInfo* nsComponentManagerLog = nullptr;
+static LazyLogModule nsComponentManagerLog("nsComponentManager");
 
 #if 0 || defined (DEBUG_timeless)
  #define SHOW_DENIED_ON_SHUTDOWN
  #define SHOW_CI_ON_EXISTING_SERVICE
 #endif
 
 // Bloated registry buffer size to improve startup performance -- needs to
 // be big enough to fit the entire file into memory or it'll thrash.
@@ -364,20 +364,16 @@ nsComponentManagerImpl::InitializeModule
   sModuleLocations = new nsTArray<ComponentLocation>;
 }
 
 nsresult
 nsComponentManagerImpl::Init()
 {
   PR_ASSERT(NOT_INITIALIZED == mStatus);
 
-  if (!nsComponentManagerLog) {
-    nsComponentManagerLog = PR_NewLogModule("nsComponentManager");
-  }
-
   // Initialize our arena
   PL_INIT_ARENA_POOL(&mArena, "ComponentManagerArena", NS_CM_BLOCK_SIZE);
 
 #if !defined(MOZILLA_XPCOMRT_API)
   nsCOMPtr<nsIFile> greDir =
     GetLocationFromDirectoryService(NS_GRE_DIR);
   nsCOMPtr<nsIFile> appDir =
     GetLocationFromDirectoryService(NS_XPCOM_CURRENT_PROCESS_DIR);
--- a/xpcom/components/nsNativeModuleLoader.cpp
+++ b/xpcom/components/nsNativeModuleLoader.cpp
@@ -44,27 +44,18 @@
 #endif
 
 #ifdef DEBUG
 #define IMPLEMENT_BREAK_AFTER_LOAD
 #endif
 
 using namespace mozilla;
 
-static PRLogModuleInfo*
-GetNativeModuleLoaderLog()
-{
-  static PRLogModuleInfo* sLog;
-  if (!sLog) {
-    sLog = PR_NewLogModule("nsNativeModuleLoader");
-  }
-  return sLog;
-}
-
-#define LOG(level, args) MOZ_LOG(GetNativeModuleLoaderLog(), level, args)
+static LazyLogModule sNativeModuleLoaderLog("nsNativeModuleLoader");
+#define LOG(level, args) MOZ_LOG(sNativeModuleLoaderLog, level, args)
 
 nsresult
 nsNativeModuleLoader::Init()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Startup not on main thread?");
   LOG(LogLevel::Debug, ("nsNativeModuleLoader::Init()"));
   return NS_OK;
 }
@@ -197,17 +188,17 @@ nsNativeModuleLoader::UnloadLibraries()
   MOZ_ASSERT(NS_IsMainThread(), "Shutdown not on main thread?");
 
   for (auto iter = mLibraries.Iter(); !iter.Done(); iter.Next()) {
     NativeLoadData& loadData = iter.Data();
     loadData.mModule = nullptr;
   }
 
   for (auto iter = mLibraries.Iter(); !iter.Done(); iter.Next()) {
-    if (MOZ_LOG_TEST(GetNativeModuleLoaderLog(), LogLevel::Debug)) {
+    if (MOZ_LOG_TEST(sNativeModuleLoaderLog, LogLevel::Debug)) {
       nsIHashable* hashedFile = iter.Key();
       nsCOMPtr<nsIFile> file(do_QueryInterface(hashedFile));
 
       nsAutoCString filePath;
       file->GetNativePath(filePath);
 
       LOG(LogLevel::Debug,
           ("nsNativeModuleLoader::UnloaderFunc(\"%s\")", filePath.get()));
--- a/xpcom/ds/nsObserverService.cpp
+++ b/xpcom/ds/nsObserverService.cpp
@@ -25,26 +25,18 @@
 //
 // To enable logging (see prlog.h for full details):
 //
 //    set NSPR_LOG_MODULES=ObserverService:5
 //    set NSPR_LOG_FILE=nspr.log
 //
 // this enables LogLevel::Debug level information and places all output in
 // the file nspr.log
-static PRLogModuleInfo*
-GetObserverServiceLog()
-{
-  static PRLogModuleInfo* sLog;
-  if (!sLog) {
-    sLog = PR_NewLogModule("ObserverService");
-  }
-  return sLog;
-}
-#define LOG(x)  MOZ_LOG(GetObserverServiceLog(), mozilla::LogLevel::Debug, x)
+static mozilla::LazyLogModule sObserverServiceLog("ObserverService");
+#define LOG(x) MOZ_LOG(sObserverServiceLog, mozilla::LogLevel::Debug, x)
 
 using namespace mozilla;
 
 NS_IMETHODIMP
 nsObserverService::CollectReports(nsIHandleReportCallback* aHandleReport,
                                   nsISupports* aData, bool aAnonymize)
 {
   struct SuspectObserver
--- a/xpcom/glue/moz.build
+++ b/xpcom/glue/moz.build
@@ -70,17 +70,16 @@ EXPORTS.mozilla += [
     'AutoRestore.h',
     'BlockingResourceBase.h',
     'CondVar.h',
     'DeadlockDetector.h',
     'EnumeratedArrayCycleCollection.h',
     'FileUtils.h',
     'GenericFactory.h',
     'IntentionalCrash.h',
-    'Logging.h',
     'Monitor.h',
     'Mutex.h',
     'Observer.h',
     'ReentrantMonitor.h',
 ]
 
 include('objs.mozbuild')
 
--- a/xpcom/io/nsInputStreamTee.cpp
+++ b/xpcom/io/nsInputStreamTee.cpp
@@ -17,26 +17,19 @@
 #include "nsIEventTarget.h"
 #include "nsThreadUtils.h"
 
 using namespace mozilla;
 
 #ifdef LOG
 #undef LOG
 #endif
-static PRLogModuleInfo*
-GetTeeLog()
-{
-  static PRLogModuleInfo* sLog;
-  if (!sLog) {
-    sLog = PR_NewLogModule("nsInputStreamTee");
-  }
-  return sLog;
-}
-#define LOG(args) MOZ_LOG(GetTeeLog(), mozilla::LogLevel::Debug, args)
+
+static LazyLogModule sTeeLog("nsInputStreamTee");
+#define LOG(args) MOZ_LOG(sTeeLog, mozilla::LogLevel::Debug, args)
 
 class nsInputStreamTee final : public nsIInputStreamTee
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSIINPUTSTREAMTEE
 
--- a/xpcom/io/nsPipe3.cpp
+++ b/xpcom/io/nsPipe3.cpp
@@ -26,26 +26,18 @@
 using namespace mozilla;
 
 #ifdef LOG
 #undef LOG
 #endif
 //
 // set NSPR_LOG_MODULES=nsPipe:5
 //
-static PRLogModuleInfo*
-GetPipeLog()
-{
-  static PRLogModuleInfo* sLog;
-  if (!sLog) {
-    sLog = PR_NewLogModule("nsPipe");
-  }
-  return sLog;
-}
-#define LOG(args) MOZ_LOG(GetPipeLog(), mozilla::LogLevel::Debug, args)
+static LazyLogModule sPipeLog("nsPipe");
+#define LOG(args) MOZ_LOG(sPipeLog, mozilla::LogLevel::Debug, args)
 
 #define DEFAULT_SEGMENT_SIZE  4096
 #define DEFAULT_SEGMENT_COUNT 16
 
 class nsPipe;
 class nsPipeEvents;
 class nsPipeInputStream;
 class nsPipeOutputStream;
--- a/xpcom/io/nsStorageStream.cpp
+++ b/xpcom/io/nsStorageStream.cpp
@@ -36,29 +36,21 @@ using mozilla::ipc::StringInputStreamPar
 // To enable logging (see prlog.h for full details):
 //
 //    set NSPR_LOG_MODULES=StorageStreamLog:5
 //    set NSPR_LOG_FILE=nspr.log
 //
 // this enables LogLevel::Debug level information and places all output in
 // the file nspr.log
 //
-static PRLogModuleInfo*
-GetStorageStreamLog()
-{
-  static PRLogModuleInfo* sLog;
-  if (!sLog) {
-    sLog = PR_NewLogModule("nsStorageStream");
-  }
-  return sLog;
-}
+static LazyLogModule sStorageStreamLog("nsStorageStream");
 #ifdef LOG
 #undef LOG
 #endif
-#define LOG(args) MOZ_LOG(GetStorageStreamLog(), mozilla::LogLevel::Debug, args)
+#define LOG(args) MOZ_LOG(sStorageStreamLog, mozilla::LogLevel::Debug, args)
 
 nsStorageStream::nsStorageStream()
   : mSegmentedBuffer(0), mSegmentSize(0), mWriteInProgress(false),
     mLastSegmentNum(-1), mWriteCursor(0), mSegmentEnd(0), mLogicalLength(0)
 {
   LOG(("Creating nsStorageStream [%p].\n", this));
 }
 
--- a/xpcom/threads/AbstractThread.cpp
+++ b/xpcom/threads/AbstractThread.cpp
@@ -17,16 +17,19 @@
 
 #include "nsThreadUtils.h"
 #include "nsContentUtils.h"
 #include "nsServiceManagerUtils.h"
 
 
 namespace mozilla {
 
+LazyLogModule gMozPromiseLog("MozPromise");
+LazyLogModule gStateWatchingLog("StateWatching");
+
 StaticRefPtr<AbstractThread> sMainThread;
 ThreadLocal<AbstractThread*> AbstractThread::sCurrentThreadTLS;
 
 class XPCOMThreadWrapper : public AbstractThread
 {
 public:
   explicit XPCOMThreadWrapper(nsIThread* aTarget, bool aRequireTailDispatch)
     : AbstractThread(aRequireTailDispatch)
@@ -112,19 +115,16 @@ AbstractThread::MainThread()
 {
   MOZ_ASSERT(sMainThread);
   return sMainThread;
 }
 
 void
 AbstractThread::InitStatics()
 {
-  gMozPromiseLog = PR_NewLogModule("MozPromise");
-  gStateWatchingLog = PR_NewLogModule("StateWatching");
-
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!sMainThread);
   nsCOMPtr<nsIThread> mainThread;
   NS_GetMainThread(getter_AddRefs(mainThread));
   MOZ_DIAGNOSTIC_ASSERT(mainThread);
   sMainThread = new XPCOMThreadWrapper(mainThread.get(), /* aRequireTailDispatch = */ true);
   ClearOnShutdown(&sMainThread);
 
--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -15,20 +15,19 @@
 #include "mozilla/Monitor.h"
 #include "mozilla/Tuple.h"
 
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 
-extern PRLogModuleInfo* gMozPromiseLog;
+extern LazyLogModule gMozPromiseLog;
 
 #define PROMISE_LOG(x, ...) \
-  MOZ_ASSERT(gMozPromiseLog); \
   MOZ_LOG(gMozPromiseLog, mozilla::LogLevel::Debug, (x, ##__VA_ARGS__))
 
 namespace detail {
 template<typename ThisType, typename Ret, typename ArgType>
 static TrueType TakesArgumentHelper(Ret (ThisType::*)(ArgType));
 template<typename ThisType, typename Ret, typename ArgType>
 static TrueType TakesArgumentHelper(Ret (ThisType::*)(ArgType) const);
 template<typename ThisType, typename Ret>
--- a/xpcom/threads/StateWatching.h
+++ b/xpcom/threads/StateWatching.h
@@ -51,20 +51,19 @@
  * and watch the mirrored value.
  *
  * Given that semantics may change and comments tend to go out of date, we
  * deliberately don't provide usage examples here. Grep around to find them.
  */
 
 namespace mozilla {
 
-extern PRLogModuleInfo* gStateWatchingLog;
+extern LazyLogModule gStateWatchingLog;
 
 #define WATCH_LOG(x, ...) \
-  MOZ_ASSERT(gStateWatchingLog); \
   MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__))
 
 /*
  * AbstractWatcher is a superclass from which all watchers must inherit.
  */
 class AbstractWatcher
 {
 public:
--- a/xpcom/threads/nsEventQueue.cpp
+++ b/xpcom/threads/nsEventQueue.cpp
@@ -8,29 +8,21 @@
 #include "nsAutoPtr.h"
 #include "mozilla/Logging.h"
 #include "nsThreadUtils.h"
 #include "prthread.h"
 #include "mozilla/ChaosMode.h"
 
 using namespace mozilla;
 
-static PRLogModuleInfo*
-GetLog()
-{
-  static PRLogModuleInfo* sLog;
-  if (!sLog) {
-    sLog = PR_NewLogModule("nsEventQueue");
-  }
-  return sLog;
-}
+static LazyLogModule sEventQueueLog("nsEventQueue");
 #ifdef LOG
 #undef LOG
 #endif
-#define LOG(args) MOZ_LOG(GetLog(), mozilla::LogLevel::Debug, args)
+#define LOG(args) MOZ_LOG(sEventQueueLog, mozilla::LogLevel::Debug, args)
 
 nsEventQueue::nsEventQueue(Mutex& aLock)
   : mHead(nullptr)
   , mTail(nullptr)
   , mOffsetHead(0)
   , mOffsetTail(0)
   , mEventsAvailable(aLock, "[nsEventQueue.mEventsAvailable]")
 {
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -81,29 +81,21 @@
 #ifdef MOZ_TASK_TRACER
 #include "GeckoTaskTracer.h"
 #include "TracedTaskCommon.h"
 using namespace mozilla::tasktracer;
 #endif
 
 using namespace mozilla;
 
-static PRLogModuleInfo*
-GetThreadLog()
-{
-  static PRLogModuleInfo* sLog;
-  if (!sLog) {
-    sLog = PR_NewLogModule("nsThread");
-  }
-  return sLog;
-}
+static LazyLogModule sThreadLog("nsThread");
 #ifdef LOG
 #undef LOG
 #endif
-#define LOG(args) MOZ_LOG(GetThreadLog(), mozilla::LogLevel::Debug, args)
+#define LOG(args) MOZ_LOG(sThreadLog, mozilla::LogLevel::Debug, args)
 
 NS_DECL_CI_INTERFACE_GETTER(nsThread)
 
 //-----------------------------------------------------------------------------
 // Because we do not have our own nsIFactory, we have to implement nsIClassInfo
 // somewhat manually.
 
 class nsThreadClassInfo : public nsIClassInfo
--- a/xpcom/threads/nsThreadPool.cpp
+++ b/xpcom/threads/nsThreadPool.cpp
@@ -11,29 +11,21 @@
 #include "nsMemory.h"
 #include "nsAutoPtr.h"
 #include "prinrval.h"
 #include "mozilla/Logging.h"
 #include "nsThreadSyncDispatch.h"
 
 using namespace mozilla;
 
-static PRLogModuleInfo*
-GetThreadPoolLog()
-{
-  static PRLogModuleInfo* sLog;
-  if (!sLog) {
-    sLog = PR_NewLogModule("nsThreadPool");
-  }
-  return sLog;
-}
+static LazyLogModule sThreadPoolLog("nsThreadPool");
 #ifdef LOG
 #undef LOG
 #endif
-#define LOG(args) MOZ_LOG(GetThreadPoolLog(), mozilla::LogLevel::Debug, args)
+#define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args)
 
 // DESIGN:
 //  o  Allocate anonymous threads.
 //  o  Use nsThreadPool::Run as the main routine for each thread.
 //  o  Each thread waits on the event queue's monitor, checking for
 //     pending events and rescheduling itself as an idle thread.
 
 #define DEFAULT_THREAD_LIMIT 4
--- a/xpcom/threads/nsTimerImpl.cpp
+++ b/xpcom/threads/nsTimerImpl.cpp
@@ -35,24 +35,22 @@ using mozilla::Atomic;
 using mozilla::LogLevel;
 using mozilla::TimeDuration;
 using mozilla::TimeStamp;
 
 static Atomic<int32_t>  gGenerator;
 static TimerThread*     gThread = nullptr;
 
 // This module prints info about the precision of timers.
-PRLogModuleInfo*
+static mozilla::LazyLogModule sTimerLog("nsTimerImpl");
+
+mozilla::LogModule*
 GetTimerLog()
 {
-  static PRLogModuleInfo* sLog;
-  if (!sLog) {
-    sLog = PR_NewLogModule("nsTimerImpl");
-  }
-  return sLog;
+  return sTimerLog;
 }
 
 // This module prints info about which timers are firing, which is useful for
 // wakeups for the purposes of power profiling. Set the following environment
 // variable before starting the browser.
 //
 //   NSPR_LOG_MODULES=TimerFirings:4
 //
@@ -78,24 +76,22 @@ GetTimerLog()
 //   cat out | grep timer | sort | uniq -c | sort -r -n
 //
 // This will show how often each unique line appears, with the most common ones
 // first.
 //
 // More detailed docs are here:
 // https://developer.mozilla.org/en-US/docs/Mozilla/Performance/TimerFirings_logging
 //
-PRLogModuleInfo*
+static mozilla::LazyLogModule sTimerFiringsLog("TimerFirings");
+
+mozilla::LogModule*
 GetTimerFiringsLog()
 {
-  static PRLogModuleInfo* sLog;
-  if (!sLog) {
-    sLog = PR_NewLogModule("TimerFirings");
-  }
-  return sLog;
+  return sTimerFiringsLog;
 }
 
 #include <math.h>
 
 double nsTimerImpl::sDeltaSumSquared = 0;
 double nsTimerImpl::sDeltaSum = 0;
 double nsTimerImpl::sDeltaNum = 0;
 
--- a/xpcom/threads/nsTimerImpl.h
+++ b/xpcom/threads/nsTimerImpl.h
@@ -17,17 +17,17 @@
 #include "mozilla/Logging.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Variant.h"
 
 #ifdef MOZ_TASK_TRACER
 #include "TracedTaskCommon.h"
 #endif
 
-extern PRLogModuleInfo* GetTimerLog();
+extern mozilla::LogModule* GetTimerLog();
 
 #define NS_TIMER_CID \
 { /* 5ff24248-1dd2-11b2-8427-fbab44f29bc8 */         \
      0x5ff24248,                                     \
      0x1dd2,                                         \
      0x11b2,                                         \
     {0x84, 0x27, 0xfb, 0xab, 0x44, 0xf2, 0x9b, 0xc8} \
 }