Merge inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 05 Mar 2016 19:12:44 -0500
changeset 323221 bb10b6a54c0df280e6aafffee324217f19b3b676
parent 323100 5a2e0878d6c258b36b0ee8712a2afcde6ad94c78 (current diff)
parent 323220 a20f8c2cf8bb75075e198c499264bba973f5c37a (diff)
child 323222 fcd55efa0672b393db88ab6bfd2d4b190072c161
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone47.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 inbound to m-c. a=merge
dom/base/test/test_websocket.html
dom/canvas/test/test_offscreencanvas_basic_webgl.html
js/src/jit-test/tests/ion/bug1239075.js
testing/web-platform/meta/XMLHttpRequest/formdata-set.htm.ini
testing/web-platform/meta/touch-events/create-touch-touchlist.html.ini
testing/web-platform/tests/websockets/websocket.js
--- a/accessible/base/Logging.cpp
+++ b/accessible/base/Logging.cpp
@@ -164,18 +164,22 @@ LogDocState(nsIDocument* aDocumentNode)
 
   printf("doc state: %s", docState);
   printf(", %sinitial", aDocumentNode->IsInitialDocument() ? "" : "not ");
   printf(", %sshowing", aDocumentNode->IsShowing() ? "" : "not ");
   printf(", %svisible", aDocumentNode->IsVisible() ? "" : "not ");
   printf(", %svisible considering ancestors", aDocumentNode->IsVisibleConsideringAncestors() ? "" : "not ");
   printf(", %sactive", aDocumentNode->IsActive() ? "" : "not ");
   printf(", %sresource", aDocumentNode->IsResourceDoc() ? "" : "not ");
-  printf(", has %srole content",
-         nsCoreUtils::GetRoleContent(aDocumentNode) ? "" : "no ");
+
+  dom::Element* rootEl = aDocumentNode->GetBodyElement();
+  if (!rootEl) {
+    rootEl = aDocumentNode->GetRootElement();
+  }
+  printf(", has %srole content", rootEl ? "" : "no ");
 }
 
 static void
 LogPresShell(nsIDocument* aDocumentNode)
 {
   nsIPresShell* ps = aDocumentNode->GetShell();
   printf("presshell: %p", static_cast<void*>(ps));
 
--- a/accessible/base/nsCoreUtils.cpp
+++ b/accessible/base/nsCoreUtils.cpp
@@ -218,38 +218,16 @@ nsCoreUtils::GetDOMNodeFromDOMPoint(nsIN
     // from the given DOM point is used as result node.
     if (aOffset != childCount)
       return aNode->GetChildAt(aOffset);
   }
 
   return aNode;
 }
 
-dom::Element*
-nsCoreUtils::GetRoleContent(nsINode *aNode)
-{
-  nsCOMPtr<nsIContent> content(do_QueryInterface(aNode));
-  if (!content) {
-    nsCOMPtr<nsIDocument> doc(do_QueryInterface(aNode));
-    if (doc) {
-      nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(aNode));
-      if (htmlDoc) {
-        nsCOMPtr<nsIDOMHTMLElement> bodyElement;
-        htmlDoc->GetBody(getter_AddRefs(bodyElement));
-        content = do_QueryInterface(bodyElement);
-      }
-      else {
-        return doc->GetDocumentElement();
-      }
-    }
-  }
-
-  return (content && content->IsElement()) ? content->AsElement() : nullptr;
-}
-
 bool
 nsCoreUtils::IsAncestorOf(nsINode *aPossibleAncestorNode,
                           nsINode *aPossibleDescendantNode,
                           nsINode *aRootNode)
 {
   NS_ENSURE_TRUE(aPossibleAncestorNode && aPossibleDescendantNode, false);
 
   nsINode *parentNode = aPossibleDescendantNode;
--- a/accessible/base/nsCoreUtils.h
+++ b/accessible/base/nsCoreUtils.h
@@ -104,27 +104,16 @@ public:
   static nsIContent* GetDOMElementFor(nsIContent *aContent);
 
   /**
    * Return DOM node for the given DOM point.
    */
   static nsINode *GetDOMNodeFromDOMPoint(nsINode *aNode, uint32_t aOffset);
 
   /**
-   * Return the nsIContent* to check for ARIA attributes on -- this may not
-   * always be the DOM node for the accessible. Specifically, for doc
-   * accessibles, it is not the document node, but either the root element or
-   * <body> in HTML.
-   *
-   * @param aNode  [in] DOM node for the accessible that may be affected by ARIA
-   * @return        the nsIContent which may have ARIA markup
-   */
-  static mozilla::dom::Element* GetRoleContent(nsINode *aNode);
-
-  /**
    * Is the first passed in node an ancestor of the second?
    * Note: A node is not considered to be the ancestor of itself.
    *
    * @param  aPossibleAncestorNode   [in] node to test for ancestor-ness of
    *                                   aPossibleDescendantNode
    * @param  aPossibleDescendantNode [in] node to test for descendant-ness of
    *                                   aPossibleAncestorNode
    * @param  aRootNode               [in, optional] the root node that search
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -76,16 +76,17 @@
 #include "mozilla/EventStates.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/unused.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/CanvasRenderingContext2D.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/TreeWalker.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // Accessible: nsISupports and cycle collection
@@ -996,17 +997,17 @@ Accessible::NativeAttributes()
   // override properties on a widget they used in an iframe.
   nsIContent* startContent = mContent;
   while (startContent) {
     nsIDocument* doc = startContent->GetComposedDoc();
     if (!doc)
       break;
 
     nsAccUtils::SetLiveContainerAttributes(attributes, startContent,
-                                           nsCoreUtils::GetRoleContent(doc));
+                                           doc->GetRootElement());
 
     // Allow ARIA live region markup from outer documents to override
     nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell();
     if (!docShellTreeItem)
       break;
 
     nsCOMPtr<nsIDocShellTreeItem> sameTypeParent;
     docShellTreeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent));
--- a/accessible/generic/DocAccessible-inl.h
+++ b/accessible/generic/DocAccessible-inl.h
@@ -8,16 +8,17 @@
 #define mozilla_a11y_DocAccessible_inl_h_
 
 #include "DocAccessible.h"
 #include "nsAccessibilityService.h"
 #include "nsAccessiblePivot.h"
 #include "NotificationController.h"
 #include "States.h"
 #include "nsIScrollableFrame.h"
+#include "nsIDocumentInlines.h"
 
 #ifdef A11Y_LOG
 #include "Logging.h"
 #endif
 
 namespace mozilla {
 namespace a11y {
 
@@ -73,16 +74,29 @@ DocAccessible::UpdateText(nsIContent* aT
   NS_ASSERTION(mNotificationController, "The document was shut down!");
 
   // Ignore the notification if initial tree construction hasn't been done yet.
   if (mNotificationController && HasLoadState(eTreeConstructed))
     mNotificationController->ScheduleTextUpdate(aTextNode);
 }
 
 inline void
+DocAccessible::UpdateRootElIfNeeded()
+{
+  dom::Element* rootEl = mDocumentNode->GetBodyElement();
+  if (!rootEl) {
+    rootEl = mDocumentNode->GetRootElement();
+  }
+  if (rootEl != mContent) {
+    mContent = rootEl;
+    SetRoleMapEntry(aria::GetRoleMap(rootEl));
+  }
+}
+
+inline void
 DocAccessible::AddScrollListener()
 {
   // Delay scroll initializing until the document has a root frame.
   if (!mPresShell->GetRootFrame())
     return;
 
   mDocFlags |= eScrollInitialized;
   nsIScrollableFrame* sf = mPresShell->GetRootScrollFrameAsScrollable();
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1455,24 +1455,18 @@ DocAccessible::NotifyOfLoading(bool aIsR
 void
 DocAccessible::DoInitialUpdate()
 {
   if (nsCoreUtils::IsTabDocument(mDocumentNode))
     mDocFlags |= eTabDocument;
 
   mLoadState |= eTreeConstructed;
 
-  // The content element may be changed before the initial update and then we
-  // miss the notification (since content tree change notifications are ignored
-  // prior to initial update). Make sure the content element is valid.
-  dom::Element* rootEl = nsCoreUtils::GetRoleContent(mDocumentNode);
-  if (rootEl) {
-    mContent = rootEl;
-    SetRoleMapEntry(aria::GetRoleMap(rootEl));
-  }
+  // Set up a root element and ARIA role mapping.
+  UpdateRootElIfNeeded();
 
   // Build initial tree.  Since its the initial tree there's no group info to
   // invalidate.
   AutoTreeMutation mut(this, false);
   CacheChildrenInSubtree(this);
 
   // Fire reorder event after the document tree is constructed. Note, since
   // this reorder event is processed by parent document then events targeted to
@@ -1695,21 +1689,17 @@ DocAccessible::ProcessContentInserted(Ac
 
     Accessible* container =
       GetContainerAccessible(aInsertedContent->ElementAt(idx));
     if (container != aContainer)
       continue;
 
     if (container == this) {
       // If new root content has been inserted then update it.
-      dom::Element* rootEl = nsCoreUtils::GetRoleContent(mDocumentNode);
-      if (rootEl != mContent) {
-        mContent = rootEl;
-        SetRoleMapEntry(aria::GetRoleMap(rootEl));
-      }
+      UpdateRootElIfNeeded();
 
       // Continue to update the tree even if we don't have root content.
       // For example, elements may be inserted under the document element while
       // there is no HTML body element.
     }
 
     // We have a DOM/layout change under the container accessible, and its tree
     // might need an update. Since DOM/layout change of the element may affect
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -372,16 +372,21 @@ protected:
 
   /**
    * Perform initial update (create accessible tree).
    * Can be overridden by wrappers to prepare initialization work.
    */
   virtual void DoInitialUpdate();
 
   /**
+   * Updates root element and picks up ARIA role on it if any.
+   */
+  void UpdateRootElIfNeeded();
+
+  /**
    * Process document load notification, fire document load and state busy
    * events if applicable.
    */
   void ProcessLoad();
 
   /**
    * Add/remove scroll listeners, @see nsIScrollPositionListener interface.
    */
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -403,25 +403,33 @@ HyperTextAccessible::OffsetToDOMPoint(in
 
   int32_t childIdx = GetChildIndexAtOffset(aOffset);
   if (childIdx == -1)
     return DOMPoint();
 
   Accessible* child = GetChildAt(childIdx);
   int32_t innerOffset = aOffset - GetChildOffset(childIdx);
 
-  // A text leaf case. The point is inside the text node.
+  // A text leaf case.
   if (child->IsTextLeaf()) {
-    nsIContent* content = child->GetContent();
-    int32_t idx = 0;
-    if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(),
-                                          innerOffset, &idx)))
-      return DOMPoint();
+    // The point is inside the text node. This is always true for any text leaf
+    // except a last child one. See assertion below.
+    if (aOffset < GetChildOffset(childIdx + 1)) {
+      nsIContent* content = child->GetContent();
+      int32_t idx = 0;
+      if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(),
+                                            innerOffset, &idx)))
+        return DOMPoint();
 
-    return DOMPoint(content, idx);
+      return DOMPoint(content, idx);
+    }
+
+    // Set the DOM point right after the text node.
+    MOZ_ASSERT(static_cast<uint32_t>(aOffset) == CharacterCount());
+    innerOffset = 1;
   }
 
   // Case of embedded object. The point is either before or after the element.
   NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!");
   nsINode* node = child->GetNode();
   nsINode* parentNode = node->GetParentNode();
   return parentNode ?
     DOMPoint(parentNode, parentNode->IndexOf(node) + innerOffset) :
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1020,16 +1020,17 @@ pref("apz.axis_lock.mode", 2); // Use "s
 pref("apz.fling_curve_function_x1", "0.41");
 pref("apz.fling_curve_function_y1", "0.0");
 pref("apz.fling_curve_function_x2", "0.80");
 pref("apz.fling_curve_function_y2", "1.0");
 pref("apz.fling_curve_threshold_inches_per_ms", "0.01");
 pref("apz.fling_friction", "0.0019");
 pref("apz.max_velocity_inches_per_ms", "0.07");
 pref("apz.overscroll.enabled", true);
+pref("apz.displayport_expiry_ms", 0); // causes issues on B2G, see bug 1250924
 
 // For event-regions based hit-testing
 pref("layout.event-regions.enabled", true);
 
 // This preference allows FirefoxOS apps (and content, I think) to force
 // the use of software (instead of hardware accelerated) 2D canvases by
 // creating a context like this:
 //
--- a/browser/base/content/webrtcIndicator.js
+++ b/browser/base/content/webrtcIndicator.js
@@ -135,22 +135,24 @@ var PositionHandler = {
   adjustPosition: function() {
     if (!this.positionCustomized) {
       // Center the window horizontally on the screen (not the available area).
       // Until we have moved the window to y=0, 'screen.width' may give a value
       // for a secondary screen, so use values from the screen manager instead.
       let primaryScreen = Cc["@mozilla.org/gfx/screenmanager;1"]
                             .getService(Ci.nsIScreenManager)
                             .primaryScreen;
-      let width = {};
-      primaryScreen.GetRectDisplayPix({}, {}, width, {});
-      let availTop = {};
-      primaryScreen.GetAvailRectDisplayPix({}, availTop, {}, {});
-      window.moveTo((width.value - document.documentElement.clientWidth) / 2,
-                    availTop.value);
+      let widthDevPix = {};
+      primaryScreen.GetRect({}, {}, widthDevPix, {});
+      let availTopDevPix = {};
+      primaryScreen.GetAvailRect({}, availTopDevPix, {}, {});
+      let scaleFactor = primaryScreen.defaultCSSScaleFactor;
+      let widthCss = widthDevPix.value / scaleFactor;
+      window.moveTo((widthCss - document.documentElement.clientWidth) / 2,
+                    availTopDevPix.value / scaleFactor);
     } else {
       // This will ensure we're at y=0.
       this.setXPosition(window.screenX);
     }
   },
   setXPosition: function(desiredX) {
     // Ensure the indicator isn't moved outside the available area of the screen.
     desiredX = Math.max(desiredX, screen.availLeft);
--- a/browser/components/extensions/ext-bookmarks.js
+++ b/browser/components/extensions/ext-bookmarks.js
@@ -110,16 +110,20 @@ extensions.registerSchemaAPI("bookmarks"
       getSubTree: function(id) {
         return getTree(id, false);
       },
 
       search: function(query) {
         return Bookmarks.search(query).then(result => result.map(convert));
       },
 
+      getRecent: function(numberOfItems) {
+        return Bookmarks.getRecent(numberOfItems).then(result => result.map(convert));
+      },
+
       create: function(bookmark) {
         let info = {
           title: bookmark.title || "",
         };
 
         // If url is NULL or missing, it will be a folder.
         if (bookmark.url !== null) {
           info.type = Bookmarks.TYPE_BOOKMARK;
--- a/browser/components/extensions/schemas/bookmarks.json
+++ b/browser/components/extensions/schemas/bookmarks.json
@@ -160,17 +160,16 @@
                 "items": { "$ref": "BookmarkTreeNode"}
               }
             ]
           }
         ]
       },
       {
         "name": "getRecent",
-        "unsupported": true,
         "type": "function",
         "description": "Retrieves the recently added bookmarks.",
         "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "minimum": 1,
             "name": "numberOfItems",
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -90,18 +90,18 @@ var gMainPane = {
 
     let e10sPref = document.getElementById("browser.tabs.remote.autostart");
     let e10sTempPref = document.getElementById("e10sTempPref");
     let e10sForceEnable = document.getElementById("e10sForceEnable");
 
     let preffedOn = e10sPref.value || e10sTempPref.value || e10sForceEnable.value;
 
     if (preffedOn) {
-      // The checkbox is checked if e10s is preffed on.
-      e10sCheckbox.checked = true;
+      // The checkbox is checked if e10s is preffed on and enabled.
+      e10sCheckbox.checked = Services.appinfo.browserTabsRemoteAutostart;
 
       // but if it's force disabled, then the checkbox is disabled.
       e10sCheckbox.disabled = !Services.appinfo.browserTabsRemoteAutostart;
     }
 
 #endif
 
 #ifdef MOZ_DEV_EDITION
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -370,18 +370,22 @@ def bootstrap(topsrcdir, mozilla_dir=Non
 
         session = requests.Session()
         for filename in os.listdir(outgoing):
             path = os.path.join(outgoing, filename)
             if os.path.isdir(path) or not path.endswith('.json'):
                 continue
             with open(path, 'r') as f:
                 data = f.read()
-                r = session.post(BUILD_TELEMETRY_SERVER, data=data,
-                                 headers={'Content-Type': 'application/json'})
+                try:
+                    r = session.post(BUILD_TELEMETRY_SERVER, data=data,
+                                     headers={'Content-Type': 'application/json'})
+                except Exception as e:
+                    print('Exception posting to telemetry server: %s' % str(e))
+                    break
                 # TODO: some of these errors are likely not recoverable, as
                 # written, we'll retry indefinitely
                 if r.status_code != 200:
                     print('Error posting to telemetry: %s %s' %
                           (r.status_code, r.text))
                     continue
 
             os.rename(os.path.join(outgoing, filename),
--- a/build/mobile/sutagent/android/DoCommand.java
+++ b/build/mobile/sutagent/android/DoCommand.java
@@ -2848,29 +2848,29 @@ private void CancelNotification()
 
     public String UpdateCallBack(String sFileName)
         {
         String sRet = sErrorPrefix + "No file specified";
         String sIP = "";
         String sPort = "";
         int nEnd = 0;
         int nStart = 0;
+        FileInputStream fis = null;
 
         if ((sFileName == null) || (sFileName.length() == 0))
             return(sRet);
 
         Context ctx = contextWrapper.getApplicationContext();
         try {
-            FileInputStream fis = ctx.openFileInput(sFileName);
+            fis = ctx.openFileInput(sFileName);
             int nBytes = fis.available();
             if (nBytes > 0)
                 {
                 byte [] buffer = new byte [nBytes + 1];
                 int nRead = fis.read(buffer, 0, nBytes);
-                fis.close();
                 ctx.deleteFile(sFileName);
                 if (nRead > 0)
                     {
                     String sBuffer = new String(buffer);
                     nEnd = sBuffer.indexOf(',');
                     if (nEnd > 0)
                         {
                         sIP = (sBuffer.substring(0, nEnd)).trim();
@@ -2893,16 +2893,29 @@ private void CancelNotification()
         catch (IOException e)
             {
             sRet = sErrorPrefix + "Couldn't send info to " + sIP + ":" + sPort;
             }
         catch (InterruptedException e)
             {
             e.printStackTrace();
             }
+        finally
+            {
+            if (fis != null)
+                {
+                try {
+                    fis.close();
+                    }
+                catch(IOException e)
+                    {
+                    e.printStackTrace();
+                    }
+                }
+            }
         return(sRet);
         }
 
     public String RegisterTheDevice(String sSrvr, String sPort, String sData)
         {
         String sRet = "";
         String line = "";
 
--- a/devtools/client/shared/developer-toolbar.js
+++ b/devtools/client/shared/developer-toolbar.js
@@ -294,17 +294,17 @@ DeveloperToolbar.prototype.createToolbar
     return;
   }
   let toolbar = this._doc.createElement("toolbar");
   toolbar.setAttribute("id", "developer-toolbar");
   toolbar.setAttribute("hidden", "true");
 
   let close = this._doc.createElement("toolbarbutton");
   close.setAttribute("id", "developer-toolbar-closebutton");
-  close.setAttribute("class", "devtools-closebutton");
+  close.setAttribute("class", "close-icon");
   close.setAttribute("oncommand", "DeveloperToolbar.hide();");
   close.setAttribute("tooltiptext", "developerToolbarCloseButton.tooltiptext");
 
   let stack = this._doc.createElement("stack");
   stack.setAttribute("flex", "1");
 
   let input = this._doc.createElement("textbox");
   input.setAttribute("class", "gclitoolbar-input-node");
@@ -328,20 +328,25 @@ DeveloperToolbar.prototype.createToolbar
     toolbar.appendChild(stack);
     toolbar.appendChild(toolboxBtn);
   } else {
     toolbar.appendChild(stack);
     toolbar.appendChild(toolboxBtn);
     toolbar.appendChild(close);
   }
 
+  this._element = toolbar;
   let bottomBox = this._doc.getElementById("browser-bottombox");
-  this._element = toolbar;
-  bottomBox.appendChild(this._element);
-
+  if (bottomBox) {
+    bottomBox.appendChild(this._element);
+  } else { // SeaMonkey does not have a "browser-bottombox".
+    let statusBar = this._doc.getElementById("status-bar");
+    if (statusBar)
+      statusBar.parentNode.insertBefore(this._element, statusBar);
+  }
   this._errorCounterButton = toolboxBtn
   this._errorCounterButton._defaultTooltipText =
       this._errorCounterButton.getAttribute("tooltiptext");
 };
 
 /**
  * Called from browser.xul in response to menu-click or keyboard shortcut to
  * toggle the toolbar
--- a/devtools/client/themes/jit-optimizations.css
+++ b/devtools/client/themes/jit-optimizations.css
@@ -4,122 +4,120 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * JIT View
  */
 
 #jit-optimizations-view {
   width: 350px;
-  overflow-x: auto;
   min-width: 200px;
   white-space: nowrap;
   --jit-tree-row-height: 14;
   --jit-tree-header-height: 16;
 }
 
-#jit-optimizations-view > div {
-  flex: 1;
-}
-
+/* Override layout styles applied by minimal-xul.css */
 #jit-optimizations-view div {
   display: block;
 }
-
-.tree {
-  /**
-   * Flexing to fill out remaining vertical space.
-   */
-  flex: 1;
-  overflow-y: auto;
-  height: 100%;
-  background-color: var(--theme-body-background);
+#jit-optimizations-view span {
+  display: inline-block;
 }
 
-.optimization-header {
+#jit-optimizations-view > div {
+  /* For elements that need to flex to fill the available space and/or
+   * scroll on overflow, we need to use the old flexbox model, since the
+   * parent nodes are in the XUL namespace. The new flexbox model can't
+   * properly compute dimensions and will ignore `flex: ${number}` properties,
+   * since no other parent node has a flex display. */
+  display: -moz-box;
+  -moz-box-flex: 1;
+  -moz-box-orient: vertical;
+}
+
+#jit-optimizations-view .optimization-header,
+#jit-optimizations-view .tree * {
+  /* We can, however, display child nodes as flex to take advantage of
+   * horizontal/vertical inlining. */
+  display: flex;
+}
+
+#jit-optimizations-view .optimization-header {
   height: var(--jit-tree-header-height);
   padding: 2px 5px;
   background-color: var(--theme-tab-toolbar-background);
 }
 
 #jit-optimizations-view .header-title {
   font-weight: bold;
-  padding-right: 7px;
-}
-
-.tree-node {
-  height: var(--jit-tree-row-height);
-  clear: both;
-}
-
-.tree-node button {
-  display: none;
+  padding-inline-end: 7px;
 }
 
-#jit-optimizations-view .optimization-tree-item {
-  display: flex;
+#jit-optimizations-view .tree {
+  display: -moz-box;
+  -moz-box-flex: 1;
+  -moz-box-orient: vertical;
+  overflow: auto;
+  background-color: var(--theme-body-background);
 }
 
-#jit-optimizations-view .arrow,
-#jit-optimizations-view .optimization-site,
-#jit-optimizations-view .optimization-attempts,
-#jit-optimizations-view .optimization-attempt,
-#jit-optimizations-view .optimization-types,
-#jit-optimizations-view .optimization-ion-type,
-#jit-optimizations-view .optimization-observed-type {
-  float: left;
+#jit-optimizations-view .tree-node {
+  height: var(--jit-tree-row-height);
+}
+
+#jit-optimizations-view .tree-node button {
+  display: none;
 }
 
 #jit-optimizations-view .optimization-outcome.success {
   color: var(--theme-highlight-green);
 }
 #jit-optimizations-view .optimization-outcome.failure {
   color: var(--theme-highlight-red);
 }
 
+.theme-dark .opt-icon::before {
+  background-image: url(chrome://devtools/skin/images/webconsole.svg);
+}
+.theme-light .opt-icon::before {
+  background-image: url(chrome://devtools/skin/images/webconsole.svg#light-icons);
+}
+
 .opt-icon::before {
+  display: inline-block;
   content: "";
-  background-image: url(chrome://devtools/skin/images/webconsole.svg);
   background-repeat: no-repeat;
   background-size: 72px 60px;
   /* show grey "i" bubble by default */
   background-position: -36px -36px;
   width: 10px;
   height: 10px;
-  display: inline-block;
-
   max-height: 12px;
 }
 
-#jit-optimizations-view .opt-icon {
-  float: left;
-}
-
-#jit-optimizations-view .opt-icon::before {
+.opt-icon::before {
   margin: 1px 6px 0 0;
 }
 
-.theme-light .opt-icon::before {
-  background-image: url(chrome://devtools/skin/images/webconsole.svg#light-icons);
-}
 .opt-icon.warning::before {
   background-position: -24px -24px;
 }
 
 /* Frame Component */
 .focused .frame-link-filename,
 .focused .frame-link-column,
 .focused .frame-link-line,
 .focused .frame-link-host,
 .focused .frame-link-colon {
   color: var(--theme-selection-color);
 }
 
 .frame-link {
-  margin-left: 7px;
+  margin-inline-start: 7px;
 }
 
 .frame-link-filename {
   color: var(--theme-highlight-blue);
   cursor: pointer;
 }
 
 .frame-link-filename:hover {
--- a/devtools/client/themes/performance.css
+++ b/devtools/client/themes/performance.css
@@ -642,36 +642,15 @@ menuitem.experimental-option::before {
   background-image: url(chrome://devtools/skin/images/webconsole.svg#light-icons);
 }
 
 #performance-options-menupopup:not(.experimental-enabled) .experimental-option,
 #performance-options-menupopup:not(.experimental-enabled) .experimental-option::before {
   display: none;
 }
 
-.opt-icon::before {
-  content: "";
-  background-image: url(chrome://devtools/skin/images/webconsole.svg);
-  background-repeat: no-repeat;
-  background-size: 72px 60px;
-  /* show grey "i" bubble by default */
-  background-position: -36px -36px;
-  width: 10px;
-  height: 10px;
-  display: inline-block;
-
-  max-height: 12px;
-}
-
-.theme-light .opt-icon::before {
-  background-image: url(chrome://devtools/skin/images/webconsole.svg#light-icons);
-}
-.opt-icon.warning::before {
-  background-position: -24px -24px;
-}
-
 /* for call tree */
 description.opt-icon {
   margin: 0px 0px 0px 0px;
 }
 description.opt-icon::before {
   margin: 1px 4px 0px 0px;
 }
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -10565,21 +10565,49 @@ nsDocShell::DoURILoad(nsIURI* aURI,
     }
   }
 
   // open a channel for the url
   nsCOMPtr<nsIChannel> channel;
 
   bool isSrcdoc = !aSrcdoc.IsVoid();
 
+  // There are three cases we care about:
+  // * Null mScriptGlobal: shouldn't happen but does (see bug 1240246). In this
+  //   case, we create a loadingPrincipal as for a top-level load, but we leave
+  //   requestingNode and requestingWindow null.
+  // * Top-level load (GetFrameElementInternal returns null). In this case,
+  //   requestingNode is null, but requestingWindow is our mScriptGlobal.
+  //   TODO we want to pass null for loadingPrincipal in this case.
+  // * Subframe load: requestingWindow is null, but requestingNode is the frame
+  //   element for the load. loadingPrincipal is the NodePrincipal of the frame
+  //   element.
   nsCOMPtr<nsINode> requestingNode;
+  nsCOMPtr<nsPIDOMWindowOuter> requestingWindow;
+
+  nsCOMPtr<nsIPrincipal> loadingPrincipal;
   if (mScriptGlobal) {
     requestingNode = mScriptGlobal->AsOuter()->GetFrameElementInternal();
-    if (!requestingNode) {
-      requestingNode = mScriptGlobal->GetExtantDoc();
+    if (requestingNode) {
+      // If we have a requesting node, then use that as our loadingPrincipal.
+      loadingPrincipal = requestingNode->NodePrincipal();
+    } else {
+      MOZ_ASSERT(aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
+      requestingWindow = mScriptGlobal->AsOuter();
+    }
+  }
+
+  if (!loadingPrincipal) {
+    if (mItemType != typeChrome) {
+      nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+      ssm->GetDocShellCodebasePrincipal(aURI, this, getter_AddRefs(loadingPrincipal));
+    } else {
+      // This is a top-level chrome load, use a system principal for the
+      // loadingPrincipal.
+      loadingPrincipal = nsContentUtils::GetSystemPrincipal();
     }
   }
 
   bool isSandBoxed = mSandboxFlags & SANDBOXED_ORIGIN;
   // only inherit if we have a triggeringPrincipal
   bool inherit = false;
 
   nsCOMPtr<nsIPrincipal> triggeringPrincipal = do_QueryInterface(aOwner);
@@ -10600,29 +10628,29 @@ nsDocShell::DoURILoad(nsIURI* aURI,
   nsSecurityFlags securityFlags = nsILoadInfo::SEC_NORMAL;
   if (inherit) {
     securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
   }
   if (isSandBoxed) {
     securityFlags |= nsILoadInfo::SEC_SANDBOXED;
   }
 
+  nsCOMPtr<nsILoadInfo> loadInfo =
+    requestingWindow ?
+      new LoadInfo(requestingWindow, loadingPrincipal, triggeringPrincipal,
+                   securityFlags) :
+      new LoadInfo(loadingPrincipal, triggeringPrincipal, requestingNode,
+                   securityFlags, aContentPolicyType);
   if (!isSrcdoc) {
     rv = NS_NewChannelInternal(getter_AddRefs(channel),
                                aURI,
-                               requestingNode,
-                               requestingNode
-                                 ? requestingNode->NodePrincipal()
-                                 : triggeringPrincipal.get(),
-                                triggeringPrincipal,
-                                securityFlags,
-                                aContentPolicyType,
-                                nullptr,   // loadGroup
-                                static_cast<nsIInterfaceRequestor*>(this),
-                                loadFlags);
+                               loadInfo,
+                               nullptr,   // loadGroup
+                               static_cast<nsIInterfaceRequestor*>(this),
+                               loadFlags);
 
     if (NS_FAILED(rv)) {
       if (rv == NS_ERROR_UNKNOWN_PROTOCOL) {
         // This is a uri with a protocol scheme we don't know how
         // to handle.  Embedders might still be interested in
         // handling the load, though, so we fire a notification
         // before throwing the load away.
         bool abort = false;
@@ -10647,36 +10675,23 @@ nsDocShell::DoURILoad(nsIURI* aURI,
     bool isViewSource;
     aURI->SchemeIs("view-source", &isViewSource);
 
     if (isViewSource) {
       nsViewSourceHandler* vsh = nsViewSourceHandler::GetInstance();
       NS_ENSURE_TRUE(vsh, NS_ERROR_FAILURE);
 
       rv = vsh->NewSrcdocChannel(aURI, aBaseURI, aSrcdoc,
-                                 requestingNode,
-                                 requestingNode
-                                   ? requestingNode->NodePrincipal()
-                                   : triggeringPrincipal.get(),
-                                 triggeringPrincipal,
-                                 securityFlags,
-                                 aContentPolicyType,
-                                 getter_AddRefs(channel));
+                                 loadInfo, getter_AddRefs(channel));
     } else {
       rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel),
                                             aURI,
                                             aSrcdoc,
                                             NS_LITERAL_CSTRING("text/html"),
-                                            requestingNode,
-                                            requestingNode ?
-                                              requestingNode->NodePrincipal() :
-                                              triggeringPrincipal.get(),
-                                            triggeringPrincipal,
-                                            securityFlags,
-                                            aContentPolicyType,
+                                            loadInfo,
                                             true);
       NS_ENSURE_SUCCESS(rv, rv);
       nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(channel);
       MOZ_ASSERT(isc);
       isc->SetBaseURI(aBaseURI);
     }
   }
 
new file mode 100644
--- /dev/null
+++ b/dom/animation/AnimationPerformanceWarning.cpp
@@ -0,0 +1,72 @@
+/* -*- 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 "AnimationPerformanceWarning.h"
+
+#include "nsContentUtils.h"
+
+namespace mozilla {
+
+bool
+AnimationPerformanceWarning::ToLocalizedString(
+  nsXPIDLString& aLocalizedString) const
+{
+  const char* key = nullptr;
+
+  switch (mType) {
+    case Type::ContentTooLarge:
+    {
+      MOZ_ASSERT(mParams && mParams->Length() == 7,
+                 "Parameter's length should be 7 for ContentTooLarge");
+
+      MOZ_ASSERT(mParams->Length() <= kMaxParamsForLocalization,
+                 "Parameter's length should be less than "
+                 "kMaxParamsForLocalization");
+      // We can pass an array of parameters whose length is greater than 7 to
+      // nsContentUtils::FormatLocalizedString because
+      // nsTextFormatter drops those extra parameters in the end.
+      nsAutoString strings[kMaxParamsForLocalization];
+      const char16_t* charParams[kMaxParamsForLocalization];
+
+      for (size_t i = 0, n = mParams->Length(); i < n; i++) {
+        strings[i].AppendInt((*mParams)[i]);
+        charParams[i] = strings[i].get();
+      }
+
+      nsresult rv = nsContentUtils::FormatLocalizedString(
+        nsContentUtils::eLAYOUT_PROPERTIES,
+        "AnimationWarningContentTooLarge",
+        charParams,
+        aLocalizedString);
+      return NS_SUCCEEDED(rv);
+    }
+    case Type::TransformBackfaceVisibilityHidden:
+      key = "AnimationWarningTransformBackfaceVisibilityHidden";
+      break;
+    case Type::TransformPreserve3D:
+      key = "AnimationWarningTransformPreserve3D";
+      break;
+    case Type::TransformSVG:
+      key = "AnimationWarningTransformSVG";
+      break;
+    case Type::TransformFrameInactive:
+      key = "AnimationWarningTransformFrameInactive";
+      break;
+    case Type::OpacityFrameInactive:
+      key = "AnimationWarningOpacityFrameInactive";
+      break;
+    case Type::WithGeometricProperties:
+      key = "AnimationWarningWithGeometricProperties";
+      break;
+  }
+
+  nsresult rv =
+    nsContentUtils::GetLocalizedString(nsContentUtils::eLAYOUT_PROPERTIES,
+                                       key, aLocalizedString);
+  return NS_SUCCEEDED(rv);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/animation/AnimationPerformanceWarning.h
@@ -0,0 +1,74 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_AnimationPerformanceWarning_h
+#define mozilla_dom_AnimationPerformanceWarning_h
+
+#include "mozilla/InitializerList.h"
+
+class nsXPIDLString;
+
+namespace mozilla {
+
+// Represents the reason why we can't run the CSS property on the compositor.
+struct AnimationPerformanceWarning
+{
+  enum class Type : uint8_t {
+    ContentTooLarge,
+    TransformBackfaceVisibilityHidden,
+    TransformPreserve3D,
+    TransformSVG,
+    TransformFrameInactive,
+    OpacityFrameInactive,
+    WithGeometricProperties
+  };
+
+  explicit AnimationPerformanceWarning(Type aType)
+    : mType(aType) { }
+
+  AnimationPerformanceWarning(Type aType,
+                              std::initializer_list<int32_t> aParams)
+    : mType(aType)
+  {
+    // FIXME:  Once std::initializer_list::size() become a constexpr function,
+    // we should use static_assert here.
+    MOZ_ASSERT(aParams.size() <= kMaxParamsForLocalization,
+      "The length of parameters should be less than "
+      "kMaxParamsForLocalization");
+    mParams.emplace(aParams);
+  }
+
+  // Maximum number of parameters passed to
+  // nsContentUtils::FormatLocalizedString to localize warning messages.
+  //
+  // NOTE: This constexpr can't be forward declared, so if you want to use
+  // this variable, please include this header file directly.
+  // This value is the same as the limit of nsStringBundle::FormatString.
+  // See the implementation of nsStringBundle::FormatString.
+  static MOZ_CONSTEXPR_VAR uint8_t kMaxParamsForLocalization = 10;
+
+  // Indicates why this property could not be animated on the compositor.
+  Type mType;
+
+  // Optional parameters that may be used for localization.
+  Maybe<nsTArray<int32_t>> mParams;
+
+  bool ToLocalizedString(nsXPIDLString& aLocalizedString) const;
+
+  bool operator==(const AnimationPerformanceWarning& aOther) const
+  {
+    return mType == aOther.mType &&
+           mParams == aOther.mParams;
+  }
+  bool operator!=(const AnimationPerformanceWarning& aOther) const
+  {
+    return !(*this == aOther);
+  }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_dom_AnimationPerformanceWarning_h
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "EffectCompositor.h"
 
 #include "mozilla/dom/Animation.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/KeyframeEffect.h" // For KeyframeEffectReadOnly
 #include "mozilla/AnimationUtils.h"
+#include "mozilla/AnimationPerformanceWarning.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/InitializerList.h"
 #include "mozilla/LayerAnimationInfo.h"
 #include "mozilla/RestyleManagerHandle.h"
 #include "mozilla/RestyleManagerHandleInlines.h"
 #include "nsComputedDOMStyle.h" // nsComputedDOMStyle::GetPresShellForContent
 #include "nsCSSPropertySet.h"
 #include "nsCSSProps.h"
@@ -104,20 +105,24 @@ FindAnimationsForCompositor(const nsIFra
   for (KeyframeEffectReadOnly* effect : *effects) {
     MOZ_ASSERT(effect && effect->GetAnimation());
     Animation* animation = effect->GetAnimation();
 
     if (!animation->IsPlaying()) {
       continue;
     }
 
-    if (effect->ShouldBlockCompositorAnimations(aFrame)) {
+    AnimationPerformanceWarning::Type warningType;
+    if (effect->ShouldBlockCompositorAnimations(aFrame,
+                                                warningType)) {
       if (aMatches) {
         aMatches->Clear();
       }
+      effect->SetPerformanceWarning(
+        aProperty, AnimationPerformanceWarning(warningType));
       return false;
     }
 
     if (!effect->HasAnimationOfProperty(aProperty)) {
       continue;
     }
 
     if (aMatches) {
@@ -711,16 +716,32 @@ EffectCompositor::GetPresContext(Element
   MOZ_ASSERT(aElement);
   nsIPresShell* shell = nsComputedDOMStyle::GetPresShellForContent(aElement);
   if (!shell) {
     return nullptr;
   }
   return shell->GetPresContext();
 }
 
+/* static */ void
+EffectCompositor::SetPerformanceWarning(
+  const nsIFrame *aFrame,
+  nsCSSProperty aProperty,
+  const AnimationPerformanceWarning& aWarning)
+{
+  EffectSet* effects = EffectSet::GetEffectSet(aFrame);
+  if (!effects) {
+    return;
+  }
+
+  for (KeyframeEffectReadOnly* effect : *effects) {
+    effect->SetPerformanceWarning(aProperty, aWarning);
+  }
+}
+
 // ---------------------------------------------------------
 //
 // Nested class: AnimationStyleRuleProcessor
 //
 // ---------------------------------------------------------
 
 NS_IMPL_ISUPPORTS(EffectCompositor::AnimationStyleRuleProcessor,
                   nsIStyleRuleProcessor)
--- a/dom/animation/EffectCompositor.h
+++ b/dom/animation/EffectCompositor.h
@@ -25,16 +25,17 @@ class nsIStyleRule;
 class nsPresContext;
 class nsStyleContext;
 
 namespace mozilla {
 
 class EffectSet;
 class RestyleTracker;
 enum class CSSPseudoElementType : uint8_t;
+struct AnimationPerformanceWarning;
 
 namespace dom {
 class Animation;
 class Element;
 }
 
 class EffectCompositor
 {
@@ -182,16 +183,23 @@ public:
   // AnimationCollection), *not* the generated content.
   //
   // Returns an empty result when a suitable element cannot be found including
   // when the frame represents a pseudo-element on which we do not support
   // animations.
   static Maybe<Pair<dom::Element*, CSSPseudoElementType>>
   GetAnimationElementAndPseudoForFrame(const nsIFrame* aFrame);
 
+  // Associates a performance warning with effects on |aFrame| that animates
+  // |aProperty|.
+  static void SetPerformanceWarning(
+    const nsIFrame* aFrame,
+    nsCSSProperty aProperty,
+    const AnimationPerformanceWarning& aWarning);
+
 private:
   ~EffectCompositor() = default;
 
   // Rebuilds the animation rule corresponding to |aCascadeLevel| on the
   // EffectSet associated with the specified (pseudo-)element.
   static void ComposeAnimationRule(dom::Element* aElement,
                                    CSSPseudoElementType aPseudoType,
                                    CascadeLevel aCascadeLevel,
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -628,16 +628,22 @@ KeyframeEffectReadOnly::SetIsRunningOnCo
 {
   MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
                                       CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
              "Property being animated on compositor is a recognized "
              "compositor-animatable property");
   for (AnimationProperty& property : mProperties) {
     if (property.mProperty == aProperty) {
       property.mIsRunningOnCompositor = aIsRunning;
+      // We currently only set a performance warning message when animations
+      // cannot be run on the compositor, so if this animation is running
+      // on the compositor we don't need a message.
+      if (aIsRunning) {
+        property.mPerformanceWarning.reset();
+      }
       return;
     }
   }
 }
 
 KeyframeEffectReadOnly::~KeyframeEffectReadOnly()
 {
 }
@@ -1866,16 +1872,42 @@ KeyframeEffectReadOnly::GetFrames(JSCont
       previousEntry = entry;
       entry = &entries[i];
     } while (entry->SameKeyframe(*previousEntry));
 
     aResult.AppendElement(keyframe);
   }
 }
 
+
+void
+KeyframeEffectReadOnly::GetPropertyState(
+    nsTArray<AnimationPropertyState>& aStates) const
+{
+  for (const AnimationProperty& property : mProperties) {
+    // Bug 1252730: We should also expose this winsInCascade as well.
+    if (!property.mWinsInCascade) {
+      continue;
+    }
+
+    AnimationPropertyState state;
+    state.mProperty.Construct(
+      NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(property.mProperty)));
+    state.mRunningOnCompositor.Construct(property.mIsRunningOnCompositor);
+
+    nsXPIDLString localizedString;
+    if (property.mPerformanceWarning &&
+        property.mPerformanceWarning->ToLocalizedString(localizedString)) {
+      state.mWarning.Construct(localizedString);
+    }
+
+    aStates.AppendElement(state);
+  }
+}
+
 /* static */ const TimeDuration
 KeyframeEffectReadOnly::OverflowRegionRefreshInterval()
 {
   // The amount of time we can wait between updating throttled animations
   // on the main thread that influence the overflow region.
   static const TimeDuration kOverflowRegionRefreshInterval =
     TimeDuration::FromMilliseconds(200);
 
@@ -2064,101 +2096,104 @@ KeyframeEffectReadOnly::IsGeometricPrope
     default:
       return false;
   }
 }
 
 /* static */ bool
 KeyframeEffectReadOnly::CanAnimateTransformOnCompositor(
   const nsIFrame* aFrame,
-  const nsIContent* aContent)
+  AnimationPerformanceWarning::Type& aPerformanceWarning)
 {
   // Disallow OMTA for preserve-3d transform. Note that we check the style property
   // rather than Extend3DContext() since that can recurse back into this function
-  // via HasOpacity().
+  // via HasOpacity(). See bug 779598.
   if (aFrame->Combines3DTransformWithAncestors() ||
       aFrame->StyleDisplay()->mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) {
-    if (aContent) {
-      nsCString message;
-      message.AppendLiteral("Gecko bug: Async animation of 'preserve-3d' "
-        "transforms is not supported.  See bug 779598");
-      AnimationUtils::LogAsyncAnimationFailure(message, aContent);
-    }
+    aPerformanceWarning = AnimationPerformanceWarning::Type::TransformPreserve3D;
     return false;
   }
   // Note that testing BackfaceIsHidden() is not a sufficient test for
   // what we need for animating backface-visibility correctly if we
   // remove the above test for Extend3DContext(); that would require
-  // looking at backface-visibility on descendants as well.
+  // looking at backface-visibility on descendants as well. See bug 1186204.
   if (aFrame->StyleDisplay()->BackfaceIsHidden()) {
-    if (aContent) {
-      nsCString message;
-      message.AppendLiteral("Gecko bug: Async animation of "
-        "'backface-visibility: hidden' transforms is not supported."
-        "  See bug 1186204.");
-      AnimationUtils::LogAsyncAnimationFailure(message, aContent);
-    }
+    aPerformanceWarning =
+      AnimationPerformanceWarning::Type::TransformBackfaceVisibilityHidden;
     return false;
   }
+  // Async 'transform' animations of aFrames with SVG transforms is not
+  // supported.  See bug 779599.
   if (aFrame->IsSVGTransformed()) {
-    if (aContent) {
-      nsCString message;
-      message.AppendLiteral("Gecko bug: Async 'transform' animations of "
-        "aFrames with SVG transforms is not supported.  See bug 779599");
-      AnimationUtils::LogAsyncAnimationFailure(message, aContent);
-    }
+    aPerformanceWarning = AnimationPerformanceWarning::Type::TransformSVG;
     return false;
   }
 
   return true;
 }
 
 bool
-KeyframeEffectReadOnly::ShouldBlockCompositorAnimations(const nsIFrame*
-                                                          aFrame) const
+KeyframeEffectReadOnly::ShouldBlockCompositorAnimations(
+  const nsIFrame* aFrame,
+  AnimationPerformanceWarning::Type& aPerformanceWarning) const
 {
   // We currently only expect this method to be called when this effect
   // is attached to a playing Animation. If that ever changes we'll need
   // to update this to only return true when that is the case since paused,
   // filling, cancelled Animations etc. shouldn't stop other Animations from
   // running on the compositor.
   MOZ_ASSERT(mAnimation && mAnimation->IsPlaying());
 
-  bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled();
-
   for (const AnimationProperty& property : mProperties) {
     // If a property is overridden in the CSS cascade, it should not block other
     // animations from running on the compositor.
     if (!property.mWinsInCascade) {
       continue;
     }
     // Check for geometric properties
     if (IsGeometricProperty(property.mProperty)) {
-      if (shouldLog) {
-        nsCString message;
-        message.AppendLiteral("Performance warning: Async animation of "
-          "'transform' or 'opacity' not possible due to animation of geometric"
-          "properties on the same element");
-        AnimationUtils::LogAsyncAnimationFailure(message, aFrame->GetContent());
-      }
+      aPerformanceWarning =
+        AnimationPerformanceWarning::Type::WithGeometricProperties;
       return true;
     }
 
     // Check for unsupported transform animations
     if (property.mProperty == eCSSProperty_transform) {
       if (!CanAnimateTransformOnCompositor(aFrame,
-            shouldLog ? aFrame->GetContent() : nullptr)) {
+                                           aPerformanceWarning)) {
         return true;
       }
     }
   }
 
   return false;
 }
 
+void
+KeyframeEffectReadOnly::SetPerformanceWarning(
+  nsCSSProperty aProperty,
+  const AnimationPerformanceWarning& aWarning)
+{
+  for (AnimationProperty& property : mProperties) {
+    if (property.mProperty == aProperty &&
+        (!property.mPerformanceWarning ||
+         *property.mPerformanceWarning != aWarning)) {
+      property.mPerformanceWarning = Some(aWarning);
+
+      nsXPIDLString localizedString;
+      if (nsLayoutUtils::IsAnimationLoggingEnabled() &&
+          property.mPerformanceWarning->ToLocalizedString(localizedString)) {
+        nsAutoCString logMessage = NS_ConvertUTF16toUTF8(localizedString);
+        AnimationUtils::LogAsyncAnimationFailure(logMessage, mTarget);
+      }
+      return;
+    }
+  }
+}
+
 //---------------------------------------------------------------------
 //
 // KeyframeEffect
 //
 //---------------------------------------------------------------------
 
 KeyframeEffect::KeyframeEffect(nsIDocument* aDocument,
                                Element* aTarget,
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_KeyframeEffect_h
 #define mozilla_dom_KeyframeEffect_h
 
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIDocument.h"
 #include "nsWrapperCache.h"
+#include "mozilla/AnimationPerformanceWarning.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ComputedTimingFunction.h" // ComputedTimingFunction
 #include "mozilla/LayerAnimationInfo.h"     // LayerAnimations::kRecords
 #include "mozilla/OwningNonNull.h"          // OwningNonNull<...>
 #include "mozilla/StickyTimeDuration.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/AnimationEffectReadOnly.h"
@@ -38,16 +39,17 @@ class AnimValuesStyleRule;
 enum class CSSPseudoElementType : uint8_t;
 
 namespace dom {
 class ElementOrCSSPseudoElement;
 class OwningElementOrCSSPseudoElement;
 class UnrestrictedDoubleOrKeyframeEffectOptions;
 enum class IterationCompositeOperation : uint32_t;
 enum class CompositeOperation : uint32_t;
+struct AnimationPropertyState;
 }
 
 /**
  * Stores the results of calculating the timing properties of an animation
  * at a given sample time.
  */
 struct ComputedTiming
 {
@@ -140,16 +142,18 @@ struct AnimationProperty
   // between calling RequestRestyle on its EffectCompositor and when the
   // restyle is performed, this member may temporarily become false even if
   // the animation remains on the layer after the restyle.
   //
   // **NOTE**: This member is not included when comparing AnimationProperty
   // objects for equality.
   bool mIsRunningOnCompositor = false;
 
+  Maybe<AnimationPerformanceWarning> mPerformanceWarning;
+
   InfallibleTArray<AnimationPropertySegment> mSegments;
 
   // NOTE: This operator does *not* compare the mWinsInCascade member *or* the
   // mIsRunningOnCompositor member.
   // This is because AnimationProperty objects are compared when recreating
   // CSS animations to determine if mutation observer change records need to
   // be created or not. However, at the point when these objects are compared
   // neither the mWinsInCascade nor the mIsRunningOnCompositor will have been
@@ -298,35 +302,50 @@ public:
   // contained in |aSetProperties|.
   // Any updated properties are added to |aSetProperties|.
   void ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
                     nsCSSPropertySet& aSetProperties);
   // Returns true if at least one property is being animated on compositor.
   bool IsRunningOnCompositor() const;
   void SetIsRunningOnCompositor(nsCSSProperty aProperty, bool aIsRunning);
 
+  void GetPropertyState(nsTArray<AnimationPropertyState>& aStates) const;
+
   // Returns true if this effect, applied to |aFrame|, contains
   // properties that mean we shouldn't run *any* compositor animations on this
   // element.
   //
   // For example, if we have an animation of geometric properties like 'left'
   // and 'top' on an element, we force all 'transform' and 'opacity' animations
   // running at the same time on the same element to run on the main thread.
   //
   // Similarly, some transform animations cannot be run on the compositor and
   // when that is the case we simply disable all compositor animations
   // on the same element.
   //
   // Bug 1218620 - It seems like we don't need to be this restrictive. Wouldn't
   // it be ok to do 'opacity' animations on the compositor in either case?
-  bool ShouldBlockCompositorAnimations(const nsIFrame* aFrame) const;
+  //
+  // When returning true, |aOutPerformanceWarning| stores the reason why
+  // we shouldn't run the compositor animations.
+  bool ShouldBlockCompositorAnimations(
+    const nsIFrame* aFrame,
+    AnimationPerformanceWarning::Type& aPerformanceWarning) const;
 
   nsIDocument* GetRenderedDocument() const;
   nsPresContext* GetPresContext() const;
 
+  // Associates a warning with the animated property on the specified frame
+  // indicating why, for example, the property could not be animated on the
+  // compositor. |aParams| and |aParamsLength| are optional parameters which
+  // will be used to generate a localized message for devtools.
+  void SetPerformanceWarning(
+    nsCSSProperty aProperty,
+    const AnimationPerformanceWarning& aWarning);
+
 protected:
   KeyframeEffectReadOnly(nsIDocument* aDocument,
                          Element* aTarget,
                          CSSPseudoElementType aPseudoType,
                          AnimationEffectTimingReadOnly* aTiming);
 
   virtual ~KeyframeEffectReadOnly();
 
@@ -378,21 +397,21 @@ protected:
 
 private:
   nsIFrame* GetAnimationFrame() const;
 
   bool CanThrottle() const;
   bool CanThrottleTransformChanges(nsIFrame& aFrame) const;
 
   // Returns true unless Gecko limitations prevent performing transform
-  // animations for |aFrame|. Any limitations that are encountered are
-  // logged using |aContent| to describe the affected content.
-  // If |aContent| is nullptr, no logging is performed
-  static bool CanAnimateTransformOnCompositor(const nsIFrame* aFrame,
-                                              const nsIContent* aContent);
+  // animations for |aFrame|. When returning true, the reason for the
+  // limitation is stored in |aOutPerformanceWarning|.
+  static bool CanAnimateTransformOnCompositor(
+    const nsIFrame* aFrame,
+    AnimationPerformanceWarning::Type& aPerformanceWarning);
   static bool IsGeometricProperty(const nsCSSProperty aProperty);
 
   static const TimeDuration OverflowRegionRefreshInterval();
 };
 
 class KeyframeEffect : public KeyframeEffectReadOnly
 {
 public:
--- a/dom/animation/moz.build
+++ b/dom/animation/moz.build
@@ -15,30 +15,32 @@ EXPORTS.mozilla.dom += [
     'AnimationTimeline.h',
     'CSSPseudoElement.h',
     'DocumentTimeline.h',
     'KeyframeEffect.h',
 ]
 
 EXPORTS.mozilla += [
     'AnimationComparator.h',
+    'AnimationPerformanceWarning.h',
     'AnimationUtils.h',
     'AnimValuesStyleRule.h',
     'ComputedTimingFunction.h',
     'EffectCompositor.h',
     'EffectSet.h',
     'PendingAnimationTracker.h',
     'PseudoElementHashEntry.h',
 ]
 
 UNIFIED_SOURCES += [
     'Animation.cpp',
     'AnimationEffectReadOnly.cpp',
     'AnimationEffectTiming.cpp',
     'AnimationEffectTimingReadOnly.cpp',
+    'AnimationPerformanceWarning.cpp',
     'AnimationTimeline.cpp',
     'AnimationUtils.cpp',
     'AnimValuesStyleRule.cpp',
     'ComputedTimingFunction.cpp',
     'CSSPseudoElement.cpp',
     'DocumentTimeline.cpp',
     'EffectCompositor.cpp',
     'EffectSet.cpp',
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -2,11 +2,12 @@
 support-files =
   testcommon.js
   ../../imptests/testharness.js
   ../../imptests/testharnessreport.js
 [chrome/test_animate_xrays.html]
 # file_animate_xrays.html needs to go in mochitest.ini since it is served
 # over HTTP
 [chrome/test_animation_observers.html]
+[chrome/test_animation_property_state.html]
 [chrome/test_restyles.html]
 [chrome/test_running_on_compositor.html]
 skip-if = buildapp == 'b2g'
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/chrome/test_animation_property_state.html
@@ -0,0 +1,330 @@
+<!doctype html>
+<head>
+<meta charset=utf-8>
+<title>Bug 1196114 - Animation property which indicates
+       running on the compositor or not</title>
+<script type="application/javascript" src="../testharness.js"></script>
+<script type="application/javascript" src="../testharnessreport.js"></script>
+<script type="application/javascript" src="../testcommon.js"></script>
+<style>
+.compositable {
+  /* Element needs geometry to be eligible for layerization */
+  width: 100px;
+  height: 100px;
+  background-color: white;
+}
+</style>
+</head>
+<body>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1196114"
+  target="_blank">Mozilla Bug 1196114</a>
+<div id="log"></div>
+<script>
+'use strict';
+
+// This is used for obtaining localized strings.
+var gStringBundle;
+
+SpecialPowers.pushPrefEnv({ "set": [["general.useragent.locale", "en-US"]] },
+                          start);
+
+function compare_property_state(a, b) {
+  if (a.property > b.property) {
+    return -1;
+  } else if (a.property < b.property) {
+    return 1;
+  }
+  if (a.runningOnCompositor != b.runningOnCompositor) {
+    return a.runningOnCompositor ? 1 : -1;
+  }
+  return a.warning > b.warning ? -1 : 1;
+}
+
+function assert_animation_property_state_equals(actual, expected) {
+  assert_equals(actual.length, expected.length);
+
+  var sortedActual = actual.sort(compare_property_state);
+  var sortedExpected = expected.sort(compare_property_state);
+
+  for (var i = 0; i < sortedActual.length; i++) {
+    assert_equals(sortedActual[i].property,
+                  sortedExpected[i].property,
+                  'CSS property name should match');
+    assert_equals(sortedActual[i].runningOnCompositor,
+                  sortedExpected[i].runningOnCompositor,
+                  'runningOnCompositor property should match');
+    if (sortedExpected[i].warning instanceof RegExp) {
+      assert_regexp_match(sortedActual[i].warning,
+                          sortedExpected[i].warning,
+                          'warning message should match');
+    } else if (sortedExpected[i].warning) {
+      assert_equals(sortedActual[i].warning,
+                    gStringBundle.GetStringFromName(sortedExpected[i].warning),
+                    'warning message should match');
+    }
+  }
+}
+
+// Check that the animation is running on compositor and
+// warning property is not set for the CSS property regardless
+// expected values.
+function assert_property_state_on_compositor(actual, expected) {
+  assert_equals(actual.length, expected.length);
+
+  var sortedActual = actual.sort(compare_property_state);
+  var sortedExpected = expected.sort(compare_property_state);
+
+  for (var i = 0; i < sortedActual.length; i++) {
+    assert_equals(sortedActual[i].property,
+                  sortedExpected[i].property,
+                  'CSS property name should match');
+    assert_true(sortedActual[i].runningOnCompositor,
+                'runningOnCompositor property should be true');
+    assert_not_exists(sortedActual[i], 'warning',
+                      'warning property should not be set');
+  }
+}
+
+var gAnimationsTests = [
+  {
+    desc: 'animations on compositor',
+    frames: {
+      opacity: [0, 1]
+    },
+    expected: [
+      {
+        property: 'opacity',
+        runningOnCompositor: true
+      }
+    ]
+  },
+  {
+    desc: 'animations on main thread',
+    frames: {
+      backgroundColor: ['white', 'red']
+    },
+    expected: [
+      {
+        property: 'background-color',
+        runningOnCompositor: false
+      }
+    ]
+  },
+  {
+    desc: 'animations on both threads',
+    frames: {
+      backgroundColor: ['white', 'red'],
+      transform: ['translate(0px)', 'translate(100px)']
+    },
+    expected: [
+      {
+        property: 'background-color',
+        runningOnCompositor: false
+      },
+      {
+        property: 'transform',
+        runningOnCompositor: true
+      }
+    ]
+  },
+  {
+    desc: 'two animation properties on compositor thread',
+    frames: {
+      opacity: [0, 1],
+      transform: ['translate(0px)', 'translate(100px)']
+    },
+    expected: [
+      {
+        property: 'opacity',
+        runningOnCompositor: true
+      },
+      {
+        property: 'transform',
+        runningOnCompositor: true
+      }
+    ]
+  },
+  {
+    // FIXME: Once we have KeyframeEffect.setFrames, we should rewrite
+    // this test case to check that runningOnCompositor is restored to true
+    // after 'width' keyframe is removed from the keyframes.
+    desc: 'animation on compositor with animation of geometric properties',
+    frames: {
+      width: ['100px', '200px'],
+      transform: ['translate(0px)', 'translate(100px)']
+    },
+    expected: [
+      {
+        property: 'width',
+        runningOnCompositor: false
+      },
+      {
+        property: 'transform',
+        runningOnCompositor: false,
+        warning: 'AnimationWarningWithGeometricProperties'
+      }
+    ]
+  },
+];
+
+gAnimationsTests.forEach(function(subtest) {
+  promise_test(function(t) {
+    var div = addDiv(t, { class: 'compositable' });
+    var animation = div.animate(subtest.frames, 100000);
+    return animation.ready.then(t.step_func(function() {
+      assert_animation_property_state_equals(
+        animation.effect.getPropertyState(),
+        subtest.expected);
+    }));
+  }, subtest.desc);
+});
+
+var gPerformanceWarningTests = [
+  {
+    desc: 'preserve-3d transform',
+    frames: {
+      transform: ['translate(0px)', 'translate(100px)']
+    },
+    style: 'transform-style: preserve-3d',
+    expected: [
+      {
+        property: 'transform',
+        runningOnCompositor: false,
+        warning: 'AnimationWarningTransformPreserve3D'
+      }
+    ]
+  },
+  {
+    desc: 'transform with backface-visibility:hidden',
+    frames: {
+      transform: ['translate(0px)', 'translate(100px)']
+    },
+    style: 'backface-visibility: hidden;',
+    expected: [
+      {
+        property: 'transform',
+        runningOnCompositor: false,
+        warning: 'AnimationWarningTransformBackfaceVisibilityHidden'
+      }
+    ]
+  },
+];
+
+function start() {
+  var bundleService = SpecialPowers.Cc['@mozilla.org/intl/stringbundle;1']
+    .getService(SpecialPowers.Ci.nsIStringBundleService);
+  gStringBundle = bundleService
+    .createBundle("chrome://global/locale/layout_errors.properties");
+
+  gAnimationsTests.forEach(function(subtest) {
+    promise_test(function(t) {
+      var div = addDiv(t, { class: 'compositable' });
+      var animation = div.animate(subtest.frames, 100000);
+      return animation.ready.then(t.step_func(function() {
+        assert_animation_property_state_equals(
+          animation.effect.getPropertyState(),
+          subtest.expected);
+      }));
+    }, subtest.desc);
+  });
+
+  gPerformanceWarningTests.forEach(function(subtest) {
+    promise_test(function(t) {
+      var div = addDiv(t, { class: 'compositable' });
+      var animation = div.animate(subtest.frames, 100000);
+      return animation.ready.then(t.step_func(function() {
+        assert_property_state_on_compositor(
+          animation.effect.getPropertyState(),
+          subtest.expected);
+        div.style = subtest.style;
+        return waitForFrame();
+      })).then(t.step_func(function() {
+        assert_animation_property_state_equals(
+          animation.effect.getPropertyState(),
+          subtest.expected);
+        div.style = '';
+        return waitForFrame();
+      })).then(t.step_func(function() {
+        assert_property_state_on_compositor(
+          animation.effect.getPropertyState(),
+          subtest.expected);
+      }));
+    }, subtest.desc);
+  });
+
+  promise_test(function(t) {
+    var div = addDiv(t, { class: 'compositable' });
+    var animation = div.animate(
+      { transform: ['translate(0px)', 'translate(100px)'] }, 100000);
+    return animation.ready.then(t.step_func(function() {
+      assert_animation_property_state_equals(
+        animation.effect.getPropertyState(),
+        [ { property: 'transform', runningOnCompositor: true } ]);
+      div.style = 'width: 10000px; height: 10000px';
+      return waitForFrame();
+    })).then(t.step_func(function() {
+      // viewport depends on test environment.
+      var expectedWarning = new RegExp(
+        "Async animation disabled because frame size \\(10000, 10000\\) is " +
+        "bigger than the viewport \\(\\d+, \\d+\\) or the visual rectangle " +
+        "\\(10000, 10000\\) is larger than the max allowed value \\(\\d+\\)");
+      assert_animation_property_state_equals(
+        animation.effect.getPropertyState(),
+        [ {
+          property: 'transform',
+          runningOnCompositor: false,
+          warning: expectedWarning
+        } ]);
+      div.style = 'width: 100px; height: 100px';
+      return waitForFrame();
+    })).then(t.step_func(function() {
+      // FIXME: Bug 1253164: the animation should get back on compositor.
+      assert_animation_property_state_equals(
+        animation.effect.getPropertyState(),
+        [ { property: 'transform', runningOnCompositor: false } ]);
+    }));
+  }, 'transform on too big element');
+
+  promise_test(function(t) {
+    var svg  = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+    svg.setAttribute('width', '100');
+    svg.setAttribute('height', '100');
+    var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+    rect.setAttribute('width', '100');
+    rect.setAttribute('height', '100');
+    rect.setAttribute('fill', 'red');
+    svg.appendChild(rect);
+    document.body.appendChild(svg);
+    t.add_cleanup(function() {
+      svg.remove();
+    });
+
+    var animation = svg.animate(
+      { transform: ['translate(0px)', 'translate(100px)'] }, 100000);
+    return animation.ready.then(t.step_func(function() {
+      assert_animation_property_state_equals(
+        animation.effect.getPropertyState(),
+        [ { property: 'transform', runningOnCompositor: true } ]);
+      svg.setAttribute('transform', 'translate(10, 20)');
+      return waitForFrame();
+    })).then(t.step_func(function() {
+      assert_animation_property_state_equals(
+        animation.effect.getPropertyState(),
+        [ {
+          property: 'transform',
+          runningOnCompositor: false,
+          warning: 'AnimationWarningTransformSVG'
+        } ]);
+      svg.removeAttribute('transform');
+      return waitForFrame();
+    })).then(t.step_func(function() {
+      assert_animation_property_state_equals(
+        animation.effect.getPropertyState(),
+        [ { property: 'transform', runningOnCompositor: true } ]);
+    }));
+  }, 'transform of nsIFrame with SVG transform');
+}
+
+</script>
+
+</body>
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -107,17 +107,17 @@
 #ifdef MOZ_XUL
 #include "nsIXULDocument.h"
 #endif /* MOZ_XUL */
 
 #include "nsCCUncollectableMarker.h"
 
 #include "mozAutoDocUpdate.h"
 
-#include "prprf.h"
+#include "mozilla/Snprintf.h"
 #include "nsDOMMutationObserver.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsCycleCollector.h"
 #include "xpcpublic.h"
 #include "nsIScriptError.h"
 #include "mozilla/Telemetry.h"
 
 #include "mozilla/CORSMode.h"
@@ -1878,23 +1878,23 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     if (!tmp->IsInDoc() &&
         // Ignore xbl:content, which is never in the document and hence always
         // appears to be orphaned.
         !tmp->NodeInfo()->Equals(nsGkAtoms::content, kNameSpaceID_XBL)) {
       orphan.AppendLiteral(" (orphan)");
     }
 
     const char* nsuri = nsid < ArrayLength(kNSURIs) ? kNSURIs[nsid] : "";
-    PR_snprintf(name, sizeof(name), "FragmentOrElement%s %s%s%s%s %s",
-                nsuri,
-                localName.get(),
-                NS_ConvertUTF16toUTF8(id).get(),
-                NS_ConvertUTF16toUTF8(classes).get(),
-                orphan.get(),
-                uri.get());
+    snprintf_literal(name, "FragmentOrElement%s %s%s%s%s %s",
+                     nsuri,
+                     localName.get(),
+                     NS_ConvertUTF16toUTF8(id).get(),
+                     NS_ConvertUTF16toUTF8(classes).get(),
+                     orphan.get(),
+                     uri.get());
     cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
   }
   else {
     NS_IMPL_CYCLE_COLLECTION_DESCRIBE(FragmentOrElement, tmp->mRefCnt.get())
   }
 
   // Always need to traverse script objects, so do that before we check
   // if we're uncollectable.
--- a/dom/base/NodeInfo.cpp
+++ b/dom/base/NodeInfo.cpp
@@ -20,17 +20,17 @@
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsIAtom.h"
 #include "nsDOMString.h"
 #include "nsCRT.h"
 #include "nsContentUtils.h"
 #include "nsReadableUtils.h"
 #include "nsAutoPtr.h"
-#include "prprf.h"
+#include "mozilla/Snprintf.h"
 #include "nsIDocument.h"
 #include "nsGkAtoms.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsNameSpaceManager.h"
 
 using namespace mozilla;
 using mozilla::dom::NodeInfo;
 
@@ -125,21 +125,21 @@ static const char* kNodeInfoNSURIs[] = {
 };
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(NodeInfo)
   if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
     char name[72];
     uint32_t nsid = tmp->NamespaceID();
     nsAtomCString localName(tmp->NameAtom());
     if (nsid < ArrayLength(kNodeInfoNSURIs)) {
-      PR_snprintf(name, sizeof(name), "NodeInfo%s %s", kNodeInfoNSURIs[nsid],
-                  localName.get());
+      snprintf_literal(name, "NodeInfo%s %s", kNodeInfoNSURIs[nsid],
+                       localName.get());
     }
     else {
-      PR_snprintf(name, sizeof(name), "NodeInfo %s", localName.get());
+      snprintf_literal(name, "NodeInfo %s", localName.get());
     }
 
     cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
   }
   else {
     NS_IMPL_CYCLE_COLLECTION_DESCRIBE(NodeInfo, tmp->mRefCnt.get())
   }
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -18,17 +18,17 @@
 #include "mozilla/EffectSet.h"
 #include "mozilla/IntegerRange.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Likely.h"
 #include <algorithm>
 
 #include "mozilla/Logging.h"
 #include "plstr.h"
-#include "prprf.h"
+#include "mozilla/Snprintf.h"
 
 #include "mozilla/Telemetry.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsILoadContext.h"
 #include "nsUnicharUtils.h"
 #include "nsContentList.h"
 #include "nsCSSPseudoElements.h"
@@ -1772,22 +1772,22 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     } else {
       loadedAsData.AssignLiteral("normal");
     }
     uint32_t nsid = tmp->GetDefaultNamespaceID();
     nsAutoCString uri;
     if (tmp->mDocumentURI)
       tmp->mDocumentURI->GetSpec(uri);
     if (nsid < ArrayLength(kNSURIs)) {
-      PR_snprintf(name, sizeof(name), "nsDocument %s %s %s",
-                  loadedAsData.get(), kNSURIs[nsid], uri.get());
+      snprintf_literal(name, "nsDocument %s %s %s",
+                       loadedAsData.get(), kNSURIs[nsid], uri.get());
     }
     else {
-      PR_snprintf(name, sizeof(name), "nsDocument %s %s",
-                  loadedAsData.get(), uri.get());
+      snprintf_literal(name, "nsDocument %s %s",
+                       loadedAsData.get(), uri.get());
     }
     cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
   }
   else {
     NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsDocument, tmp->mRefCnt.get())
   }
 
   // Always need to traverse script objects, so do that before we check
@@ -2946,20 +2946,19 @@ nsDocument::GetLastModified(nsAString& a
 
 static void
 GetFormattedTimeString(PRTime aTime, nsAString& aFormattedTimeString)
 {
   PRExplodedTime prtime;
   PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
   // "MM/DD/YYYY hh:mm:ss"
   char formatedTime[24];
-  if (PR_snprintf(formatedTime, sizeof(formatedTime),
-                  "%02ld/%02ld/%04hd %02ld:%02ld:%02ld",
-                  prtime.tm_month + 1, prtime.tm_mday, prtime.tm_year,
-                  prtime.tm_hour     ,  prtime.tm_min,  prtime.tm_sec)) {
+  if (snprintf_literal(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d",
+                       prtime.tm_month + 1, prtime.tm_mday, int(prtime.tm_year),
+                       prtime.tm_hour     ,  prtime.tm_min,  prtime.tm_sec)) {
     CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString);
   } else {
     // If we for whatever reason failed to find the last modified time
     // (or even the current time), fall back to what NS4.x returned.
     aFormattedTimeString.AssignLiteral(MOZ_UTF16("01/01/1970 00:00:00"));
   }
 }
 
--- a/dom/base/nsGenericDOMDataNode.cpp
+++ b/dom/base/nsGenericDOMDataNode.cpp
@@ -29,17 +29,17 @@
 #include "nsCOMArray.h"
 #include "nsNodeUtils.h"
 #include "mozilla/dom/DirectionalityUtils.h"
 #include "nsBindingManager.h"
 #include "nsCCUncollectableMarker.h"
 #include "mozAutoDocUpdate.h"
 
 #include "PLDHashTable.h"
-#include "prprf.h"
+#include "mozilla/Snprintf.h"
 #include "nsWrapperCacheInlines.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsGenericDOMDataNode::nsGenericDOMDataNode(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsIContent(aNodeInfo)
 {
@@ -85,18 +85,18 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGenericDOMDataNode)
   return Element::CanSkipThis(tmp);
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGenericDOMDataNode)
   if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
     char name[40];
-    PR_snprintf(name, sizeof(name), "nsGenericDOMDataNode (len=%d)",
-                tmp->mText.GetLength());
+    snprintf_literal(name, "nsGenericDOMDataNode (len=%d)",
+                     tmp->mText.GetLength());
     cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
   } else {
     NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGenericDOMDataNode, tmp->mRefCnt.get())
   }
 
   // Always need to traverse script objects, so do that before we check
   // if we're uncollectable.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
@@ -421,17 +421,17 @@ nsGenericDOMDataNode::ToCString(nsAStrin
       if (ch == '&') {
         aBuf.AppendLiteral("&amp;");
       } else if (ch == '<') {
         aBuf.AppendLiteral("&lt;");
       } else if (ch == '>') {
         aBuf.AppendLiteral("&gt;");
       } else if ((ch < ' ') || (ch >= 127)) {
         char buf[10];
-        PR_snprintf(buf, sizeof(buf), "\\u%04x", ch);
+        snprintf_literal(buf, "\\u%04x", ch);
         AppendASCIItoUTF16(buf, aBuf);
       } else {
         aBuf.Append(ch);
       }
     }
   } else {
     unsigned char* cp = (unsigned char*)mText.Get1b() + aOffset;
     const unsigned char* end = cp + aLen;
@@ -441,17 +441,17 @@ nsGenericDOMDataNode::ToCString(nsAStrin
       if (ch == '&') {
         aBuf.AppendLiteral("&amp;");
       } else if (ch == '<') {
         aBuf.AppendLiteral("&lt;");
       } else if (ch == '>') {
         aBuf.AppendLiteral("&gt;");
       } else if ((ch < ' ') || (ch >= 127)) {
         char buf[10];
-        PR_snprintf(buf, sizeof(buf), "\\u%04x", ch);
+        snprintf_literal(buf, "\\u%04x", ch);
         AppendASCIItoUTF16(buf, aBuf);
       } else {
         aBuf.Append(ch);
       }
     }
   }
 }
 #endif
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -15,16 +15,17 @@
 #include "nsScreen.h"
 #include "nsHistory.h"
 #include "nsPerformance.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsIDOMStorageManager.h"
 #include "mozilla/dom/DOMStorage.h"
 #include "mozilla/dom/StorageEvent.h"
 #include "mozilla/dom/StorageEventBinding.h"
+#include "mozilla/IntegerPrintfMacros.h"
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
 #include "mozilla/dom/WindowOrientationObserver.h"
 #endif
 #include "nsDOMOfflineResourceList.h"
 #include "nsError.h"
 #include "nsIIdleService.h"
 #include "nsISizeOfEventTarget.h"
 #include "nsDOMJSUtils.h"
@@ -1811,19 +1812,18 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalW
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow)
   if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
     char name[512];
     nsAutoCString uri;
     if (tmp->mDoc && tmp->mDoc->GetDocumentURI()) {
       tmp->mDoc->GetDocumentURI()->GetSpec(uri);
     }
-    PR_snprintf(name, sizeof(name), "nsGlobalWindow #%llu %s %s",
-                tmp->mWindowID, tmp->IsInnerWindow() ? "inner" : "outer",
-                uri.get());
+    snprintf_literal(name, "nsGlobalWindow # %" PRIu64 " %s %s", tmp->mWindowID,
+                     tmp->IsInnerWindow() ? "inner" : "outer", uri.get());
     cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
   } else {
     NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGlobalWindow, tmp->mRefCnt.get())
   }
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
--- a/dom/base/nsXMLContentSerializer.cpp
+++ b/dom/base/nsXMLContentSerializer.cpp
@@ -18,17 +18,17 @@
 #include "nsIDOMDocumentType.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsIDocumentEncoder.h"
 #include "nsIParserService.h"
 #include "nsNameSpaceManager.h"
 #include "nsTextFragment.h"
 #include "nsString.h"
-#include "prprf.h"
+#include "mozilla/Snprintf.h"
 #include "nsUnicharUtils.h"
 #include "nsCRT.h"
 #include "nsContentUtils.h"
 #include "nsAttrName.h"
 #include "nsILineBreaker.h"
 #include "mozilla/dom/Element.h"
 #include "nsParserConstants.h"
 
@@ -603,17 +603,17 @@ nsXMLContentSerializer::ConfirmPrefix(ns
   return true;
 }
 
 void
 nsXMLContentSerializer::GenerateNewPrefix(nsAString& aPrefix)
 {
   aPrefix.Assign('a');
   char buf[128];
-  PR_snprintf(buf, sizeof(buf), "%d", mPrefixIndex++);
+  snprintf_literal(buf, "%d", mPrefixIndex++);
   AppendASCIItoUTF16(buf, aPrefix);
 }
 
 bool
 nsXMLContentSerializer::SerializeAttr(const nsAString& aPrefix,
                                       const nsAString& aName,
                                       const nsAString& aValue,
                                       nsAString& aStr,
--- a/dom/base/test/browser_bug902350.js
+++ b/dom/base/test/browser_bug902350.js
@@ -33,17 +33,16 @@ function test() {
 
   BrowserTestUtils.browserLoaded(gTestBrowser, true /*includeSubFrames*/).then(MixedTest1A);
   var url = gHttpTestRoot + "file_bug902350.html";
   gTestBrowser.loadURI(url);
 }
 
 // Need to capture 2 loads, one for the main page and one for the iframe
 function MixedTest1A() {
-  dump("XYZ\n");
   BrowserTestUtils.browserLoaded(gTestBrowser, true /*includeSubFrames*/).then(MixedTest1B);
 }
 
 // Find the iframe and click the link in it
 function MixedTest1B() {
   BrowserTestUtils.browserLoaded(gTestBrowser).then(MixedTest1C);
 
   ContentTask.spawn(gTestBrowser, null, function() {
@@ -57,14 +56,13 @@ function MixedTest1B() {
   ok (!gIdentityHandler._identityBox.classList.contains("mixedActiveBlocked"),
       "Mixed Content Doorhanger did not appear when trying to navigate top");
 }
 
 function MixedTest1C() {
   ContentTask.spawn(gTestBrowser, null, function() {
     return content.location.href;
   }).then(url => {
-    ok(gTestBrowser.contentWindow.location == "http://example.com/", "Navigating to insecure domain through target='_top' failed.")
+    is(url, "http://example.com/", "Navigating to insecure domain through target='_top' failed.")
     MixedTestsCompleted();
   });
 }
 
-
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -255,16 +255,17 @@ support-files =
   performance_observer.html
   test_anonymousContent_style_csp.html^headers^
   file_explicit_user_agent.sjs
   referrer_change_server.sjs
   file_change_policy_redirect.html
   file_bug1198095.js
   file_bug1250148.sjs
   mozbrowser_api_utils.js
+  websocket_helpers.js
 
 [test_anonymousContent_api.html]
 [test_anonymousContent_append_after_reflow.html]
 [test_anonymousContent_canvas.html]
 skip-if = buildapp == 'b2g' # Requires webgl support
 [test_anonymousContent_insert.html]
 [test_anonymousContent_manipulate_content.html]
 [test_anonymousContent_style_csp.html]
@@ -803,17 +804,25 @@ skip-if = toolkit == 'android'
 [test_textnode_split_in_selection.html]
 [test_title.html]
 [test_treewalker_nextsibling.xml]
 [test_viewport_scroll.html]
 [test_viewsource_forbidden_in_object.html]
 [test_w3element_traversal.html]
 [test_w3element_traversal.xhtml]
 [test_w3element_traversal_svg.html]
-[test_websocket.html]
+[test_websocket1.html]
+skip-if = buildapp == 'b2g' || toolkit == 'android' # TC: Bug 1144079 - Re-enable Mulet mochitests and reftests taskcluster-specific disables.
+[test_websocket2.html]
+skip-if = buildapp == 'b2g' || toolkit == 'android' # TC: Bug 1144079 - Re-enable Mulet mochitests and reftests taskcluster-specific disables.
+[test_websocket3.html]
+skip-if = buildapp == 'b2g' || toolkit == 'android' # TC: Bug 1144079 - Re-enable Mulet mochitests and reftests taskcluster-specific disables.
+[test_websocket4.html]
+skip-if = buildapp == 'b2g' || toolkit == 'android' # TC: Bug 1144079 - Re-enable Mulet mochitests and reftests taskcluster-specific disables.
+[test_websocket5.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' # TC: Bug 1144079 - Re-enable Mulet mochitests and reftests taskcluster-specific disables.
 [test_websocket_basic.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android'
 [test_websocket_hello.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android'
 [test_websocket_permessage_deflate.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android'
 [test_x-frame-options.html]
deleted file mode 100644
--- a/dom/base/test/test_websocket.html
+++ /dev/null
@@ -1,1493 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
-  <title>WebSocket test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onload="testWebSocket()">
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=472529">Mozilla Bug </a>
-<p id="display">
-</p>
-<div id="content">
-</div>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-/*
- * tests:
- *  1. client tries to connect to a http scheme location;
- *  2. assure serialization of the connections;
- *  3. client tries to connect to an non-existent ws server;
- *  4. client tries to connect using a relative url;
- *  5. client uses an invalid protocol value;
- *  6. counter and encoding check;
- *  7. onmessage event origin property check
- *  8. client calls close() and the server sends the close frame (with no code
- *     or reason) in acknowledgement;
- *  9. client closes the connection before the ws connection is established;
- * 10. client sends a message before the ws connection is established;
- * 11. a simple hello echo;
- * 12. client sends a message containing unpaired surrogates
- * 13. server sends an invalid message;
- * 14. server sends the close frame, it doesn't close the tcp connection and
- *     it keeps sending normal ws messages;
- * 15. server closes the tcp connection, but it doesn't send the close frame;
- * 16. client calls close() and tries to send a message;
- * 17. see bug 572975 - all event listeners set
- * 18. client tries to connect to an http resource;
- * 19. server closes the tcp connection before establishing the ws connection;
- * 20. see bug 572975 - only on error and onclose event listeners set
- * 21. see bug 572975 - same as test 17, but delete strong event listeners when
- *     receiving the message event;
- * 22. server takes too long to establish the ws connection;
- * 23. should detect WebSocket on window object;
- * 24. server rejects sub-protocol string
- * 25. ctor with valid empty sub-protocol array
- * 26. ctor with invalid sub-protocol array containing 1 empty element
- * 27. ctor with invalid sub-protocol array containing an empty element in list
- * 28. ctor using valid 1 element sub-protocol array
- * 29. ctor using all valid 5 element sub-protocol array
- * 30. ctor using valid 1 element sub-protocol array with element server will
- *     reject
- * 31. ctor using valid 2 element sub-protocol array with 1 element server
- *     will reject and one server will accept.
- * 32. ctor using invalid sub-protocol array that contains duplicate items
- * 33. test for sending/receiving custom close code (but no close reason)
- * 34. test for receiving custom close code and reason
- * 35. test for sending custom close code and reason
- * 36. negative test for sending out of range close code
- * 37. negative test for too long of a close reason
- * 38. ensure extensions attribute is defined
- * 39. a basic wss:// connectivity test
- * 40. negative test for wss:// with no cert
- * 41. HSTS
- * 42. non-char utf-8 sequences
- * 43. Test setting binaryType attribute
- * 44. Test sending/receving binary ArrayBuffer
- * 45. Test sending/receving binary Blob
- * 46. Test that we don't dispatch incoming msgs once in CLOSING state
- * 47. Make sure onerror/onclose aren't called during close()
- */
-
-var first_test = 1;
-var last_test = 47;
-
-
-// Set this to >1 if you want to run the suite multiple times to probe for
-// random orange failures.
-// - Do NOT check into mozilla-central with a value != 1.
-// - Too large a count will wind up causing tryserver to timeout the test (which
-//   is ok, but means all testruns will be orange).  If I set first_test to >22
-//   (i.e don't run any of the tests that require waiting) I can get ~250-300
-//   iterations of the remaining tests w/o a timeout.
-var testsuite_iterations = 1;
-
-
-var current_test = first_test;
-var testsuite_iteration = 1;
-
-var test_started = new Array(last_test);
-var all_ws = [];
-
-function shouldNotOpen(e)
-{
-  var ws = e.target;
-  ok(false, "onopen shouldn't be called on test " + ws._testNumber + "!");
-}
-
-function shouldNotReceiveCloseEvent(e)
-{
-  var ws = e.target;
-  var extendedErrorInfo = "";
-  if (!ws._testNumber) {
-    extendedErrorInfo += "\nws members:\n";
-    for (var i in ws) {
-      extendedErrorInfo += (i + ": " + ws[i] + "\n");
-    }
-
-    extendedErrorInfo += "\ne members:\n";
-    for (var i in e) {
-      extendedErrorInfo += (i + ": " + e[i] + "\n");
-    }
-  }
-
-  // FIXME: see bug 578276. This should be a test failure, but it's too flaky on the tbox.
-  ok(true, "onclose shouldn't be called on test " + ws._testNumber + "!" + extendedErrorInfo);
-}
-
-function shouldCloseCleanly(e)
-{
-  var ws = e.target;
-  ok(e.wasClean, "the ws connection in test " + ws._testNumber + " should be closed cleanly");
-}
-
-function shouldCloseNotCleanly(e)
-{
-  var ws = e.target;
-  ok(!e.wasClean, "the ws connection in test " + ws._testNumber + " shouldn't be closed cleanly");
-}
-
-function ignoreError(e)
-{
-}
-
-function CreateTestWS(ws_location, ws_protocol, no_increment_test)
-{
-  var ws;
-
-  try {
-    if (ws_protocol == undefined) {
-      ws = new WebSocket(ws_location);
-    } else {
-      ws = new WebSocket(ws_location, ws_protocol);
-    }
-
-
-    ws._testNumber = current_test;
-    ws._receivedCloseEvent = false;
-    ok(true, "Created websocket for test " + ws._testNumber +"\n");
-
-    ws.onerror = function(e)
-    {
-      ok(false, "onerror called on test " + e.target._testNumber + "!");
-    };
-    ws.addEventListener("close", function(e)
-    {
-      ws._receivedCloseEvent = true;
-    }, false);
-  }
-  catch (e) {
-    throw e;
-  }
-  finally {
-    if (!no_increment_test) {
-      current_test++;
-    }
-  }
-
-  all_ws.push(ws);
-  return ws;
-}
-
-function forcegc()
-{
-  SpecialPowers.forceGC();
-  SpecialPowers.gc();
-  setTimeout(function()
-  {
-    SpecialPowers.gc();
-  }, 0);
-}
-
-function doTest(number)
-{
-  if (number > last_test) {
-    ranAllTests = true;
-    maybeFinished();
-    return;
-  }
-
-  if (testsuite_iteration > 1) {
-    $("feedback").innerHTML = "test suite iteration #" + testsuite_iteration + " of " + testsuite_iterations +
-      ": executing test: " + number + " of " + last_test + " tests.";
-  } else {
-    $("feedback").innerHTML = "executing test: " + number + " of " + last_test + " tests.";
-  }
-
-  var fnTest = eval("test" + number + "");
-
-  if (test_started[number] === true) {
-    doTest(number + 1);
-    return;
-  }
-
-  test_started[number] = true;
-  fnTest();
-}
-doTest.timeoutId = null;
-
-function test1()
-{
-  try {
-    var ws = CreateTestWS("http://mochi.test:8888/tests/dom/base/test/file_websocket");
-    ok(false, "test1 failed");
-  }
-  catch (e) {
-    ok(true, "test1 failed");
-  }
-  doTest(2);
-}
-
-// this test expects that the serialization list to connect to the proxy
-// is empty. Use different domain so we can run this in the background
-// and not delay other tests.
-
-var waitTest2Part1 = false;
-var waitTest2Part2 = false;
-
-function test2()
-{
-  waitTest2Part1 = true;
-  waitTest2Part2 = true;
-
-  var ws1 = CreateTestWS("ws://sub2.test2.example.com/tests/dom/base/test/file_websocket", "test-2.1");
-  current_test--; // CreateTestWS incremented this
-  var ws2 = CreateTestWS("ws://sub2.test2.example.com/tests/dom/base/test/file_websocket", "test-2.2");
-
-  var ws2CanConnect = false;
-
-  // the server will delay ws1 for 5 seconds, but the other tests can
-  // proceed in parallel
-  doTest(3);
-
-  ws1.onopen = function()
-  {
-    ok(true, "ws1 open in test 2");
-    ws2CanConnect = true;
-    ws1.close();
-  }
-
-  ws1.onclose = function(e)
-  {
-    waitTest2Part1 = false;
-    maybeFinished();
-  };
-
-  ws2.onopen = function()
-  {
-    ok(ws2CanConnect, "shouldn't connect yet in test-2!");
-    ws2.close();
-  }
-
-  ws2.onclose = function(e)
-  {
-    waitTest2Part2 = false;
-    maybeFinished();
-  };
-}
-
-function test3()
-{
-  var hasError = false;
-  var ws = CreateTestWS("ws://this.websocket.server.probably.does.not.exist");
-  ws.onopen = shouldNotOpen;
-  ws.onerror = function (e)
-  {
-    hasError = true;
-  }
-
-  ws.onclose = function(e)
-  {
-    shouldCloseNotCleanly(e);
-    ok(hasError, "rcvd onerror event");
-    ok(e.code == 1006, "test-3 close code should be 1006 but is:" + e.code);
-    doTest(4);
-  };
-}
-
-function test4()
-{
-  try {
-    var ws = CreateTestWS("file_websocket");
-    ok(false, "test-4 failed");
-  }
-  catch (e) {
-    ok(true, "test-4 failed");
-  }
-  doTest(5);
-}
-
-function test5()
-{
-  try {
-    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "");
-    ok(false, "couldn't accept an empty string in the protocol parameter");
-  }
-  catch (e) {
-    ok(true, "couldn't accept an empty string in the protocol parameter");
-  }
-  current_test--; // CreateTestWS incremented this
-  try {
-    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "\n");
-    ok(false, "couldn't accept any not printable ASCII character in the protocol parameter");
-  }
-  catch (e) {
-    ok(true, "couldn't accept any not printable ASCII character in the protocol parameter");
-  }
-  current_test--; // CreateTestWS incremented this
-  try {
-    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test 5");
-    ok(false, "U+0020 not acceptable in protocol parameter");
-  }
-  catch (e) {
-    ok(true, "U+0020 not acceptable in protocol parameter");
-  }
-  doTest(6);
-}
-
-function test6()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-6");
-  var counter = 1;
-  ws.onopen = function()
-  {
-    ws.send(counter);
-  }
-  ws.onmessage = function(e)
-  {
-    if (counter == 5) {
-      ok(e.data == "あいうえお", "test-6 counter 5 data ok");
-      ws.close();
-    } else {
-      ok(e.data == counter+1, "bad counter");
-      counter += 2;
-      ws.send(counter);
-    }
-  }
-  ws.onclose = function(e)
-  {
-    shouldCloseCleanly(e);
-    doTest(7);
-  };
-}
-
-function test7()
-{
-  var ws = CreateTestWS("ws://sub2.test2.example.org/tests/dom/base/test/file_websocket", "test-7");
-  var gotmsg = false;
-
-  ws.onopen = function()
-  {
-    ok(true, "test 7 open");
-  }
-  ws.onmessage = function(e)
-  {
-    ok(true, "test 7 message");
-    ok(e.origin == "ws://sub2.test2.example.org", "onmessage origin set to ws:// host");
-    gotmsg = true;
-    ws.close();
-  }
-  ws.onclose = function(e)
-  {
-    ok(gotmsg, "recvd message in test 7 before close");
-    shouldCloseCleanly(e);
-    doTest(8);
-  };
-}
-
-function test8()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-8");
-  ws.onopen = function()
-  {
-    ok(ws.protocol == "test-8", "test-8 subprotocol selection");
-    ws.close();
-  }
-  ws.onclose = function(e)
-  {
-    shouldCloseCleanly(e);
-    // We called close() with no close code: so pywebsocket will also send no
-    // close code, which translates to code 1005
-    ok(e.code == 1005, "test-8 close code has wrong value:" + e.code);
-    ok(e.reason == "", "test-8 close reason has wrong value:" + e.reason);
-    doTest(9);
-  };
-}
-
-var waitTest9 = false;
-
-function test9()
-{
-  waitTest9 = true;
-
-  var ws = CreateTestWS("ws://test2.example.org/tests/dom/base/test/file_websocket", "test-9");
-  ws._receivedErrorEvent = false;
-  ws.onopen = shouldNotOpen;
-  ws.onerror = function(e)
-  {
-    ws._receivedErrorEvent = true;
-  };
-  ws.onclose = function(e)
-  {
-    ok(ws._receivedErrorEvent, "Didn't received the error event in test 9.");
-    shouldCloseNotCleanly(e);
-    waitTest9 = false;
-    maybeFinished();
-  };
-
-  ws.close();
-
-  // the server injects a delay, so proceed with this in the background
-  doTest(10);
-}
-
-var waitTest10 = false;
-
-function test10()
-{
-  waitTest10 = true;
-
-  var ws = CreateTestWS("ws://sub1.test1.example.com/tests/dom/base/test/file_websocket", "test-10");
-  ws.onclose = function(e)
-  {
-    shouldCloseCleanly(e);
-    waitTest10 = false;
-    maybeFinished();
-  }
-
-  try {
-    ws.send("client data");
-    ok(false, "Couldn't send data before connecting!");
-  }
-  catch (e) {
-    ok(true, "Couldn't send data before connecting!");
-  }
-  ws.onopen = function()
-  {
-    ok(true, "test 10 opened");
-    ws.close();
-  }
-
-  // proceed with this test in the background
-  doTest(11);
-}
-
-function test11()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-11");
-  ok(ws.readyState == 0, "create bad readyState in test-11!");
-  ws.onopen = function()
-  {
-    ok(ws.readyState == 1, "open bad readyState in test-11!");
-    ws.send("client data");
-  }
-  ws.onmessage = function(e)
-  {
-    ok(e.data == "server data", "bad received message in test-11!");
-    ws.close(1000, "Have a nice day");
-
-// this ok() is disabled due to a race condition - it state may have
-// advanced through 2 (closing) and into 3 (closed) before it is evald
-//    ok(ws.readyState == 2, "onmessage bad readyState in test-11!");
-  }
-  ws.onclose = function(e)
-  {
-    ok(ws.readyState == 3, "onclose bad readyState in test-11!");
-    shouldCloseCleanly(e);
-    ok(e.code == 1000, "test 11 got wrong close code: " + e.code);
-    ok(e.reason == "Have a nice day", "test 11 got wrong close reason: " + e.reason);
-    doTest(12);
-  }
-}
-
-function test12()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-12");
-  ws.onopen = function()
-  {
-    try {
-      // send an unpaired surrogate
-      ws._gotMessage = false;
-      ws.send("a\ud800b");
-      ok(true, "ok to send an unpaired surrogate");
-    }
-    catch (e) {
-      ok(false, "shouldn't fail any more when sending an unpaired surrogate!");
-    }
-  }
-
-  ws.onmessage = function(msg)
-  {
-    ok(msg.data == "SUCCESS", "Unpaired surrogate in UTF-16 not converted in test-12");
-    ws._gotMessage = true;
-    // Must support unpaired surrogates in close reason, too
-    ws.close(1000, "a\ud800b");
-  }
-
-  ws.onclose = function(e)
-  {
-    ok(ws.readyState == 3, "onclose bad readyState in test-12!");
-    ok(ws._gotMessage, "didn't receive message!");
-    shouldCloseCleanly(e);
-    ok(e.code == 1000, "test 12 got wrong close code: " + e.code);
-    ok(e.reason == "a\ufffdb", "test 11 didn't get replacement char in close reason: " + e.reason);
-    doTest(13);
-  }
-}
-
-function test13()
-{
-    // previous versions of this test counted the number of protocol errors returned, but the
-    // protocol stack typically closes down after reporting a protocol level error - trying
-    // to resync is too dangerous
-
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-13");
-  ws._timesCalledOnError = 0;
-  ws.onerror = function()
-  {
-    ws._timesCalledOnError++;
-  }
-  ws.onclose = function(e)
-  {
-    ok(ws._timesCalledOnError > 0, "no error events");
-    doTest(14);
-  }
-}
-
-function test14()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-14");
-  ws.onmessage = function()
-  {
-    ok(false, "shouldn't received message after the server sent the close frame");
-  }
-  ws.onclose = function(e)
-  {
-    shouldCloseCleanly(e);
-    // Skip test 15 for now: broken
-    doTest(16);
-  };
-}
-
-/*
- * DISABLED: see comments for test-15 case in file_websocket_wsh.py
- *
-function test15()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-15");
-  ws.onclose = function(e)
-  {
-    shouldCloseNotCleanly(e);
-    doTest(16);
-  };
-
-  // termination of the connection might cause an error event if it happens in OPEN
-  ws.onerror = function()
-  {
-  }
-
-}
-*/
-
-function test16()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-16");
-  ws.onopen = function()
-  {
-    ws.close();
-    ok(!ws.send("client data"), "shouldn't send message after calling close()");
-  }
-  ws.onmessage = function()
-  {
-    ok(false, "shouldn't send message after calling close()");
-  }
-
-  ws.onerror = function()
-  {
-  }
-  ws.onclose = function()
-  {
-    doTest(17);
-  }
-}
-
-var status_test17 = "not started";
-
-var waitTest17 = false;
-
-var test17func = function()
-{
-  waitTest17 = true;
-
-  var local_ws = new WebSocket("ws://sub1.test2.example.org/tests/dom/base/test/file_websocket", "test-17");
-  local_ws._testNumber = "local17";
-  local_ws._testNumber = current_test++;
-
-  status_test17 = "started";
-
-  local_ws.onopen = function(e)
-  {
-    status_test17 = "opened";
-    e.target.send("client data");
-    forcegc();
-  };
-
-  local_ws.onerror = function()
-  {
-    ok(false, "onerror called on test " + e.target._testNumber + "!");
-  };
-
-  local_ws.onmessage = function(e)
-  {
-    ok(e.data == "server data", "Bad message in test-17");
-    status_test17 = "got message";
-    forcegc();
-  };
-
-  local_ws.onclose = function(e)
-  {
-    ok(status_test17 == "got message", "Didn't got message in test-17!");
-    shouldCloseCleanly(e);
-    status_test17 = "closed";
-    forcegc();
-    waitTest17 = false;
-    maybeFinished();
-  };
-
-  local_ws = null;
-  window._test17 = null;
-  forcegc();
-
-// do this in the background
-  doTest(18);
-  forcegc();
-}
-
-function test17()
-{
-  window._test17 = test17func;
-  window._test17();
-}
-
-// The tests that expects that their websockets neither open nor close MUST
-// be in the end of the tests, i.e. HERE, in order to prevent blocking the other
-// tests.
-
-function test18()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket_http_resource.txt");
-  ws.onopen = shouldNotOpen;
-  ws.onerror = ignoreError;
-  ws.onclose = function(e)
-  {
-    shouldCloseNotCleanly(e);
-    doTest(19);
-  };
-}
-
-function test19()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-19");
-  ws.onopen = shouldNotOpen;
-  ws.onerror = ignoreError;
-  ws.onclose = function(e)
-  {
-    shouldCloseNotCleanly(e);
-    doTest(20);
-  };
-}
-
-var waitTest20 = false;
-
-var test20func = function()
-{
-  waitTest20 = true;
-
-  var local_ws = new WebSocket("ws://sub1.test1.example.org/tests/dom/base/test/file_websocket", "test-20");
-  local_ws._testNumber = "local20";
-  local_ws._testNumber = current_test++;
-
-  local_ws.onerror = function()
-  {
-    ok(false, "onerror called on test " + e.target._testNumber + "!");
-  };
-
-  local_ws.onclose = function(e)
-  {
-    ok(true, "test 20 closed despite gc");
-    waitTest20 = false;
-    maybeFinished();
-  };
-
-  local_ws = null;
-  window._test20 = null;
-  forcegc();
-
-  // let test run in the background
-  doTest(21);
-}
-
-function test20()
-{
-  window._test20 = test20func;
-  window._test20();
-}
-
-var waitTest21 = false;
-
-test21func = function()
-{
-  waitTest21 = true;
-
-  var local_ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-21");
-  local_ws._testNumber = current_test++;
-  var received_message = false;
-
-  local_ws.onopen = function(e)
-  {
-    e.target.send("client data");
-    forcegc();
-    e.target.onopen = null;
-    forcegc();
-  };
-
-  local_ws.onerror = function()
-  {
-    ok(false, "onerror called on test " + e.target._testNumber + "!");
-  };
-
-  local_ws.onmessage = function(e)
-  {
-    ok(e.data == "server data", "Bad message in test-21");
-    received_message = true;
-    forcegc();
-    e.target.onmessage = null;
-    forcegc();
-  };
-
-  local_ws.onclose = function(e)
-  {
-    shouldCloseCleanly(e);
-    ok(received_message, "close transitioned through onmessage");
-    waitTest21 = false;
-    maybeFinished();
-  };
-
-  local_ws = null;
-  window._test21 = null;
-  forcegc();
-
-  doTest(22);
-
-}
-
-function test21()
-{
-  window._test21 = test21func;
-  window._test21();
-}
-
-var waitTest22 = false;
-
-function test22()
-{
-  waitTest22 = true;
-
-  const pref_open = "network.websocket.timeout.open";
-  SpecialPowers.setIntPref(pref_open, 5);
-
-  var ws = CreateTestWS("ws://sub2.test2.example.org/tests/dom/base/test/file_websocket", "test-22");
-  ws.onopen = shouldNotOpen;
-  ws.onerror = ignoreError;
-  ws.onclose = function(e)
-  {
-    shouldCloseNotCleanly(e);
-    waitTest22 = false;
-    maybeFinished();
-  };
-
-  SpecialPowers.clearUserPref(pref_open);
-  doTest(23);
-}
-
-function test23()
-{
-  current_test++;
-  is(true, "WebSocket" in window, "WebSocket should be available on window object");
-  doTest(24);
-}
-
-function test24()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-does-not-exist");
-  ws.onopen = shouldNotOpen;
-  ws.onclose = function(e)
-  {
-    shouldCloseNotCleanly(e);
-    doTest(25);
-  };
-  ws.onerror = function()
-  {
-  }
-}
-
-function test25()
-{
-  var prots=[];
-
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-
-  // This test errors because the server requires a sub-protocol, but
-  // the test just wants to ensure that the ctor doesn't generate an
-  // exception
-  ws.onerror = ignoreError;
-  ws.onopen = shouldNotOpen;
-
-  ws.onclose = function(e)
-  {
-    ok(ws.protocol == "", "test25 subprotocol selection");
-    ok(true, "test 25 protocol array close");
-    doTest(26);
-  };
-}
-
-function test26()
-{
-  var prots=[""];
-
-  try {
-    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-    ok(false, "testing empty element sub protocol array");
-  }
-  catch (e) {
-    ok(true, "testing empty sub element protocol array");
-  }
-  doTest(27);
-}
-
-function test27()
-{
-  var prots=["test27", ""];
-
-  try {
-    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-    ok(false, "testing empty element mixed sub protocol array");
-  }
-  catch (e) {
-    ok(true, "testing empty element mixed sub protocol array");
-  }
-  doTest(28);
-}
-
-function test28()
-{
-  var prots=["test28"];
-
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-  ws.onopen = function(e)
-  {
-    ok(true, "test 28 protocol array open");
-    ws.close();
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(ws.protocol == "test28", "test28 subprotocol selection");
-    ok(true, "test 28 protocol array close");
-    doTest(29);
-  };
-}
-
-function test29()
-{
-  var prots=["test29a", "test29b"];
-
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-  ws.onopen = function(e)
-  {
-    ok(true, "test 29 protocol array open");
-    ws.close();
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(true, "test 29 protocol array close");
-    doTest(30);
-  };
-}
-
-function test30()
-{
-  var prots=["test-does-not-exist"];
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-
-  ws.onopen = shouldNotOpen;
-  ws.onclose = function(e)
-  {
-    shouldCloseNotCleanly(e);
-    doTest(31);
-  };
-  ws.onerror = function()
-  {
-  }
-}
-
-function test31()
-{
-  var prots=["test-does-not-exist", "test31"];
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-
-  ws.onopen = function(e)
-  {
-    ok(true, "test 31 protocol array open");
-    ws.close();
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(ws.protocol == "test31", "test31 subprotocol selection");
-    ok(true, "test 31 protocol array close");
-    doTest(32);
-  };
-}
-
-function test32()
-{
-  var prots=["test32","test32"];
-
-  try {
-    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-    ok(false, "testing duplicated element sub protocol array");
-  }
-  catch (e) {
-    ok(true, "testing duplicated sub element protocol array");
-  }
-  doTest(33);
-}
-
-function test33()
-{
-  var prots=["test33"];
-
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-  ws.onopen = function(e)
-  {
-    ok(true, "test 33 open");
-    ws.close(3131);   // pass code but not reason
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(true, "test 33 close");
-    shouldCloseCleanly(e);
-    ok(e.code == 3131, "test 33 got wrong close code: " + e.code);
-    ok(e.reason === "", "test 33 got wrong close reason: " + e.reason);
-    doTest(34);
-  };
-}
-
-function test34()
-{
-  var prots=["test-34"];
-
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-  ws.onopen = function(e)
-  {
-    ok(true, "test 34 open");
-    ws.close();
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(true, "test 34 close");
-    ok(e.wasClean, "test 34 closed cleanly");
-    ok(e.code == 1001, "test 34 custom server code");
-    ok(e.reason == "going away now", "test 34 custom server reason");
-    doTest(35);
-  };
-}
-
-function test35()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-35a");
-
-  ws.onopen = function(e)
-  {
-    ok(true, "test 35a open");
-    ws.close(3500, "my code");
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(true, "test 35a close");
-    ok(e.wasClean, "test 35a closed cleanly");
-    current_test--; // CreateTestWS for 35a incremented this
-    var wsb = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-35b");
-
-  wsb.onopen = function(e)
-  {
-    ok(true, "test 35b open");
-    wsb.close();
-  };
-
-  wsb.onclose = function(e)
-  {
-    ok(true, "test 35b close");
-    ok(e.wasClean, "test 35b closed cleanly");
-    ok(e.code == 3501, "test 35 custom server code");
-    ok(e.reason == "my code", "test 35 custom server reason");
-    doTest(36);
-  };
-  }
-}
-
-function test36()
-{
-  var prots=["test-36"];
-
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-  ws.onopen = function(e)
-  {
-    ok(true, "test 36 open");
-
-    try {
-      ws.close(13200);
-      ok(false, "testing custom close code out of range");
-     }
-     catch (e) {
-       ok(true, "testing custom close code out of range");
-       ws.close(3200);
-     }
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(true, "test 36 close");
-    ok(e.wasClean, "test 36 closed cleanly");
-    doTest(37);
-  };
-}
-
-function test37()
-{
-  var prots=["test-37"];
-
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-  ws.onopen = function(e)
-  {
-    ok(true, "test 37 open");
-
-    try {
-	ws.close(3100,"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123");
-      ok(false, "testing custom close reason out of range");
-     }
-     catch (e) {
-       ok(true, "testing custom close reason out of range");
-       ws.close(3100,"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012");
-     }
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(true, "test 37 close");
-    ok(e.wasClean, "test 37 closed cleanly");
-
-    current_test--; // CreateTestWS for 37 incremented this
-    var wsb = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-37b");
-
-    wsb.onopen = function(e)
-    {
-      // now test that a rejected close code and reason dont persist
-      ok(true, "test 37b open");
-      try {
-        wsb.close(3101,"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123");
-        ok(false, "testing custom close reason out of range 37b");
-      }
-      catch (e) {
-        ok(true, "testing custom close reason out of range 37b");
-        wsb.close();
-     }
-    }
-
-    wsb.onclose = function(e)
-    {
-      ok(true, "test 37b close");
-      ok(e.wasClean, "test 37b closed cleanly");
-
-      current_test--; // CreateTestWS for 37 incremented this
-      var wsc = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-37c");
-
-      wsc.onopen = function(e)
-      {
-        ok(true, "test 37c open");
-        wsc.close();
-      }
-
-      wsc.onclose = function(e)
-      {
-         ok(e.code != 3101, "test 37c custom server code not present");
-         ok(e.reason == "", "test 37c custom server reason not present");
-         doTest(38);
-      }
-    }
-  }
-}
-
-function test38()
-{
-  var prots=["test-38"];
-
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-  ws.onopen = function(e)
-  {
-    ok(true, "test 38 open");
-    ok(ws.extensions != undefined, "extensions attribute defined");
-//    ok(ws.extensions == "deflate-stream", "extensions attribute deflate-stream");
-    ws.close();
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(true, "test 38 close");
-    doTest(39);
-  };
-}
-
-function test39()
-{
-  var prots=["test-39"];
-
-  var ws = CreateTestWS("wss://example.com/tests/dom/base/test/file_websocket", prots);
-  status_test39 = "started";
-  ws.onopen = function(e)
-  {
-    status_test39 = "opened";
-    ok(true, "test 39 open");
-    ws.close();
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(true, "test 39 close");
-    ok(status_test39 == "opened", "test 39 did open");
-    doTest(40);
-  };
-}
-
-function test40()
-{
-  var prots=["test-40"];
-
-  var ws = CreateTestWS("wss://nocert.example.com/tests/dom/base/test/file_websocket", prots);
-
-  status_test40 = "started";
-  ws.onerror = ignoreError;
-
-  ws.onopen = function(e)
-  {
-    status_test40 = "opened";
-    ok(false, "test 40 open");
-    ws.close();
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(true, "test 40 close");
-    ok(status_test40 == "started", "test 40 did not open");
-    doTest(41);
-  };
-}
-
-function test41()
-{
-  var ws = CreateTestWS("ws://example.com/tests/dom/base/test/file_websocket", "test-41a", 1);
-
-  ws.onopen = function(e)
-  {
-    ok(true, "test 41a open");
-    ok(ws.url == "ws://example.com/tests/dom/base/test/file_websocket",
-       "test 41a initial ws should not be redirected");
-    ws.close();
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(true, "test 41a close");
-
-    // establish a hsts policy for example.com
-    var wsb = CreateTestWS("wss://example.com/tests/dom/base/test/file_websocket", "test-41b", 1);
-    wsb.onopen = function(e)
-    {
-      ok(true, "test 41b open");
-      wsb.close();
-    }
-
-    wsb.onclose = function(e)
-    {
-      ok(true, "test 41b close");
-
-      // try ws:// again, it should be done over wss:// now due to hsts
-      var wsc = CreateTestWS("ws://example.com/tests/dom/base/test/file_websocket", "test-41c");
-
-      wsc.onopen = function(e)
-      {
-        ok(true, "test 41c open");
-        ok(wsc.url == "wss://example.com/tests/dom/base/test/file_websocket",
-           "test 41c ws should be redirected by hsts to wss");
-        wsc.close();
-      }
-
-      wsc.onclose = function(e)
-      {
-        ok(true, "test 41c close");
-
-        // clean up the STS state
-        const Ci = SpecialPowers.Ci;
-        var loadContext = SpecialPowers.wrap(window)
-                          .QueryInterface(Ci.nsIInterfaceRequestor)
-                          .getInterface(Ci.nsIWebNavigation)
-                          .QueryInterface(Ci.nsILoadContext);
-        var flags = 0;
-        if (loadContext.usePrivateBrowsing)
-          flags |= Ci.nsISocketProvider.NO_PERMANENT_STORAGE;
-        SpecialPowers.cleanUpSTSData("http://example.com", flags);
-        doTest(42);
-       }
-     }
-  }
-}
-
-function test42()
-{
-// test some utf-8 non-characters. They should be allowed in the
-// websockets context. Test via round trip echo.
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-42");
-  var data = ["U+FFFE \ufffe",
-		"U+FFFF \uffff",
-		"U+10FFFF \udbff\udfff"];
-  var index = 0;
-
-  ws.onopen = function()
-  {
-    ws.send(data[0]);
-    ws.send(data[1]);
-    ws.send(data[2]);
-  }
-
-  ws.onmessage = function(e)
-  {
-    ok(e.data == data[index], "bad received message in test-42! index="+index);
-    index++;
-    if (index == 3)
-      ws.close();
-  }
-
-  ws.onclose = function(e)
-  {
-    doTest(43);
-  }
-}
-
-function test43()
-{
-  var prots=["test-43"];
-
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
-  ws.onopen = function(e)
-  {
-    ok(true, "test 43 open");
-    // Test binaryType setting
-    ws.binaryType = "arraybuffer";
-    ws.binaryType = "blob";
-    ws.binaryType = "";  // illegal
-    is(ws.binaryType, "blob");
-    ws.binaryType = "ArrayBuffer";  // illegal
-    is(ws.binaryType, "blob");
-    ws.binaryType = "Blob";  // illegal
-    is(ws.binaryType, "blob");
-    ws.binaryType = "mcfoofluu";  // illegal
-    is(ws.binaryType, "blob");
-    ws.close();
-  };
-
-  ws.onclose = function(e)
-  {
-    ok(true, "test 43 close");
-    doTest(44);
-  };
-}
-
-
-function test44()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-44");
-  ok(ws.readyState == 0, "bad readyState in test-44!");
-  ws.binaryType = "arraybuffer";
-  ws.onopen = function()
-  {
-    ok(ws.readyState == 1, "open bad readyState in test-44!");
-    var buf = new ArrayBuffer(3);
-    // create byte view
-    var view = new Uint8Array(buf);
-    view[0] = 5;
-    view[1] = 0; // null byte
-    view[2] = 7;
-    ws.send(buf);
-  }
-  ws.onmessage = function(e)
-  {
-    ok(e.data instanceof ArrayBuffer, "Should receive an arraybuffer!");
-    var view = new Uint8Array(e.data);
-    ok(view.length == 2 && view[0] == 0 && view[1] ==4, "testing Reply arraybuffer" );
-    ws.close();
-  }
-  ws.onclose = function(e)
-  {
-    ok(ws.readyState == 3, "onclose bad readyState in test-44!");
-    shouldCloseCleanly(e);
-    SpecialPowers.createFiles([{name: "testBlobFile", data: "flob"}], function (files) {
-      blobFile = files[0];
-      doTest(45);
-    },
-    function (msg) {
-      testFailed("Failed to create file for test45: " + msg);
-      doTest(46);
-    });
-  }
-}
-
-var blobFile;
-
-function test45()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-45");
-  ok(ws.readyState == 0, "bad readyState in test-45!");
-
-//  ws.binaryType = "blob";  // Don't need to specify: blob is the default
-
-  ws.onopen = function()
-  {
-    ok(ws.readyState == 1, "open bad readyState in test-45!");
-    ws.send(blobFile);
-  }
-
-  var test45blob;
-
-  ws.onmessage = function(e)
-  {
-    test45blob = e.data;
-    ok(test45blob instanceof Blob, "We should be receiving a Blob");
-
-    ws.close();
-  }
-
-  ws.onclose = function(e)
-  {
-    ok(ws.readyState == 3, "onclose bad readyState in test-45!");
-    shouldCloseCleanly(e);
-
-    // check blob contents
-    var reader = new FileReader();
-    reader.onload = function(event)
-    {
-      ok(reader.result == "flob", "response should be 'flob': got '"
-         + reader.result + "'");
-    };
-    reader.onerror = function(event)
-    {
-      testFailed("Failed to read blob: error code = " + reader.error.code);
-    };
-    reader.onloadend = function(event)
-    {
-      doTest(46);
-    };
-
-    reader.readAsBinaryString(test45blob);
-  }
-}
-
-function test46()
-{
-  var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-46");
-  ok(ws.readyState == 0, "create bad readyState in test-46!");
-  ws.onopen = function()
-  {
-    ok(ws.readyState == 1, "open bad readyState in test-46!");
-    ws.close()
-    ok(ws.readyState == 2, "close must set readyState to 2 in test-46!");
-  }
-  ws.onmessage = function(e)
-  {
-    ok(false, "received message after calling close in test-46!");
-  }
-  ws.onclose = function(e)
-  {
-    ok(ws.readyState == 3, "onclose bad readyState in test-46!");
-    shouldCloseCleanly(e);
-    doTest(47);
-  }
-}
-
-function test47()
-{
-  var hasError = false;
-  var ws = CreateTestWS("ws://another.websocket.server.that.probably.does.not.exist");
-  ws.onopen = shouldNotOpen;
-
-  ws.onerror = function (e)
-  {
-    ok(ws.readyState == 3, "test-47: readyState should be CLOSED(3) in onerror: got "
-       + ws.readyState);
-    ok(!ws._withinClose, "onerror() called during close()!");
-    hasError = true;
-  }
-
-  ws.onclose = function(e)
-  {
-    shouldCloseNotCleanly(e);
-    ok(hasError, "test-47: should have called onerror before onclose");
-    ok(ws.readyState == 3, "test-47: readyState should be CLOSED(3) in onclose: got "
-       + ws.readyState);
-    ok(!ws._withinClose, "onclose() called during close()!");
-    ok(e.code == 1006, "test-47 close code should be 1006 but is:" + e.code);
-    doTest(48);
-  };
-
-  // Call close before we're connected: throws error
-  // Make sure we call onerror/onclose asynchronously
-  ws._withinClose = 1;
-  ws.close(3333, "Closed before we were open: error");
-  ws._withinClose = 0;
-  ok(ws.readyState == 2, "test-47: readyState should be CLOSING(2) after close(): got "
-     + ws.readyState);
-}
-
-
-var ranAllTests = false;
-
-function maybeFinished()
-{
-  if (!ranAllTests)
-    return;
-
-  if (waitTest2Part1 || waitTest2Part2 || waitTest9 || waitTest10 ||
-      waitTest17 || waitTest20 || waitTest21 || waitTest22)
-    return;
-
-  for (i = 0; i < all_ws.length; ++i) {
-    if (all_ws[i] != shouldNotReceiveCloseEvent &&
-        !all_ws[i]._receivedCloseEvent) {
-      ok(false, "didn't called close on test " + all_ws[i]._testNumber + "!");
-    }
-  }
-
-  if (testsuite_iteration++ < testsuite_iterations) {
-    // play it again, Sam...
-    ok(1, "starting testsuite iteration " + testsuite_iteration);
-    test_started = new Array(last_test);
-    doTest(current_test = first_test);
-  } else {
-    // all done
-    SimpleTest.finish();
-  }
-}
-
-function testWebSocket ()
-{
-  doTest(first_test);
-}
-
-SimpleTest.requestFlakyTimeout("The web socket tests are really fragile, but avoiding timeouts might be hard, since it's testing stuff on the network. " +
-                               "Expect all sorts of flakiness in this test...");
-SimpleTest.waitForExplicitFinish();
-
-</script>
-</pre>
-
-<div id="feedback">
-</div>
-
-</body>
-</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_websocket1.html
@@ -0,0 +1,268 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
+  <title>WebSocket test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="websocket_helpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="testWebSocket()">
+<script class="testbody" type="text/javascript">
+
+function test1() {
+  return new Promise(function(resolve, reject) {
+    try {
+      var ws = CreateTestWS("http://mochi.test:8888/tests/dom/base/test/file_websocket");
+      ok(false, "test1 failed");
+    } catch (e) {
+      ok(true, "test1 failed");
+    }
+
+    resolve();
+  });
+}
+
+// this test expects that the serialization list to connect to the proxy
+// is empty.
+function test2() {
+  return new Promise(function(resolve, reject) {
+    var waitTest2Part1 = true;
+    var waitTest2Part2 = true;
+
+    var ws1 = CreateTestWS("ws://sub2.test2.example.com/tests/dom/base/test/file_websocket", "test-2.1");
+    var ws2 = CreateTestWS("ws://sub2.test2.example.com/tests/dom/base/test/file_websocket", "test-2.2");
+
+    var ws2CanConnect = false;
+
+    function maybeFinished() {
+      if (!waitTest2Part1 && !waitTest2Part2) {
+        resolve();
+      }
+    }
+
+    ws1.onopen = function() {
+      ok(true, "ws1 open in test 2");
+      ws2CanConnect = true;
+      ws1.close();
+    }
+
+    ws1.onclose = function(e) {
+      waitTest2Part1 = false;
+      maybeFinished();
+    }
+
+    ws2.onopen = function() {
+      ok(ws2CanConnect, "shouldn't connect yet in test-2!");
+      ws2.close();
+    }
+
+    ws2.onclose = function(e) {
+      waitTest2Part2 = false;
+      maybeFinished();
+    }
+  });
+}
+
+function test3() {
+  return new Promise(function(resolve, reject) {
+    var hasError = false;
+    var ws = CreateTestWS("ws://this.websocket.server.probably.does.not.exist");
+
+    ws.onopen = shouldNotOpen;
+
+    ws.onerror = function (e) {
+      hasError = true;
+    }
+
+    ws.onclose = function(e) {
+      shouldCloseNotCleanly(e);
+      ok(hasError, "rcvd onerror event");
+      is(e.code, 1006, "test-3 close code should be 1006 but is:" + e.code);
+      resolve();
+    }
+  });
+}
+
+function test4() {
+  return new Promise(function(resolve, reject) {
+    try {
+      var ws = CreateTestWS("file_websocket");
+      ok(false, "test-4 failed");
+    } catch (e) {
+      ok(true, "test-4 failed");
+    }
+
+    resolve();
+  });
+}
+
+function test5() {
+  return new Promise(function(resolve, reject) {
+    try {
+      var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "");
+      ok(false, "couldn't accept an empty string in the protocol parameter");
+    } catch (e) {
+      ok(true, "couldn't accept an empty string in the protocol parameter");
+    }
+
+    try {
+      var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "\n");
+      ok(false, "couldn't accept any not printable ASCII character in the protocol parameter");
+    } catch (e) {
+      ok(true, "couldn't accept any not printable ASCII character in the protocol parameter");
+    }
+
+    try {
+      var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test 5");
+      ok(false, "U+0020 not acceptable in protocol parameter");
+    } catch (e) {
+      ok(true, "U+0020 not acceptable in protocol parameter");
+    }
+
+    resolve();
+  });
+}
+
+function test6() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-6");
+    var counter = 1;
+
+    ws.onopen = function() {
+      ws.send(counter);
+    }
+
+    ws.onmessage = function(e) {
+      if (counter == 5) {
+        is(e.data, "あいうえお", "test-6 counter 5 data ok");
+        ws.close();
+      } else {
+        is(parseInt(e.data), counter+1, "bad counter");
+        counter += 2;
+        ws.send(counter);
+      }
+    }
+
+    ws.onclose = function(e) {
+      shouldCloseCleanly(e);
+      resolve();
+    }
+  });
+}
+
+function test7() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://sub2.test2.example.org/tests/dom/base/test/file_websocket", "test-7");
+    var gotmsg = false;
+
+    ws.onopen = function() {
+      ok(true, "test 7 open");
+    }
+
+    ws.onmessage = function(e) {
+      ok(true, "test 7 message");
+      is(e.origin, "ws://sub2.test2.example.org", "onmessage origin set to ws:// host");
+      gotmsg = true;
+      ws.close();
+    }
+
+    ws.onclose = function(e) {
+      ok(gotmsg, "recvd message in test 7 before close");
+      shouldCloseCleanly(e);
+      resolve();
+    }
+  });
+}
+
+function test8() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-8");
+
+    ws.onopen = function() {
+      is(ws.protocol, "test-8", "test-8 subprotocol selection");
+      ws.close();
+    }
+
+    ws.onclose = function(e) {
+      shouldCloseCleanly(e);
+      // We called close() with no close code: so pywebsocket will also send no
+      // close code, which translates to code 1005
+      is(e.code, 1005, "test-8 close code has wrong value:" + e.code);
+      is(e.reason, "", "test-8 close reason has wrong value:" + e.reason);
+      resolve();
+    }
+  });
+}
+
+function test9() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://test2.example.org/tests/dom/base/test/file_websocket", "test-9");
+
+    ws._receivedErrorEvent = false;
+
+    ws.onopen = shouldNotOpen;
+
+    ws.onerror = function(e) {
+      ws._receivedErrorEvent = true;
+    }
+
+    ws.onclose = function(e) {
+      ok(ws._receivedErrorEvent, "Didn't received the error event in test 9.");
+      shouldCloseNotCleanly(e);
+      resolve();
+    }
+
+    ws.close();
+  });
+}
+
+function test10() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://sub1.test1.example.com/tests/dom/base/test/file_websocket", "test-10");
+
+    ws.onclose = function(e) {
+      shouldCloseCleanly(e);
+      resolve();
+    }
+
+    try {
+      ws.send("client data");
+      ok(false, "Couldn't send data before connecting!");
+    } catch (e) {
+      ok(true, "Couldn't send data before connecting!");
+    }
+
+    ws.onopen = function()
+    {
+      ok(true, "test 10 opened");
+      ws.close();
+    }
+  });
+}
+
+var tests = [
+  test1,  // client tries to connect to a http scheme location;
+  test2,  // assure serialization of the connections;
+  test3,  // client tries to connect to an non-existent ws server;
+  test4,  // client tries to connect using a relative url;
+  test5,  // client uses an invalid protocol value;
+  test6,  // counter and encoding check;
+  test7,  // onmessage event origin property check
+  test8,  // client calls close() and the server sends the close frame (with no
+          // code or reason) in acknowledgement;
+  test9,  // client closes the connection before the ws connection is established;
+  test10, // client sends a message before the ws connection is established;
+];
+
+function testWebSocket() {
+  doTest();
+}
+
+</script>
+
+<div id="feedback">
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_websocket2.html
@@ -0,0 +1,272 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
+  <title>WebSocket test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="websocket_helpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="testWebSocket()">
+<script class="testbody" type="text/javascript">
+
+function test11() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-11");
+    is(ws.readyState, 0, "create bad readyState in test-11!");
+
+    ws.onopen = function() {
+      is(ws.readyState, 1, "open bad readyState in test-11!");
+      ws.send("client data");
+    }
+
+    ws.onmessage = function(e) {
+      is(e.data, "server data", "bad received message in test-11!");
+      ws.close(1000, "Have a nice day");
+
+     // this ok() is disabled due to a race condition - it state may have
+     // advanced through 2 (closing) and into 3 (closed) before it is evald
+     // ok(ws.readyState == 2, "onmessage bad readyState in test-11!");
+    }
+
+    ws.onclose = function(e) {
+      is(ws.readyState, 3, "onclose bad readyState in test-11!");
+      shouldCloseCleanly(e);
+      is(e.code, 1000, "test 11 got wrong close code: " + e.code);
+      is(e.reason, "Have a nice day", "test 11 got wrong close reason: " + e.reason);
+      resolve();
+    }
+  });
+}
+
+function test12() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-12");
+
+    ws.onopen = function() {
+      try {
+        // send an unpaired surrogate
+        ws._gotMessage = false;
+        ws.send("a\ud800b");
+        ok(true, "ok to send an unpaired surrogate");
+      } catch (e) {
+        ok(false, "shouldn't fail any more when sending an unpaired surrogate!");
+      }
+    }
+
+    ws.onmessage = function(msg) {
+      is(msg.data, "SUCCESS", "Unpaired surrogate in UTF-16 not converted in test-12");
+      ws._gotMessage = true;
+      // Must support unpaired surrogates in close reason, too
+      ws.close(1000, "a\ud800b");
+    }
+
+    ws.onclose = function(e) {
+      is(ws.readyState, 3, "onclose bad readyState in test-12!");
+      ok(ws._gotMessage, "didn't receive message!");
+      shouldCloseCleanly(e);
+      is(e.code, 1000, "test 12 got wrong close code: " + e.code);
+      is(e.reason, "a\ufffdb", "test 11 didn't get replacement char in close reason: " + e.reason);
+      resolve();
+    }
+  });
+}
+
+function test13() {
+  return new Promise(function(resolve, reject) {
+    // previous versions of this test counted the number of protocol errors
+    // returned, but the protocol stack typically closes down after reporting a
+    // protocol level error - trying to resync is too dangerous
+
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-13");
+    ws._timesCalledOnError = 0;
+
+    ws.onerror = function() {
+      ws._timesCalledOnError++;
+    }
+
+    ws.onclose = function(e) {
+      ok(ws._timesCalledOnError > 0, "no error events");
+      resolve();
+    }
+  });
+}
+
+function test14() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-14");
+
+    ws.onmessage = function() {
+      ok(false, "shouldn't received message after the server sent the close frame");
+    }
+
+    ws.onclose = function(e) {
+      shouldCloseCleanly(e);
+      resolve();
+    };
+  });
+}
+
+function test15() {
+  return new Promise(function(resolve, reject) {
+    /*
+     * DISABLED: see comments for test-15 case in file_websocket_wsh.py
+     */
+   resolve();
+
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-15");
+    ws.onclose = function(e) {
+      shouldCloseNotCleanly(e);
+      resolve();
+    }
+
+    // termination of the connection might cause an error event if it happens in OPEN
+    ws.onerror = function() {
+    }
+  });
+}
+
+function test16() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-16");
+
+    ws.onopen = function() {
+      ws.close();
+      ok(!ws.send("client data"), "shouldn't send message after calling close()");
+    }
+
+    ws.onmessage = function() {
+      ok(false, "shouldn't send message after calling close()");
+    }
+
+    ws.onerror = function() {
+    }
+
+    ws.onclose = function() {
+      resolve();
+    }
+  });
+}
+
+function test17() {
+  return new Promise(function(resolve, reject) {
+    var status_test17 = "not started";
+
+    var test17func = function() {
+      var local_ws = new WebSocket("ws://sub1.test2.example.org/tests/dom/base/test/file_websocket", "test-17");
+      status_test17 = "started";
+
+      local_ws.onopen = function(e) {
+        status_test17 = "opened";
+        e.target.send("client data");
+        forcegc();
+      };
+
+      local_ws.onerror = function() {
+        ok(false, "onerror called on test " + current_test + "!");
+      };
+
+      local_ws.onmessage = function(e) {
+        ok(e.data == "server data", "Bad message in test-17");
+        status_test17 = "got message";
+        forcegc();
+      };
+
+      local_ws.onclose = function(e) {
+        ok(status_test17 == "got message", "Didn't got message in test-17!");
+        shouldCloseCleanly(e);
+        status_test17 = "closed";
+        forcegc();
+        resolve();
+      };
+
+      window._test17 = null;
+      forcegc();
+    }
+
+    window._test17 = test17func;
+    window._test17();
+  });
+}
+
+// The tests that expects that their websockets neither open nor close MUST
+// be in the end of the tests, i.e. HERE, in order to prevent blocking the other
+// tests.
+
+function test18() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket_http_resource.txt");
+    ws.onopen = shouldNotOpen;
+    ws.onerror = ignoreError;
+    ws.onclose = function(e)
+    {
+      shouldCloseNotCleanly(e);
+      resolve();
+    }
+  });
+}
+
+function test19() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-19");
+    ws.onopen = shouldNotOpen;
+    ws.onerror = ignoreError;
+    ws.onclose = function(e)
+    {
+      shouldCloseNotCleanly(e);
+      resolve();
+    }
+  });
+}
+
+function test20() {
+  return new Promise(function(resolve, reject) {
+    var test20func = function() {
+      var local_ws = new WebSocket("ws://sub1.test1.example.org/tests/dom/base/test/file_websocket", "test-20");
+
+      local_ws.onerror = function() {
+        ok(false, "onerror called on test " + current_test + "!");
+      }
+
+      local_ws.onclose = function(e) {
+        ok(true, "test 20 closed despite gc");
+        resolve();
+      }
+
+      local_ws = null;
+      window._test20 = null;
+      forcegc();
+    }
+
+    window._test20 = test20func;
+    window._test20();
+  });
+}
+
+var tests = [
+  test11, // a simple hello echo;
+  test12, // client sends a message containing unpaired surrogates
+  test13, //server sends an invalid message;
+  test14, // server sends the close frame, it doesn't close the tcp connection
+          // and it keeps sending normal ws messages;
+  test15, // server closes the tcp connection, but it doesn't send the close
+          // frame;
+  test16, // client calls close() and tries to send a message;
+  test17, // see bug 572975 - all event listeners set
+  test18, // client tries to connect to an http resource;
+  test19, // server closes the tcp connection before establishing the ws
+          // connection;
+  test20, // see bug 572975 - only on error and onclose event listeners set
+];
+
+function testWebSocket() {
+  doTest();
+}
+
+</script>
+
+<div id="feedback">
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_websocket3.html
@@ -0,0 +1,225 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
+  <title>WebSocket test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="websocket_helpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="testWebSocket()">
+<script class="testbody" type="text/javascript">
+
+function test21() {
+  return new Promise(function(resolve, reject) {
+    var test21func = function() {
+      var local_ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-21");
+      var received_message = false;
+
+      local_ws.onopen = function(e) {
+        e.target.send("client data");
+        forcegc();
+        e.target.onopen = null;
+        forcegc();
+      }
+
+      local_ws.onerror = function() {
+        ok(false, "onerror called on test " + current_test + "!");
+      }
+
+      local_ws.onmessage = function(e) {
+        is(e.data, "server data", "Bad message in test-21");
+        received_message = true;
+        forcegc();
+        e.target.onmessage = null;
+        forcegc();
+      }
+
+      local_ws.onclose = function(e) {
+        shouldCloseCleanly(e);
+        ok(received_message, "close transitioned through onmessage");
+        resolve();
+      }
+
+      local_ws = null;
+      window._test21 = null;
+      forcegc();
+    }
+
+    window._test21 = test21func;
+    window._test21();
+  });
+}
+
+function test22() {
+  return new Promise(function(resolve, reject) {
+    const pref_open = "network.websocket.timeout.open";
+    SpecialPowers.setIntPref(pref_open, 5);
+  
+    var ws = CreateTestWS("ws://sub2.test2.example.org/tests/dom/base/test/file_websocket", "test-22");
+
+    ws.onopen = shouldNotOpen;
+    ws.onerror = ignoreError;
+
+    ws.onclose = function(e) {
+      shouldCloseNotCleanly(e);
+      resolve();
+    }
+  
+    SpecialPowers.clearUserPref(pref_open);
+  });
+}
+
+function test23() {
+  return new Promise(function(resolve, reject) {
+    ok("WebSocket" in window, "WebSocket should be available on window object");
+    resolve();
+  });
+}
+
+function test24() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-does-not-exist");
+
+    ws.onopen = shouldNotOpen;
+    ws.onclose = function(e) {
+      shouldCloseNotCleanly(e);
+      resolve();
+    }
+
+    ws.onerror = function() {
+    }
+  });
+}
+
+function test25() {
+  return new Promise(function(resolve, reject) {
+    var prots=[];
+  
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+  
+    // This test errors because the server requires a sub-protocol, but
+    // the test just wants to ensure that the ctor doesn't generate an
+    // exception
+    ws.onerror = ignoreError;
+    ws.onopen = shouldNotOpen;
+  
+    ws.onclose = function(e) {
+      is(ws.protocol, "", "test25 subprotocol selection");
+      ok(true, "test 25 protocol array close");
+      resolve();
+    }
+  });
+}
+
+function test26() {
+  return new Promise(function(resolve, reject) {
+    var prots=[""];
+  
+    try {
+      var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+      ok(false, "testing empty element sub protocol array");
+    } catch (e) {
+      ok(true, "testing empty sub element protocol array");
+    }
+
+    resolve();
+  });
+}
+
+function test27() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test27", ""];
+  
+    try {
+      var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+      ok(false, "testing empty element mixed sub protocol array");
+    } catch (e) {
+      ok(true, "testing empty element mixed sub protocol array");
+    }
+
+    resolve();
+  });
+}
+
+function test28() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test28"];
+  
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+    ws.onopen = function(e) {
+      ok(true, "test 28 protocol array open");
+      ws.close();
+    }
+  
+    ws.onclose = function(e) {
+      is(ws.protocol, "test28", "test28 subprotocol selection");
+      ok(true, "test 28 protocol array close");
+      resolve();
+    }
+  });
+}
+
+function test29() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test29a", "test29b"];
+  
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+    ws.onopen = function(e) {
+      ok(true, "test 29 protocol array open");
+      ws.close();
+    }
+  
+    ws.onclose = function(e) {
+      ok(true, "test 29 protocol array close");
+      resolve();
+    }
+  });
+}
+
+function test30() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test-does-not-exist"];
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+  
+    ws.onopen = shouldNotOpen;
+
+    ws.onclose = function(e) {
+      shouldCloseNotCleanly(e);
+      resolve();
+    }
+
+    ws.onerror = function() {
+    }
+  });
+}
+
+var tests = [
+ test21, // see bug 572975 - same as test 17, but delete strong event listeners
+         // when receiving the message event;
+ test22, // server takes too long to establish the ws connection;
+ test23, // should detect WebSocket on window object;
+ test24, // server rejects sub-protocol string
+ test25, // ctor with valid empty sub-protocol array
+ test26, // ctor with invalid sub-protocol array containing 1 empty element
+ test27, // ctor with invalid sub-protocol array containing an empty element in
+         // list
+ test28, // ctor using valid 1 element sub-protocol array
+ test29, // ctor using all valid 5 element sub-protocol array
+ test30, // ctor using valid 1 element sub-protocol array with element server
+         // will reject
+];
+
+function testWebSocket() {
+  doTest();
+}
+
+</script>
+
+<div id="feedback">
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_websocket4.html
@@ -0,0 +1,290 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
+  <title>WebSocket test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="websocket_helpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="testWebSocket()">
+<script class="testbody" type="text/javascript">
+
+function test31() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test-does-not-exist", "test31"];
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+    ws.onopen = function(e) {
+      ok(true, "test 31 protocol array open");
+      ws.close();
+    }
+
+    ws.onclose = function(e) {
+      is(ws.protocol, "test31", "test31 subprotocol selection");
+      ok(true, "test 31 protocol array close");
+      resolve();
+    }
+  });
+}
+
+function test32() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test32","test32"];
+
+    try {
+      var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+      ok(false, "testing duplicated element sub protocol array");
+    } catch (e) {
+      ok(true, "testing duplicated sub element protocol array");
+    }
+
+    resolve();
+  });
+}
+
+function test33() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test33"];
+
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+    ws.onopen = function(e) {
+      ok(true, "test 33 open");
+      ws.close(3131);   // pass code but not reason
+    }
+
+    ws.onclose = function(e) {
+      ok(true, "test 33 close");
+      shouldCloseCleanly(e);
+      is(e.code, 3131, "test 33 got wrong close code: " + e.code);
+      is(e.reason, "", "test 33 got wrong close reason: " + e.reason);
+      resolve();
+    }
+  });
+}
+
+function test34() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test-34"];
+
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+    ws.onopen = function(e) {
+      ok(true, "test 34 open");
+      ws.close();
+    }
+
+    ws.onclose = function(e)
+    {
+      ok(true, "test 34 close");
+      ok(e.wasClean, "test 34 closed cleanly");
+      is(e.code, 1001, "test 34 custom server code");
+      is(e.reason, "going away now", "test 34 custom server reason");
+      resolve();
+    }
+  });
+}
+
+function test35() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-35a");
+
+    ws.onopen = function(e) {
+      ok(true, "test 35a open");
+      ws.close(3500, "my code");
+    }
+
+    ws.onclose = function(e) {
+      ok(true, "test 35a close");
+      ok(e.wasClean, "test 35a closed cleanly");
+      var wsb = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-35b");
+
+      wsb.onopen = function(e) {
+        ok(true, "test 35b open");
+        wsb.close();
+      }
+
+      wsb.onclose = function(e) {
+        ok(true, "test 35b close");
+        ok(e.wasClean, "test 35b closed cleanly");
+        is(e.code, 3501, "test 35 custom server code");
+        is(e.reason, "my code", "test 35 custom server reason");
+        resolve();
+      }
+    }
+  });
+}
+
+function test36() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test-36"];
+
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+    ws.onopen = function(e) {
+      ok(true, "test 36 open");
+
+      try {
+        ws.close(13200);
+        ok(false, "testing custom close code out of range");
+       } catch (e) {
+         ok(true, "testing custom close code out of range");
+         ws.close(3200);
+       }
+    }
+
+    ws.onclose = function(e) {
+      ok(true, "test 36 close");
+      ok(e.wasClean, "test 36 closed cleanly");
+      resolve();
+    }
+  });
+}
+
+function test37() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test-37"];
+
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+    ws.onopen = function(e) {
+      ok(true, "test 37 open");
+
+      try {
+	ws.close(3100,"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123");
+        ok(false, "testing custom close reason out of range");
+       } catch (e) {
+         ok(true, "testing custom close reason out of range");
+         ws.close(3100,"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012");
+       }
+    }
+
+    ws.onclose = function(e) {
+      ok(true, "test 37 close");
+      ok(e.wasClean, "test 37 closed cleanly");
+
+      var wsb = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-37b");
+
+      wsb.onopen = function(e) {
+        // now test that a rejected close code and reason dont persist
+        ok(true, "test 37b open");
+        try {
+          wsb.close(3101,"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123");
+          ok(false, "testing custom close reason out of range 37b");
+        } catch (e) {
+          ok(true, "testing custom close reason out of range 37b");
+          wsb.close();
+        }
+      }
+
+      wsb.onclose = function(e) {
+        ok(true, "test 37b close");
+        ok(e.wasClean, "test 37b closed cleanly");
+
+        var wsc = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-37c");
+
+        wsc.onopen = function(e) {
+          ok(true, "test 37c open");
+          wsc.close();
+        }
+
+        wsc.onclose = function(e) {
+          isnot(e.code, 3101, "test 37c custom server code not present");
+          is(e.reason, "", "test 37c custom server reason not present");
+          resolve();
+        }
+      }
+    }
+  });
+}
+
+function test38() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test-38"];
+
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+    ws.onopen = function(e) {
+      ok(true, "test 38 open");
+      isnot(ws.extensions, undefined, "extensions attribute defined");
+      //  is(ws.extensions, "deflate-stream", "extensions attribute deflate-stream");
+      ws.close();
+    }
+
+    ws.onclose = function(e) {
+      ok(true, "test 38 close");
+      resolve();
+    }
+  });
+}
+
+function test39() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test-39"];
+
+    var ws = CreateTestWS("wss://example.com/tests/dom/base/test/file_websocket", prots);
+    status_test39 = "started";
+
+    ws.onopen = function(e) {
+      status_test39 = "opened";
+      ok(true, "test 39 open");
+      ws.close();
+    }
+
+    ws.onclose = function(e) {
+      ok(true, "test 39 close");
+      is(status_test39, "opened", "test 39 did open");
+      resolve();
+    }
+  });
+}
+
+function test40() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test-40"];
+
+    var ws = CreateTestWS("wss://nocert.example.com/tests/dom/base/test/file_websocket", prots);
+
+    status_test40 = "started";
+    ws.onerror = ignoreError;
+
+    ws.onopen = function(e) {
+      status_test40 = "opened";
+      ok(false, "test 40 open");
+      ws.close();
+    }
+
+    ws.onclose = function(e) {
+      ok(true, "test 40 close");
+      is(status_test40, "started", "test 40 did not open");
+      resolve();
+    }
+  });
+}
+
+var tests = [
+  test31, // ctor using valid 2 element sub-protocol array with 1 element server
+          // will reject and one server will accept
+  test32, // ctor using invalid sub-protocol array that contains duplicate items
+  test33, // test for sending/receiving custom close code (but no close reason)
+  test34, // test for receiving custom close code and reason
+  test35, // test for sending custom close code and reason
+  test36, // negative test for sending out of range close code
+  test37, // negative test for too long of a close reason
+  test38, // ensure extensions attribute is defined
+  test39, // a basic wss:// connectivity test
+  test40, // negative test for wss:// with no cert
+];
+
+function testWebSocket() {
+  doTest();
+}
+
+</script>
+
+<div id="feedback">
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_websocket5.html
@@ -0,0 +1,293 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
+  <title>WebSocket test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="websocket_helpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="testWebSocket()">
+<script class="testbody" type="text/javascript">
+
+function test41() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://example.com/tests/dom/base/test/file_websocket", "test-41a", 1);
+
+    ws.onopen = function(e) {
+      ok(true, "test 41a open");
+      is(ws.url, "ws://example.com/tests/dom/base/test/file_websocket",
+         "test 41a initial ws should not be redirected");
+      ws.close();
+    }
+
+    ws.onclose = function(e) {
+      ok(true, "test 41a close");
+
+      // establish a hsts policy for example.com
+      var wsb = CreateTestWS("wss://example.com/tests/dom/base/test/file_websocket", "test-41b", 1);
+
+      wsb.onopen = function(e) {
+        ok(true, "test 41b open");
+        wsb.close();
+      }
+
+      wsb.onclose = function(e) {
+        ok(true, "test 41b close");
+
+        // try ws:// again, it should be done over wss:// now due to hsts
+        var wsc = CreateTestWS("ws://example.com/tests/dom/base/test/file_websocket", "test-41c");
+
+        wsc.onopen = function(e) {
+          ok(true, "test 41c open");
+          is(wsc.url, "wss://example.com/tests/dom/base/test/file_websocket",
+             "test 41c ws should be redirected by hsts to wss");
+          wsc.close();
+        }
+
+        wsc.onclose = function(e) {
+          ok(true, "test 41c close");
+
+          // clean up the STS state
+          const Ci = SpecialPowers.Ci;
+          var loadContext = SpecialPowers.wrap(window)
+                            .QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsIWebNavigation)
+                            .QueryInterface(Ci.nsILoadContext);
+          var flags = 0;
+          if (loadContext.usePrivateBrowsing)
+            flags |= Ci.nsISocketProvider.NO_PERMANENT_STORAGE;
+          SpecialPowers.cleanUpSTSData("http://example.com", flags);
+          resolve();
+         }
+       }
+    }
+  });
+}
+
+function test42() {
+  return new Promise(function(resolve, reject) {
+    // test some utf-8 non-characters. They should be allowed in the
+    // websockets context. Test via round trip echo.
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-42");
+    var data = ["U+FFFE \ufffe",
+		"U+FFFF \uffff",
+		"U+10FFFF \udbff\udfff"];
+    var index = 0;
+
+    ws.onopen = function() {
+      ws.send(data[0]);
+      ws.send(data[1]);
+      ws.send(data[2]);
+    }
+
+    ws.onmessage = function(e) {
+      is(e.data, data[index], "bad received message in test-42! index="+index);
+      index++;
+      if (index == 3) {
+        ws.close();
+      }
+    }
+
+    ws.onclose = function(e) {
+      resolve();
+    }
+  });
+}
+
+function test43() {
+  return new Promise(function(resolve, reject) {
+    var prots=["test-43"];
+
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+    ws.onopen = function(e) {
+      ok(true, "test 43 open");
+      // Test binaryType setting
+      ws.binaryType = "arraybuffer";
+      ws.binaryType = "blob";
+      ws.binaryType = "";  // illegal
+      is(ws.binaryType, "blob");
+      ws.binaryType = "ArrayBuffer";  // illegal
+      is(ws.binaryType, "blob");
+      ws.binaryType = "Blob";  // illegal
+      is(ws.binaryType, "blob");
+      ws.binaryType = "mcfoofluu";  // illegal
+      is(ws.binaryType, "blob");
+      ws.close();
+    }
+
+    ws.onclose = function(e) {
+      ok(true, "test 43 close");
+      resolve();
+    }
+  });
+}
+
+
+function test44() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-44");
+    is(ws.readyState, 0, "bad readyState in test-44!");
+    ws.binaryType = "arraybuffer";
+
+    ws.onopen = function() {
+      is(ws.readyState, 1, "open bad readyState in test-44!");
+      var buf = new ArrayBuffer(3);
+      // create byte view
+      var view = new Uint8Array(buf);
+      view[0] = 5;
+      view[1] = 0; // null byte
+      view[2] = 7;
+      ws.send(buf);
+    }
+
+    ws.onmessage = function(e) {
+      ok(e.data instanceof ArrayBuffer, "Should receive an arraybuffer!");
+      var view = new Uint8Array(e.data);
+      ok(view.length == 2 && view[0] == 0 && view[1] ==4, "testing Reply arraybuffer" );
+      ws.close();
+    }
+
+    ws.onclose = function(e) {
+      is(ws.readyState, 3, "onclose bad readyState in test-44!");
+      shouldCloseCleanly(e);
+      resolve();
+    }
+  });
+}
+
+function test45()
+{
+  return new Promise(function(resolve, reject) {
+    function test45Real(blobFile) {
+      var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-45");
+      is(ws.readyState, 0, "bad readyState in test-45!");
+      // ws.binaryType = "blob";  // Don't need to specify: blob is the default
+
+      ws.onopen = function() {
+        is(ws.readyState, 1, "open bad readyState in test-45!");
+        ws.send(blobFile);
+      }
+
+      var test45blob;
+
+      ws.onmessage = function(e) {
+        test45blob = e.data;
+        ok(test45blob instanceof Blob, "We should be receiving a Blob");
+
+        ws.close();
+      }
+
+      ws.onclose = function(e) {
+        is(ws.readyState, 3, "onclose bad readyState in test-45!");
+        shouldCloseCleanly(e);
+
+        // check blob contents
+        var reader = new FileReader();
+        reader.onload = function(event) {
+          is(reader.result, "flob", "response should be 'flob': got '"
+             + reader.result + "'");
+        }
+
+        reader.onerror = function(event) {
+          testFailed("Failed to read blob: error code = " + reader.error.code);
+        }
+
+        reader.onloadend = function(event) {
+          resolve();
+        }
+
+        reader.readAsBinaryString(test45blob);
+      }
+    }
+
+    SpecialPowers.createFiles([{name: "testBlobFile", data: "flob"}],
+    function(files) {
+      test45Real(files[0]);
+    },
+    function(msg) {
+      testFailed("Failed to create file for test45: " + msg);
+      resolve();
+    });
+  });
+}
+
+function test46() {
+  return new Promise(function(resolve, reject) {
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-46");
+    is(ws.readyState, 0, "create bad readyState in test-46!");
+
+    ws.onopen = function() {
+      is(ws.readyState, 1, "open bad readyState in test-46!");
+      ws.close()
+      is(ws.readyState, 2, "close must set readyState to 2 in test-46!");
+    }
+
+    ws.onmessage = function(e) {
+      ok(false, "received message after calling close in test-46!");
+    }
+
+    ws.onclose = function(e) {
+      is(ws.readyState, 3, "onclose bad readyState in test-46!");
+      shouldCloseCleanly(e);
+      resolve();
+    }
+  });
+}
+
+function test47() {
+  return new Promise(function(resolve, reject) {
+    var hasError = false;
+    var ws = CreateTestWS("ws://another.websocket.server.that.probably.does.not.exist");
+
+    ws.onopen = shouldNotOpen;
+
+    ws.onerror = function (e) {
+      is(ws.readyState, 3, "test-47: readyState should be CLOSED(3) in onerror: got "
+         + ws.readyState);
+      ok(!ws._withinClose, "onerror() called during close()!");
+      hasError = true;
+    }
+
+    ws.onclose = function(e) {
+      shouldCloseNotCleanly(e);
+      ok(hasError, "test-47: should have called onerror before onclose");
+      is(ws.readyState, 3, "test-47: readyState should be CLOSED(3) in onclose: got "
+         + ws.readyState);
+      ok(!ws._withinClose, "onclose() called during close()!");
+      is(e.code, 1006, "test-47 close code should be 1006 but is:" + e.code);
+      resolve();
+    }
+
+    // Call close before we're connected: throws error
+    // Make sure we call onerror/onclose asynchronously
+    ws._withinClose = 1;
+    ws.close(3333, "Closed before we were open: error");
+    ws._withinClose = 0;
+    is(ws.readyState, 2, "test-47: readyState should be CLOSING(2) after close(): got "
+       + ws.readyState);
+  });
+}
+
+var tests = [
+  test41, // HSTS
+  test42, // non-char utf-8 sequences
+  test43, // Test setting binaryType attribute
+  test44, // Test sending/receving binary ArrayBuffer
+  test45, // Test sending/receving binary Blob
+  test46, // Test that we don't dispatch incoming msgs once in CLOSING state
+  test47, // Make sure onerror/onclose aren't called during close()
+];
+
+function testWebSocket() {
+  doTest();
+}
+
+</script>
+
+<div id="feedback">
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/websocket_helpers.js
@@ -0,0 +1,62 @@
+var current_test = 0;
+
+function shouldNotOpen(e) {
+  var ws = e.target;
+  ok(false, "onopen shouldn't be called on test " + ws._testNumber + "!");
+}
+
+function shouldCloseCleanly(e) {
+  var ws = e.target;
+  ok(e.wasClean, "the ws connection in test " + ws._testNumber + " should be closed cleanly");
+}
+
+function shouldCloseNotCleanly(e) {
+  var ws = e.target;
+  ok(!e.wasClean, "the ws connection in test " + ws._testNumber + " shouldn't be closed cleanly");
+}
+
+function ignoreError(e) {
+}
+
+function CreateTestWS(ws_location, ws_protocol) {
+  var ws;
+
+  try {
+    if (ws_protocol == undefined) {
+      ws = new WebSocket(ws_location);
+    } else {
+      ws = new WebSocket(ws_location, ws_protocol);
+    }
+
+    ws._testNumber = current_test;
+    ok(true, "Created websocket for test " + ws._testNumber +"\n");
+
+    ws.onerror = function(e) {
+      ok(false, "onerror called on test " + e.target._testNumber + "!");
+    }
+
+  } catch (e) {
+    throw e;
+  }
+
+  return ws;
+}
+
+function forcegc() {
+  SpecialPowers.forceGC();
+  SpecialPowers.gc();
+}
+
+function doTest() {
+  if (current_test >= tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  $("feedback").innerHTML = "executing test: " + (current_test+1) + " of " + tests.length + " tests.";
+  tests[current_test++]().then(doTest);
+}
+
+SimpleTest.requestFlakyTimeout("The web socket tests are really fragile, but avoiding timeouts might be hard, since it's testing stuff on the network. " +
+                               "Expect all sorts of flakiness in this test...");
+SimpleTest.waitForExplicitFinish();
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -25,17 +25,17 @@
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIXPConnect.h"
 #include "nsUTF8Utils.h"
 #include "WrapperFactory.h"
 #include "xpcprivate.h"
 #include "XrayWrapper.h"
 #include "nsPrintfCString.h"
-#include "prprf.h"
+#include "mozilla/Snprintf.h"
 #include "nsGlobalWindow.h"
 
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/DOMErrorBinding.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/ElementBinding.h"
 #include "mozilla/dom/HTMLObjectElement.h"
@@ -52,17 +52,17 @@
 #include "ipc/ErrorIPCUtils.h"
 #include "mozilla/UseCounter.h"
 
 namespace mozilla {
 namespace dom {
 
 JSErrorFormatString ErrorFormatString[] = {
 #define MSG_DEF(_name, _argc, _exn, _str) \
-  { _str, _argc, _exn },
+  { #_name, _str, _argc, _exn },
 #include "mozilla/dom/Errors.msg"
 #undef MSG_DEF
 };
 
 #define MSG_DEF(_name, _argc, _exn, _str) \
   static_assert(_argc < JS::MaxNumErrorArguments, \
                 #_name " must only have as many error arguments as the JS engine can support");
 #include "mozilla/dom/Errors.msg"
@@ -2419,23 +2419,23 @@ ConvertJSValueToByteString(JSContext* cx
 
     if (foundBadChar) {
       MOZ_ASSERT(badCharIndex < length);
       MOZ_ASSERT(badChar > 255);
       // The largest unsigned 64 bit number (18,446,744,073,709,551,615) has
       // 20 digits, plus one more for the null terminator.
       char index[21];
       static_assert(sizeof(size_t) <= 8, "index array too small");
-      PR_snprintf(index, sizeof(index), "%d", badCharIndex);
+      snprintf_literal(index, "%d", badCharIndex);
       // A char16_t is 16 bits long.  The biggest unsigned 16 bit
       // number (65,535) has 5 digits, plus one more for the null
       // terminator.
       char badCharArray[6];
       static_assert(sizeof(char16_t) <= 2, "badCharArray too small");
-      PR_snprintf(badCharArray, sizeof(badCharArray), "%d", badChar);
+      snprintf_literal(badCharArray, "%d", badChar);
       ThrowErrorMessage(cx, MSG_INVALID_BYTESTRING, index, badCharArray);
       return false;
     }
   } else {
     length = js::GetStringLength(s);
   }
 
   static_assert(js::MaxStringLength < UINT32_MAX,
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -19,16 +19,17 @@
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/CallbackObject.h"
 #include "mozilla/dom/DOMJSClass.h"
 #include "mozilla/dom/DOMJSProxyHandler.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/NonRefcountedDOMObject.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/SegmentedVector.h"
 #include "mozilla/dom/workers/Workers.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsIDocument.h"
 #include "nsIGlobalObject.h"
 #include "nsIXPConnect.h"
 #include "nsJSUtils.h"
@@ -2844,37 +2845,37 @@ private:
 template<class T>
 struct DeferredFinalizerImpl
 {
   typedef typename Conditional<IsSame<T, nsISupports>::value,
                                nsCOMPtr<T>,
                                typename Conditional<IsRefcounted<T>::value,
                                                     RefPtr<T>,
                                                     nsAutoPtr<T>>::Type>::Type SmartPtr;
-  typedef nsTArray<SmartPtr> SmartPtrArray;
+  typedef SegmentedVector<SmartPtr> SmartPtrArray;
 
   static_assert(IsSame<T, nsISupports>::value || !IsBaseOf<nsISupports, T>::value,
                 "nsISupports classes should all use the nsISupports instantiation");
 
   static inline void
-  AppendAndTake(nsTArray<nsCOMPtr<nsISupports>>& smartPtrArray, nsISupports* ptr)
+  AppendAndTake(SegmentedVector<nsCOMPtr<nsISupports>>& smartPtrArray, nsISupports* ptr)
   {
-    smartPtrArray.AppendElement(dont_AddRef(ptr));
+    smartPtrArray.InfallibleAppend(dont_AddRef(ptr));
   }
   template<class U>
   static inline void
-  AppendAndTake(nsTArray<RefPtr<U>>& smartPtrArray, U* ptr)
+  AppendAndTake(SegmentedVector<RefPtr<U>>& smartPtrArray, U* ptr)
   {
-    smartPtrArray.AppendElement(dont_AddRef(ptr));
+    smartPtrArray.InfallibleAppend(dont_AddRef(ptr));
   }
   template<class U>
   static inline void
-  AppendAndTake(nsTArray<nsAutoPtr<U>>& smartPtrArray, U* ptr)
+  AppendAndTake(SegmentedVector<nsAutoPtr<U>>& smartPtrArray, U* ptr)
   {
-    smartPtrArray.AppendElement(ptr);
+    smartPtrArray.InfallibleAppend(ptr);
   }
 
   static void*
   AppendDeferredFinalizePointer(void* aData, void* aObject)
   {
     SmartPtrArray* pointers = static_cast<SmartPtrArray*>(aData);
     if (!pointers) {
       pointers = new SmartPtrArray();
@@ -2887,17 +2888,17 @@ struct DeferredFinalizerImpl
   {
     MOZ_ASSERT(aSlice > 0, "nonsensical/useless call with aSlice == 0");
     SmartPtrArray* pointers = static_cast<SmartPtrArray*>(aData);
     uint32_t oldLen = pointers->Length();
     if (oldLen < aSlice) {
       aSlice = oldLen;
     }
     uint32_t newLen = oldLen - aSlice;
-    pointers->RemoveElementsAt(newLen, aSlice);
+    pointers->PopLastN(aSlice);
     if (newLen == 0) {
       delete pointers;
       return true;
     }
     return false;
   }
 };
 
--- a/dom/bluetooth/bluez/BluetoothSocket.cpp
+++ b/dom/bluetooth/bluez/BluetoothSocket.cpp
@@ -610,17 +610,17 @@ BluetoothSocket::Connect(const Bluetooth
 nsresult
 BluetoothSocket::Listen(const nsAString& aServiceName,
                         const BluetoothUuid& aServiceUuid,
                         BluetoothSocketType aType,
                         int aChannel,
                         bool aAuth, bool aEncrypt)
 {
   nsAutoPtr<BluetoothUnixSocketConnector> connector(
-    new BluetoothUnixSocketConnector(BluetoothAddress::ANY, aType,
+    new BluetoothUnixSocketConnector(BluetoothAddress::ANY(), aType,
                                      aChannel, aAuth, aEncrypt));
 
   nsresult rv = Listen(connector);
   if (NS_FAILED(rv)) {
     BluetoothAddress address;
     GetAddress(address);
 
     nsAutoString addressStr;
--- a/dom/canvas/test/_webgl-conformance.ini
+++ b/dom/canvas/test/_webgl-conformance.ini
@@ -1,16 +1,16 @@
 # This is a GENERATED FILE. Do not edit it directly.
 # Regenerated it by using `python generate-wrappers-and-manifest.py`.
 # Mark skipped tests in mochitest-errata.ini.
 # Mark failing tests in mochi-single.html.
 
 [DEFAULT]
 subsuite = webgl
-skip-if = e10s || os == 'b2g' || ((os == 'linux') && (buildapp == 'b2g')) || ((os == 'linux') && (buildapp == 'mulet'))
+skip-if = os == 'b2g' || ((os == 'linux') && (buildapp == 'b2g')) || ((os == 'linux') && (buildapp == 'mulet'))
 
 support-files = webgl-conformance/../webgl-mochitest/driver-info.js
                 webgl-conformance/always-fail.html
                 webgl-conformance/conformance/00_readme.txt
                 webgl-conformance/conformance/00_test_list.txt
                 webgl-conformance/conformance/LICENSE_CHROMIUM
                 webgl-conformance/conformance/attribs/00_test_list.txt
                 webgl-conformance/conformance/attribs/gl-enable-vertex-attrib.html
--- a/dom/events/DOMEventTargetHelper.cpp
+++ b/dom/events/DOMEventTargetHelper.cpp
@@ -1,17 +1,17 @@
 /* -*- 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 "nsContentUtils.h"
 #include "nsIDocument.h"
-#include "prprf.h"
+#include "mozilla/Snprintf.h"
 #include "nsGlobalWindow.h"
 #include "ScriptSettings.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/Likely.h"
 
 namespace mozilla {
@@ -26,18 +26,18 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(DOMEventTargetHelper)
   if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
     char name[512];
     nsAutoString uri;
     if (tmp->mOwnerWindow && tmp->mOwnerWindow->GetExtantDoc()) {
       tmp->mOwnerWindow->GetExtantDoc()->GetDocumentURI(uri);
     }
-    PR_snprintf(name, sizeof(name), "DOMEventTargetHelper %s",
-                NS_ConvertUTF16toUTF8(uri).get());
+    snprintf_literal(name, "DOMEventTargetHelper %s",
+                     NS_ConvertUTF16toUTF8(uri).get());
     cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
   } else {
     NS_IMPL_CYCLE_COLLECTION_DESCRIBE(DOMEventTargetHelper, tmp->mRefCnt.get())
   }
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
--- a/dom/events/test/bug299673.js
+++ b/dom/events/test/bug299673.js
@@ -90,20 +90,16 @@ function doTest1(expectedEventLog,focusA
   } catch(e) {
     if (popup)
       popup.close();
     throw e;
   }
 }
 
 function setPrefAndDoTest(expectedEventLog,focusAfterCloseId,prefValue) {
-  var origPrefValue = SpecialPowers.getIntPref("browser.link.open_newwindow");
   var select1 = document.getElementById('Select1');
   select1.blur();
   result = "";
   log({},"Test with browser.link.open_newwindow = "+prefValue);
-  try {
-    SpecialPowers.setIntPref("browser.link.open_newwindow", prefValue);
-    doTest1(expectedEventLog,focusAfterCloseId);
-  } finally {
-    SpecialPowers.setIntPref("browser.link.open_newwindow", origPrefValue);
-  }
+   SpecialPowers.pushPrefEnv({"set": [['browser.link.open_newwindow', prefValue]]}, function() {
+     doTest1(expectedEventLog,focusAfterCloseId);
+   });
 }
--- a/dom/events/test/test_bug299673-1.html
+++ b/dom/events/test/test_bug299673-1.html
@@ -48,14 +48,14 @@ SELECT(Select1): blur \n\
 INPUT(popupText1): blur \n\
 : blur popup-doc\n\
 : focus top-doc\n\
 '
 
   setPrefAndDoTest(eventLogForNewWindow,'Body',2);  // 2 = open new window as window
 }
 
-todo(false, "Please write a test for bug 299673 that actually works");
+todo(false, "Please write a test for bug 299673 that actually works, see bug 553417");
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/events/test/test_bug299673-2.html
+++ b/dom/events/test/test_bug299673-2.html
@@ -36,25 +36,25 @@ https://bugzilla.mozilla.org/show_bug.cg
 function doTest(expectedEventLog) {
   var eventLogForNewTab = '\
  :  Test with browser.link.open_newwindow = 3\n\
 : focus top-doc\n\
 SELECT(Select1): focus \n\
 SELECT(Select1): change \n\
  :  >>> OpenWindow\n\
 : blur top-doc\n\
+: focus popup-doc\n\
 INPUT(popupText1): focus \n\
  :  <<< OpenWindow\n\
 SELECT(Select1): blur \n\
 INPUT(popupText1): blur \n\
 : blur popup-doc\n\
 : focus top-doc\n\
 '
   setPrefAndDoTest(eventLogForNewTab,'Body',3);  // 3 = open new window as tab
-
 }
 
-todo(false, "Please write a test for bug 299673 that actually works");
+todo(false, "Please write a test for bug 299673 that actually works, see bug 553417");
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -402,31 +402,27 @@ nsGeolocationRequest::~nsGeolocationRequ
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGeolocationRequest)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
   NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
   NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGeolocationRequest)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGeolocationRequest)
-
 NS_IMPL_CYCLE_COLLECTION(nsGeolocationRequest, mCallback, mErrorCallback, mLocator)
-
 void
 nsGeolocationRequest::Notify()
 {
-  StopTimeoutTimer();
+  SetTimeoutTimer();
   NotifyErrorAndShutdown(nsIDOMGeoPositionError::TIMEOUT);
 }
-
 void
 nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode)
 {
   MOZ_ASSERT(!mShutdown, "timeout after shutdown");
-
   if (!mIsWatchPositionRequest) {
     Shutdown();
     mLocator->RemoveRequest(this);
   }
 
   NotifyError(aErrorCode);
 }
 
@@ -713,26 +709,23 @@ nsGeolocationRequest::SendLocation(nsIDO
   nsAutoMicroTask mt;
   if (mCallback.HasWebIDLCallback()) {
     PositionCallback* callback = mCallback.GetWebIDLCallback();
 
     MOZ_ASSERT(callback);
     callback->Call(*wrapped);
   } else {
     nsIDOMGeoPositionCallback* callback = mCallback.GetXPCOMCallback();
-
     MOZ_ASSERT(callback);
     callback->HandleEvent(aPosition);
   }
-
-  StopTimeoutTimer();
+  SetTimeoutTimer();
   MOZ_ASSERT(mShutdown || mIsWatchPositionRequest,
              "non-shutdown getCurrentPosition request after callback!");
 }
-
 nsIPrincipal*
 nsGeolocationRequest::GetPrincipal()
 {
   if (!mLocator) {
     return nullptr;
   }
   return mLocator->GetPrincipal();
 }
@@ -740,32 +733,20 @@ nsGeolocationRequest::GetPrincipal()
 NS_IMETHODIMP
 nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition)
 {
   nsCOMPtr<nsIDOMGeoPosition> pos = AdjustedLocation(aPosition);
   nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(pos, this);
   NS_DispatchToMainThread(ev);
   return NS_OK;
 }
-
-NS_IMETHODIMP
-nsGeolocationRequest::LocationUpdatePending()
-{
-  if (!mTimeoutTimer) {
-    SetTimeoutTimer();
-  }
-
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 nsGeolocationRequest::NotifyError(uint16_t aErrorCode)
 {
   MOZ_ASSERT(NS_IsMainThread());
-
   RefPtr<PositionError> positionError = new PositionError(mLocator, aErrorCode);
   positionError->NotifyCallback(mErrorCallback);
   return NS_OK;
 }
 
 void
 nsGeolocationRequest::Shutdown()
 {
@@ -1025,40 +1006,27 @@ nsGeolocationService::Observe(nsISupport
 }
 
 NS_IMETHODIMP
 nsGeolocationService::Update(nsIDOMGeoPosition *aSomewhere)
 {
   if (aSomewhere) {
     SetCachedPosition(aSomewhere);
   }
-
   for (uint32_t i = 0; i< mGeolocators.Length(); i++) {
     mGeolocators[i]->Update(aSomewhere);
   }
   return NS_OK;
 }
-
-NS_IMETHODIMP
-nsGeolocationService::LocationUpdatePending()
-{
-  for (uint32_t i = 0; i< mGeolocators.Length(); i++) {
-    mGeolocators[i]->LocationUpdatePending();
-  }
-
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 nsGeolocationService::NotifyError(uint16_t aErrorCode)
 {
   for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
     mGeolocators[i]->NotifyError(aErrorCode);
   }
-
   return NS_OK;
 }
 
 void
 nsGeolocationService::SetCachedPosition(nsIDOMGeoPosition* aPosition)
 {
   mLastPosition.position = aPosition;
   mLastPosition.isHighAccuracy = mHigherAccuracy;
@@ -1471,39 +1439,25 @@ Geolocation::Update(nsIDOMGeoPosition *a
     mPendingCallbacks[i-1]->Update(aSomewhere);
     RemoveRequest(mPendingCallbacks[i-1]);
   }
 
   // notify everyone that is watching
   for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
     mWatchingCallbacks[i]->Update(aSomewhere);
   }
-
   return NS_OK;
 }
-
-NS_IMETHODIMP
-Geolocation::LocationUpdatePending()
-{
-  // this event is only really interesting for watch callbacks
-  for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
-    mWatchingCallbacks[i]->LocationUpdatePending();
-  }
-
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 Geolocation::NotifyError(uint16_t aErrorCode)
 {
   if (!WindowOwnerStillExists()) {
     Shutdown();
     return NS_OK;
   }
-
   mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_ERROR, true);
 
   for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) {
     mPendingCallbacks[i-1]->NotifyErrorAndShutdown(aErrorCode);
     //NotifyErrorAndShutdown() removes the request from the array
   }
 
   // notify everyone that is watching
--- a/dom/html/ImageDocument.cpp
+++ b/dom/html/ImageDocument.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "ImageDocument.h"
 #include "mozilla/dom/ImageDocumentBinding.h"
+#include "mozilla/dom/HTMLImageElement.h"
 #include "nsRect.h"
 #include "nsIImageLoadingContent.h"
 #include "nsGenericHTMLElement.h"
 #include "nsDocShell.h"
 #include "nsIDocumentInlines.h"
 #include "nsDOMTokenList.h"
 #include "nsIDOMHTMLImageElement.h"
 #include "nsIDOMEvent.h"
@@ -331,16 +332,33 @@ ImageDocument::GetImageRequest(imgIReque
 void
 ImageDocument::ShrinkToFit()
 {
   if (!mImageContent) {
     return;
   }
   if (GetZoomLevel() != mOriginalZoomLevel && mImageIsResized &&
       !nsContentUtils::IsChildOfSameType(this)) {
+    // If we're zoomed, so that we don't maintain the invariant that
+    // mImageIsResized if and only if its displayed width/height fit in
+    // mVisibleWidth/mVisibleHeight, then we may need to switch to/from the
+    // overflowingVertical class here, because our viewport size may have
+    // changed and we don't plan to adjust the image size to compensate.  Since
+    // mImageIsResized it has a "height" attribute set, and we can just get the
+    // displayed image height by getting .height on the HTMLImageElement.
+    HTMLImageElement* img = HTMLImageElement::FromContent(mImageContent);
+    uint32_t imageHeight = img->Height();
+    nsDOMTokenList* classList = img->ClassList();
+    ErrorResult ignored;
+    if (imageHeight > mVisibleHeight) {
+      classList->Add(NS_LITERAL_STRING("overflowingVertical"), ignored);
+    } else {
+      classList->Remove(NS_LITERAL_STRING("overflowingVertical"), ignored);
+    }
+    ignored.SuppressException();
     return;
   }
 
   // Keep image content alive while changing the attributes.
   nsCOMPtr<nsIContent> imageContent = mImageContent;
   nsCOMPtr<nsIDOMHTMLImageElement> image = do_QueryInterface(mImageContent);
   image->SetWidth(std::max(1, NSToCoordFloor(GetRatio() * mImageWidth)));
   image->SetHeight(std::max(1, NSToCoordFloor(GetRatio() * mImageHeight)));
@@ -496,17 +514,17 @@ ImageDocument::OnHasTransparency()
   mozilla::ErrorResult rv;
   classList->Add(NS_LITERAL_STRING("transparent"), rv);
 }
 
 void
 ImageDocument::SetModeClass(eModeClasses mode)
 {
   nsDOMTokenList* classList = mImageContent->AsElement()->ClassList();
-  mozilla::ErrorResult rv;
+  ErrorResult rv;
 
   if (mode == eShrinkToFit) {
     classList->Add(NS_LITERAL_STRING("shrinkToFit"), rv);
   } else {
     classList->Remove(NS_LITERAL_STRING("shrinkToFit"), rv);
   }
 
   if (mode == eOverflowingVertical) {
@@ -515,16 +533,18 @@ ImageDocument::SetModeClass(eModeClasses
     classList->Remove(NS_LITERAL_STRING("overflowingVertical"), rv);
   }
 
   if (mode == eOverflowingHorizontalOnly) {
     classList->Add(NS_LITERAL_STRING("overflowingHorizontalOnly"), rv);
   } else {
     classList->Remove(NS_LITERAL_STRING("overflowingHorizontalOnly"), rv);
   }
+
+  rv.SuppressException();
 }
 
 nsresult
 ImageDocument::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage)
 {
   // Styles have not yet been applied, so we don't know the final size. For now,
   // default to the image's intrinsic size.
   aImage->GetWidth(&mImageWidth);
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -1913,17 +1913,17 @@ nsTextEditorState::GetValue(nsAString& a
       mCachedValue = aValue;
     } else {
       mCachedValue.Truncate();
     }
   } else {
     if (!mTextCtrlElement->ValueChanged() || !mValue) {
       mTextCtrlElement->GetDefaultValueFromContent(aValue);
     } else {
-      aValue = NS_ConvertUTF8toUTF16(*mValue);
+      aValue = *mValue;
     }
   }
 }
 
 bool
 nsTextEditorState::SetValue(const nsAString& aValue, uint32_t aFlags)
 {
   nsAutoString newValue(aValue);
@@ -2134,26 +2134,26 @@ nsTextEditorState::SetValue(const nsAStr
         plaintextEditor->SetMaxTextLength(savedMaxLength);
         mEditor->SetFlags(savedFlags);
         if (selPriv)
           selPriv->EndBatchChanges();
       }
     }
   } else {
     if (!mValue) {
-      mValue = new nsCString;
+      mValue.emplace();
     }
     nsString value;
     if (!value.Assign(newValue, fallible)) {
       return false;
     }
     if (!nsContentUtils::PlatformToDOMLineBreaks(value, fallible)) {
       return false;
     }
-    if (!CopyUTF16toUTF8(value, *mValue, fallible)) {
+    if (!mValue->Assign(value, fallible)) {
       return false;
     }
 
     // Update the frame display if needed
     if (mBoundFrame) {
       mBoundFrame->UpdateValueDisplay(true);
     }
   }
--- a/dom/html/nsTextEditorState.h
+++ b/dom/html/nsTextEditorState.h
@@ -9,16 +9,17 @@
 
 #include "nsAutoPtr.h"
 #include "nsString.h"
 #include "nsITextControlElement.h"
 #include "nsITextControlFrame.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/WeakPtr.h"
 
 class nsTextInputListener;
 class nsTextControlFrame;
 class nsTextInputSelectionImpl;
 class nsAnonDivObserver;
 class nsISelectionController;
 class nsFrameSelection;
@@ -288,17 +289,17 @@ private:
   nsITextControlElement* const MOZ_NON_OWNING_REF mTextCtrlElement;
   RefPtr<nsTextInputSelectionImpl> mSelCon;
   RefPtr<RestoreSelectionState> mRestoringSelection;
   nsCOMPtr<nsIEditor> mEditor;
   nsCOMPtr<mozilla::dom::Element> mRootNode;
   nsCOMPtr<mozilla::dom::Element> mPlaceholderDiv;
   nsTextControlFrame* mBoundFrame;
   RefPtr<nsTextInputListener> mTextListener;
-  nsAutoPtr<nsCString> mValue;
+  mozilla::Maybe<nsString> mValue;
   RefPtr<nsAnonDivObserver> mMutationObserver;
   mutable nsString mCachedValue; // Caches non-hard-wrapped value on a multiline control.
   // mValueBeingSet is available only while SetValue() is requesting to commit
   // composition.  I.e., this is valid only while mIsCommittingComposition is
   // true.  While active composition is being committed, GetValue() needs
   // the latest value which is set by SetValue().  So, this is cache for that.
   nsString mValueBeingSet;
   SelectionProperties mSelectionProperties;
--- a/dom/ipc/PProcessHangMonitor.ipdl
+++ b/dom/ipc/PProcessHangMonitor.ipdl
@@ -1,29 +1,31 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: sw=2 ts=8 et :
  */
 /* 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/. */
 
+using base::ProcessId from "base/process.h";
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 
 namespace mozilla {
 
 struct SlowScriptData
 {
   TabId tabId;
   nsCString filename;
   uint32_t lineno;
 };
 
 struct PluginHangData
 {
   uint32_t pluginId;
+  ProcessId contentProcessId;
 };
 
 union HangData
 {
   SlowScriptData;
   PluginHangData;
 };
 
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -423,17 +423,18 @@ HangMonitorChild::NotifyPluginHang(uint3
 
 void
 HangMonitorChild::NotifyPluginHangAsync(uint32_t aPluginId)
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   // bounce back to parent on background thread
   if (mIPCOpen) {
-    Unused << SendHangEvidence(PluginHangData(aPluginId));
+    Unused << SendHangEvidence(PluginHangData(aPluginId,
+                                              base::GetCurrentProcId()));
   }
 }
 
 void
 HangMonitorChild::ClearHang()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -814,17 +815,18 @@ HangMonitoredProcess::TerminatePlugin()
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   if (mHangData.type() != HangData::TPluginHangData) {
     return NS_ERROR_UNEXPECTED;
   }
 
   // generates a crash report that includes a browser report taken here
   // earlier, the content process, and any plugin process(es).
   uint32_t id = mHangData.get_PluginHangData().pluginId();
-  plugins::TerminatePlugin(id, NS_LITERAL_CSTRING("HangMonitor"),
+  base::ProcessId contentPid = mHangData.get_PluginHangData().contentProcessId();
+  plugins::TerminatePlugin(id, contentPid, NS_LITERAL_CSTRING("HangMonitor"),
                            mBrowserDumpId);
 
   if (mActor) {
     mActor->CleanupPluginHang(id, false);
   }
   return NS_OK;
 }
 
--- a/dom/locales/en-US/chrome/layout/layout_errors.properties
+++ b/dom/locales/en-US/chrome/layout/layout_errors.properties
@@ -5,8 +5,30 @@
 ImageMapRectBoundsError=The "coords" attribute of the <area shape="rect"> tag is not in the "left,top,right,bottom" format.
 ImageMapCircleWrongNumberOfCoords=The "coords" attribute of the <area shape="circle"> tag is not in the "center-x,center-y,radius" format.
 ImageMapCircleNegativeRadius=The "coords" attribute of the <area shape="circle"> tag has a negative radius.
 ImageMapPolyWrongNumberOfCoords=The "coords" attribute of the <area shape="poly"> tag is not in the "x1,y1,x2,y2 …" format.
 ImageMapPolyOddNumberOfCoords=The "coords" attribute of the <area shape="poly"> tag is missing the last "y" coordinate (the correct format is "x1,y1,x2,y2 …").
 
 TablePartRelPosWarning=Relative positioning of table rows and row groups is now supported. This site may need to be updated because it may depend on this feature having no effect.
 ScrollLinkedEffectFound2=This site appears to use a scroll-linked positioning effect. This may not work well with asynchronous panning; see https://developer.mozilla.org/docs/Mozilla/Performance/ScrollLinkedEffects for further details and to join the discussion on related tools and features!
+
+## LOCALIZATION NOTE(AnimationWarningContentTooLarge):
+## (%1$S, %2$S) is a pair of integer values of the frame size
+## (%3$S, %4$S) is a pair of integer values of the viewport size
+## (%5$S, %6$S) is a pair of integer values of the visual rectangle size
+## (%7$S) is an integer value
+AnimationWarningContentTooLarge=Async animation disabled because frame size (%1$S, %2$S) is bigger than the viewport (%3$S, %4$S) or the visual rectangle (%5$S, %6$S) is larger than the max allowed value (%7$S)
+## LOCALIZATION NOTE(AnimationWarningTransformBackfaceVisibilityHidde):
+## 'backface-visibility: hidden' is a CSS property, don't translate it.
+AnimationWarningTransformBackfaceVisibilityHidden=Async animation of 'backface-visibility: hidden' transforms is not supported
+## LOCALIZATION NOTE(AnimationWarningTransformPreserve3D):
+## 'transform-style: preserve-3d' is a CSS property, don't translate it.
+AnimationWarningTransformPreserve3D=Async animation of 'transform-style: preserve-3d' transforms is not supported
+## LOCALIZATION NOTE(AnimationWarningTransformSVG,
+##                   AnimationWarningTransformFrameInactive,
+##                   AnimationWarningOpacityFrameInactive,
+##                   AnimationWarningWithGeometricProperties):
+## 'transform' and 'opacity' mean CSS property names, don't translate it.
+AnimationWarningTransformSVG=Async 'transform' animations of aFrames with SVG transforms is not supported
+AnimationWarningTransformFrameInactive=Async animation disabled because frame was not marked active for 'transform' animation
+AnimationWarningOpacityFrameInactive=Async animation disabled because frame was not marked active for 'opacity' animation
+AnimationWarningWithGeometricProperties=Async animation of 'transform' or 'opacity' not possible due to animation of geometric properties on the same element
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -49,18 +49,18 @@
 #include "mozilla/media/MediaChild.h"
 #include "MediaTrackConstraints.h"
 #include "VideoUtils.h"
 #include "Latency.h"
 #include "nsProxyRelease.h"
 #include "nsNullPrincipal.h"
 #include "nsVariant.h"
 
-// For PR_snprintf
-#include "prprf.h"
+// For snprintf
+#include "mozilla/Snprintf.h"
 
 #include "nsJSUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsIUUIDGenerator.h"
 #include "nspr.h"
 #include "nss.h"
 #include "pk11pub.h"
 
@@ -2535,17 +2535,17 @@ MediaManager::RemoveWindowID(uint64_t aW
     LOG(("No outer window for inner %llu", aWindowId));
     return;
   }
 
   uint64_t outerID = outer->WindowID();
 
   // Notify the UI that this window no longer has gUM active
   char windowBuffer[32];
-  PR_snprintf(windowBuffer, sizeof(windowBuffer), "%llu", outerID);
+  snprintf_literal(windowBuffer, "%" PRIu64, outerID);
   nsString data = NS_ConvertUTF8toUTF16(windowBuffer);
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   obs->NotifyObservers(nullptr, "recording-window-ended", data.get());
   LOG(("Sent recording-window-ended for window %llu (outer %llu)",
        aWindowId, outerID));
 }
 
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -23,16 +23,17 @@
 #include "nsIRunnable.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsPIDOMWindow.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Logging.h"
 #include "nsServiceManagerUtils.h"
 #include "gfxPlatform.h"
+#include "mozilla/Snprintf.h"
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidBridge.h"
 #endif
 
 struct JSContext;
 class JSObject;
 
--- a/dom/media/mediasource/ResourceQueue.cpp
+++ b/dom/media/mediasource/ResourceQueue.cpp
@@ -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/. */
 
 #include "ResourceQueue.h"
 #include "nsDeque.h"
 #include "MediaData.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Logging.h"
+#include "mozilla/Snprintf.h"
 
 extern mozilla::LogModule* GetSourceBufferResourceLog();
 
 #define SBR_DEBUG(arg, ...) MOZ_LOG(GetSourceBufferResourceLog(), mozilla::LogLevel::Debug, ("ResourceQueue(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 #define SBR_DEBUGV(arg, ...) MOZ_LOG(GetSourceBufferResourceLog(), mozilla::LogLevel::Verbose, ("ResourceQueue(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 
 namespace mozilla {
 
@@ -159,17 +160,17 @@ ResourceQueue::SizeOfExcludingThis(Mallo
 #if defined(DEBUG)
 void
 ResourceQueue::Dump(const char* aPath)
 {
   for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
     ResourceItem* item = ResourceAt(i);
 
     char buf[255];
-    PR_snprintf(buf, sizeof(buf), "%s/%08u.bin", aPath, i);
+    snprintf_literal(buf, "%s/%08u.bin", aPath, i);
     FILE* fp = fopen(buf, "wb");
     if (!fp) {
       return;
     }
     fwrite(item->mData->Elements(), item->mData->Length(), 1, fp);
     fclose(fp);
   }
 }
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -13,17 +13,18 @@
 #include "gfx2DGlue.h"
 #include "mozilla/Endian.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/SharedThreadPool.h"
 #include "MediaDataDemuxer.h"
 #include "nsAutoRef.h"
 #include "NesteggPacketHolder.h"
 #include "XiphExtradata.h"
-#include "prprf.h"
+#include "prprf.h"           // leaving it for PR_vsnprintf()
+#include "mozilla/Snprintf.h"
 
 #include <algorithm>
 #include <stdint.h>
 
 #define VPX_DONT_DEFINE_STDINT_TYPES
 #include "vpx/vp8dx.h"
 #include "vpx/vpx_decoder.h"
 
@@ -112,17 +113,17 @@ static void webmdemux_log(nestegg* aCont
       break;
     default:
       sevStr = "UNK";
       break;
   }
 
   va_start(args, aFormat);
 
-  PR_snprintf(msg, sizeof(msg), "%p [Nestegg-%s] ", aContext, sevStr);
+  snprintf_literal(msg, "%p [Nestegg-%s] ", aContext, sevStr);
   PR_vsnprintf(msg+strlen(msg), sizeof(msg)-strlen(msg), aFormat, args);
   MOZ_LOG(gNesteggLog, LogLevel::Debug, (msg));
 
   va_end(args);
 }
 
 
 WebMDemuxer::WebMDemuxer(MediaResource* aResource)
--- a/dom/media/webrtc/RTCCertificate.cpp
+++ b/dom/media/webrtc/RTCCertificate.cpp
@@ -8,16 +8,17 @@
 
 #include <cmath>
 #include "cert.h"
 #include "jsapi.h"
 #include "mozilla/dom/CryptoKey.h"
 #include "mozilla/dom/RTCCertificateBinding.h"
 #include "mozilla/dom/WebCryptoCommon.h"
 #include "mozilla/dom/WebCryptoTask.h"
+#include "mozilla/Snprintf.h"
 
 #include <cstdio>
 
 namespace mozilla {
 namespace dom {
 
 #define RTCCERTIFICATE_SC_VERSION 0x00000001
 
@@ -90,17 +91,17 @@ private:
                                              sizeof(randomName));
     if (rv != SECSuccess) {
       return nullptr;
     }
 
     char buf[sizeof(randomName) * 2 + 4];
     PL_strncpy(buf, "CN=", 3);
     for (size_t i = 0; i < sizeof(randomName); ++i) {
-      PR_snprintf(&buf[i * 2 + 3], 2, "%.2x", randomName[i]);
+      snprintf(&buf[i * 2 + 3], 2, "%.2x", randomName[i]);
     }
     buf[sizeof(buf) - 1] = '\0';
 
     return CERT_AsciiToName(buf);
   }
 
   nsresult GenerateCertificate()
   {
--- a/dom/network/PUDPSocket.ipdl
+++ b/dom/network/PUDPSocket.ipdl
@@ -36,17 +36,17 @@ namespace mozilla {
 namespace net {
 
 //-------------------------------------------------------------------
 protocol PUDPSocket
 {
   manager PNecko or PBackground;
 
 parent:
-  async Bind(UDPAddressInfo addressInfo, bool addressReuse, bool loopback);
+  async Bind(UDPAddressInfo addressInfo, bool addressReuse, bool loopback, uint32_t recvBufferSize);
   async Connect(UDPAddressInfo addressInfo);
 
   async OutgoingData(UDPData data, UDPSocketAddr addr);
 
   async JoinMulticast(nsCString multicastAddress, nsCString iface);
   async LeaveMulticast(nsCString multicastAddress, nsCString iface);
 
   async Close();
--- a/dom/network/UDPSocket.cpp
+++ b/dom/network/UDPSocket.cpp
@@ -498,17 +498,18 @@ UDPSocket::InitRemote(const nsAString& a
     return NS_ERROR_FAILURE;
   }
 
   rv = sock->Bind(mListenerProxy,
                   principal,
                   NS_ConvertUTF16toUTF8(aLocalAddress),
                   aLocalPort,
                   mAddressReuse,
-                  mLoopback);
+                  mLoopback,
+                  0);
 
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   mSocketChild = sock;
 
   return NS_OK;
--- a/dom/network/UDPSocketChild.cpp
+++ b/dom/network/UDPSocketChild.cpp
@@ -166,17 +166,18 @@ UDPSocketChild::SetBackgroundSpinsEvents
 }
 
 NS_IMETHODIMP
 UDPSocketChild::Bind(nsIUDPSocketInternal* aSocket,
                      nsIPrincipal* aPrincipal,
                      const nsACString& aHost,
                      uint16_t aPort,
                      bool aAddressReuse,
-                     bool aLoopback)
+                     bool aLoopback,
+                     uint32_t recvBufferSize)
 {
   UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort));
 
   NS_ENSURE_ARG(aSocket);
 
   mSocket = aSocket;
   AddIPDLReference();
 
@@ -185,17 +186,17 @@ UDPSocketChild::Bind(nsIUDPSocketInterna
     // convert it to a PrincipalInfo
     MOZ_ASSERT(!aPrincipal);
     mBackgroundManager->SendPUDPSocketConstructor(this, void_t(), mFilterName);
   } else {
     gNeckoChild->SendPUDPSocketConstructor(this, IPC::Principal(aPrincipal),
                                            mFilterName);
   }
 
-  SendBind(UDPAddressInfo(nsCString(aHost), aPort), aAddressReuse, aLoopback);
+  SendBind(UDPAddressInfo(nsCString(aHost), aPort), aAddressReuse, aLoopback, recvBufferSize);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 UDPSocketChild::Connect(nsIUDPSocketInternal* aSocket, const nsACString & aHost, uint16_t aPort)
 {
   UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort));
 
--- a/dom/network/UDPSocketParent.cpp
+++ b/dom/network/UDPSocketParent.cpp
@@ -156,21 +156,22 @@ UDPSocketParent::Init(const IPC::Princip
   }
   return true;
 }
 
 // PUDPSocketParent methods
 
 bool
 UDPSocketParent::RecvBind(const UDPAddressInfo& aAddressInfo,
-                          const bool& aAddressReuse, const bool& aLoopback)
+                          const bool& aAddressReuse, const bool& aLoopback,
+                          const uint32_t& recvBufferSize)
 {
   UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, aAddressInfo.addr().get(), aAddressInfo.port()));
 
-  if (NS_FAILED(BindInternal(aAddressInfo.addr(), aAddressInfo.port(), aAddressReuse, aLoopback))) {
+  if (NS_FAILED(BindInternal(aAddressInfo.addr(), aAddressInfo.port(), aAddressReuse, aLoopback, recvBufferSize))) {
     FireInternalError(__LINE__);
     return true;
   }
 
   nsCOMPtr<nsINetAddr> localAddr;
   mSocket->GetLocalAddr(getter_AddRefs(localAddr));
 
   nsCString addr;
@@ -188,21 +189,22 @@ UDPSocketParent::RecvBind(const UDPAddre
   UDPSOCKET_LOG(("%s: SendCallbackOpened: %s:%u", __FUNCTION__, addr.get(), port));
   mozilla::Unused << SendCallbackOpened(UDPAddressInfo(addr, port));
 
   return true;
 }
 
 nsresult
 UDPSocketParent::BindInternal(const nsCString& aHost, const uint16_t& aPort,
-                              const bool& aAddressReuse, const bool& aLoopback)
+                              const bool& aAddressReuse, const bool& aLoopback,
+                              const uint32_t& recvBufferSize)
 {
   nsresult rv;
 
-  UDPSOCKET_LOG(("%s: [this=%p] %s:%u addressReuse: %d loopback: %d", __FUNCTION__, this, nsCString(aHost).get(), aPort, aAddressReuse, aLoopback));
+  UDPSOCKET_LOG(("%s: [this=%p] %s:%u addressReuse: %d loopback: %d recvBufferSize: %lu", __FUNCTION__, this, nsCString(aHost).get(), aPort, aAddressReuse, aLoopback, recvBufferSize));
 
   nsCOMPtr<nsIUDPSocket> sock =
       do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -238,16 +240,22 @@ UDPSocketParent::BindInternal(const nsCS
     return rv;
   }
   if (family == nsINetAddr::FAMILY_INET) {
     rv = sock->SetMulticastLoopback(aLoopback);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
+  if (recvBufferSize != 0) {
+    rv = sock->SetRecvBufferSize(recvBufferSize);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      UDPSOCKET_LOG(("%s: [this=%p] %s:%u failed to set recv buffer size to: %lu", __FUNCTION__, this, nsCString(aHost).get(), aPort, recvBufferSize));
+    }
+  }
 
   // register listener
   rv = sock->AsyncListen(this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   mSocket = sock;
--- a/dom/network/UDPSocketParent.h
+++ b/dom/network/UDPSocketParent.h
@@ -30,17 +30,18 @@ public:
   NS_DECL_NSIUDPSOCKETLISTENER
 
   explicit UDPSocketParent(PBackgroundParent* aManager);
   explicit UDPSocketParent(PNeckoParent* aManager);
 
   bool Init(const IPC::Principal& aPrincipal, const nsACString& aFilter);
 
   virtual bool RecvBind(const UDPAddressInfo& aAddressInfo,
-                        const bool& aAddressReuse, const bool& aLoopback) override;
+                        const bool& aAddressReuse, const bool& aLoopback,
+                        const uint32_t& recvBufferSize) override;
   virtual bool RecvConnect(const UDPAddressInfo& aAddressInfo) override;
   void DoSendConnectResponse(const UDPAddressInfo& aAddressInfo);
   void SendConnectResponse(nsIEventTarget *aThread,
                            const UDPAddressInfo& aAddressInfo);
   void DoConnect(nsCOMPtr<nsIUDPSocket>& aSocket,
                  nsCOMPtr<nsIEventTarget>& aReturnThread,
                  const UDPAddressInfo& aAddressInfo);
 
@@ -57,17 +58,18 @@ public:
 
 private:
   virtual ~UDPSocketParent();
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
   void Send(const InfallibleTArray<uint8_t>& aData, const UDPSocketAddr& aAddr);
   void Send(const InputStreamParams& aStream, const UDPSocketAddr& aAddr);
   nsresult BindInternal(const nsCString& aHost, const uint16_t& aPort,
-                        const bool& aAddressReuse, const bool& aLoopback);
+                        const bool& aAddressReuse, const bool& aLoopback,
+                        const uint32_t& recvBufferSize);
   nsresult ConnectInternal(const nsCString& aHost, const uint16_t& aPort);
   void FireInternalError(uint32_t aLineNo);
   void SendInternalError(nsIEventTarget *aThread,
                          uint32_t aLineNo);
 
   // One of these will be null and the other non-null.
   PBackgroundParent* mBackgroundManager;
   PNeckoParent* mNeckoManager;
--- a/dom/network/interfaces/nsIUDPSocketChild.idl
+++ b/dom/network/interfaces/nsIUDPSocketChild.idl
@@ -27,17 +27,17 @@ interface nsIUDPSocketChild : nsISupport
   attribute AUTF8String filterName;
 
   // Allow hosting this over PBackground instead of PNecko
   [noscript] void setBackgroundSpinsEvents();
 
   // Tell the chrome process to bind the UDP socket to a given local host and port
   void bind(in nsIUDPSocketInternal socket, in nsIPrincipal principal,
             in AUTF8String host, in unsigned short port,
-            in bool addressReuse, in bool loopback);
+            in bool addressReuse, in bool loopback, in uint32_t recvBufferSize);
 
   // Tell the chrome process to connect the UDP socket to a given remote host and port
   void connect(in nsIUDPSocketInternal socket, in AUTF8String host, in unsigned short port);
 
   // Tell the chrome process to perform equivalent operations to all following methods
   void send(in AUTF8String host, in unsigned short port,
             [const, array, size_is(byteLength)] in uint8_t bytes,
             in unsigned long byteLength);
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -68,17 +68,17 @@
 #include "nsIXULRuntime.h"
 
 // for the dialog
 #include "nsIWindowWatcher.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMWindow.h"
 
 #include "nsNetCID.h"
-#include "prprf.h"
+#include "mozilla/Snprintf.h"
 #include "nsThreadUtils.h"
 #include "nsIInputStreamTee.h"
 #include "nsQueryObject.h"
 
 #include "nsDirectoryServiceDefs.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsPluginDirServiceProvider.h"
 
@@ -3780,18 +3780,18 @@ nsPluginHost::ParsePostBufferToFixHeader
     }
   } else  if (dataLen) { // no ContentLenHeader is found but there is a data
     // make new output buffer big enough
     // to keep ContentLenHeader+value followed by data
     uint32_t l = sizeof(ContentLenHeader) + sizeof(CRLFCRLF) + 32;
     newBufferLen = dataLen + l;
     if (!(*outPostData = p = (char*)moz_xmalloc(newBufferLen)))
       return NS_ERROR_OUT_OF_MEMORY;
-    headersLen = PR_snprintf(p, l,"%s: %ld%s", ContentLenHeader, dataLen, CRLFCRLF);
-    if (headersLen == l) { // if PR_snprintf has ate all extra space consider this as an error
+    headersLen = snprintf(p, l,"%s: %u%s", ContentLenHeader, dataLen, CRLFCRLF);
+    if (headersLen == l) { // if snprintf has ate all extra space consider this as an error
       free(p);
       *outPostData = 0;
       return NS_ERROR_FAILURE;
     }
     p += headersLen;
     newBufferLen = headersLen + dataLen;
   }
   // at this point we've done with headers.
--- a/dom/plugins/ipc/PluginBridge.h
+++ b/dom/plugins/ipc/PluginBridge.h
@@ -2,16 +2,18 @@
  * vim: sw=2 ts=2 et :
  * 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_plugins_PluginBridge_h
 #define mozilla_plugins_PluginBridge_h
 
+#include "base/process.h"
+
 namespace mozilla {
 
 namespace dom {
 class ContentParent;
 } // namespace dom
 
 namespace plugins {
 
@@ -21,15 +23,16 @@ SetupBridge(uint32_t aPluginId, dom::Con
 
 nsresult
 FindPluginsForContent(uint32_t aPluginEpoch,
                       nsTArray<PluginTag>* aPlugins,
                       uint32_t* aNewPluginEpoch);
 
 void
 TerminatePlugin(uint32_t aPluginId,
+                base::ProcessId aContentProcessId,
                 const nsCString& aMonitorDescription,
                 const nsAString& aBrowserDumpId);
 
 } // namespace plugins
 } // namespace mozilla
 
 #endif // mozilla_plugins_PluginBridge_h
--- a/dom/plugins/ipc/PluginHangUIParent.cpp
+++ b/dom/plugins/ipc/PluginHangUIParent.cpp
@@ -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/. */
 
 #include "PluginHangUI.h"
 
 #include "PluginHangUIParent.h"
 
 #include "mozilla/Telemetry.h"
+#include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/plugins/PluginModuleParent.h"
 
 #include "nsContentUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIFile.h"
 #include "nsIProperties.h"
 #include "nsIWindowMediator.h"
 #include "nsIWinTaskbar.h"
@@ -349,16 +350,17 @@ PluginHangUIParent::RecvUserResponse(con
   mLastUserResponse = aResponse;
   mResponseTicks = ::GetTickCount();
   mIsShowing = false;
   // responseCode: 1 = Stop, 2 = Continue, 3 = Cancel
   int responseCode;
   if (aResponse & HANGUI_USER_RESPONSE_STOP) {
     // User clicked Stop
     mModule->TerminateChildProcess(mMainThreadMessageLoop,
+                                   mozilla::ipc::kInvalidProcessId,
                                    NS_LITERAL_CSTRING("ModalHangUI"),
                                    EmptyString());
     responseCode = 1;
   } else if(aResponse & HANGUI_USER_RESPONSE_CONTINUE) {
     mModule->OnHangUIContinue();
     // User clicked Continue
     responseCode = 2;
   } else {
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -12,16 +12,17 @@
 
 #include "base/process_util.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PCrashReporterParent.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/plugins/BrowserStreamParent.h"
 #include "mozilla/plugins/PluginAsyncSurrogate.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/plugins/PluginInstanceParent.h"
 #include "mozilla/Preferences.h"
 #ifdef MOZ_ENABLE_PROFILER_SPS
 #include "mozilla/ProfileGatherer.h"
 #endif
@@ -122,17 +123,19 @@ mozilla::plugins::SetupBridge(uint32_t a
     if (NS_FAILED(*rv)) {
         return true;
     }
     PluginModuleChromeParent* chromeParent = static_cast<PluginModuleChromeParent*>(plugin->GetLibrary());
     *rv = chromeParent->GetRunID(runID);
     if (NS_FAILED(*rv)) {
         return true;
     }
-    chromeParent->SetContentParent(aContentParent);
+    if (chromeParent->IsStartingAsync()) {
+        chromeParent->SetContentParent(aContentParent);
+    }
     if (!aForceBridgeNow && chromeParent->IsStartingAsync() &&
         PluginModuleChromeParent::DidInstantiate()) {
         // We'll handle the bridging asynchronously
         return true;
     }
     *rv = PPluginModule::Bridge(aContentParent, chromeParent);
     return true;
 }
@@ -353,30 +356,32 @@ PRCList PluginModuleMapping::sModuleList
     PR_INIT_STATIC_CLIST(&PluginModuleMapping::sModuleListHead);
 
 bool PluginModuleMapping::sIsLoadModuleOnStack = false;
 
 } // namespace
 
 void
 mozilla::plugins::TerminatePlugin(uint32_t aPluginId,
+                                  base::ProcessId aContentProcessId,
                                   const nsCString& aMonitorDescription,
                                   const nsAString& aBrowserDumpId)
 {
     MOZ_ASSERT(XRE_IsParentProcess());
 
     RefPtr<nsPluginHost> host = nsPluginHost::GetInst();
     nsPluginTag* pluginTag = host->PluginWithId(aPluginId);
     if (!pluginTag || !pluginTag->mPlugin) {
         return;
     }
     RefPtr<nsNPAPIPlugin> plugin = pluginTag->mPlugin;
     PluginModuleChromeParent* chromeParent =
         static_cast<PluginModuleChromeParent*>(plugin->GetLibrary());
     chromeParent->TerminateChildProcess(MessageLoop::current(),
+                                        aContentProcessId,
                                         aMonitorDescription,
                                         aBrowserDumpId);
 }
 
 /* static */ PluginLibrary*
 PluginModuleContentParent::LoadModule(uint32_t aPluginId,
                                       nsPluginTag* aPluginTag)
 {
@@ -468,17 +473,18 @@ PluginModuleContentParent::OnLoadPluginR
     MOZ_ASSERT(parent);
     parent->RecvNP_InitializeResult(aResult ? NPERR_NO_ERROR
                                             : NPERR_GENERIC_ERROR);
 }
 
 void
 PluginModuleChromeParent::SetContentParent(dom::ContentParent* aContentParent)
 {
-    MOZ_ASSERT(aContentParent);
+    // mContentParent is to be used ONLY during async plugin init!
+    MOZ_ASSERT(aContentParent && mIsStartingAsync);
     mContentParent = aContentParent;
 }
 
 bool
 PluginModuleChromeParent::SendAssociatePluginId()
 {
     MOZ_ASSERT(mContentParent);
     return mContentParent->SendAssociatePluginId(mPluginId, OtherPid());
@@ -708,17 +714,17 @@ PluginModuleChromeParent::PluginModuleCh
                                                    uint32_t aPluginId,
                                                    int32_t aSandboxLevel,
                                                    bool aAllowAsyncInit)
     : PluginModuleParent(true, aAllowAsyncInit)
     , mSubprocess(new PluginProcessParent(aFilePath))
     , mPluginId(aPluginId)
     , mChromeTaskFactory(this)
     , mHangAnnotationFlags(0)
-    , mHangAnnotatorMutex("PluginModuleChromeParent::mHangAnnotatorMutex")
+    , mProtocolCallStackMutex("PluginModuleChromeParent::mProtocolCallStackMutex")
 #ifdef XP_WIN
     , mPluginCpuUsageOnHang()
     , mHangUIParent(nullptr)
     , mHangUIEnabled(true)
     , mIsTimerReset(true)
 #ifdef MOZ_CRASHREPORTER
     , mCrashReporterMutex("PluginModuleChromeParent::mCrashReporterMutex")
     , mCrashReporter(nullptr)
@@ -980,41 +986,41 @@ GetProcessCpuUsage(const InfallibleTArra
 
 #endif // #ifdef XP_WIN
 
 void
 PluginModuleChromeParent::OnEnteredCall()
 {
     mozilla::ipc::IProtocol* protocol = GetInvokingProtocol();
     MOZ_ASSERT(protocol);
-    mozilla::MutexAutoLock lock(mHangAnnotatorMutex);
+    mozilla::MutexAutoLock lock(mProtocolCallStackMutex);
     mProtocolCallStack.AppendElement(protocol);
 }
 
 void
 PluginModuleChromeParent::OnExitedCall()
 {
-    mozilla::MutexAutoLock lock(mHangAnnotatorMutex);
+    mozilla::MutexAutoLock lock(mProtocolCallStackMutex);
     MOZ_ASSERT(!mProtocolCallStack.IsEmpty());
     mProtocolCallStack.RemoveElementAt(mProtocolCallStack.Length() - 1);
 }
 
 void
 PluginModuleChromeParent::OnEnteredSyncSend()
 {
     mozilla::ipc::IProtocol* protocol = GetInvokingProtocol();
     MOZ_ASSERT(protocol);
-    mozilla::MutexAutoLock lock(mHangAnnotatorMutex);
+    mozilla::MutexAutoLock lock(mProtocolCallStackMutex);
     mProtocolCallStack.AppendElement(protocol);
 }
 
 void
 PluginModuleChromeParent::OnExitedSyncSend()
 {
-    mozilla::MutexAutoLock lock(mHangAnnotatorMutex);
+    mozilla::MutexAutoLock lock(mProtocolCallStackMutex);
     MOZ_ASSERT(!mProtocolCallStack.IsEmpty());
     mProtocolCallStack.RemoveElementAt(mProtocolCallStack.Length() - 1);
 }
 
 /**
  * This function converts the topmost routing id on the call stack (as recorded
  * by the MessageChannel) into a pointer to a IProtocol object.
  */
@@ -1166,16 +1172,17 @@ PluginModuleChromeParent::ShouldContinue
     if (LaunchHangUI()) {
         return true;
     }
     // If LaunchHangUI returned false then we should proceed with the 
     // original plugin hang behaviour and kill the plugin container.
     FinishHangUI();
 #endif // XP_WIN
     TerminateChildProcess(MessageLoop::current(),
+                          mozilla::ipc::kInvalidProcessId,
                           NS_LITERAL_CSTRING("ModalHangUI"),
                           EmptyString());
     GetIPCChannel()->CloseWithTimeout();
     return false;
 }
 
 bool
 PluginModuleContentParent::ShouldContinueFromReplyTimeout()
@@ -1191,16 +1198,17 @@ PluginModuleContentParent::ShouldContinu
 void
 PluginModuleContentParent::OnExitedSyncSend()
 {
     ProcessHangMonitor::ClearHang();
 }
 
 void
 PluginModuleChromeParent::TerminateChildProcess(MessageLoop* aMsgLoop,
+                                                base::ProcessId aContentPid,
                                                 const nsCString& aMonitorDescription,
                                                 const nsAString& aBrowserDumpId)
 {
 #ifdef MOZ_CRASHREPORTER
 #ifdef XP_WIN
     mozilla::MutexAutoLock lock(mCrashReporterMutex);
     CrashReporterParent* crashReporter = mCrashReporter;
     if (!crashReporter) {
@@ -1276,19 +1284,19 @@ PluginModuleChromeParent::TerminateChild
                                      NS_LITERAL_CSTRING("flash1"))) {
                 additionalDumps.AppendLiteral(",flash1");
             }
             if (CreatePluginMinidump(mFlashProcess2, 0, pluginDumpFile,
                                      NS_LITERAL_CSTRING("flash2"))) {
                 additionalDumps.AppendLiteral(",flash2");
             }
 #endif
-            if (mContentParent) {
+            if (aContentPid != mozilla::ipc::kInvalidProcessId) {
                 // Include the content process minidump
-                if (CreatePluginMinidump(mContentParent->OtherPid(), 0,
+                if (CreatePluginMinidump(aContentPid, 0,
                                          pluginDumpFile,
                                          NS_LITERAL_CSTRING("content"))) {
                     additionalDumps.AppendLiteral(",content");
                 }
             }
         }
         crashReporter->AnnotateCrashReport(
             NS_LITERAL_CSTRING("additional_minidumps"),
@@ -2234,17 +2242,19 @@ PluginModuleChromeParent::RecvNP_Initial
     bool initOk = aError == NPERR_NO_ERROR;
     if (initOk) {
         SetPluginFuncs(mNPPIface);
         if (mIsStartingAsync && !SendAssociatePluginId()) {
             initOk = false;
         }
     }
     mNPInitialized = initOk;
-    return mContentParent->SendLoadPluginResult(mPluginId, initOk);
+    bool result = mContentParent->SendLoadPluginResult(mPluginId, initOk);
+    mContentParent = nullptr;
+    return result;
 }
 
 #else
 
 nsresult
 PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error)
 {
     PLUGIN_LOG_DEBUG_METHOD;
@@ -2341,19 +2351,20 @@ PluginModuleParent::RecvNP_InitializeRes
 bool
 PluginModuleChromeParent::RecvNP_InitializeResult(const NPError& aError)
 {
     bool ok = true;
     if (mContentParent) {
         if ((ok = SendAssociatePluginId())) {
             ok = mContentParent->SendLoadPluginResult(mPluginId,
                                                       aError == NPERR_NO_ERROR);
+            mContentParent = nullptr;
         }
     } else if (aError == NPERR_NO_ERROR) {
-        // Initialization steps when e10s is disabled
+        // Initialization steps for (e10s && !asyncInit) || !e10s
 #if defined XP_WIN
         if (mIsStartingAsync) {
             SetPluginFuncs(mNPPIface);
         }
 
         // Send the info needed to join the chrome process's audio session to the
         // plugin process
         nsID id;
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -413,25 +413,28 @@ class PluginModuleChromeParent
 
     /*
      * Terminates the plugin process associated with this plugin module. Also
      * generates appropriate crash reports. Takes ownership of the file
      * associated with aBrowserDumpId on success.
      *
      * @param aMsgLoop the main message pump associated with the module
      *   protocol.
+     * @param aContentPid PID of the e10s content process from which a hang was
+     *   reported. May be kInvalidProcessId if not applicable.
      * @param aMonitorDescription a string describing the hang monitor that
      *   is making this call. This string is added to the crash reporter
      *   annotations for the plugin process.
      * @param aBrowserDumpId (optional) previously taken browser dump id. If
      *   provided TerminateChildProcess will use this browser dump file in
      *   generating a multi-process crash report. If not provided a browser
      *   dump will be taken at the time of this call.
      */
     void TerminateChildProcess(MessageLoop* aMsgLoop,
+                               base::ProcessId aContentPid,
                                const nsCString& aMonitorDescription,
                                const nsAString& aBrowserDumpId);
 
 #ifdef XP_WIN
     /**
      * Called by Plugin Hang UI to notify that the user has clicked continue.
      * Used for chrome hang annotations.
      */
@@ -542,17 +545,17 @@ private:
     enum HangAnnotationFlags
     {
         kInPluginCall = (1u << 0),
         kHangUIShown = (1u << 1),
         kHangUIContinued = (1u << 2),
         kHangUIDontShow = (1u << 3)
     };
     Atomic<uint32_t> mHangAnnotationFlags;
-    mozilla::Mutex mHangAnnotatorMutex;
+    mozilla::Mutex mProtocolCallStackMutex;
     InfallibleTArray<mozilla::ipc::IProtocol*> mProtocolCallStack;
 #ifdef XP_WIN
     InfallibleTArray<float> mPluginCpuUsageOnHang;
     PluginHangUIParent *mHangUIParent;
     bool mHangUIEnabled;
     bool mIsTimerReset;
 #ifdef MOZ_CRASHREPORTER
     /**
@@ -620,16 +623,20 @@ private:
         PluginModuleChromeParent* mModule;
     };
 
     friend class LaunchedTask;
 
     bool                mInitOnAsyncConnect;
     nsresult            mAsyncInitRv;
     NPError             mAsyncInitError;
+    // mContentParent is to be used ONLY during the IPC dance that occurs
+    // when ContentParent::RecvLoadPlugin is called under async plugin init!
+    // In other contexts it is *unsafe*, as there might be multiple content
+    // processes in existence!
     dom::ContentParent* mContentParent;
     nsCOMPtr<nsIObserver> mOfflineObserver;
 #ifdef MOZ_ENABLE_PROFILER_SPS
     RefPtr<mozilla::ProfileGatherer> mGatherer;
 #endif
     nsCString mProfile;
     bool mIsBlocklisted;
     static bool sInstantiated;
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -19,19 +19,16 @@ Cu.import("resource://gre/modules/Promis
 
 const {PushServiceWebSocket} = Cu.import("resource://gre/modules/PushServiceWebSocket.jsm");
 const {PushServiceHttp2} = Cu.import("resource://gre/modules/PushServiceHttp2.jsm");
 const {PushCrypto} = Cu.import("resource://gre/modules/PushCrypto.jsm");
 
 // Currently supported protocols: WebSocket.
 const CONNECTION_PROTOCOLS = [PushServiceWebSocket, PushServiceHttp2];
 
-XPCOMUtils.defineLazyModuleGetter(this, "AlarmService",
-                                  "resource://gre/modules/AlarmService.jsm");
-
 XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
                                    "@mozilla.org/contentsecuritymanager;1",
                                    "nsIContentSecurityManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gPushNotifier",
                                    "@mozilla.org/push/Notifier;1",
                                    "nsIPushNotifier");
 
@@ -91,17 +88,16 @@ const UNINIT_EVENT = 3;
  * (PushServiceWebSocket) to communicate with the server and PushDB (IndexedDB)
  * for persistence.
  */
 this.PushService = {
   _service: null,
   _state: PUSH_SERVICE_UNINIT,
   _db: null,
   _options: null,
-  _alarmID: null,
   _visibleNotifications: new Map(),
 
   // Callback that is called after attempting to
   // reduce the quota for a record. Used for testing purposes.
   _updateQuotaTestCallback: null,
 
   // When serverURI changes (this is used for testing), db is cleaned up and a
   // a new db is started. This events must be sequential.
@@ -543,23 +539,21 @@ this.PushService = {
    */
   _stopService: function(event) {
     console.debug("stopService()");
 
     if (this._state < PUSH_SERVICE_ACTIVATING) {
       return;
     }
 
-    this.stopAlarm();
     this._stopObservers();
 
     this._service.disconnect();
     this._service.uninit();
     this._service = null;
-    this.stopAlarm();
 
     if (!this._db) {
       return Promise.resolve();
     }
     if (event == UNINIT_EVENT) {
       // If it is uninitialized just close db.
       this._db.close();
       this._db = null;
@@ -604,67 +598,16 @@ this.PushService = {
     this._stateChangeProcessEnqueue(_ =>
       {
         this._changeServerURL("", UNINIT_EVENT);
         this._setState(PUSH_SERVICE_UNINIT);
         console.debug("uninit: shutdown complete!");
       });
   },
 
-  /** |delay| should be in milliseconds. */
-  setAlarm: function(delay) {
-    if (this._state <= PUSH_SERVICE_ACTIVATING) {
-      return;
-    }
-
-    // Bug 909270: Since calls to AlarmService.add() are async, calls must be
-    // 'queued' to ensure only one alarm is ever active.
-    if (this._settingAlarm) {
-        // onSuccess will handle the set. Overwriting the variable enforces the
-        // last-writer-wins semantics.
-        this._queuedAlarmDelay = delay;
-        this._waitingForAlarmSet = true;
-        return;
-    }
-
-    // Stop any existing alarm.
-    this.stopAlarm();
-
-    this._settingAlarm = true;
-    AlarmService.add(
-      {
-        date: new Date(Date.now() + delay),
-        ignoreTimezone: true
-      },
-      () => {
-        if (this._state > PUSH_SERVICE_ACTIVATING) {
-          this._service.onAlarmFired();
-        }
-      }, (alarmID) => {
-        this._alarmID = alarmID;
-        console.debug("setAlarm: Set alarm", delay, "in the future",
-          this._alarmID);
-        this._settingAlarm = false;
-
-        if (this._waitingForAlarmSet) {
-          this._waitingForAlarmSet = false;
-          this.setAlarm(this._queuedAlarmDelay);
-        }
-      }
-    );
-  },
-
-  stopAlarm: function() {
-    if (this._alarmID !== null) {
-      console.debug("stopAlarm: Stopped existing alarm", this._alarmID);
-      AlarmService.remove(this._alarmID);
-      this._alarmID = null;
-    }
-  },
-
   /**
    * Drops all active registrations and notifies the associated service
    * workers. This function is called when the user switches Push servers,
    * or when the server invalidates all existing registrations.
    *
    * We ignore expired registrations because they're already handled in other
    * code paths. Registrations that expired after exceeding their quotas are
    * evicted at startup, or on the next `idle-daily` event. Registrations that
--- a/dom/push/PushServiceHttp2.jsm
+++ b/dom/push/PushServiceHttp2.jsm
@@ -460,17 +460,17 @@ this.PushServiceHttp2 = {
         result.p256dhPublicKey = publicKey;
         result.p256dhPrivateKey = privateKey;
         result.authenticationSecret = PushCrypto.generateAuthenticationSecret();
         this._conns[result.subscriptionUri] = {
           channel: null,
           listener: null,
           countUnableToConnect: 0,
           lastStartListening: 0,
-          waitingForAlarm: false
+          retryTimerID: 0,
         };
         this._listenForMsgs(result.subscriptionUri);
         return result;
       })
     );
   },
 
   _subscribeResourceInternal: function(aSubInfo) {
@@ -578,38 +578,30 @@ this.PushServiceHttp2 = {
     if (this._conns[aSubscriptionUri].countUnableToConnect >= maxRetries) {
       this._shutdownSubscription(aSubscriptionUri);
       this._resubscribe(aSubscriptionUri);
       return;
     }
 
     if (retryAfter !== -1) {
       // This is a 5xx response.
-      // To respect RetryAfter header, setTimeout is used. setAlarm sets a
-      // cumulative alarm so it will not always respect RetryAfter header.
       this._conns[aSubscriptionUri].countUnableToConnect++;
-      setTimeout(_ => this._listenForMsgs(aSubscriptionUri), retryAfter);
+      this._conns[aSubscriptionUri].retryTimerID =
+        setTimeout(_ => this._listenForMsgs(aSubscriptionUri), retryAfter);
       return;
     }
 
-    // we set just one alarm because most probably all connection will go over
-    // a single TCP connection.
     retryAfter = prefs.get("http2.retryInterval") *
       Math.pow(2, this._conns[aSubscriptionUri].countUnableToConnect);
 
     retryAfter = retryAfter * (0.8 + Math.random() * 0.4); // add +/-20%.
 
     this._conns[aSubscriptionUri].countUnableToConnect++;
-
-    if (retryAfter === 0) {
-      setTimeout(_ => this._listenForMsgs(aSubscriptionUri), 0);
-    } else {
-      this._conns[aSubscriptionUri].waitingForAlarm = true;
-      this._mainPushService.setAlarm(retryAfter);
-    }
+    this._conns[aSubscriptionUri].retryTimerID =
+      setTimeout(_ => this._listenForMsgs(aSubscriptionUri), retryAfter);
 
     console.debug("retryAfterBackoff: Retry in", retryAfter);
   },
 
   // Close connections.
   _shutdownConnections: function(deleteInfo) {
     console.debug("shutdownConnections()");
 
@@ -621,17 +613,21 @@ this.PushServiceHttp2 = {
 
         if (this._conns[subscriptionUri].channel) {
           try {
             this._conns[subscriptionUri].channel.cancel(Cr.NS_ERROR_ABORT);
           } catch (e) {}
         }
         this._conns[subscriptionUri].listener = null;
         this._conns[subscriptionUri].channel = null;
-        this._conns[subscriptionUri].waitingForAlarm = false;
+
+        if (this._conns[subscriptionUri].retryTimerID > 0) {
+          clearTimeout(this._conns[subscriptionUri].retryTimerID);
+        }
+
         if (deleteInfo) {
           delete this._conns[subscriptionUri];
         }
       }
     }
   },
 
   // Start listening if subscriptions present.
@@ -650,37 +646,23 @@ this.PushServiceHttp2 = {
   },
 
   _startSingleConnection: function(record) {
     console.debug("_startSingleConnection()");
     if (typeof this._conns[record.subscriptionUri] != "object") {
       this._conns[record.subscriptionUri] = {channel: null,
                                              listener: null,
                                              countUnableToConnect: 0,
-                                             waitingForAlarm: false};
+                                             retryTimerID: 0};
     }
     if (!this._conns[record.subscriptionUri].conn) {
-      this._conns[record.subscriptionUri].waitingForAlarm = false;
       this._listenForMsgs(record.subscriptionUri);
     }
   },
 
-  // Start listening if subscriptions present.
-  _startConnectionsWaitingForAlarm: function() {
-    console.debug("startConnectionsWaitingForAlarm()");
-    for (let subscriptionUri in this._conns) {
-      if ((this._conns[subscriptionUri]) &&
-          !this._conns[subscriptionUri].conn &&
-          this._conns[subscriptionUri].waitingForAlarm) {
-        this._conns[subscriptionUri].waitingForAlarm = false;
-        this._listenForMsgs(subscriptionUri);
-      }
-    }
-  },
-
   // Close connection and notify apps that subscription are gone.
   _shutdownSubscription: function(aSubscriptionUri) {
     console.debug("shutdownSubscriptions()");
 
     if (typeof this._conns[aSubscriptionUri] == "object") {
       if (this._conns[aSubscriptionUri].listener) {
         this._conns[aSubscriptionUri].listener._pushService = null;
       }
@@ -780,20 +762,16 @@ this.PushServiceHttp2 = {
       }
     )
     .then(_ => this._ackMsgRecv(aAckUri))
     .catch(err => {
       console.error("pushChannelOnStop: Error receiving message",
         err);
     });
   },
-
-  onAlarmFired: function() {
-    this._startConnectionsWaitingForAlarm();
-  },
 };
 
 function PushRecordHttp2(record) {
   PushRecord.call(this, record);
   this.subscriptionUri = record.subscriptionUri;
   this.pushReceiptEndpoint = record.pushReceiptEndpoint;
 }
 
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -135,54 +135,122 @@ this.PushServiceWebSocket = {
     return "WebSocket";
   },
 
   disconnect: function() {
     this._shutdownWS();
   },
 
   observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "nsPref:changed" && aData == "dom.push.userAgentID") {
+      this._onUAIDChanged();
+    } else if (aTopic == "timer-callback") {
+      this._onTimerFired(aSubject);
+    }
+  },
 
-    switch (aTopic) {
-    case "nsPref:changed":
-      if (aData == "dom.push.userAgentID") {
-        this._shutdownWS();
-        this._reconnectAfterBackoff();
+  /**
+   * Handles a UAID change. Unlike reconnects, we cancel all pending requests
+   * after disconnecting. Existing subscriptions stored in IndexedDB will be
+   * dropped on reconnect.
+   */
+  _onUAIDChanged() {
+    console.debug("onUAIDChanged()");
+
+    this._shutdownWS();
+    this._startBackoffTimer();
+  },
+
+  /** Handles a ping, backoff, or request timeout timer event. */
+  _onTimerFired(timer) {
+    console.debug("onTimerFired()");
+
+    if (timer == this._pingTimer) {
+      this._sendPing();
+      return;
+    }
+
+    if (timer == this._backoffTimer) {
+      console.debug("onTimerFired: Reconnecting after backoff");
+      if (this._reconnectTestCallback) {
+        // Notify the test callback once the client reconnects.
+        let actualRetryTimeout = Date.now() - this._lastDisconnect;
+        this._reconnectTestCallback(actualRetryTimeout);
       }
-      break;
-    case "timer-callback":
-      if (aSubject == this._requestTimeoutTimer) {
-        if (Object.keys(this._registerRequests).length === 0) {
-          this._requestTimeoutTimer.cancel();
-        }
+      this._beginWSSetup();
+      return;
+    }
+
+    if (timer == this._requestTimeoutTimer) {
+      this._timeOutRequests();
+      return;
+    }
+  },
 
-        // Set to true if at least one request timed out.
-        let requestTimedOut = false;
-        for (let channelID in this._registerRequests) {
-          let duration = Date.now() - this._registerRequests[channelID].ctime;
-          // If any of the registration requests time out, all the ones after it
-          // also made to fail, since we are going to be disconnecting the
-          // socket.
-          if (requestTimedOut || duration > this._requestTimeout) {
-            requestTimedOut = true;
-            this._registerRequests[channelID]
-              .reject(new Error("Register request timed out for channel ID " +
-                  channelID));
+  /**
+   * Sends a ping to the server. Bypasses the request queue, but starts the
+   * request timeout timer. If the socket is already closed, or the server
+   * does not respond within the timeout, the client will reconnect.
+   */
+  _sendPing() {
+    console.debug("sendPing()");
+
+    this._startRequestTimeoutTimer();
+    try {
+      this._wsSendMessage({});
+      this._lastPingTime = Date.now();
+    } catch (e) {
+      console.debug("sendPing: Error sending ping", e);
+      this._reconnect();
+    }
+  },
+
+  /** Times out any pending requests. */
+  _timeOutRequests() {
+    console.debug("timeOutRequests()");
 
-            delete this._registerRequests[channelID];
-          }
-        }
+    if (!this._hasPendingRequests()) {
+      // Cancel the repeating timer and exit early if we aren't waiting for
+      // pongs or requests.
+      this._requestTimeoutTimer.cancel();
+      return;
+    }
+
+    let now = Date.now();
+
+    // Set to true if at least one request timed out, or we're still waiting
+    // for a pong after the request timeout.
+    let requestTimedOut = false;
+
+    if (this._lastPingTime > 0 &&
+        now - this._lastPingTime > this._requestTimeout) {
 
-        // The most likely reason for a registration request timing out is
-        // that the socket has disconnected. Best to reconnect.
+      console.debug("timeOutRequests: Did not receive pong in time");
+      requestTimedOut = true;
+
+    } else {
+      for (let [channelID, request] of this._registerRequests) {
+        let duration = now - request.ctime;
+        // If any of the registration requests time out, all the ones after it
+        // also made to fail, since we are going to be disconnecting the
+        // socket.
+        requestTimedOut |= duration > this._requestTimeout;
         if (requestTimedOut) {
-          this._reconnect();
+          request.reject(new Error(
+            "Register request timed out for channel ID " + channelID));
+
+          this._registerRequests.delete(channelID);
         }
       }
-      break;
+    }
+
+    // The most likely reason for a pong or registration request timing out is
+    // that the socket has disconnected. Best to reconnect.
+    if (requestTimedOut) {
+      this._reconnect();
     }
   },
 
   validServerURI: function(serverURI) {
     return serverURI.scheme == "ws" || serverURI.scheme == "wss";
   },
 
   get _UAID() {
@@ -195,17 +263,17 @@ this.PushServiceWebSocket = {
         "Not updating userAgentID");
       return;
     }
     console.debug("New _UAID", newID);
     prefs.set("userAgentID", newID);
   },
 
   _ws: null,
-  _registerRequests: {},
+  _registerRequests: new Map(),
   _currentState: STATE_SHUT_DOWN,
   _requestTimeout: 0,
   _requestTimeoutTimer: null,
   _retryFailCount: 0,
 
   /**
    * According to the WS spec, servers should immediately close the underlying
    * TCP connection after they close a WebSocket. This causes wsOnStop to be
@@ -252,16 +320,44 @@ this.PushServiceWebSocket = {
    * Maximum ping interval that we can reach.
    */
   _upperLimit: 0,
 
   /** Indicates whether the server supports Web Push-style message delivery. */
   _dataEnabled: false,
 
   /**
+   * The last time the client sent a ping to the server. If non-zero, keeps the
+   * request timeout timer active. Reset to zero when the server responds with
+   * a pong or pending messages.
+   */
+  _lastPingTime: 0,
+
+  /** The last time the connection was closed. */
+  _lastDisconnect: 0,
+
+  /**
+   * A one-shot timer used to ping the server, to avoid timing out idle
+   * connections. Reset to the ping interval on each incoming message.
+   */
+  _pingTimer: null,
+
+  /** A one-shot timer fired after the reconnect backoff period. */
+  _backoffTimer: null,
+
+  /**
+   * A function called when the client reconnects after backing off.
+   *
+   * @param {Number} actualRetryTimeout The time elapsed between the last
+   *  disconnect and reconnect time. This should be >= the backoff delay for
+   *  that attempt.
+   */
+  _reconnectTestCallback: null,
+
+  /**
    * Sends a message to the Push Server through an open websocket.
    * typeof(msg) shall be an object
    */
   _wsSendMessage: function(msg) {
     if (!this._ws) {
       console.warn("wsSendMessage: No WebSocket initialized.",
         "Cannot send a message");
       return;
@@ -301,17 +397,17 @@ this.PushServiceWebSocket = {
     this._upperLimit = prefs.get('adaptive.upperLimit');
 
     return Promise.resolve();
   },
 
   _reconnect: function () {
     console.debug("reconnect()");
     this._shutdownWS(false);
-    this._reconnectAfterBackoff();
+    this._startBackoffTimer();
   },
 
   _shutdownWS: function(shouldCancelPending = true) {
     console.debug("shutdownWS()");
     this._currentState = STATE_SHUT_DOWN;
     this._willBeWokenUpByUDP = false;
 
     prefs.ignore("userAgentID", this);
@@ -319,89 +415,123 @@ this.PushServiceWebSocket = {
     if (this._wsListener) {
       this._wsListener._pushService = null;
     }
     try {
         this._ws.close(0, null);
     } catch (e) {}
     this._ws = null;
 
-    this._waitingForPong = false;
-    if (this._mainPushService) {
-      this._mainPushService.stopAlarm();
-    } else {
-      console.error("shutdownWS: Uninitialized push service");
+    this._lastPingTime = 0;
+
+    if (this._pingTimer) {
+      this._pingTimer.cancel();
     }
 
     if (shouldCancelPending) {
       this._cancelRegisterRequests();
     }
 
     if (this._notifyRequestQueue) {
       this._notifyRequestQueue();
       this._notifyRequestQueue = null;
     }
+
+    this._lastDisconnect = Date.now();
   },
 
   uninit: function() {
     if (this._udpServer) {
       this._udpServer.close();
       this._udpServer = null;
     }
 
     // All pending requests (ideally none) are dropped at this point. We
     // shouldn't have any applications performing registration/unregistration
     // or receiving notifications.
     this._shutdownWS();
 
+    if (this._backoffTimer) {
+      this._backoffTimer.cancel();
+    }
     if (this._requestTimeoutTimer) {
       this._requestTimeoutTimer.cancel();
     }
 
     this._mainPushService = null;
 
     this._dataEnabled = false;
   },
 
   /**
    * How retries work:  The goal is to ensure websocket is always up on
    * networks not supporting UDP. So the websocket should only be shutdown if
    * onServerClose indicates UDP wakeup.  If WS is closed due to socket error,
-   * _reconnectAfterBackoff() is called.  The retry alarm is started and when
+   * _startBackoffTimer() is called. The retry timer is started and when
    * it times out, beginWSSetup() is called again.
    *
-   * On a successful connection, the alarm is cancelled in
-   * wsOnMessageAvailable() when the ping alarm is started.
-   *
    * If we are in the middle of a timeout (i.e. waiting), but
    * a register/unregister is called, we don't want to wait around anymore.
    * _sendRequest will automatically call beginWSSetup(), which will cancel the
    * timer. In addition since the state will have changed, even if a pending
    * timer event comes in (because the timer fired the event before it was
    * cancelled), so the connection won't be reset.
    */
-  _reconnectAfterBackoff: function() {
-    console.debug("reconnectAfterBackoff()");
+  _startBackoffTimer() {
+    console.debug("startBackoffTimer()");
     //Calculate new ping interval
     this._calculateAdaptivePing(true /* wsWentDown */);
 
     // Calculate new timeout, but cap it to pingInterval.
     let retryTimeout = prefs.get("retryBaseInterval") *
                        Math.pow(2, this._retryFailCount);
     retryTimeout = Math.min(retryTimeout, prefs.get("pingInterval"));
 
     this._retryFailCount++;
 
-    console.debug("reconnectAfterBackoff: Retry in", retryTimeout,
+    console.debug("startBackoffTimer: Retry in", retryTimeout,
       "Try number", this._retryFailCount);
-    if (this._mainPushService) {
-      this._mainPushService.setAlarm(retryTimeout);
-    } else {
-      console.error("reconnectAfterBackoff: Uninitialized push service");
+
+    if (!this._backoffTimer) {
+      this._backoffTimer = Cc["@mozilla.org/timer;1"]
+                               .createInstance(Ci.nsITimer);
     }
+    this._backoffTimer.init(this, retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
+  },
+
+  /** Indicates whether we're waiting for pongs or requests. */
+  _hasPendingRequests() {
+    return this._lastPingTime > 0 || this._registerRequests.size > 0;
+  },
+
+  /**
+   * Starts the request timeout timer unless we're already waiting for a pong
+   * or register request.
+   */
+  _startRequestTimeoutTimer() {
+    if (this._hasPendingRequests()) {
+      return;
+    }
+    if (!this._requestTimeoutTimer) {
+      this._requestTimeoutTimer = Cc["@mozilla.org/timer;1"]
+                                    .createInstance(Ci.nsITimer);
+    }
+    this._requestTimeoutTimer.init(this,
+                                   this._requestTimeout,
+                                   Ci.nsITimer.TYPE_REPEATING_SLACK);
+  },
+
+  /** Starts or resets the ping timer. */
+  _startPingTimer() {
+    if (!this._pingTimer) {
+      this._pingTimer = Cc["@mozilla.org/timer;1"]
+                          .createInstance(Ci.nsITimer);
+    }
+    this._pingTimer.init(this, prefs.get("pingInterval"),
+                         Ci.nsITimer.TYPE_ONE_SHOT);
   },
 
   /**
    * We need to calculate a new ping based on:
    *  1) Latest good ping
    *  2) A safe gap between 1) and the calculated new ping (which is
    *  by default, 1 minute)
    *
@@ -575,18 +705,18 @@ this.PushServiceWebSocket = {
     console.debug("beginWSSetup()");
     if (this._currentState != STATE_SHUT_DOWN) {
       console.error("_beginWSSetup: Not in shutdown state! Current state",
         this._currentState);
       return;
     }
 
     // Stop any pending reconnects scheduled for the near future.
-    if (this._mainPushService) {
-      this._mainPushService.stopAlarm();
+    if (this._backoffTimer) {
+      this._backoffTimer.cancel();
     }
 
     let uri = this._serverURI;
     if (!uri) {
       return;
     }
     let socket = this._makeWebSocket(uri);
     if (!socket) {
@@ -618,84 +748,16 @@ this.PushServiceWebSocket = {
       this._beginWSSetup();
     }
   },
 
   isConnected: function() {
     return !!this._ws;
   },
 
-  /**
-   * There is only one alarm active at any time. This alarm has 3 intervals
-   * corresponding to 3 tasks.
-   *
-   * 1) Reconnect on ping timeout.
-   *    If we haven't received any messages from the server by the time this
-   *    alarm fires, the connection is closed and PushService tries to
-   *    reconnect, repurposing the alarm for (3).
-   *
-   * 2) Send a ping.
-   *    The protocol sends a ping ({}) on the wire every pingInterval ms. Once
-   *    it sends the ping, the alarm goes to task (1) which is waiting for
-   *    a pong. If data is received after the ping is sent,
-   *    _wsOnMessageAvailable() will reset the ping alarm (which cancels
-   *    waiting for the pong). So as long as the connection is fine, pong alarm
-   *    never fires.
-   *
-   * 3) Reconnect after backoff.
-   *    The alarm is set by _reconnectAfterBackoff() and increases in duration
-   *    every time we try and fail to connect.  When it triggers, websocket
-   *    setup begins again. On successful socket setup, the socket starts
-   *    receiving messages. The alarm now goes to (2) where it monitors the
-   *    WebSocket by sending a ping.  Since incoming data is a sign of the
-   *    connection being up, the ping alarm is reset every time data is
-   *    received.
-   */
-  onAlarmFired: function() {
-    // Conditions are arranged in decreasing specificity.
-    // i.e. when _waitingForPong is true, other conditions are also true.
-    if (this._waitingForPong) {
-      console.debug("onAlarmFired: Did not receive pong in time.",
-        "Reconnecting WebSocket");
-      this._reconnect();
-    }
-    else if (this._currentState == STATE_READY) {
-      // Send a ping.
-      // Bypass the queue; we don't want this to be kept pending.
-      // Watch out for exception in case the socket has disconnected.
-      // When this happens, we pretend the ping was sent and don't specially
-      // handle the exception, as the lack of a pong will lead to the socket
-      // being reset.
-      try {
-        this._wsSendMessage({});
-      } catch (e) {
-      }
-
-      this._waitingForPong = true;
-      this._mainPushService.setAlarm(prefs.get("requestTimeout"));
-    }
-    else if (this._mainPushService && this._mainPushService._alarmID !== null) {
-      console.debug("onAlarmFired: reconnect alarm fired");
-      // Reconnect after back-off.
-      // The check for a non-null _alarmID prevents a situation where the alarm
-      // fires, but _shutdownWS() is called from another code-path (e.g.
-      // network state change) and we don't want to reconnect.
-      //
-      // It also handles the case where _beginWSSetup() is called from another
-      // code-path.
-      //
-      // alarmID will be non-null only when no shutdown/connect is
-      // called between _reconnectAfterBackoff() setting the alarm and the
-      // alarm firing.
-
-      // Websocket is shut down. Backoff interval expired, try to connect.
-      this._beginWSSetup();
-    }
-  },
-
   _acquireWakeLock: function() {
     if (!AppConstants.MOZ_B2G) {
       return;
     }
 
     // Disable the wake lock on non-B2G platforms to work around bug 1154492.
     if (!this._socketWakeLock) {
       console.debug("acquireWakeLock: Acquiring Socket Wakelock");
@@ -813,24 +875,23 @@ this.PushServiceWebSocket = {
   },
 
   /**
    * Protocol handler invoked by server message.
    */
   _handleRegisterReply: function(reply) {
     console.debug("handleRegisterReply()");
     if (typeof reply.channelID !== "string" ||
-        typeof this._registerRequests[reply.channelID] !== "object") {
+        !this._registerRequests.has(reply.channelID)) {
       return;
     }
 
-    let tmp = this._registerRequests[reply.channelID];
-    delete this._registerRequests[reply.channelID];
-    if (Object.keys(this._registerRequests).length === 0 &&
-        this._requestTimeoutTimer) {
+    let tmp = this._registerRequests.get(reply.channelID);
+    this._registerRequests.delete(reply.channelID);
+    if (!this._hasPendingRequests()) {
       this._requestTimeoutTimer.cancel();
     }
 
     if (reply.status == 200) {
       try {
         Services.io.newURI(reply.pushEndpoint, null, null);
       }
       catch (e) {
@@ -959,37 +1020,30 @@ this.PushServiceWebSocket = {
                           .getService(Ci.nsIUUIDGenerator);
     // generateUUID() gives a UUID surrounded by {...}, slice them off.
     return uuidGenerator.generateUUID().toString().slice(1, -1);
   },
 
   request: function(action, record) {
     console.debug("request() ", action);
 
-    if (Object.keys(this._registerRequests).length === 0) {
-      // start the timer since we now have at least one request
-      if (!this._requestTimeoutTimer) {
-        this._requestTimeoutTimer = Cc["@mozilla.org/timer;1"]
-                                      .createInstance(Ci.nsITimer);
-      }
-      this._requestTimeoutTimer.init(this,
-                                     this._requestTimeout,
-                                     Ci.nsITimer.TYPE_REPEATING_SLACK);
-    }
+    // start the timer since we now have at least one request
+    this._startRequestTimeoutTimer();
 
     if (action == "register") {
       let data = {channelID: this._generateID(),
                   messageType: action};
 
       return new Promise((resolve, reject) => {
-        this._registerRequests[data.channelID] = {record: record,
-                                                 resolve: resolve,
-                                                 reject: reject,
-                                                 ctime: Date.now()
-                                                };
+        this._registerRequests.set(data.channelID, {
+          record: record,
+          resolve: resolve,
+          reject: reject,
+          ctime: Date.now(),
+        });
         this._queueRequest(data);
       }).then(record => {
         if (!this._dataEnabled) {
           return record;
         }
         return PushCrypto.generateKeys()
           .then(([publicKey, privateKey]) => {
             record.p256dhPublicKey = publicKey;
@@ -1016,31 +1070,33 @@ this.PushServiceWebSocket = {
     this._queue = this._queue
                     .then(op)
                     .catch(_ => {});
   },
 
   _send(data) {
     if (this._currentState == STATE_READY) {
       if (data.messageType != "register" ||
-        typeof this._registerRequests[data.channelID] == "object") {
+          this._registerRequests.has(data.channelID)) {
 
         // check if request has not been cancelled
         this._wsSendMessage(data);
       }
     }
   },
 
   _sendRegisterRequests() {
-    this._enqueue(_ => Promise.all(Object.keys(this._registerRequests).map(channelID =>
-      this._send({
-        messageType: "register",
-        channelID: channelID,
-      })
-    )));
+    this._enqueue(_ => {
+      for (let channelID of this._registerRequests.keys()) {
+        this._send({
+          messageType: "register",
+          channelID: channelID,
+        });
+      }
+    });
   },
 
   _queueRequest(data) {
     if (data.messageType != "register") {
       if (this._currentState != STATE_READY && !this._notifyRequestQueue) {
         let promise = new Promise((resolve, reject) => {
           this._notifyRequestQueue = resolve;
         });
@@ -1143,17 +1199,18 @@ this.PushServiceWebSocket = {
     }
 
     this._shutdownWS();
   },
 
   _wsOnMessageAvailable: function(context, message) {
     console.debug("wsOnMessageAvailable()", message);
 
-    this._waitingForPong = false;
+    // Clearing the last ping time indicates we're no longer waiting for a pong.
+    this._lastPingTime = 0;
 
     let reply;
     try {
       reply = JSON.parse(message);
     } catch(e) {
       console.warn("wsOnMessageAvailable: Invalid JSON", message, e);
       return;
     }
@@ -1169,18 +1226,18 @@ this.PushServiceWebSocket = {
         (reply.messageType === "ping") ||
         (typeof reply.messageType != "string")) {
       console.debug("wsOnMessageAvailable: Pong received");
       this._calculateAdaptivePing(false);
       doNotHandle = true;
     }
 
     // Reset the ping timer.  Note: This path is executed at every step of the
-    // handshake, so this alarm does not need to be set explicitly at startup.
-    this._mainPushService.setAlarm(prefs.get("pingInterval"));
+    // handshake, so this timer does not need to be set explicitly at startup.
+    this._startPingTimer();
 
     // If it is a ping, do not handle the message.
     if (doNotHandle) {
       return;
     }
 
     // A whitelist of protocol handlers. Add to these if new messages are added
     // in the protocol.
@@ -1227,21 +1284,20 @@ this.PushServiceWebSocket = {
       // TODO: there should be no pending requests
     }
   },
 
   /**
    * Rejects all pending register requests with errors.
    */
   _cancelRegisterRequests: function() {
-    for (let channelID in this._registerRequests) {
-      let request = this._registerRequests[channelID];
-      delete this._registerRequests[channelID];
+    for (let request of this._registerRequests.values()) {
       request.reject(new Error("Register request aborted"));
     }
+    this._registerRequests.clear();
   },
 
   _makeUDPSocket: function() {
     return Cc["@mozilla.org/network/udp-socket;1"]
              .createInstance(Ci.nsIUDPSocket);
   },
 
   /**
--- a/dom/push/test/xpcshell/test_retry_ws.js
+++ b/dom/push/test/xpcshell/test_retry_ws.js
@@ -26,32 +26,29 @@ add_task(function* test_ws_retry() {
     pushEndpoint: 'https://example.org/push/1',
     scope: 'https://example.net/push/1',
     version: 1,
     originAttributes: '',
     quota: Infinity,
   });
 
   let alarmDelays = [];
-  let setAlarm = PushService.setAlarm;
-  PushService.setAlarm = function(delay) {
+  PushServiceWebSocket._reconnectTestCallback = function(delay) {
     alarmDelays.push(delay);
-    setAlarm.apply(this, arguments);
   };
 
   let handshakeDone;
   let handshakePromise = new Promise(resolve => handshakeDone = resolve);
   PushService.init({
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     makeWebSocket(uri) {
       return new MockWebSocket(uri, {
         onHello(request) {
           if (alarmDelays.length == 10) {
-            PushService.setAlarm = setAlarm;
             this.serverSendMsg(JSON.stringify({
               messageType: 'hello',
               status: 200,
               uaid: userAgentID,
             }));
             handshakeDone();
             return;
           }
@@ -61,11 +58,13 @@ add_task(function* test_ws_retry() {
     },
   });
 
   yield waitForPromise(
     handshakePromise,
     45000,
     'Timed out waiting for successful handshake'
   );
-  deepEqual(alarmDelays, [25, 50, 100, 200, 400, 800, 1600, 3200, 6400, 10000],
-    'Wrong reconnect alarm delays');
+  [25, 50, 100, 200, 400, 800, 1600, 3200, 6400, 10000].forEach(function(minDelay, index) {
+    ok(alarmDelays[index] >= minDelay, `Should wait at least ${
+      minDelay}ms before attempt ${index + 1}`);
+  });
 });
--- a/dom/system/gonk/GonkGPSGeolocationProvider.cpp
+++ b/dom/system/gonk/GonkGPSGeolocationProvider.cpp
@@ -959,33 +959,24 @@ GonkGPSGeolocationProvider::NetworkLocat
       }
 
       // This is a fallback case so that the GPS provider responds with its last
       // location rather than waiting for a more recent GPS or network location.
       // The service decides if the location is too old, not the provider.
       provider->mLocationCallback->Update(provider->mLastGPSPosition);
     }
   }
-
   provider->InjectLocation(lat, lon, acc);
   return NS_OK;
 }
-
-NS_IMETHODIMP
-GonkGPSGeolocationProvider::NetworkLocationUpdate::LocationUpdatePending()
-{
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 GonkGPSGeolocationProvider::NetworkLocationUpdate::NotifyError(uint16_t error)
 {
   return NS_OK;
 }
-
 NS_IMETHODIMP
 GonkGPSGeolocationProvider::Startup()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mStarted) {
     return NS_OK;
   }
--- a/dom/system/gonk/MozMtpDatabase.cpp
+++ b/dom/system/gonk/MozMtpDatabase.cpp
@@ -809,35 +809,33 @@ MozMtpDatabase::getObjectList(MtpStorage
 
   // aStorageID == 0xFFFFFFFF for all storage
   // aFormat    == 0          for all formats
   // aParent    == 0xFFFFFFFF for objects with no parents
   // aParent    == 0          for all objects
 
   //TODO: Optimize
 
-  ScopedDeletePtr<MtpObjectHandleList> list;
-
-  list = new MtpObjectHandleList();
+  UniquePtr<MtpObjectHandleList> list(new MtpObjectHandleList());
 
   MutexAutoLock lock(mMutex);
 
   ProtectedDbArray::size_type numEntries = mDb.Length();
   ProtectedDbArray::index_type entryIndex;
   for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
     RefPtr<DbEntry> entry = mDb[entryIndex];
     if (entry &&
         (aStorageID == 0xFFFFFFFF || entry->mStorageID == aStorageID) &&
         (aFormat == 0 || entry->mObjectFormat == aFormat) &&
         (aParent == 0 || entry->mParent == aParent)) {
       list->push(entry->mHandle);
     }
   }
   MTP_LOG("  returning %d items", list->size());
-  return list.forget();
+  return list.release();
 }
 
 //virtual
 int
 MozMtpDatabase::getNumObjects(MtpStorageID aStorageID,
                               MtpObjectFormat aFormat,
                               MtpObjectHandle aParent)
 {
--- a/dom/system/gonk/NetworkUtils.cpp
+++ b/dom/system/gonk/NetworkUtils.cpp
@@ -10,17 +10,17 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "NetworkUtils.h"
 
-#include "prprf.h"
+#include "mozilla/Snprintf.h"
 #include "SystemProperty.h"
 
 #include <android/log.h>
 #include <limits>
 #include "mozilla/dom/network/NetUtils.h"
 #include "mozilla/fallible.h"
 
 #include <errno.h>
@@ -394,21 +394,21 @@ static void convertUTF8toUTF16(nsTArray<
 }
 
 /**
  * Helper function to get network interface properties from the system property table.
  */
 static void getIFProperties(const char* ifname, IFProperties& prop)
 {
   char key[Property::KEY_MAX_LENGTH];
-  PR_snprintf(key, Property::KEY_MAX_LENGTH - 1, "net.%s.gw", ifname);
+  snprintf(key, Property::KEY_MAX_LENGTH - 1, "net.%s.gw", ifname);
   Property::Get(key, prop.gateway, "");
-  PR_snprintf(key, Property::KEY_MAX_LENGTH - 1, "net.%s.dns1", ifname);
+  snprintf(key, Property::KEY_MAX_LENGTH - 1, "net.%s.dns1", ifname);
   Property::Get(key, prop.dns1, "");
-  PR_snprintf(key, Property::KEY_MAX_LENGTH - 1, "net.%s.dns2", ifname);
+  snprintf(key, Property::KEY_MAX_LENGTH - 1, "net.%s.dns2", ifname);
   Property::Get(key, prop.dns2, "");
 }
 
 static int getIpType(const char *aIp) {
   struct addrinfo hint, *ip_info = NULL;
 
   memset(&hint, 0, sizeof(hint));
   hint.ai_family = AF_UNSPEC;
@@ -519,17 +519,17 @@ bool CommandResult::isPending() const
 void NetworkUtils::nextNetdCommand()
 {
   if (gCommandQueue.IsEmpty() || gPending) {
     return;
   }
 
   gCurrentCommand.chain = GET_CURRENT_CHAIN;
   gCurrentCommand.callback = GET_CURRENT_CALLBACK;
-  PR_snprintf(gCurrentCommand.command, MAX_COMMAND_SIZE - 1, "%s", GET_CURRENT_COMMAND);
+  snprintf(gCurrentCommand.command, MAX_COMMAND_SIZE - 1, "%s", GET_CURRENT_COMMAND);
 
   NU_DBG("Sending \'%s\' command to netd.", gCurrentCommand.command);
   SendNetdCommand(GET_CURRENT_NETD_COMMAND);
 
   gCommandQueue.RemoveElementAt(0);
   gPending = true;
 }
 
@@ -544,19 +544,19 @@ void NetworkUtils::nextNetdCommand()
 void NetworkUtils::doCommand(const char* aCommand, CommandChain* aChain, CommandCallback aCallback)
 {
   NU_DBG("Preparing to send \'%s\' command...", aCommand);
 
   NetdCommand* netdCommand = new NetdCommand();
 
   // Android JB version adds sequence number to netd command.
   if (SDK_VERSION >= 16) {
-    PR_snprintf((char*)netdCommand->mData, MAX_COMMAND_SIZE - 1, "0 %s", aCommand);
+    snprintf((char*)netdCommand->mData, MAX_COMMAND_SIZE - 1, "0 %s", aCommand);
   } else {
-    PR_snprintf((char*)netdCommand->mData, MAX_COMMAND_SIZE - 1, "%s", aCommand);
+    snprintf((char*)netdCommand->mData, MAX_COMMAND_SIZE - 1, "%s", aCommand);
   }
   netdCommand->mSize = strlen((char*)netdCommand->mData) + 1;
 
   gCommandQueue.AppendElement(QueueData(netdCommand, aChain, aCallback));
 
   nextNetdCommand();
 }
 
@@ -566,17 +566,17 @@ void NetworkUtils::doCommand(const char*
 #define GET_CHAR(prop) NS_ConvertUTF16toUTF8(aChain->getParams().prop).get()
 #define GET_FIELD(prop) aChain->getParams().prop
 
 void NetworkUtils::wifiFirmwareReload(CommandChain* aChain,
                                       CommandCallback aCallback,
                                       NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "softap fwreload %s %s", GET_CHAR(mIfname), GET_CHAR(mMode));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "softap fwreload %s %s", GET_CHAR(mIfname), GET_CHAR(mMode));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::startAccessPointDriver(CommandChain* aChain,
                                           CommandCallback aCallback,
                                           NetworkResultOptions& aResult)
 {
@@ -584,17 +584,17 @@ void NetworkUtils::startAccessPointDrive
   if (SDK_VERSION >= 16) {
     aResult.mResultCode = 0;
     aResult.mResultReason = NS_ConvertUTF8toUTF16("");
     aCallback(aChain, false, aResult);
     return;
   }
 
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "softap start %s", GET_CHAR(mIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "softap start %s", GET_CHAR(mIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::stopAccessPointDriver(CommandChain* aChain,
                                          CommandCallback aCallback,
                                          NetworkResultOptions& aResult)
 {
@@ -602,17 +602,17 @@ void NetworkUtils::stopAccessPointDriver
   if (SDK_VERSION >= 16) {
     aResult.mResultCode = 0;
     aResult.mResultReason = NS_ConvertUTF8toUTF16("");
     aCallback(aChain, false, aResult);
     return;
   }
 
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "softap stop %s", GET_CHAR(mIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "softap stop %s", GET_CHAR(mIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 /**
  * Command format for sdk version < 16
  *   Arguments:
  *     argv[2] - wlan interface
@@ -646,55 +646,55 @@ void NetworkUtils::setAccessPoint(Comman
   char command[MAX_COMMAND_SIZE];
   nsCString ssid(GET_CHAR(mSsid));
   nsCString key(GET_CHAR(mKey));
 
   escapeQuote(ssid);
   escapeQuote(key);
 
   if (SDK_VERSION >= 19) {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s \"%s\" broadcast 6 %s \"%s\"",
-                     GET_CHAR(mIfname),
-                     ssid.get(),
-                     GET_CHAR(mSecurity),
-                     key.get());
+    snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s \"%s\" broadcast 6 %s \"%s\"",
+             GET_CHAR(mIfname),
+             ssid.get(),
+             GET_CHAR(mSecurity),
+             key.get());
   } else if (SDK_VERSION >= 16) {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s \"%s\" %s \"%s\"",
-                     GET_CHAR(mIfname),
-                     ssid.get(),
-                     GET_CHAR(mSecurity),
-                     key.get());
+    snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s \"%s\" %s \"%s\"",
+             GET_CHAR(mIfname),
+             ssid.get(),
+             GET_CHAR(mSecurity),
+             key.get());
   } else {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s %s \"%s\" %s \"%s\" 6 0 8",
-                     GET_CHAR(mIfname),
-                     GET_CHAR(mWifictrlinterfacename),
-                     ssid.get(),
-                     GET_CHAR(mSecurity),
-                     key.get());
+    snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s %s \"%s\" %s \"%s\" 6 0 8",
+             GET_CHAR(mIfname),
+             GET_CHAR(mWifictrlinterfacename),
+             ssid.get(),
+             GET_CHAR(mSecurity),
+             key.get());
   }
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::cleanUpStream(CommandChain* aChain,
                                  CommandCallback aCallback,
                                  NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "nat disable %s %s 0", GET_CHAR(mPreInternalIfname), GET_CHAR(mPreExternalIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "nat disable %s %s 0", GET_CHAR(mPreInternalIfname), GET_CHAR(mPreExternalIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::createUpStream(CommandChain* aChain,
                                   CommandCallback aCallback,
                                   NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "nat enable %s %s 0", GET_CHAR(mCurInternalIfname), GET_CHAR(mCurExternalIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "nat enable %s %s 0", GET_CHAR(mCurInternalIfname), GET_CHAR(mCurExternalIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::startSoftAP(CommandChain* aChain,
                                CommandCallback aCallback,
                                NetworkResultOptions& aResult)
 {
@@ -735,77 +735,78 @@ void NetworkUtils::disableAlarm(CommandC
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::setQuota(CommandChain* aChain,
                             CommandCallback aCallback,
                             NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setiquota %s %lld", GET_CHAR(mIfname), LLONG_MAX);
+  snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setiquota %s % " PRId64, GET_CHAR(mIfname), INT64_MAX);
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::removeQuota(CommandChain* aChain,
                                CommandCallback aCallback,
                                NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeiquota %s", GET_CHAR(mIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeiquota %s", GET_CHAR(mIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::setAlarm(CommandChain* aChain,
                             CommandCallback aCallback,
                             NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setinterfacealert %s %lld", GET_CHAR(mIfname), GET_FIELD(mThreshold));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setinterfacealert %s %lld",
+           GET_CHAR(mIfname), GET_FIELD(mThreshold));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::removeAlarm(CommandChain* aChain,
                             CommandCallback aCallback,
                             NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeinterfacealert %s", GET_CHAR(mIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeinterfacealert %s", GET_CHAR(mIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::setGlobalAlarm(CommandChain* aChain,
                                   CommandCallback aCallback,
                                   NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
 
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setglobalalert %ld", GET_FIELD(mThreshold));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setglobalalert %lld", GET_FIELD(mThreshold));
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::removeGlobalAlarm(CommandChain* aChain,
                                      CommandCallback aCallback,
                                      NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
 
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeglobalalert");
+  snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeglobalalert");
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::tetherInterface(CommandChain* aChain,
                                    CommandCallback aCallback,
                                    NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface add %s", GET_CHAR(mIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface add %s", GET_CHAR(mIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::addInterfaceToLocalNetwork(CommandChain* aChain,
                                               CommandCallback aCallback,
                                               NetworkResultOptions& aResult)
 {
@@ -813,18 +814,18 @@ void NetworkUtils::addInterfaceToLocalNe
   if (SDK_VERSION < 20) {
     aResult.mResultCode = 0;
     aResult.mResultReason = NS_ConvertUTF8toUTF16("");
     aCallback(aChain, false, aResult);
     return;
   }
 
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "network interface add local %s",
-              GET_CHAR(mInternalIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "network interface add local %s",
+           GET_CHAR(mInternalIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::addRouteToLocalNetwork(CommandChain* aChain,
                                           CommandCallback aCallback,
                                           NetworkResultOptions& aResult)
 {
@@ -836,43 +837,43 @@ void NetworkUtils::addRouteToLocalNetwor
     return;
   }
 
   char command[MAX_COMMAND_SIZE];
   uint32_t prefix = atoi(GET_CHAR(mPrefix));
   uint32_t ip = inet_addr(GET_CHAR(mIp));
   char* networkAddr = getNetworkAddr(ip, prefix);
 
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "network route add local %s %s/%s",
-              GET_CHAR(mInternalIfname), networkAddr, GET_CHAR(mPrefix));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "network route add local %s %s/%s",
+           GET_CHAR(mInternalIfname), networkAddr, GET_CHAR(mPrefix));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::preTetherInterfaceList(CommandChain* aChain,
                                           CommandCallback aCallback,
                                           NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
   if (SDK_VERSION >= 16) {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface list");
+    snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface list");
   } else {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface list 0");
+    snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface list 0");
   }
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::postTetherInterfaceList(CommandChain* aChain,
                                            CommandCallback aCallback,
                                            NetworkResultOptions& aResult)
 {
   // Send the dummy command to continue the function chain.
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
+  snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
 
   char buf[BUF_SIZE];
   NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
 
   size_t length = reason.Length() + 1 < BUF_SIZE ? reason.Length() + 1 : BUF_SIZE;
   memcpy(buf, reason.get(), length);
   split(buf, INTERFACE_DELIMIT, GET_FIELD(mInterfaceList));
 
@@ -922,18 +923,18 @@ void NetworkUtils::addUpstreamInterface(
   }
 
   if (SUPPORT_IPV6_TETHERING == 0 || !isCommandChainIPv6(aChain, interface.get())) {
     aCallback(aChain, false, aResult);
     return;
   }
 
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface add_upstream %s",
-              interface.get());
+  snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface add_upstream %s",
+           interface.get());
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::removeUpstreamInterface(CommandChain* aChain,
                                            CommandCallback aCallback,
                                            NetworkResultOptions& aResult)
 {
   nsCString interface(GET_CHAR(mExternalIfname));
@@ -942,36 +943,36 @@ void NetworkUtils::removeUpstreamInterfa
   }
 
   if (SUPPORT_IPV6_TETHERING == 0 || !isCommandChainIPv6(aChain, interface.get())) {
     aCallback(aChain, false, aResult);
     return;
   }
 
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface remove_upstream %s",
-              interface.get());
+  snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface remove_upstream %s",
+           interface.get());
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::setIpForwardingEnabled(CommandChain* aChain,
                                           CommandCallback aCallback,
                                           NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
 
   if (GET_FIELD(mEnable)) {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "ipfwd enable");
+    snprintf(command, MAX_COMMAND_SIZE - 1, "ipfwd enable");
   } else {
     // Don't disable ip forwarding because others interface still need it.
     // Send the dummy command to continue the function chain.
     if (GET_FIELD(mInterfaceList).Length() > 1) {
-      PR_snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
+      snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
     } else {
-      PR_snprintf(command, MAX_COMMAND_SIZE - 1, "ipfwd disable");
+      snprintf(command, MAX_COMMAND_SIZE - 1, "ipfwd disable");
     }
   }
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::tetheringStatus(CommandChain* aChain,
                                    CommandCallback aCallback,
@@ -985,57 +986,57 @@ void NetworkUtils::stopTethering(Command
                                  CommandCallback aCallback,
                                  NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
 
   // Don't stop tethering because others interface still need it.
   // Send the dummy to continue the function chain.
   if (GET_FIELD(mInterfaceList).Length() > 1) {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
+    snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
   } else {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "tether stop");
+    snprintf(command, MAX_COMMAND_SIZE - 1, "tether stop");
   }
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::startTethering(CommandChain* aChain,
                                   CommandCallback aCallback,
                                   NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
 
   // We don't need to start tethering again.
   // Send the dummy command to continue the function chain.
   if (aResult.mResultReason.Find("started") != kNotFound) {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
+    snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
   } else {
     // If usbStartIp/usbEndIp is not valid, don't append them since
     // the trailing white spaces will be parsed to extra empty args
     // See: http://androidxref.com/4.3_r2.1/xref/system/core/libsysutils/src/FrameworkListener.cpp#78
     if (!GET_FIELD(mUsbStartIp).IsEmpty() && !GET_FIELD(mUsbEndIp).IsEmpty()) {
-      PR_snprintf(command, MAX_COMMAND_SIZE - 1, "tether start %s %s %s %s",
-                  GET_CHAR(mWifiStartIp), GET_CHAR(mWifiEndIp),
-                  GET_CHAR(mUsbStartIp),  GET_CHAR(mUsbEndIp));
+      snprintf(command, MAX_COMMAND_SIZE - 1, "tether start %s %s %s %s",
+               GET_CHAR(mWifiStartIp), GET_CHAR(mWifiEndIp),
+               GET_CHAR(mUsbStartIp),  GET_CHAR(mUsbEndIp));
     } else {
-      PR_snprintf(command, MAX_COMMAND_SIZE - 1, "tether start %s %s",
-                  GET_CHAR(mWifiStartIp), GET_CHAR(mWifiEndIp));
+      snprintf(command, MAX_COMMAND_SIZE - 1, "tether start %s %s",
+               GET_CHAR(mWifiStartIp), GET_CHAR(mWifiEndIp));
     }
   }
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::untetherInterface(CommandChain* aChain,
                                      CommandCallback aCallback,
                                      NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface remove %s", GET_CHAR(mIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface remove %s", GET_CHAR(mIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::removeInterfaceFromLocalNetwork(CommandChain* aChain,
                                                    CommandCallback aCallback,
                                                    NetworkResultOptions& aResult)
 {
@@ -1043,34 +1044,34 @@ void NetworkUtils::removeInterfaceFromLo
   if (SDK_VERSION < 20) {
     aResult.mResultCode = 0;
     aResult.mResultReason = NS_ConvertUTF8toUTF16("");
     aCallback(aChain, false, aResult);
     return;
   }
 
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "network interface remove local %s",
-              GET_CHAR(mIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "network interface remove local %s",
+           GET_CHAR(mIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::setDnsForwarders(CommandChain* aChain,
                                     CommandCallback aCallback,
                                     NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
 
   if (SDK_VERSION >= 20) {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "tether dns set %d %s %s",
-                GET_FIELD(mNetId), GET_CHAR(mDns1), GET_CHAR(mDns2));
+    snprintf(command, MAX_COMMAND_SIZE - 1, "tether dns set %d %s %s",
+             GET_FIELD(mNetId), GET_CHAR(mDns1), GET_CHAR(mDns2));
   } else {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "tether dns set %s %s",
-                GET_CHAR(mDns1), GET_CHAR(mDns2));
+    snprintf(command, MAX_COMMAND_SIZE - 1, "tether dns set %s %s",
+             GET_CHAR(mDns1), GET_CHAR(mDns2));
   }
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::enableNat(CommandChain* aChain,
                              CommandCallback aCallback,
                              NetworkResultOptions& aResult)
@@ -1078,55 +1079,55 @@ void NetworkUtils::enableNat(CommandChai
   char command[MAX_COMMAND_SIZE];
 
   if (!GET_FIELD(mIp).IsEmpty() && !GET_FIELD(mPrefix).IsEmpty()) {
     uint32_t prefix = atoi(GET_CHAR(mPrefix));
     uint32_t ip = inet_addr(GET_CHAR(mIp));
     char* networkAddr = getNetworkAddr(ip, prefix);
 
     // address/prefix will only take effect when secondary routing table exists.
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "nat enable %s %s 1 %s/%s",
-      GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname), networkAddr,
-      GET_CHAR(mPrefix));
+    snprintf(command, MAX_COMMAND_SIZE - 1, "nat enable %s %s 1 %s/%s",
+             GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname), networkAddr,
+             GET_CHAR(mPrefix));
   } else {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "nat enable %s %s 0",
-      GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
+    snprintf(command, MAX_COMMAND_SIZE - 1, "nat enable %s %s 0",
+             GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
   }
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::disableNat(CommandChain* aChain,
                               CommandCallback aCallback,
                               NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
 
   if (!GET_FIELD(mIp).IsEmpty() && !GET_FIELD(mPrefix).IsEmpty()) {
     uint32_t prefix = atoi(GET_CHAR(mPrefix));
     uint32_t ip = inet_addr(GET_CHAR(mIp));
     char* networkAddr = getNetworkAddr(ip, prefix);
 
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "nat disable %s %s 1 %s/%s",
-      GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname), networkAddr,
-      GET_CHAR(mPrefix));
+    snprintf(command, MAX_COMMAND_SIZE - 1, "nat disable %s %s 1 %s/%s",
+             GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname), networkAddr,
+             GET_CHAR(mPrefix));
   } else {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1, "nat disable %s %s 0",
-      GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
+    snprintf(command, MAX_COMMAND_SIZE - 1, "nat disable %s %s 0",
+             GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
   }
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::setDefaultInterface(CommandChain* aChain,
                                        CommandCallback aCallback,
                                        NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "resolver setdefaultif %s", GET_CHAR(mIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "resolver setdefaultif %s", GET_CHAR(mIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::removeDefaultRoute(CommandChain* aChain,
                                       CommandCallback aCallback,
                                       NetworkResultOptions& aResult)
 {
@@ -1135,19 +1136,19 @@ void NetworkUtils::removeDefaultRoute(Co
     return;
   }
 
   char command[MAX_COMMAND_SIZE];
   nsTArray<nsString>& gateways = GET_FIELD(mGateways);
   NS_ConvertUTF16toUTF8 autoGateway(gateways[GET_FIELD(mLoopIndex)]);
 
   int type = getIpType(autoGateway.get());
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "network route remove %d %s %s/0 %s",
-              GET_FIELD(mNetId), GET_CHAR(mIfname),
-              type == AF_INET6 ? "::" : "0.0.0.0", autoGateway.get());
+  snprintf(command, MAX_COMMAND_SIZE - 1, "network route remove %d %s %s/0 %s",
+           GET_FIELD(mNetId), GET_CHAR(mIfname),
+           type == AF_INET6 ? "::" : "0.0.0.0", autoGateway.get());
 
   struct MyCallback {
     static void callback(CommandCallback::CallbackType aOriginalCallback,
                          CommandChain* aChain,
                          bool aError,
                          mozilla::dom::NetworkResultOptions& aResult)
     {
       NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
@@ -1168,30 +1169,30 @@ void NetworkUtils::removeDefaultRoute(Co
 void NetworkUtils::setInterfaceDns(CommandChain* aChain,
                                    CommandCallback aCallback,
                                    NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
   int written;
 
   if (SDK_VERSION >= 20) {
-    written = PR_snprintf(command, sizeof command, "resolver setnetdns %d %s",
-                                                   GET_FIELD(mNetId), GET_CHAR(mDomain));
+    written = snprintf_literal(command, "resolver setnetdns %d %s",
+                               GET_FIELD(mNetId), GET_CHAR(mDomain));
   } else {
-    written = PR_snprintf(command, sizeof command, "resolver setifdns %s %s",
-                                                   GET_CHAR(mIfname), GET_CHAR(mDomain));
+    written = snprintf_literal(command, "resolver setifdns %s %s",
+                               GET_CHAR(mIfname), GET_CHAR(mDomain));
   }
 
   nsTArray<nsString>& dnses = GET_FIELD(mDnses);
   uint32_t length = dnses.Length();
 
   for (uint32_t i = 0; i < length; i++) {
     NS_ConvertUTF16toUTF8 autoDns(dnses[i]);
 
-    int ret = PR_snprintf(command + written, sizeof(command) - written, " %s", autoDns.get());
+    int ret = snprintf(command + written, sizeof(command) - written, " %s", autoDns.get());
     if (ret <= 1) {
       command[written] = '\0';
       continue;
     }
 
     if (((size_t)ret + written) >= sizeof(command)) {
       command[written] = '\0';
       break;
@@ -1244,48 +1245,48 @@ void NetworkUtils::setConfig(CommandChai
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::clearAddrForInterface(CommandChain* aChain,
                                          CommandCallback aCallback,
                                          NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "interface clearaddrs %s", GET_CHAR(mIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "interface clearaddrs %s", GET_CHAR(mIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::createNetwork(CommandChain* aChain,
                                  CommandCallback aCallback,
                                  NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "network create %d", GET_FIELD(mNetId));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "network create %d", GET_FIELD(mNetId));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::destroyNetwork(CommandChain* aChain,
                                   CommandCallback aCallback,
                                   NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "network destroy %d", GET_FIELD(mNetId));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "network destroy %d", GET_FIELD(mNetId));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::addInterfaceToNetwork(CommandChain* aChain,
                                          CommandCallback aCallback,
                                          NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "network interface add %d %s",
-                    GET_FIELD(mNetId), GET_CHAR(mIfname));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "network interface add %d %s",
+           GET_FIELD(mNetId), GET_CHAR(mIfname));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::addRouteToInterface(CommandChain* aChain,
                                        CommandCallback aCallback,
                                        NetworkResultOptions& aResult)
 {
@@ -1334,20 +1335,20 @@ void NetworkUtils::modifyRouteOnInterfac
     ipOrSubnetIp = getSubnetIp(ipOrSubnetIp, GET_FIELD(mPrefixLength));
     legacyOrEmpty = ""; // Add to interface table for scope link route.
   } else {
     gatewayOrEmpty = nsCString(" ") + NS_ConvertUTF16toUTF8(GET_FIELD(mGateway));
   }
 
   const char* action = aDoAdd ? "add" : "remove";
 
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "network route %s%s %d %s %s/%d%s",
-              legacyOrEmpty, action,
-              GET_FIELD(mNetId), GET_CHAR(mIfname), ipOrSubnetIp.get(),
-              GET_FIELD(mPrefixLength), gatewayOrEmpty.get());
+  snprintf(command, MAX_COMMAND_SIZE - 1, "network route %s%s %d %s %s/%d%s",
+           legacyOrEmpty, action,
+           GET_FIELD(mNetId), GET_CHAR(mIfname), ipOrSubnetIp.get(),
+           GET_FIELD(mPrefixLength), gatewayOrEmpty.get());
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::addDefaultRouteToNetwork(CommandChain* aChain,
                                             CommandCallback aCallback,
                                             NetworkResultOptions& aResult)
 {
@@ -1356,19 +1357,19 @@ void NetworkUtils::addDefaultRouteToNetw
     return;
   }
 
   char command[MAX_COMMAND_SIZE];
   nsTArray<nsString>& gateways = GET_FIELD(mGateways);
   NS_ConvertUTF16toUTF8 autoGateway(gateways[GET_FIELD(mLoopIndex)]);
 
   int type = getIpType(autoGateway.get());
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "network route add %d %s %s/0 %s",
-              GET_FIELD(mNetId), GET_CHAR(mIfname),
-              type == AF_INET6 ? "::" : "0.0.0.0", autoGateway.get());
+  snprintf(command, MAX_COMMAND_SIZE - 1, "network route add %d %s %s/0 %s",
+           GET_FIELD(mNetId), GET_CHAR(mIfname),
+           type == AF_INET6 ? "::" : "0.0.0.0", autoGateway.get());
 
   struct MyCallback {
     static void callback(CommandCallback::CallbackType aOriginalCallback,
                          CommandChain* aChain,
                          bool aError,
                          mozilla::dom::NetworkResultOptions& aResult)
     {
       NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
@@ -1386,80 +1387,80 @@ void NetworkUtils::addDefaultRouteToNetw
   doCommand(command, aChain, wrappedCallback);
 }
 
 void NetworkUtils::setDefaultNetwork(CommandChain* aChain,
                                      CommandCallback aCallback,
                                      NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "network default set %d", GET_FIELD(mNetId));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "network default set %d", GET_FIELD(mNetId));
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::addRouteToSecondaryTable(CommandChain* aChain,
                                             CommandCallback aCallback,
                                             NetworkResultOptions& aResult) {
 
   char command[MAX_COMMAND_SIZE];
 
   if (SDK_VERSION >= 20) {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1,
-                "network route add %d %s %s/%s %s",
-                GET_FIELD(mNetId),
-                GET_CHAR(mIfname),
-                GET_CHAR(mIp),
-                GET_CHAR(mPrefix),
-                GET_CHAR(mGateway));
+    snprintf(command, MAX_COMMAND_SIZE - 1,
+             "network route add %d %s %s/%s %s",
+             GET_FIELD(mNetId),
+             GET_CHAR(mIfname),
+             GET_CHAR(mIp),
+             GET_CHAR(mPrefix),
+             GET_CHAR(mGateway));
   } else {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1,
-                "interface route add %s secondary %s %s %s",
-                GET_CHAR(mIfname),
-                GET_CHAR(mIp),
-                GET_CHAR(mPrefix),
-                GET_CHAR(mGateway));
+    snprintf(command, MAX_COMMAND_SIZE - 1,
+             "interface route add %s secondary %s %s %s",
+             GET_CHAR(mIfname),
+             GET_CHAR(mIp),
+             GET_CHAR(mPrefix),
+             GET_CHAR(mGateway));
   }
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::removeRouteFromSecondaryTable(CommandChain* aChain,
                                                  CommandCallback aCallback,
                                                  NetworkResultOptions& aResult) {
   char command[MAX_COMMAND_SIZE];
 
   if (SDK_VERSION >= 20) {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1,
-                "network route remove %d %s %s/%s %s",
-                GET_FIELD(mNetId),
-                GET_CHAR(mIfname),
-                GET_CHAR(mIp),
-                GET_CHAR(mPrefix),
-                GET_CHAR(mGateway));
+    snprintf(command, MAX_COMMAND_SIZE - 1,
+             "network route remove %d %s %s/%s %s",
+             GET_FIELD(mNetId),
+             GET_CHAR(mIfname),
+             GET_CHAR(mIp),
+             GET_CHAR(mPrefix),
+             GET_CHAR(mGateway));
   } else {
-    PR_snprintf(command, MAX_COMMAND_SIZE - 1,
-                "interface route remove %s secondary %s %s %s",
-                GET_CHAR(mIfname),
-                GET_CHAR(mIp),
-                GET_CHAR(mPrefix),
-                GET_CHAR(mGateway));
+    snprintf(command, MAX_COMMAND_SIZE - 1,
+             "interface route remove %s secondary %s %s %s",
+             GET_CHAR(mIfname),
+             GET_CHAR(mIp),
+             GET_CHAR(mPrefix),
+             GET_CHAR(mGateway));
   }
 
   doCommand(command, aChain, aCallback);
 }
 
 void NetworkUtils::setIpv6Enabled(CommandChain* aChain,
                                   CommandCallback aCallback,
                                   NetworkResultOptions& aResult,
                                   bool aEnabled)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "interface ipv6 %s %s",
-              GET_CHAR(mIfname), aEnabled ? "enable" : "disable");
+  snprintf(command, MAX_COMMAND_SIZE - 1, "interface ipv6 %s %s",
+           GET_CHAR(mIfname), aEnabled ? "enable" : "disable");
 
   struct MyCallback {
     static void callback(CommandCallback::CallbackType aOriginalCallback,
                          CommandChain* aChain,
                          bool aError,
                          mozilla::dom::NetworkResultOptions& aResult)
     {
       aOriginalCallback(aChain, false, aResult);
@@ -1484,18 +1485,18 @@ void NetworkUtils::disableIpv6(CommandCh
   setIpv6Enabled(aChain, aCallback, aResult, false);
 }
 
 void NetworkUtils::setMtu(CommandChain* aChain,
                           CommandCallback aCallback,
                           NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
-  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "interface setmtu %s %d",
-              GET_CHAR(mIfname), GET_FIELD(mMtu));
+  snprintf(command, MAX_COMMAND_SIZE - 1, "interface setmtu %s %ld",
+           GET_CHAR(mIfname), GET_FIELD(mMtu));
 
   doCommand(command, aChain, aCallback);
 }
 
 #undef GET_CHAR
 #undef GET_FIELD
 
 /*
@@ -1871,19 +1872,19 @@ void NetworkUtils::onNetdMessage(NetdCom
   if (isBroadcastMessage(code)) {
     NU_DBG("Receiving broadcast message from netd.");
     NU_DBG("          ==> Code: %d  Reason: %s", code, reason);
     sendBroadcastMessage(code, reason);
 
     if (code == NETD_COMMAND_INTERFACE_CHANGE) {
       if (gWifiTetheringParms) {
         char linkdownReason[MAX_COMMAND_SIZE];
-        PR_snprintf(linkdownReason, MAX_COMMAND_SIZE - 1,
-                    "Iface linkstate %s down",
-                    NS_ConvertUTF16toUTF8(gWifiTetheringParms->mIfname).get());
+        snprintf(linkdownReason, MAX_COMMAND_SIZE - 1,
+                 "Iface linkstate %s down",
+                 NS_ConvertUTF16toUTF8(gWifiTetheringParms->mIfname).get());
 
         if (!strcmp(reason, linkdownReason)) {
           NU_DBG("Wifi link down, restarting tethering.");
           runChain(*gWifiTetheringParms, sWifiRetryChain, wifiTetheringFail);
         }
       }
     }
 
@@ -1950,34 +1951,34 @@ CommandResult NetworkUtils::setDNS(Netwo
 {
   uint32_t length = aOptions.mDnses.Length();
 
   if (length > 0) {
     for (uint32_t i = 0; i < length; i++) {
       NS_ConvertUTF16toUTF8 autoDns(aOptions.mDnses[i]);
 
       char dns_prop_key[Property::VALUE_MAX_LENGTH];
-      PR_snprintf(dns_prop_key, sizeof dns_prop_key, "net.dns%d", i+1);
+      snprintf_literal(dns_prop_key, "net.dns%d", i+1);
       Property::Set(dns_prop_key, autoDns.get());
     }
   } else {
     // Set dnses from system properties.
     IFProperties interfaceProperties;
     getIFProperties(GET_CHAR(mIfname), interfaceProperties);
 
     Property::Set("net.dns1", interfaceProperties.dns1);
     Property::Set("net.dns2", interfaceProperties.dns2);
   }
 
   // Bump the DNS change property.
   char dnschange[Property::VALUE_MAX_LENGTH];
   Property::Get("net.dnschange", dnschange, "0");
 
   char num[Property::VALUE_MAX_LENGTH];
-  PR_snprintf(num, Property::VALUE_MAX_LENGTH - 1, "%d", atoi(dnschange) + 1);
+  snprintf(num, Property::VALUE_MAX_LENGTH - 1, "%d", atoi(dnschange) + 1);
   Property::Set("net.dnschange", num);
 
   // DNS needs to be set through netd since JellyBean (4.3).
   if (SDK_VERSION >= 20) {
     // Lollipop.
     static CommandFunc COMMAND_CHAIN[] = {
       setInterfaceDns,
       addDefaultRouteToNetwork,
@@ -2159,17 +2160,17 @@ CommandResult NetworkUtils::setDefaultRo
         RETURN_IF_FAILED(mNetUtils->do_ifc_set_default_route(autoIfname.get(), inet_addr(autoGateway.get())));
       }
     }
   } else {
     // Set default froute from system properties.
     char key[Property::KEY_MAX_LENGTH];
     char gateway[Property::KEY_MAX_LENGTH];
 
-    PR_snprintf(key, sizeof key - 1, "net.%s.gw", autoIfname.get());
+    snprintf(key, sizeof key - 1, "net.%s.gw", autoIfname.get());
     Property::Get(key, gateway, "");
 
     int type = getIpType(gateway);
     if (type != AF_INET && type != AF_INET6) {
       return EAFNOSUPPORT;
     }
 
     if (type == AF_INET6) {
--- a/dom/system/mac/CoreLocationLocationProvider.mm
+++ b/dom/system/mac/CoreLocationLocationProvider.mm
@@ -120,35 +120,26 @@ CoreLocationLocationProvider::MLSUpdate:
 NS_IMETHODIMP
 CoreLocationLocationProvider::MLSUpdate::Update(nsIDOMGeoPosition *position)
 {
   nsCOMPtr<nsIDOMGeoPositionCoords> coords;
   position->GetCoords(getter_AddRefs(coords));
   if (!coords) {
     return NS_ERROR_FAILURE;
   }
-
   mParentLocationProvider.Update(position);
   Telemetry::Accumulate(Telemetry::GEOLOCATION_OSX_SOURCE_IS_MLS, true);
   return NS_OK;
 }
-
-NS_IMETHODIMP
-CoreLocationLocationProvider::MLSUpdate::LocationUpdatePending()
-{
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 CoreLocationLocationProvider::MLSUpdate::NotifyError(uint16_t error)
 {
   mParentLocationProvider.NotifyError(error);
   return NS_OK;
 }
-
 class CoreLocationObjects {
 public:
   NS_METHOD Init(CoreLocationLocationProvider* aProvider) {
     mLocationManager = [[CLLocationManager alloc] init];
     NS_ENSURE_TRUE(mLocationManager, NS_ERROR_NOT_AVAILABLE);
 
     mLocationDelegate = [[LocationDelegate alloc] init:aProvider];
     NS_ENSURE_TRUE(mLocationDelegate, NS_ERROR_NOT_AVAILABLE);
--- a/dom/system/windows/WindowsLocationProvider.cpp
+++ b/dom/system/windows/WindowsLocationProvider.cpp
@@ -29,38 +29,28 @@ WindowsLocationProvider::MLSUpdate::Upda
     return NS_ERROR_FAILURE;
   }
 
   nsCOMPtr<nsIDOMGeoPositionCoords> coords;
   aPosition->GetCoords(getter_AddRefs(coords));
   if (!coords) {
     return NS_ERROR_FAILURE;
   }
-
   Telemetry::Accumulate(Telemetry::GEOLOCATION_WIN8_SOURCE_IS_MLS, true);
-
   return mCallback->Update(aPosition);
 }
-
-NS_IMETHODIMP
-WindowsLocationProvider::MLSUpdate::LocationUpdatePending()
-{
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 WindowsLocationProvider::MLSUpdate::NotifyError(uint16_t aError)
 {
   if (!mCallback) {
     return NS_ERROR_FAILURE;
   }
   return mCallback->NotifyError(aError);
 }
 
-
 class LocationEvent final : public ILocationEvents
 {
 public:
   LocationEvent(nsIGeolocationUpdate* aCallback, WindowsLocationProvider *aProvider)
     : mCallback(aCallback), mProvider(aProvider), mCount(0) {
   }
 
   // IUnknown interface
--- a/dom/webidl/KeyframeEffect.webidl
+++ b/dom/webidl/KeyframeEffect.webidl
@@ -40,16 +40,26 @@ interface KeyframeEffectReadOnly : Anima
   // Not yet implemented:
   // KeyframeEffect             clone();
 
   // We use object instead of ComputedKeyframe so that we can put the
   // property-value pairs on the object.
   [Throws] sequence<object> getFrames();
 };
 
+// Non-standard extensions
+dictionary AnimationPropertyState {
+  DOMString property;
+  boolean runningOnCompositor;
+  DOMString? warning;
+};
+
+partial interface KeyframeEffectReadOnly {
+  [ChromeOnly] sequence<AnimationPropertyState> getPropertyState();
+};
 
 [Func="nsDocument::IsWebAnimationsEnabled",
  Constructor ((Element or CSSPseudoElement)? target,
               object? frames,
               optional (unrestricted double or KeyframeEffectOptions) options)]
 interface KeyframeEffect : KeyframeEffectReadOnly {
   // Bug 1067769 - Allow setting KeyframeEffect.target
   // inherit attribute Animatable?                 target;
--- a/dom/wifi/WifiUtils.cpp
+++ b/dom/wifi/WifiUtils.cpp
@@ -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/. */
 
 #include "WifiUtils.h"
 #include <dlfcn.h>
 #include <errno.h>
 #include <cutils/properties.h>
 #include "prinit.h"
+#include "mozilla/Snprintf.h"
 #include "js/CharacterEncoding.h"
 
 using namespace mozilla::dom;
 
 #define BUFFER_SIZE        4096
 #define COMMAND_SIZE       256
 
 // Intentionally not trying to dlclose() this handle. That's playing
@@ -363,20 +364,20 @@ public:
     return wifi_stop_supplicant(arg);
   }
 
   DEFINE_DLFUNC(wifi_command, int32_t, const char*, char*, size_t*)
   int32_t do_wifi_command(const char* iface, const char* cmd, char* buf, size_t* len) {
     char command[COMMAND_SIZE];
     if (!strcmp(iface, "p2p0")) {
       // Commands for p2p0 interface don't need prefix
-      PR_snprintf(command, COMMAND_SIZE, "%s", cmd);
+      snprintf_literal(command, "%s", cmd);
     }
     else {
-      PR_snprintf(command, COMMAND_SIZE, "IFNAME=%s %s", iface, cmd);
+      snprintf_literal(command, "IFNAME=%s %s", iface, cmd);
     }
     USE_DLFUNC(wifi_command)
     return wifi_command(command, buf, len);
   }
 };
 
 // Concrete class to use to access the wpa supplicant.
 WpaSupplicant::WpaSupplicant()
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -285,22 +285,28 @@ ImageContainer::ClearAllImages()
 
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   SetCurrentImageInternal(nsTArray<NonOwningImage>());
 }
 
 void
 ImageContainer::SetCurrentImageInTransaction(Image *aImage)
 {
+  AutoTArray<NonOwningImage,1> images;
+  images.AppendElement(NonOwningImage(aImage));
+  SetCurrentImagesInTransaction(images);
+}
+
+void
+ImageContainer::SetCurrentImagesInTransaction(const nsTArray<NonOwningImage>& aImages)
+{
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ASSERTION(!mImageClient, "Should use async image transfer with ImageBridge.");
 
-  AutoTArray<NonOwningImage,1> images;
-  images.AppendElement(NonOwningImage(aImage));
-  SetCurrentImageInternal(images);
+  SetCurrentImageInternal(aImages);
 }
 
 bool ImageContainer::IsAsync() const
 {
   return mImageClient != nullptr;
 }
 
 uint64_t ImageContainer::GetAsyncContainerID() const
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -429,16 +429,17 @@ public:
    * aImage can be null. While it's null, nothing will be painted.
    * 
    * The Image data must not be modified after this method is called!
    * Note that this must not be called if ENABLE_ASYNC been set.
    *
    * You won't get meaningful painted/dropped counts when using this method.
    */
   void SetCurrentImageInTransaction(Image* aImage);
+  void SetCurrentImagesInTransaction(const nsTArray<NonOwningImage>& aImages);
 
   /**
    * Returns true if this ImageContainer uses the ImageBridge IPDL protocol.
    *
    * Can be called from any thread.
    */
   bool IsAsync() const;
 
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -2970,17 +2970,20 @@ bool AsyncPanZoomController::UpdateAnima
   if (mAnimation) {
     bool continueAnimation = mAnimation->Sample(mFrameMetrics, sampleTimeDelta);
     bool wantsRepaints = mAnimation->WantsRepaints();
     *aOutDeferredTasks = mAnimation->TakeDeferredTasks();
     if (!continueAnimation) {
       mAnimation = nullptr;
       SetState(NOTHING);
     }
-    if (wantsRepaints) {
+    // Request a repaint at the end of the animation in case something such as a
+    // call to NotifyLayersUpdated was invoked during the animation and Gecko's
+    // current state is some intermediate point of the animation.
+    if (!continueAnimation || wantsRepaints) {
       RequestContentRepaint();
     }
     UpdateSharedCompositorFrameMetrics();
     return true;
   }
   return false;
 }
 
--- a/gfx/layers/apz/src/OverscrollHandoffState.h
+++ b/gfx/layers/apz/src/OverscrollHandoffState.h
@@ -10,16 +10,19 @@
 #include <vector>
 #include "nsAutoPtr.h"
 #include "nsISupportsImpl.h"  // for NS_INLINE_DECL_THREADSAFE_REFCOUNTING
 #include "APZUtils.h"         // for CancelAnimationFlags
 #include "Layers.h"           // for Layer::ScrollDirection
 #include "Units.h"            // for ScreenPoint
 
 namespace mozilla {
+
+class InputData;
+
 namespace layers {
 
 class AsyncPanZoomController;
 
 /**
  * This class represents the chain of APZCs along which overscroll is handed off.
  * It is created by APZCTreeManager by starting from an initial APZC which is
  * the target for input events, and following the scroll parent ID links (often
--- a/gfx/layers/composite/ContainerLayerComposite.cpp
+++ b/gfx/layers/composite/ContainerLayerComposite.cpp
@@ -134,18 +134,17 @@ struct PreparedLayer
   RenderTargetIntRect mClipRect;
 };
 
 
 template<class ContainerT> void
 ContainerRenderVR(ContainerT* aContainer,
                   LayerManagerComposite* aManager,
                   const gfx::IntRect& aClipRect,
-                  RefPtr<gfx::VRHMDInfo> aHMD,
-                  int32_t aInputFrameID)
+                  RefPtr<gfx::VRHMDInfo> aHMD)
 {
   int32_t inputFrameID = -1;
 
   RefPtr<CompositingRenderTarget> surface;
 
   Compositor* compositor = aManager->GetCompositor();
 
   RefPtr<CompositingRenderTarget> previousTarget = compositor->GetCurrentRenderTarget();
@@ -266,21 +265,39 @@ ContainerRenderVR(ContainerT* aContainer
       }
 
       // XXX these are both clip rects, which end up as scissor rects in the compositor.  So we just
       // pass the full target surface rect here.
       layerToRender->Prepare(RenderTargetIntRect(surfaceRect.x, surfaceRect.y,
                                                  surfaceRect.width, surfaceRect.height));
       layerToRender->RenderLayer(surfaceRect);
 
-      CompositableHost *ch = layerToRender->GetCompositableHost();
-      if (ch) {
-        int32_t compositableInputFrameID = ch->GetLastInputFrameID();
-        if (compositableInputFrameID != -1) {
-          inputFrameID = compositableInputFrameID;
+      // Search all children recursively until we find the canvas with
+      // an inputFrameID
+      std::stack<LayerComposite*> searchLayers;
+      searchLayers.push(layerToRender);
+      while (!searchLayers.empty() && inputFrameID == -1) {
+        LayerComposite* searchLayer = searchLayers.top();
+        searchLayers.pop();
+        if (searchLayer) {
+          searchLayers.push(searchLayer->GetFirstChildComposite());
+          Layer* sibling = searchLayer->GetLayer();
+          if (sibling) {
+            sibling = sibling->GetNextSibling();
+          }
+          if (sibling) {
+            searchLayers.push(sibling->AsLayerComposite());
+          }
+          CompositableHost *ch = searchLayer->GetCompositableHost();
+          if (ch) {
+            int32_t compositableInputFrameID = ch->GetLastInputFrameID();
+            if (compositableInputFrameID != -1) {
+              inputFrameID = compositableInputFrameID;
+            }
+          }
         }
       }
 
       if (restoreTransform) {
         layer->ReplaceEffectiveTransform(childTransform);
       }
     } else {
       // Gecko-rendered CSS VR -- not supported yet, so just don't render this layer!
@@ -709,17 +726,17 @@ template<class ContainerT> void
 ContainerRender(ContainerT* aContainer,
                  LayerManagerComposite* aManager,
                  const gfx::IntRect& aClipRect)
 {
   MOZ_ASSERT(aContainer->mPrepared);
 
   RefPtr<gfx::VRHMDInfo> hmdInfo = gfx::VRManager::Get()->GetDevice(aContainer->GetVRDeviceID());
   if (hmdInfo && hmdInfo->GetConfiguration().IsValid()) {
-    ContainerRenderVR(aContainer, aManager, aClipRect, hmdInfo, aContainer->GetInputFrameID());
+    ContainerRenderVR(aContainer, aManager, aClipRect, hmdInfo);
     aContainer->mPrepared = nullptr;
     return;
   }
 
   if (aContainer->UseIntermediateSurface()) {
     RefPtr<CompositingRenderTarget> surface;
 
     if (aContainer->mPrepared->mNeedsSurfaceCopy) {
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -2345,25 +2345,18 @@ CompositorParent::UpdatePluginWindowStat
   CompositorParent::LayerTreeState& lts = sIndirectLayerTrees[aId];
   if (!lts.mParent) {
     PLUGINS_LOG("[%" PRIu64 "] layer tree compositor parent pointer is null", aId);
     return false;
   }
 
   // Check if this layer tree has received any shadow layer updates
   if (!lts.mUpdatedPluginDataAvailable) {
-    if (mLastPluginUpdateLayerTreeId != aId) {
-      lts.mUpdatedPluginDataAvailable = true;
-      mPluginsLayerOffset = nsIntPoint(0,0);
-      mPluginsLayerVisibleRegion.SetEmpty();
-      PLUGINS_LOG("[%" PRIu64 "] new layer id, refreshing", aId);
-    } else {
-      PLUGINS_LOG("[%" PRIu64 "] no plugin data", aId);
-      return false;
-    }
+    PLUGINS_LOG("[%" PRIu64 "] no plugin data", aId);
+    return false;
   }
 
   // pluginMetricsChanged tracks whether we need to send plugin update
   // data to the main thread. If we do we'll have to block composition,
   // which we want to avoid if at all possible.
   bool pluginMetricsChanged = false;
 
   // Same layer tree checks
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -359,16 +359,17 @@ private:
   DECL_GFX_PREF(Once, "layers.enable-tiles",                   LayersTilesEnabled, bool, false);
   DECL_GFX_PREF(Live, "layers.flash-borders",                  FlashLayerBorders, bool, false);
   DECL_GFX_PREF(Once, "layers.force-shmem-tiles",              ForceShmemTiles, bool, false);
   DECL_GFX_PREF(Live, "layers.frame-counter",                  DrawFrameCounter, bool, false);
   DECL_GFX_PREF(Once, "layers.gralloc.disable",                DisableGralloc, bool, false);
   DECL_GFX_PREF(Live, "layers.low-precision-buffer",           UseLowPrecisionBuffer, bool, false);
   DECL_GFX_PREF(Live, "layers.low-precision-opacity",          LowPrecisionOpacity, float, 1.0f);
   DECL_GFX_PREF(Live, "layers.low-precision-resolution",       LowPrecisionResolution, float, 0.25f);
+  DECL_GFX_PREF(Live, "layers.max-active",                     MaxActiveLayers, int32_t, -1);
   DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.enabled", LayersOffMainThreadCompositionEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.force-enabled", LayersOffMainThreadCompositionForceEnabled, bool, false);
   DECL_GFX_PREF(Live, "layers.offmainthreadcomposition.frame-rate", LayersCompositionFrameRate, int32_t,-1);
   DECL_GFX_PREF(Live, "layers.orientation.sync.timeout",       OrientationSyncMillis, uint32_t, (uint32_t)0);
   DECL_GFX_PREF(Once, "layers.overzealous-gralloc-unlocking",  OverzealousGrallocUnlocking, bool, false);
   DECL_GFX_PREF(Once, "layers.prefer-d3d9",                    LayersPreferD3D9, bool, false);
   DECL_GFX_PREF(Once, "layers.prefer-opengl",                  LayersPreferOpenGL, bool, false);
   DECL_GFX_PREF(Live, "layers.progressive-paint",              ProgressivePaintDoNotUseDirectly, bool, false);
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -521,25 +521,32 @@ HMDInfoOculus::DestroyRenderTargetSet(Re
 }
 
 void
 HMDInfoOculus::SubmitFrame(RenderTargetSet *aRTSet, int32_t aInputFrameID)
 {
   RenderTargetSetOculus *rts = static_cast<RenderTargetSetOculus*>(aRTSet);
   MOZ_ASSERT(rts->hmd != nullptr);
   MOZ_ASSERT(rts->textureSet != nullptr);
+  MOZ_ASSERT(aInputFrameID >= 0);
+  if (aInputFrameID < 0) {
+    // Sanity check to prevent invalid memory access on builds with assertions
+    // disabled.
+    aInputFrameID = 0;
+  }
 
   VRHMDSensorState sensorState = mLastSensorState[aInputFrameID % kMaxLatencyFrames];
   // It is possible to get a cache miss on mLastSensorState if latency is
   // longer than kMaxLatencyFrames.  An optimization would be to find a frame
   // that is closer than the one selected with the modulus.
   // If we hit this; however, latency is already so high that the site is
   // un-viewable and a more accurate pose prediction is not likely to
   // compensate.
   ovrLayerEyeFov layer;
+  memset(&layer, 0, sizeof(layer));
   layer.Header.Type = ovrLayerType_EyeFov;
   layer.Header.Flags = 0;
   layer.ColorTexture[0] = rts->textureSet;
   layer.ColorTexture[1] = nullptr;
   layer.Fov[0] = mFOVPort[0];
   layer.Fov[1] = mFOVPort[1];
   layer.Viewport[0].Pos.x = 0;
   layer.Viewport[0].Pos.y = 0;
@@ -602,16 +609,17 @@ bool
 VRHMDManagerOculus::Init()
 {
   if (!mOculusInitialized) {
     nsIThread* thread = nullptr;
     NS_GetCurrentThread(&thread);
     mOculusThread = already_AddRefed<nsIThread>(thread);
 
     ovrInitParams params;
+    memset(&params, 0, sizeof(params));
     params.Flags = ovrInit_RequestVersion;
     params.RequestedMinorVersion = OVR_MINOR_VERSION;
     params.LogCallback = nullptr;
     params.ConnectionTimeoutMS = 0;
 
     ovrResult orv = ovr_Initialize(&params);
 
     if (orv == ovrSuccess) {
--- a/gfx/vr/gfxVROculus050.cpp
+++ b/gfx/vr/gfxVROculus050.cpp
@@ -538,16 +538,17 @@ bool
 VRHMDManagerOculus050::Init()
 {
   if (!mOculusInitialized) {
     nsIThread* thread = nullptr;
     NS_GetCurrentThread(&thread);
     mOculusThread = already_AddRefed<nsIThread>(thread);
 
     ovrInitParams params;
+    memset(&params, 0, sizeof(params));
     params.Flags = ovrInit_RequestVersion;
     params.RequestedMinorVersion = LIBOVR_MINOR_VERSION;
     params.LogCallback = nullptr;
     params.ConnectionTimeoutMS = 0;
 
     bool ok = ovr_Initialize(&params);
 
     if (ok) {
--- a/hal/gonk/GonkHal.cpp
+++ b/hal/gonk/GonkHal.cpp
@@ -53,16 +53,17 @@
 #include "mozilla/DebugOnly.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/UniquePtrExtensions.h"
 #include "nsAlgorithm.h"
 #include "nsPrintfCString.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsIRecoveryService.h"
 #include "nsIRunnable.h"
 #include "nsScreenManagerGonk.h"
 #include "nsThreadUtils.h"
@@ -1237,17 +1238,17 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
 protected:
   ~OomVictimLogger() {}
 
 private:
   double mLastLineChecked;
-  ScopedFreePtr<regex_t> mRegexes;
+  UniqueFreePtr<regex_t> mRegexes;
 };
 NS_IMPL_ISUPPORTS(OomVictimLogger, nsIObserver);
 
 NS_IMETHODIMP
 OomVictimLogger::Observe(
   nsISupports* aSubject,
   const char* aTopic,
   const char16_t* aData)
@@ -1271,19 +1272,23 @@ OomVictimLogger::Observe(
     // oom_adj and oom_score_adj can be negative
     "\\[ pid \\]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name",
     "\\[.*[0-9][0-9]*\\][ ]*[0-9][0-9]*[ ]*[0-9][0-9]*[ ]*[0-9][0-9]*[ ]*[0-9][0-9]*[ ]*[0-9][0-9]*[ ]*.[0-9][0-9]*[ ]*.[0-9][0-9]*.*"
   };
   const size_t regex_count = ArrayLength(regexes_raw);
 
   // Compile our regex just in time
   if (!mRegexes) {
-    mRegexes = static_cast<regex_t*>(malloc(sizeof(regex_t) * regex_count));
+    UniqueFreePtr<regex_t> regexes(
+      static_cast<regex_t*>(malloc(sizeof(regex_t) * regex_count))
+    );
+    mRegexes.swap(regexes);
     for (size_t i = 0; i < regex_count; i++) {
-      int compilation_err = regcomp(&(mRegexes[i]), regexes_raw[i], REG_NOSUB);
+      int compilation_err =
+        regcomp(&(mRegexes.get()[i]), regexes_raw[i], REG_NOSUB);
       if (compilation_err) {
         OOM_LOG(ANDROID_LOG_ERROR, "Cannot compile regex \"%s\"\n", regexes_raw[i]);
         return NS_OK;
       }
     }
   }
 
 #ifndef KLOG_SIZE_BUFFER
@@ -1293,26 +1298,26 @@ OomVictimLogger::Observe(
   // Our current bionic does not hit this
   // change yet so handle the future change.
   // (ICS doesn't have KLOG_SIZE_BUFFER but 
   // JB and onwards does.)
   #define KLOG_SIZE_BUFFER KLOG_WRITE
 #endif
   // Retreive kernel log
   int msg_buf_size = klogctl(KLOG_SIZE_BUFFER, NULL, 0);
-  ScopedFreePtr<char> msg_buf(static_cast<char *>(malloc(msg_buf_size + 1)));
-  int read_size = klogctl(KLOG_READ_ALL, msg_buf.rwget(), msg_buf_size);
+  UniqueFreePtr<char> msg_buf(static_cast<char *>(malloc(msg_buf_size + 1)));
+  int read_size = klogctl(KLOG_READ_ALL, msg_buf.get(), msg_buf_size);
 
   // Turn buffer into cstring
   read_size = read_size > msg_buf_size ? msg_buf_size : read_size;
-  msg_buf.rwget()[read_size] = '\0';
+  msg_buf.get()[read_size] = '\0';
 
   // Foreach line
   char* line_end;
-  char* line_begin = msg_buf.rwget();
+  char* line_begin = msg_buf.get();
   for (; (line_end = strchr(line_begin, '\n')); line_begin = line_end + 1) {
     // make line into cstring
     *line_end = '\0';
 
     // Note: Kernel messages look like:
     // <5>[63648.286409] sd 35:0:0:0: Attached scsi generic sg1 type 0
     // 5 is the loging level
     // [*] is the time timestamp, seconds since boot
@@ -1335,17 +1340,17 @@ OomVictimLogger::Observe(
       }
 
       lineTimestampFound = true;
       mLastLineChecked = lineTimestamp;
     }
 
     // Log interesting lines
     for (size_t i = 0; i < regex_count; i++) {
-      int matching = !regexec(&(mRegexes[i]), line_begin, 0, NULL, 0);
+      int matching = !regexec(&(mRegexes.get()[i]), line_begin, 0, NULL, 0);
       if (matching) {
         // Log content of kernel message. We try to skip the ], but if for
         // some reason (most likely due to buffer overflow/wraparound), we
         // can't find the ] then we just log the entire line.
         char* endOfTimestamp = strchr(line_begin, ']');
         if (endOfTimestamp && endOfTimestamp[1] == ' ') {
           // skip the ] and the space that follows it
           line_begin = endOfTimestamp + 2;
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -28,17 +28,17 @@ FrameAnimator::GetSingleLoopTime() const
     return -1;
   }
 
   // If we're not looping, a single loop time has no meaning
   if (mAnimationMode != imgIContainer::kNormalAnimMode) {
     return -1;
   }
 
-  uint32_t looptime = 0;
+  int32_t looptime = 0;
   for (uint32_t i = 0; i < mImage->GetNumFrames(); ++i) {
     int32_t timeout = GetTimeoutForFrame(i);
     if (timeout >= 0) {
       looptime += static_cast<uint32_t>(timeout);
     } else {
       // If we have a frame that never times out, we're probably in an error
       // case, but let's handle it more gracefully.
       NS_WARNING("Negative frame timeout - how did this happen?");
@@ -175,24 +175,29 @@ FrameAnimator::AdvanceFrame(TimeStamp aT
     }
 
     nextFrame->SetCompositingFailed(false);
   }
 
   mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
 
   // If we can get closer to the current time by a multiple of the image's loop
-  // time, we should.
-  uint32_t loopTime = GetSingleLoopTime();
+  // time, we should. We need to be done decoding in order to know the full loop
+  // time though!
+  int32_t loopTime = GetSingleLoopTime();
   if (loopTime > 0) {
+    // We shouldn't be advancing by a whole loop unless we are decoded and know
+    // what a full loop actually is. GetSingleLoopTime should return -1 so this
+    // never happens.
+    MOZ_ASSERT(mDoneDecoding);
     TimeDuration delay = aTime - mCurrentAnimationFrameTime;
     if (delay.ToMilliseconds() > loopTime) {
       // Explicitly use integer division to get the floor of the number of
       // loops.
-      uint32_t loops = static_cast<uint32_t>(delay.ToMilliseconds()) / loopTime;
+      uint64_t loops = static_cast<uint64_t>(delay.ToMilliseconds()) / loopTime;
       mCurrentAnimationFrameTime +=
         TimeDuration::FromMilliseconds(loops * loopTime);
     }
   }
 
   // Set currentAnimationFrameIndex at the last possible moment
   mCurrentAnimationFrameIndex = nextFrameIndex;
 
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -73,16 +73,18 @@ NS_IMPL_ISUPPORTS(RasterImage, imgIConta
 
 //******************************************************************************
 RasterImage::RasterImage(ImageURL* aURI /* = nullptr */) :
   ImageResource(aURI), // invoke superclass's constructor
   mSize(0,0),
   mLockCount(0),
   mDecodeCount(0),
   mRequestedSampleSize(0),
+  mImageProducerID(ImageContainer::AllocateProducerID()),
+  mLastFrameID(0),
   mLastImageContainerDrawResult(DrawResult::NOT_READY),
 #ifdef DEBUG
   mFramesNotified(0),
 #endif
   mSourceBuffer(new SourceBuffer()),
   mFrameCount(0),
   mHasSize(false),
   mTransient(false),
@@ -686,17 +688,21 @@ RasterImage::GetImageContainer(LayerMana
   Tie(drawResult, image) = GetCurrentImage(container, aFlags);
   if (!image) {
     return nullptr;
   }
 
   // |image| holds a reference to a SourceSurface which in turn holds a lock on
   // the current frame's VolatileBuffer, ensuring that it doesn't get freed as
   // long as the layer system keeps this ImageContainer alive.
-  container->SetCurrentImageInTransaction(image);
+  AutoTArray<ImageContainer::NonOwningImage, 1> imageList;
+  imageList.AppendElement(ImageContainer::NonOwningImage(image, TimeStamp(),
+                                                         mLastFrameID++,
+                                                         mImageProducerID));
+  container->SetCurrentImagesInTransaction(imageList);
 
   mLastImageContainerDrawResult = drawResult;
   mImageContainer = container;
 
   return container.forget();
 }
 
 void
@@ -713,17 +719,19 @@ RasterImage::UpdateImageContainer()
   RefPtr<layers::Image> image;
   Tie(drawResult, image) = GetCurrentImage(container, FLAG_NONE);
   if (!image) {
     return;
   }
 
   mLastImageContainerDrawResult = drawResult;
   AutoTArray<ImageContainer::NonOwningImage, 1> imageList;
-  imageList.AppendElement(ImageContainer::NonOwningImage(image));
+  imageList.AppendElement(ImageContainer::NonOwningImage(image, TimeStamp(),
+                                                         mLastFrameID++,
+                                                         mImageProducerID));
   container->SetCurrentImages(imageList);
 }
 
 size_t
 RasterImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
 {
   return mSourceBuffer->SizeOfIncludingThisWithComputedFallback(aMallocSizeOf);
 }
--- a/image/RasterImage.h
+++ b/image/RasterImage.h
@@ -31,16 +31,17 @@
 #include "nsIObserver.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Pair.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/UniquePtr.h"
+#include "ImageContainer.h"
 #ifdef DEBUG
   #include "imgIContainerDebug.h"
 #endif
 
 class nsIInputStream;
 class nsIRequest;
 
 #define NS_RASTERIMAGE_CID \
@@ -362,16 +363,19 @@ private: // data
 
   // A hint for image decoder that directly scale the image to smaller buffer
   int                        mRequestedSampleSize;
 
   // A weak pointer to our ImageContainer, which stays alive only as long as
   // the layer system needs it.
   WeakPtr<layers::ImageContainer> mImageContainer;
 
+  layers::ImageContainer::ProducerID mImageProducerID;
+  layers::ImageContainer::FrameID mLastFrameID;
+
   // If mImageContainer is non-null, this contains the DrawResult we obtained
   // the last time we updated it.
   DrawResult mLastImageContainerDrawResult;
 
 #ifdef DEBUG
   uint32_t                       mFramesNotified;
 #endif
 
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -967,16 +967,20 @@ MessageChannel::Send(Message* aMsg, Mess
             IPC_LOG("Got reply: seqno=%d, xid=%d", seqno, transaction);
             break;
         }
 
         MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
 
         MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction);
         bool maybeTimedOut = !WaitForSyncNotify(handleWindowsMessages);
+        if (mListener->NeedArtificialSleep()) {
+            MonitorAutoUnlock unlock(*mMonitor);
+            mListener->ArtificialSleep();
+        }
 
         if (!Connected()) {
             ReportConnectionError("MessageChannel::SendAndWait");
             mLastSendError = SyncSendError::DisconnectedDuringSend;
             return false;
         }
 
         if (WasTransactionCanceled(transaction)) {
@@ -1371,31 +1375,37 @@ MessageChannel::DispatchMessage(const Me
 
         int id = aMsg.transaction_id();
         MOZ_RELEASE_ASSERT(!aMsg.is_sync() || id == mCurrentTransaction);
 
         {
             MonitorAutoUnlock unlock(*mMonitor);
             CxxStackFrame frame(*this, IN_MESSAGE, &aMsg);
 
+            mListener->ArtificialSleep();
+
             if (aMsg.is_sync())
                 DispatchSyncMessage(aMsg, *getter_Transfers(reply));
             else if (aMsg.is_interrupt())
                 DispatchInterruptMessage(aMsg, 0);
             else
                 DispatchAsyncMessage(aMsg);
+
+            mListener->ArtificialSleep();
         }
 
         if (mCurrentTransaction != id) {
             // The transaction has been canceled. Don't send a reply.
+            IPC_LOG("Nulling out reply due to cancellation, seqno=%d, xid=%d", aMsg.seqno(), id);
             reply = nullptr;
         }
     }
 
     if (reply && ChannelConnected == mChannelState) {
+        IPC_LOG("Sending reply seqno=%d, xid=%d", aMsg.seqno(), aMsg.transaction_id());
         mLink->SendMessage(reply.forget());
     }
 }
 
 void
 MessageChannel::DispatchSyncMessage(const Message& aMsg, Message*& aReply)
 {
     AssertWorkerThread();
@@ -1629,16 +1639,25 @@ MessageChannel::WaitResponse(bool aWaitT
     }
     return true;
 }
 
 #ifndef OS_WIN
 bool
 MessageChannel::WaitForSyncNotify(bool /* aHandleWindowsMessages */)
 {
+#ifdef DEBUG
+    // WARNING: We don't release the lock here. We can't because the link thread
+    // could signal at this time and we would miss it. Instead we require
+    // ArtificialTimeout() to be extremely simple.
+    if (mListener->ArtificialTimeout()) {
+        return false;
+    }
+#endif
+
     PRIntervalTime timeout = (kNoTimeout == mTimeoutMs) ?
                              PR_INTERVAL_NO_TIMEOUT :
                              PR_MillisecondsToInterval(mTimeoutMs);
     // XXX could optimize away this syscall for "no timeout" case if desired
     PRIntervalTime waitStart = PR_IntervalNow();
 
     mMonitor->Wait(timeout);
 
@@ -1665,16 +1684,17 @@ MessageChannel::ShouldContinueFromTimeou
 {
     AssertWorkerThread();
     mMonitor->AssertCurrentThreadOwns();
 
     bool cont;
     {
         MonitorAutoUnlock unlock(*mMonitor);
         cont = mListener->OnReplyTimeout();
+        mListener->ArtificialSleep();
     }
 
     static enum { UNKNOWN, NOT_DEBUGGING, DEBUGGING } sDebuggingChildren = UNKNOWN;
 
     if (sDebuggingChildren == UNKNOWN) {
         sDebuggingChildren = getenv("MOZ_DEBUG_CHILD_PROCESS") ? DEBUGGING : NOT_DEBUGGING;
     }
     if (sDebuggingChildren == DEBUGGING) {
--- a/ipc/glue/MessageLink.h
+++ b/ipc/glue/MessageLink.h
@@ -76,16 +76,43 @@ class MessageListener
         return false;
     }
 
     // WARNING: This function is called with the MessageChannel monitor held.
     virtual void IntentionalCrash() {
         MOZ_CRASH("Intentional IPDL crash");
     }
 
+    // The code here is only useful for fuzzing. It should not be used for any
+    // other purpose.
+#ifdef DEBUG
+    // Returns true if we should simulate a timeout.
+    // WARNING: This is a testing-only function that is called with the
+    // MessageChannel monitor held. Don't do anything fancy here or we could
+    // deadlock.
+    virtual bool ArtificialTimeout() {
+        return false;
+    }
+
+    // Returns true if we want to cause the worker thread to sleep with the
+    // monitor unlocked.
+    virtual bool NeedArtificialSleep() {
+        return false;
+    }
+
+    // This function should be implemented to sleep for some amount of time on
+    // the worker thread. Will only be called if NeedArtificialSleep() returns
+    // true.
+    virtual void ArtificialSleep() {}
+#else
+    bool ArtificialTimeout() { return false; }
+    bool NeedArtificialSleep() { return false; }
+    void ArtificialSleep() {}
+#endif
+
     virtual void OnEnteredCxxStack() {
         NS_RUNTIMEABORT("default impl shouldn't be invoked");
     }
     virtual void OnExitedCxxStack() {
         NS_RUNTIMEABORT("default impl shouldn't be invoked");
     }
     virtual void OnEnteredCall() {
         NS_RUNTIMEABORT("default impl shouldn't be invoked");
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/PTestDemon.ipdl
@@ -0,0 +1,21 @@
+namespace mozilla {
+namespace _ipdltest {
+
+prio(normal upto urgent) sync protocol PTestDemon
+{
+child:
+    async Start();
+
+both:
+    async AsyncMessage(int n);
+    prio(high) sync HiPrioSyncMessage();
+
+parent:
+    sync SyncMessage(int n);
+
+    prio(urgent) async UrgentAsyncMessage(int n);
+    prio(urgent) sync UrgentSyncMessage(int n);
+};
+
+} // namespace _ipdltest
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/TestDemon.cpp
@@ -0,0 +1,402 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=4 ts=4 et :
+ */
+#include "TestDemon.h"
+
+#include <stdlib.h>
+#include <sys/time.h>
+
+#include "IPDLUnitTests.h"      // fail etc.
+#if defined(OS_POSIX)
+#include <unistd.h>
+#else
+#include <windows.h>
+#endif
+
+template<>
+struct RunnableMethodTraits<mozilla::_ipdltest::TestDemonParent>
+{
+    static void RetainCallee(mozilla::_ipdltest::TestDemonParent* obj) { }
+    static void ReleaseCallee(mozilla::_ipdltest::TestDemonParent* obj) { }
+};
+
+template<>
+struct RunnableMethodTraits<mozilla::_ipdltest::TestDemonChild>
+{
+    static void RetainCallee(mozilla::_ipdltest::TestDemonChild* obj) { }
+    static void ReleaseCallee(mozilla::_ipdltest::TestDemonChild* obj) { }
+};
+
+namespace mozilla {
+namespace _ipdltest {
+
+const int kMaxStackHeight = 4;
+
+static LazyLogModule sLogModule("demon");
+
+#define DEMON_LOG(args...) MOZ_LOG(sLogModule, LogLevel::Debug, (args))
+
+static int gStackHeight = 0;
+static bool gFlushStack = false;
+
+static int
+Choose(int count)
+{
+  return random() % count;
+}
+
+//-----------------------------------------------------------------------------
+// parent
+
+TestDemonParent::TestDemonParent()
+ : mDone(false),
+   mIncoming(),
+   mOutgoing()
+{
+  MOZ_COUNT_CTOR(TestDemonParent);
+}
+
+TestDemonParent::~TestDemonParent()
+{
+  MOZ_COUNT_DTOR(TestDemonParent);
+}
+
+void
+TestDemonParent::Main()
+{
+  if (!getenv("MOZ_TEST_IPC_DEMON")) {
+    QuitParent();
+    return;
+  }
+  srandom(time(nullptr));
+
+  DEMON_LOG("Start demon");
+
+  if (!SendStart())
+	fail("sending Start");
+
+  RunUnlimitedSequence();
+}
+
+#ifdef DEBUG
+bool
+TestDemonParent::ShouldContinueFromReplyTimeout()
+{
+  return Choose(2) == 0;
+}
+
+bool
+TestDemonParent::ArtificialTimeout()
+{
+  return Choose(5) == 0;
+}
+
+void
+TestDemonParent::ArtificialSleep()
+{
+  if (Choose(2) == 0) {
+    // Sleep for anywhere from 0 to 100 milliseconds.
+    unsigned micros = Choose(100) * 1000;
+#ifdef OS_POSIX
+    usleep(micros);
+#else
+    Sleep(micros / 1000);
+#endif
+  }
+}
+#endif
+
+bool
+TestDemonParent::RecvAsyncMessage(const int& n)
+{
+  DEMON_LOG("Start RecvAsync [%d]", n);
+
+  MOZ_ASSERT(n == mIncoming[0]);
+  mIncoming[0]++;
+
+  RunLimitedSequence();
+
+  DEMON_LOG("End RecvAsync [%d]", n);
+  return true;
+}
+
+bool
+TestDemonParent::RecvHiPrioSyncMessage()
+{
+  DEMON_LOG("Start RecvHiPrioSyncMessage");
+  RunLimitedSequence();
+  DEMON_LOG("End RecvHiPrioSyncMessage");
+  return true;
+}
+
+bool
+TestDemonParent::RecvSyncMessage(const int& n)
+{
+  DEMON_LOG("Start RecvSync [%d]", n);
+
+  MOZ_ASSERT(n == mIncoming[0]);
+  mIncoming[0]++;
+
+  RunLimitedSequence(ASYNC_ONLY);
+
+  DEMON_LOG("End RecvSync [%d]", n);
+  return true;
+}
+
+bool
+TestDemonParent::RecvUrgentAsyncMessage(const int& n)
+{
+  DEMON_LOG("Start RecvUrgentAsyncMessage [%d]", n);
+
+  MOZ_ASSERT(n == mIncoming[2]);
+  mIncoming[2]++;
+
+  RunLimitedSequence(ASYNC_ONLY);
+
+  DEMON_LOG("End RecvUrgentAsyncMessage [%d]", n);
+  return true;
+}
+
+bool
+TestDemonParent::RecvUrgentSyncMessage(const int& n)
+{
+  DEMON_LOG("Start RecvUrgentSyncMessage [%d]", n);
+
+  MOZ_ASSERT(n == mIncoming[2]);
+  mIncoming[2]++;
+
+  RunLimitedSequence(ASYNC_ONLY);
+
+  DEMON_LOG("End RecvUrgentSyncMessage [%d]", n);
+  return true;
+}
+
+void
+TestDemonParent::RunUnlimitedSequence()
+{
+  if (mDone) {
+    return;
+  }
+
+  gFlushStack = false;
+  DoAction();
+
+  MessageLoop::current()->PostTask(FROM_HERE,
+                                   NewRunnableMethod(this, &TestDemonParent::RunUnlimitedSequence));
+}
+
+void
+TestDemonParent::RunLimitedSequence(int flags)
+{
+  if (gStackHeight >= kMaxStackHeight) {
+    return;
+  }
+  gStackHeight++;
+
+  int count = Choose(20);
+  for (int i = 0; i < count; i++) {
+	if (!DoAction(flags)) {
+      gFlushStack = true;
+    }
+    if (gFlushStack) {
+      gStackHeight--;
+      return;
+    }
+  }
+
+  gStackHeight--;
+}
+
+bool
+TestDemonParent::DoAction(int flags)
+{
+  if (flags & ASYNC_ONLY) {
+    DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
+    return SendAsyncMessage(mOutgoing[0]++);
+  } else {
+	switch (Choose(3)) {
+     case 0:
+      DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
+      return SendAsyncMessage(mOutgoing[0]++);
+
+     case 1: {
+       DEMON_LOG("Start SendHiPrioSyncMessage");
+       bool r = SendHiPrioSyncMessage();
+       DEMON_LOG("End SendHiPrioSyncMessage result=%d", r);
+       return r;
+     }
+
+     case 2:
+      DEMON_LOG("Cancel");
+      GetIPCChannel()->CancelCurrentTransaction();
+      return true;
+	}
+  }
+  MOZ_CRASH();
+  return false;
+}
+
+//-----------------------------------------------------------------------------
+// child
+
+
+TestDemonChild::TestDemonChild()
+ : mIncoming(),
+   mOutgoing()
+{
+  MOZ_COUNT_CTOR(TestDemonChild);
+}
+
+TestDemonChild::~TestDemonChild()
+{
+  MOZ_COUNT_DTOR(TestDemonChild);
+}
+
+bool
+TestDemonChild::RecvStart()
+{
+  srandom(time(nullptr));
+
+  DEMON_LOG("RecvStart");
+
+  RunUnlimitedSequence();
+  return true;
+}
+
+#ifdef DEBUG
+void
+TestDemonChild::ArtificialSleep()
+{
+  if (Choose(2) == 0) {
+    // Sleep for anywhere from 0 to 100 milliseconds.
+    unsigned micros = Choose(100) * 1000;
+#ifdef OS_POSIX
+    usleep(micros);
+#else
+    Sleep(micros / 1000);
+#endif
+  }
+}
+#endif
+
+bool
+TestDemonChild::RecvAsyncMessage(const int& n)
+{
+  DEMON_LOG("Start RecvAsyncMessage [%d]", n);
+
+  MOZ_ASSERT(n == mIncoming[0]);
+  mIncoming[0]++;
+
+  RunLimitedSequence();
+
+  DEMON_LOG("End RecvAsyncMessage [%d]", n);
+  return true;
+}
+
+bool
+TestDemonChild::RecvHiPrioSyncMessage()
+{
+  DEMON_LOG("Start RecvHiPrioSyncMessage");
+  RunLimitedSequence();
+  DEMON_LOG("End RecvHiPrioSyncMessage");
+  return true;
+}
+
+void
+TestDemonChild::RunUnlimitedSequence()
+{
+  gFlushStack = false;
+  DoAction();
+
+  MessageLoop::current()->PostTask(FROM_HERE,
+                                   NewRunnableMethod(this, &TestDemonChild::RunUnlimitedSequence));
+}
+
+void
+TestDemonChild::RunLimitedSequence()
+{
+  if (gStackHeight >= kMaxStackHeight) {
+    return;
+  }
+  gStackHeight++;
+
+  int count = Choose(20);
+  for (int i = 0; i < count; i++) {
+    if (!DoAction()) {
+      gFlushStack = true;
+    }
+    if (gFlushStack) {
+      gStackHeight--;
+      return;
+    }
+  }
+
+  gStackHeight--;
+}
+
+bool
+TestDemonChild::DoAction()
+{
+  switch (Choose(6)) {
+   case 0:
+	DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
+	return SendAsyncMessage(mOutgoing[0]++);
+
+   case 1: {
+     DEMON_LOG("Start SendHiPrioSyncMessage");
+     bool r = SendHiPrioSyncMessage();
+     DEMON_LOG("End SendHiPrioSyncMessage result=%d", r);
+     return r;
+   }
+
+   case 2: {
+     DEMON_LOG("Start SendSyncMessage [%d]", mOutgoing[0]);
+     bool r = SendSyncMessage(mOutgoing[0]++);
+     switch (GetIPCChannel()->LastSendError()) {
+       case SyncSendError::PreviousTimeout:
+       case SyncSendError::SendingCPOWWhileDispatchingSync:
+       case SyncSendError::SendingCPOWWhileDispatchingUrgent:
+       case SyncSendError::NotConnectedBeforeSend:
+       case SyncSendError::CancelledBeforeSend:
+         mOutgoing[0]--;
+         break;
+       default:
+         break;
+     }
+     DEMON_LOG("End SendSyncMessage result=%d", r);
+     return r;
+   }
+
+   case 3:
+	DEMON_LOG("SendUrgentAsyncMessage [%d]", mOutgoing[2]);
+	return SendUrgentAsyncMessage(mOutgoing[2]++);
+
+   case 4: {
+     DEMON_LOG("Start SendUrgentSyncMessage [%d]", mOutgoing[2]);
+     bool r = SendUrgentSyncMessage(mOutgoing[2]++);
+     switch (GetIPCChannel()->LastSendError()) {
+       case SyncSendError::PreviousTimeout:
+       case SyncSendError::SendingCPOWWhileDispatchingSync:
+       case SyncSendError::SendingCPOWWhileDispatchingUrgent:
+       case SyncSendError::NotConnectedBeforeSend:
+       case SyncSendError::CancelledBeforeSend:
+         mOutgoing[2]--;
+         break;
+       default:
+         break;
+     }
+     DEMON_LOG("End SendUrgentSyncMessage result=%d", r);
+     return r;
+   }
+
+   case 5:
+	DEMON_LOG("Cancel");
+	GetIPCChannel()->CancelCurrentTransaction();
+	return true;
+  }
+  MOZ_CRASH();
+  return false;
+}
+
+} // namespace _ipdltest
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/TestDemon.h
@@ -0,0 +1,106 @@
+#ifndef mozilla__ipdltest_TestDemon_h
+#define mozilla__ipdltest_TestDemon_h 1
+
+#include "mozilla/_ipdltest/IPDLUnitTests.h"
+
+#include "mozilla/_ipdltest/PTestDemonParent.h"
+#include "mozilla/_ipdltest/PTestDemonChild.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace _ipdltest {
+
+
+class TestDemonParent :
+    public PTestDemonParent
+{
+public:
+    TestDemonParent();
+    virtual ~TestDemonParent();
+
+    static bool RunTestInProcesses() { return true; }
+    static bool RunTestInThreads() { return true; }
+
+    void Main();
+
+#ifdef DEBUG
+    bool ShouldContinueFromReplyTimeout() override;
+    bool ArtificialTimeout() override;
+
+    bool NeedArtificialSleep() override { return true; }
+    void ArtificialSleep() override;
+#endif
+
+    bool RecvAsyncMessage(const int& n) override;
+    bool RecvHiPrioSyncMessage() override;
+
+    bool RecvSyncMessage(const int& n) override;
+    bool RecvUrgentAsyncMessage(const int& n) override;
+    bool RecvUrgentSyncMessage(const int& n) override;
+
+    virtual void ActorDestroy(ActorDestroyReason why) override
+    {
+	mDone = true;
+	printf("Parent ActorDestroy\n");
+        passed("ok");
+        QuitParent();
+    }
+
+private:
+    bool mDone;
+    int mIncoming[3];
+    int mOutgoing[3];
+
+    enum {
+	ASYNC_ONLY = 1,
+    };
+
+    void RunUnlimitedSequence();
+    void RunLimitedSequence(int flags = 0);
+    bool DoAction(int flags = 0);
+};
+
+
+class TestDemonChild :
+    public PTestDemonChild
+{
+public:
+    TestDemonChild();
+    virtual ~TestDemonChild();
+
+    bool RecvStart() override;
+
+#ifdef DEBUG
+    bool NeedArtificialSleep() override { return true; }
+    void ArtificialSleep() override;
+#endif
+
+    bool RecvAsyncMessage(const int& n) override;
+    bool RecvHiPrioSyncMessage() override;
+
+    virtual void ActorDestroy(ActorDestroyReason why) override
+    {
+	_exit(0);
+    }
+
+    virtual void IntentionalCrash() override
+    {
+	_exit(0);
+    }
+
+private:
+    int mIncoming[3];
+    int mOutgoing[3];
+
+    void RunUnlimitedSequence();
+    void RunLimitedSequence();
+    bool DoAction();
+};
+
+
+} // namespace _ipdltest
+} // namespace mozilla
+
+
+#endif // ifndef mozilla__ipdltest_TestDemon_h
--- a/ipc/ipdl/test/cxx/moz.build
+++ b/ipc/ipdl/test/cxx/moz.build
@@ -15,16 +15,17 @@ EXPORTS.mozilla._ipdltest += [
 
 SOURCES += [
     'TestActorPunning.cpp',
     'TestBadActor.cpp',
     'TestBridgeMain.cpp',
     'TestCancel.cpp',
     'TestCrashCleanup.cpp',
     'TestDataStructures.cpp',
+    'TestDemon.cpp',
     'TestDesc.cpp',
     'TestEndpointBridgeMain.cpp',
     'TestEndpointOpens.cpp',
     'TestFailedCtor.cpp',
     'TestHangs.cpp',
     'TestHighestPrio.cpp',
     'TestInterruptErrorCleanup.cpp',
     'TestInterruptRaces.cpp',
@@ -68,16 +69,17 @@ IPDL_SOURCES += [
     'PTestBridgeMain.ipdl',
     'PTestBridgeMainSub.ipdl',
     'PTestBridgeSub.ipdl',
     'PTestCancel.ipdl',
     'PTestCrashCleanup.ipdl',
     'PTestDataStructures.ipdl',
     'PTestDataStructuresCommon.ipdlh',
     'PTestDataStructuresSub.ipdl',
+    'PTestDemon.ipdl',
     'PTestDesc.ipdl',
     'PTestDescSub.ipdl',
     'PTestDescSubsub.ipdl',
     'PTestEndpointBridgeMain.ipdl',
     'PTestEndpointBridgeMainSub.ipdl',
     'PTestEndpointBridgeSub.ipdl',
     'PTestEndpointOpens.ipdl',
     'PTestEndpointOpensOpened.ipdl',
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -2574,16 +2574,18 @@ SimdToExpr(SimdType type, SimdOperation 
 }
 
 #undef CASE
 #undef I32CASE
 #undef F32CASE
 #undef B32CASE
 #undef ENUMERATE
 
+typedef Vector<PropertyName*, 4, SystemAllocPolicy> NameVector;
+
 // Encapsulates the building of an asm bytecode function from an asm.js function
 // source code, packing the asm.js code into the asm bytecode form that can
 // be decoded and compiled with a FunctionCompiler.
 class MOZ_STACK_CLASS FunctionValidator
 {
   public:
     struct Local
     {
@@ -2600,36 +2602,54 @@ class MOZ_STACK_CLASS FunctionValidator
 
     ModuleValidator&  m_;
     ParseNode*        fn_;
 
     FunctionGenerator fg_;
     Maybe<Encoder>    encoder_;
 
     LocalMap          locals_;
-    LabelMap          labels_;
+
+    // Labels
+    LabelMap          breakLabels_;
+    LabelMap          continueLabels_;
+    Uint32Vector      breakableStack_;
+    Uint32Vector      continuableStack_;
+    uint32_t          blockDepth_;
 
     bool              hasAlreadyReturned_;
     ExprType          ret_;
 
   public:
     FunctionValidator(ModuleValidator& m, ParseNode* fn)
       : m_(m),
         fn_(fn),
         locals_(m.cx()),
-        labels_(m.cx()),
+        breakLabels_(m.cx()),
+        continueLabels_(m.cx()),
+        blockDepth_(0),
         hasAlreadyReturned_(false)
     {}
 
+    ~FunctionValidator() {
+        if (m_.hasAlreadyFailed())
+            return;
+        MOZ_ASSERT(!blockDepth_);
+        MOZ_ASSERT(breakableStack_.empty());
+        MOZ_ASSERT(continuableStack_.empty());
+        MOZ_ASSERT(breakLabels_.empty());
+        MOZ_ASSERT(continueLabels_.empty());
+    }
+
     ModuleValidator& m() const        { return m_; }
     ExclusiveContext* cx() const      { return m_.cx(); }
     ParseNode* fn() const             { return fn_; }
 
     bool init(PropertyName* name, unsigned line) {
-        if (!locals_.init() || !labels_.init())
+        if (!locals_.init() || !breakLabels_.init() || !continueLabels_.init())
             return false;
 
         if (!m_.mg().startFuncDef(line, &fg_))
             return false;
 
         encoder_.emplace(fg_.bytecode());
         return true;
     }
@@ -2675,32 +2695,113 @@ class MOZ_STACK_CLASS FunctionValidator
     }
 
     void setReturnedType(ExprType ret) {
         ret_ = ret;
         hasAlreadyReturned_ = true;
     }
 
     /**************************************************************** Labels */
-
-    uint32_t lookupLabel(PropertyName* label) const {
-        if (auto p = labels_.lookup(label))
-            return p->value();
-        return -1;
-    }
-
-    bool addLabel(PropertyName* label, uint32_t* id) {
-        *id = labels_.count();
-        return labels_.putNew(label, *id);
-    }
-
-    void removeLabel(PropertyName* label) {
-        auto p = labels_.lookup(label);
-        MOZ_ASSERT(!!p);
-        labels_.remove(p);
+  private:
+    bool writeBr(uint32_t absolute, Expr expr = Expr::Br) {
+        MOZ_ASSERT(expr == Expr::Br || expr == Expr::BrIf);
+        MOZ_ASSERT(absolute < blockDepth_);
+        return encoder().writeExpr(expr) &&
+               encoder().writeVarU32(blockDepth_ - 1 - absolute);
+    }
+    void removeLabel(PropertyName* label, LabelMap* map) {
+        LabelMap::Ptr p = map->lookup(label);
+        MOZ_ASSERT(p);
+        map->remove(p);
+    }
+
+  public:
+    bool pushBreakableBlock(uint32_t numStmts) {
+        return encoder().writeExpr(Expr::Block) &&
+               encoder().writeVarU32(numStmts) &&
+               breakableStack_.append(blockDepth_++);
+    }
+    void popBreakableBlock() {
+        JS_ALWAYS_TRUE(breakableStack_.popCopy() == --blockDepth_);
+    }
+
+    bool pushUnbreakableBlock(uint32_t numStmts, const NameVector* labels = nullptr) {
+        if (labels) {
+            for (PropertyName* label : *labels) {
+                if (!breakLabels_.putNew(label, blockDepth_))
+                    return false;
+            }
+        }
+        blockDepth_++;
+        return encoder().writeExpr(Expr::Block) &&
+               encoder().writeVarU32(numStmts);
+    }
+    void popUnbreakableBlock(const NameVector* labels = nullptr) {
+        if (labels) {
+            for (PropertyName* label : *labels)
+                removeLabel(label, &breakLabels_);
+        }
+        --blockDepth_;
+    }
+
+    bool pushContinuableBlock(uint32_t numStmts) {
+        return encoder().writeExpr(Expr::Block) &&
+               encoder().writeVarU32(numStmts) &&
+               continuableStack_.append(blockDepth_++);
+    }
+    void popContinuableBlock() {
+        JS_ALWAYS_TRUE(continuableStack_.popCopy() == --blockDepth_);
+    }
+
+    bool pushLoop(uint32_t numStmts) {
+        return encoder().writeExpr(Expr::Loop) &&
+               encoder().writeVarU32(numStmts) &&
+               breakableStack_.append(blockDepth_++) &&
+               continuableStack_.append(blockDepth_++);
+    }
+    void popLoop() {
+        JS_ALWAYS_TRUE(continuableStack_.popCopy() == --blockDepth_);
+        JS_ALWAYS_TRUE(breakableStack_.popCopy() == --blockDepth_);
+    }
+
+    bool writeBreakIf() {
+        return writeBr(breakableStack_.back(), Expr::BrIf);
+    }
+    bool writeContinueIf() {
+        return writeBr(continuableStack_.back(), Expr::BrIf);
+    }
+    bool writeUnlabeledBreakOrContinue(bool isBreak) {
+        return writeBr(isBreak? breakableStack_.back() : continuableStack_.back());
+    }
+    bool writeContinue() {
+        return writeBr(continuableStack_.back());
+    }
+
+    bool addLabels(const NameVector& labels, uint32_t relativeBreakDepth,
+                   uint32_t relativeContinueDepth)
+    {
+        for (PropertyName* label : labels) {
+            if (!breakLabels_.putNew(label, blockDepth_ + relativeBreakDepth))
+                return false;
+            if (!continueLabels_.putNew(label, blockDepth_ + relativeContinueDepth))
+                return false;
+        }
+        return true;
+    }
+    void removeLabels(const NameVector& labels) {
+        for (PropertyName* label : labels) {
+            removeLabel(label, &breakLabels_);
+            removeLabel(label, &continueLabels_);
+        }
+    }
+    bool writeLabeledBreakOrContinue(PropertyName* label, bool isBreak) {
+        LabelMap& map = isBreak ? breakLabels_ : continueLabels_;
+        if (LabelMap::Ptr p = map.lookup(label))
+            return writeBr(p->value());
+        MOZ_CRASH("nonexistent label");
     }
 
     /*************************************************** Read-only interface */
 
     const Local* lookupLocal(PropertyName* name) const {
         if (auto p = locals_.lookup(name))
             return &p->value();
         return nullptr;
@@ -3388,17 +3489,17 @@ static bool
 SetLocal(FunctionValidator& f, NumLit lit)
 {
     return f.encoder().writeExpr(Expr::SetLocal) &&
            f.encoder().writeVarU32(f.numLocals()) &&
            f.writeLit(lit);
 }
 
 static bool
-CheckVariable(FunctionValidator& f, ParseNode* var, uint32_t* numStmts)
+CheckVariable(FunctionValidator& f, ParseNode* var)
 {
     if (!IsDefinition(var))
         return f.fail(var, "local variable names must not restate argument names");
 
     PropertyName* name = var->name();
 
     if (!CheckIdentifier(f.m(), var, name))
         return false;
@@ -3410,32 +3511,31 @@ CheckVariable(FunctionValidator& f, Pars
     NumLit lit;
     if (!IsLiteralOrConst(f, initNode, &lit))
         return f.failName(var, "var '%s' initializer must be literal or const literal", name);
 
     if (!lit.valid())
         return f.failName(var, "var '%s' initializer out of range", name);
 
     if (!lit.isZeroBits()) {
-        ++*numStmts;
         if (!SetLocal(f, lit))
             return false;
     }
 
     return f.addLocal(var, name, Type::canonicalize(Type::lit(lit)));
 }
 
 static bool
-CheckVariables(FunctionValidator& f, ParseNode** stmtIter, uint32_t* numStmts)
+CheckVariables(FunctionValidator& f, ParseNode** stmtIter)
 {
     ParseNode* stmt = *stmtIter;
 
     for (; stmt && stmt->isKind(PNK_VAR); stmt = NextNonEmptyStatement(stmt)) {
         for (ParseNode* var = VarListHead(stmt); var; var = NextNode(var)) {
-            if (!CheckVariable(f, var, numStmts))
+            if (!CheckVariable(f, var))
                 return false;
         }
     }
 
     *stmtIter = stmt;
     return true;
 }
 
@@ -3500,34 +3600,37 @@ IsLiteralOrConstInt(FunctionValidator& f
     NumLit lit;
     if (!IsLiteralOrConst(f, pn, &lit))
         return false;
 
     return IsLiteralInt(lit, u32);
 }
 
 static const int32_t NoMask = -1;
+static const bool YesSimd = true;
+static const bool NoSimd = false;
 
 static bool
 CheckArrayAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr,
-                 Scalar::Type* viewType, int32_t* mask)
+                 bool isSimd, Scalar::Type* viewType, int32_t* mask)
 {
     if (!viewName->isKind(PNK_NAME))
         return f.fail(viewName, "base of array access must be a typed array view name");
 
     const ModuleValidator::Global* global = f.lookupGlobal(viewName->name());
     if (!global || !global->isAnyArrayView())
         return f.fail(viewName, "base of array access must be a typed array view name");
 
     *viewType = global->viewType();
 
     uint32_t index;
     if (IsLiteralOrConstInt(f, indexExpr, &index)) {
         uint64_t byteOffset = uint64_t(index) << TypedArrayShift(*viewType);
-        if (!f.m().tryConstantAccess(byteOffset, TypedArrayElemSize(*viewType)))
+        uint64_t width = isSimd ? Simd128DataSize : TypedArrayElemSize(*viewType);
+        if (!f.m().tryConstantAccess(byteOffset, width))
             return f.fail(indexExpr, "constant index out of range");
 
         *mask = NoMask;
         return f.writeInt32Lit(byteOffset);
     }
 
     // Mask off the low bits to account for the clearing effect of a right shift
     // followed by the left shift implicit in the array access. E.g., H32[i>>2]
@@ -3549,85 +3652,85 @@ CheckArrayAccess(FunctionValidator& f, P
 
         Type pointerType;
         if (!CheckExpr(f, pointerNode, &pointerType))
             return false;