merge inbound to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 08 Mar 2016 16:00:45 -0800
changeset 287353 886b5480b5781f204b89dc9e10bc991a0fa3d3c9
parent 287352 45382cd656518b50757a032b253747b37a17762c (current diff)
parent 287245 16448c5d4d17b606c30bef742b9a3ad03b767b45 (diff)
child 287354 41eba362e5a07d31390e35b97b3c1500c7bdab35
child 287423 74546cb2ed83c1911d2fe6c00ff890a69e688101
child 287448 a67b36e32207e880df38c98b0f6b5ca0ded86ecd
push id73149
push userkwierso@gmail.com
push dateWed, 09 Mar 2016 00:08:17 +0000
treeherdermozilla-inbound@41eba362e5a0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone48.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 MozReview-Commit-ID: BHxDQF6gIe3
CLOBBER
browser/modules/WebappManager.jsm
build/autoconf/python-virtualenv.m4
dom/media/MediaManager.cpp
dom/push/test/xpcshell/PushServiceHandler.js
dom/push/test/xpcshell/PushServiceHandler.manifest
dom/push/test/xpcshell/test_handler_service.js
dom/system/gonk/Makefile.in
dom/workers/test/extensions/bootstrap/Makefile.in
dom/workers/test/extensions/traditional/Makefile.in
dom/workers/test/test_websocket.html
dom/workers/test/websocket_worker.js
js/src/jsapi-tests/testMutex.cpp
js/src/vm/Mutex.cpp
js/src/vm/Mutex.h
probes/Makefile.in
toolkit/webapps/LinuxNativeApp.js
toolkit/webapps/MacNativeApp.js
toolkit/webapps/NativeApp.jsm
toolkit/webapps/WebappOSUtils.jsm
toolkit/webapps/WinNativeApp.js
toolkit/webapps/moz.build
toolkit/webapps/tests/.eslintrc
toolkit/webapps/tests/Makefile.in
toolkit/webapps/tests/TestWebappRT.cpp
toolkit/webapps/tests/app.sjs
toolkit/webapps/tests/chrome.ini
toolkit/webapps/tests/data/512.png
toolkit/webapps/tests/data/app/hosted_manifest.webapp
toolkit/webapps/tests/data/app/index.html
toolkit/webapps/tests/data/app/manifest.webapp
toolkit/webapps/tests/data/appcached_app/index.html
toolkit/webapps/tests/data/appcached_app/manifest.appcache
toolkit/webapps/tests/data/appcached_app/manifest.appcache^headers^
toolkit/webapps/tests/data/appcached_app/manifest.webapp
toolkit/webapps/tests/data/appcached_app/manifest.webapp^headers^
toolkit/webapps/tests/data/custom_origin.webapp
toolkit/webapps/tests/data/custom_origin.webapp^headers^
toolkit/webapps/tests/data/custom_origin.zip
toolkit/webapps/tests/data/icon.png
toolkit/webapps/tests/head.js
toolkit/webapps/tests/moz.build
toolkit/webapps/tests/test_custom_origin.xul
toolkit/webapps/tests/test_custom_origin_uninstall_install.xul
toolkit/webapps/tests/test_hosted.xul
toolkit/webapps/tests/test_hosted_checkforupdates_from_webapp_runtime.xul
toolkit/webapps/tests/test_hosted_icons.xul
toolkit/webapps/tests/test_hosted_launch.xul
toolkit/webapps/tests/test_hosted_launch_no_registry.xul
toolkit/webapps/tests/test_hosted_uninstall.xul
toolkit/webapps/tests/test_hosted_update_from_webapp_runtime.xul
toolkit/webapps/tests/test_install_appcache.xul
toolkit/webapps/tests/test_non_ascii_app_name.xul
toolkit/webapps/tests/test_packaged.xul
toolkit/webapps/tests/test_packaged_checkforupdates_from_webapp_runtime.xul
toolkit/webapps/tests/test_packaged_icons.xul
toolkit/webapps/tests/test_packaged_launch.xul
toolkit/webapps/tests/test_packaged_launch_no_registry.xul
toolkit/webapps/tests/test_packaged_uninstall.xul
toolkit/webapps/tests/test_packaged_update_from_webapp_runtime.xul
toolkit/webapps/tests/test_webapp_runtime_executable_update.xul
webapprt/CommandLineHandler.js
webapprt/ContentPermission.js
webapprt/DirectoryProvider.js
webapprt/DownloadView.jsm
webapprt/PaymentUIGlue.js
webapprt/Startup.jsm
webapprt/WebRTCHandler.jsm
webapprt/WebappManager.jsm
webapprt/WebappRT.jsm
webapprt/components.manifest
webapprt/content/downloads/download.xml
webapprt/content/downloads/downloads.css
webapprt/content/downloads/downloads.js
webapprt/content/downloads/downloads.xul
webapprt/content/getUserMediaDialog.js
webapprt/content/getUserMediaDialog.xul
webapprt/content/mochitest-shared.js
webapprt/content/mochitest.js
webapprt/content/mochitest.xul
webapprt/content/webapp.js
webapprt/content/webapp.xul
webapprt/defs.mk
webapprt/gtk/Makefile.in
webapprt/gtk/moz.build
webapprt/gtk/webapprt.cpp
webapprt/jar.mn
webapprt/locales/en-US/webapp-uninstaller/webapp-uninstaller.properties
webapprt/locales/en-US/webapprt/downloads/downloads.dtd
webapprt/locales/en-US/webapprt/getUserMediaDialog.dtd
webapprt/locales/en-US/webapprt/overrides/appstrings.properties
webapprt/locales/en-US/webapprt/overrides/dom.properties
webapprt/locales/en-US/webapprt/webapp.dtd
webapprt/locales/en-US/webapprt/webapp.properties
webapprt/locales/jar.mn
webapprt/locales/l10n.ini
webapprt/locales/moz.build
webapprt/mac/Makefile.in
webapprt/mac/moz.build
webapprt/mac/webapprt.mm
webapprt/moz.build
webapprt/prefs.js
webapprt/test/chrome/TestApp.app/Contents/Info.plist
webapprt/test/chrome/TestApp.app/Contents/MacOS/webapp.ini
webapprt/test/chrome/alarm.html
webapprt/test/chrome/alarm.webapp
webapprt/test/chrome/alarm.webapp^headers^
webapprt/test/chrome/browser_alarm.js
webapprt/test/chrome/browser_download.js
webapprt/test/chrome/browser_geolocation-prompt-noperm.js
webapprt/test/chrome/browser_geolocation-prompt-perm.js
webapprt/test/chrome/browser_getUserMedia.js
webapprt/test/chrome/browser_install-app.js
webapprt/test/chrome/browser_mozpay.js
webapprt/test/chrome/browser_noperm.js
webapprt/test/chrome/browser_sample.js
webapprt/test/chrome/browser_webperm.js
webapprt/test/chrome/browser_window-open-blank.js
webapprt/test/chrome/browser_window-open-self.js
webapprt/test/chrome/browser_window-open.js
webapprt/test/chrome/browser_window-title.js
webapprt/test/chrome/debugger.html
webapprt/test/chrome/debugger.webapp
webapprt/test/chrome/debugger.webapp^headers^
webapprt/test/chrome/download.html
webapprt/test/chrome/download.test
webapprt/test/chrome/download.webapp
webapprt/test/chrome/download.webapp^headers^
webapprt/test/chrome/downloads/browser_add_download.js
webapprt/test/chrome/downloads/browser_remove_download.js
webapprt/test/chrome/downloads/webapprt.ini
webapprt/test/chrome/geolocation-prompt-noperm.html
webapprt/test/chrome/geolocation-prompt-noperm.webapp
webapprt/test/chrome/geolocation-prompt-noperm.webapp^headers^
webapprt/test/chrome/geolocation-prompt-perm.html
webapprt/test/chrome/geolocation-prompt-perm.webapp
webapprt/test/chrome/geolocation-prompt-perm.webapp^headers^
webapprt/test/chrome/getUserMedia.html
webapprt/test/chrome/getUserMedia.webapp
webapprt/test/chrome/getUserMedia.webapp^headers^
webapprt/test/chrome/head.js
webapprt/test/chrome/install-app.html
webapprt/test/chrome/install-app.webapp
webapprt/test/chrome/install-app.webapp^headers^
webapprt/test/chrome/mozpay-failure.html
webapprt/test/chrome/mozpay-success.html
webapprt/test/chrome/mozpay.html
webapprt/test/chrome/mozpay.webapp
webapprt/test/chrome/mozpay.webapp^headers^
webapprt/test/chrome/noperm.html
webapprt/test/chrome/noperm.webapp
webapprt/test/chrome/noperm.webapp^headers^
webapprt/test/chrome/sample.html
webapprt/test/chrome/sample.webapp
webapprt/test/chrome/sample.webapp^headers^
webapprt/test/chrome/webapprt.ini
webapprt/test/chrome/webperm.html
webapprt/test/chrome/webperm.webapp
webapprt/test/chrome/webperm.webapp^headers^
webapprt/test/chrome/window-open-blank.html
webapprt/test/chrome/window-open-blank.webapp
webapprt/test/chrome/window-open-blank.webapp^headers^
webapprt/test/chrome/window-open-self.html
webapprt/test/chrome/window-open-self.webapp
webapprt/test/chrome/window-open-self.webapp^headers^
webapprt/test/chrome/window-open.html
webapprt/test/chrome/window-open.webapp
webapprt/test/chrome/window-open.webapp^headers^
webapprt/test/chrome/window-title.html
webapprt/test/chrome/window-title.webapp
webapprt/test/chrome/window-title.webapp^headers^
webapprt/test/content/test.webapp
webapprt/test/content/test.webapp^headers^
webapprt/test/content/webapprt.ini
webapprt/test/content/webapprt_indexeddb.html
webapprt/test/content/webapprt_sample.html
webapprt/themes/LICENSE
webapprt/themes/linux/downloads/downloadIcon.png
webapprt/themes/linux/downloads/downloads.css
webapprt/themes/linux/jar.mn
webapprt/themes/linux/moz.build
webapprt/themes/moz.build
webapprt/themes/osx/downloads/buttons.png
webapprt/themes/osx/downloads/downloadIcon.png
webapprt/themes/osx/downloads/downloads.css
webapprt/themes/osx/jar.mn
webapprt/themes/osx/moz.build
webapprt/themes/windows/downloads/downloadButtons-XP.png
webapprt/themes/windows/downloads/downloadButtons.png
webapprt/themes/windows/downloads/downloadIcon-XP.png
webapprt/themes/windows/downloads/downloadIcon.png
webapprt/themes/windows/downloads/downloads.css
webapprt/themes/windows/jar.mn
webapprt/themes/windows/moz.build
webapprt/webapprt.ini
webapprt/win/Makefile.in
webapprt/win/moz.build
webapprt/win/webapp-uninstaller.nsi.in
webapprt/win/webapprt.cpp
webapprt/win/webapprt.exe.manifest
webapprt/win/webapprt.rc
--- a/.eslintignore
+++ b/.eslintignore
@@ -39,17 +39,16 @@ parser/**
 probes/**
 python/**
 rdf/**
 startupcache/**
 testing/**
 tools/**
 uriloader/**
 view/**
-webapprt/**
 widget/**
 xpcom/**
 xpfe/**
 xulrunner/**
 
 # b2g exclusions (pref files).
 b2g/app/b2g.js
 b2g/graphene/graphene.js
--- a/.lldbinit
+++ b/.lldbinit
@@ -4,17 +4,17 @@
 # For documentation on all of the commands and type summaries defined here
 # and in the accompanying Python scripts, see python/lldbutils/README.txt.
 # -----------------------------------------------------------------------------
 
 # Import the module that defines complex Gecko debugging commands.  This assumes
 # you are either running lldb from the top level source directory, the objdir,
 # or the dist/bin directory.  (.lldbinit files in the objdir and dist/bin set
 # topsrcdir appropriately.)
-script topsrcdir = topsrcdir if locals().has_key("topsrcdir") else "."; sys.path.append(os.path.join(topsrcdir, "python/lldbutils")); import lldbutils; lldbutils.init()
+script topsrcdir = topsrcdir if locals().has_key("topsrcdir") else os.getcwd(); sys.path.append(os.path.join(topsrcdir, "python/lldbutils")); import lldbutils; lldbutils.init()
 
 # Mozilla's use of UNIFIED_SOURCES to include multiple source files into a
 # single compiled file breaks lldb breakpoint setting. This works around that.
 # See http://lldb.llvm.org/troubleshooting.html for more info.
 settings set target.inline-breakpoint-strategy always
 
 # Show the dynamic type of an object when using "expr".  This, for example,
 # will show a variable declared as "nsIFrame *" that points to an nsBlockFrame
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,10 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-
-Merge day clobber
\ No newline at end of file
+Bug 1250297 - Doesn't actually need a clobber, but updating the tree to an older changeset does.
--- a/accessible/base/TreeWalker.cpp
+++ b/accessible/base/TreeWalker.cpp
@@ -19,62 +19,128 @@ using namespace mozilla::a11y;
 ////////////////////////////////////////////////////////////////////////////////
 // TreeWalker
 ////////////////////////////////////////////////////////////////////////////////
 
 TreeWalker::
   TreeWalker(Accessible* aContext) :
   mDoc(aContext->Document()), mContext(aContext), mAnchorNode(nullptr),
   mARIAOwnsIdx(0),
-  mChildFilter(nsIContent::eSkipPlaceholderContent), mFlags(0)
+  mChildFilter(nsIContent::eSkipPlaceholderContent), mFlags(0),
+  mPhase(eAtStart)
 {
   mChildFilter |= mContext->NoXBLKids() ?
     nsIContent::eAllButXBL : nsIContent::eAllChildren;
 
   mAnchorNode = mContext->IsDoc() ?
     mDoc->DocumentNode()->GetRootElement() : mContext->GetContent();
 
-  if (mAnchorNode) {
-    PushState(mAnchorNode);
-  }
-
   MOZ_COUNT_CTOR(TreeWalker);
 }
 
 TreeWalker::
   TreeWalker(Accessible* aContext, nsIContent* aAnchorNode, uint32_t aFlags) :
   mDoc(aContext->Document()), mContext(aContext), mAnchorNode(aAnchorNode),
   mARIAOwnsIdx(0),
-  mChildFilter(nsIContent::eSkipPlaceholderContent), mFlags(aFlags)
+  mChildFilter(nsIContent::eSkipPlaceholderContent), mFlags(aFlags),
+  mPhase(eAtStart)
 {
   MOZ_ASSERT(aAnchorNode, "No anchor node for the accessible tree walker");
   MOZ_ASSERT(mDoc->GetAccessibleOrContainer(aAnchorNode) == mContext,
              "Unexpected anchor node was given");
 
   mChildFilter |= mContext->NoXBLKids() ?
     nsIContent::eAllButXBL : nsIContent::eAllChildren;
 
-  PushState(aAnchorNode);
-
   MOZ_COUNT_CTOR(TreeWalker);
 }
 
 TreeWalker::~TreeWalker()
 {
   MOZ_COUNT_DTOR(TreeWalker);
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// TreeWalker: private
+bool
+TreeWalker::Seek(nsIContent* aChildNode)
+{
+  MOZ_ASSERT(aChildNode, "Child cannot be null");
+
+  mPhase = eAtStart;
+  mStateStack.Clear();
+  mARIAOwnsIdx = 0;
+
+  nsIContent* childNode = nullptr;
+  nsINode* parentNode = aChildNode;
+  do {
+    childNode = parentNode->AsContent();
+    parentNode = childNode->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) &&
+      (mChildFilter & nsIContent::eAllButXBL) ?
+      childNode->GetParentNode() : childNode->GetFlattenedTreeParent();
+
+    if (!parentNode || !parentNode->IsElement()) {
+      return false;
+    }
+
+    // If ARIA owned child.
+    Accessible* child = mDoc->GetAccessible(childNode);
+    if (child && child->IsRelocated()) {
+      if (child->Parent() != mContext) {
+        return false;
+      }
+
+      Accessible* ownedChild = nullptr;
+      while ((ownedChild = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx++)) &&
+             ownedChild != child);
+
+      MOZ_ASSERT(ownedChild, "A child has to be in ARIA owned elements");
+      mPhase = eAtARIAOwns;
+      return true;
+    }
+
+    // Look in DOM.
+    dom::AllChildrenIterator* iter = PrependState(parentNode->AsElement(), true);
+    if (!iter->Seek(childNode)) {
+      return false;
+    }
+
+    if (parentNode == mAnchorNode) {
+      mPhase = eAtDOM;
+      return true;
+    }
+  } while (true);
+
+  return false;
+}
 
 Accessible*
 TreeWalker::Next()
 {
   if (mStateStack.IsEmpty()) {
-    return mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx++);
+    if (mPhase == eAtEnd) {
+      return nullptr;
+    }
+
+    if (mPhase == eAtDOM || mPhase == eAtARIAOwns) {
+      mPhase = eAtARIAOwns;
+      Accessible* child = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx);
+      if (child) {
+        mARIAOwnsIdx++;
+        return child;
+      }
+      mPhase = eAtEnd;
+      return nullptr;
+    }
+
+    if (!mAnchorNode) {
+      mPhase = eAtEnd;
+      return nullptr;
+    }
+
+    mPhase = eAtDOM;
+    PushState(mAnchorNode, true);
   }
 
   dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
   while (top) {
     while (nsIContent* childNode = top->GetNextChild()) {
       bool skipSubtree = false;
       Accessible* child = nullptr;
       if (mFlags & eWalkCache) {
@@ -91,17 +157,17 @@ TreeWalker::Next()
         if (child->IsRelocated()) {
           continue;
         }
         return child;
       }
 
       // Walk down into subtree to find accessibles.
       if (!skipSubtree && childNode->IsElement()) {
-        top = PushState(childNode);
+        top = PushState(childNode, true);
       }
     }
     top = PopState();
   }
 
   // If we traversed the whole subtree of the anchor node. Move to next node
   // relative anchor node within the context subtree if possible.
   if (mFlags != eWalkContextTree)
@@ -109,31 +175,113 @@ TreeWalker::Next()
 
   nsINode* contextNode = mContext->GetNode();
   while (mAnchorNode != contextNode) {
     nsINode* parentNode = mAnchorNode->GetFlattenedTreeParent();
     if (!parentNode || !parentNode->IsElement())
       return nullptr;
 
     nsIContent* parent = parentNode->AsElement();
-    top = PushState(parent);
+    top = PushState(parent, true);
     if (top->Seek(mAnchorNode)) {
       mAnchorNode = parent;
       return Next();
     }
 
     // XXX We really should never get here, it means we're trying to find an
     // accessible for a dom node where iterating over its parent's children
     // doesn't return it. However this sometimes happens when we're asked for
     // the nearest accessible to place holder content which we ignore.
     mAnchorNode = parent;
   }
 
   return Next();
 }
 
+Accessible*
+TreeWalker::Prev()
+{
+  if (mStateStack.IsEmpty()) {
+    if (mPhase == eAtStart || mPhase == eAtDOM) {
+      mPhase = eAtStart;
+      return nullptr;
+    }
+
+    if (mPhase == eAtEnd) {
+      mARIAOwnsIdx = mDoc->ARIAOwnedCount(mContext);
+      mPhase = eAtARIAOwns;
+    }
+
+    if (mPhase == eAtARIAOwns) {
+      if (mARIAOwnsIdx > 0) {
+        return mDoc->ARIAOwnedAt(mContext, --mARIAOwnsIdx);
+      }
+
+      if (!mAnchorNode) {
+        mPhase = eAtStart;
+        return nullptr;
+      }
+
+      mPhase = eAtDOM;
+      PushState(mAnchorNode, false);
+    }
+  }
+
+  dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
+  while (top) {
+    while (nsIContent* childNode = top->GetPreviousChild()) {
+      bool skipSubtree = false;
+
+      // No accessible creation on the way back.
+      Accessible* child = mDoc->GetAccessible(childNode);
+
+      // Ignore the accessible and its subtree if it was repositioned by means of
+      // aria-owns.
+      if (child) {
+        if (child->IsRelocated()) {
+          continue;
+        }
+        return child;
+      }
+
+      // Walk down into subtree to find accessibles.
+      if (!skipSubtree && childNode->IsElement()) {
+        top = PushState(childNode, true);
+      }
+    }
+    top = PopState();
+  }
+
+  // Move to a previous node relative the anchor node within the context
+  // subtree if asked.
+  if (mFlags != eWalkContextTree) {
+    mPhase = eAtStart;
+    return nullptr;
+  }
+
+  nsINode* contextNode = mContext->GetNode();
+  while (mAnchorNode != contextNode) {
+    nsINode* parentNode = mAnchorNode->GetFlattenedTreeParent();
+    if (!parentNode || !parentNode->IsElement()) {
+      return nullptr;
+    }
+
+    nsIContent* parent = parentNode->AsElement();
+    top = PushState(parent, true);
+    if (top->Seek(mAnchorNode)) {
+      mAnchorNode = parent;
+      return Prev();
+    }
+
+    mAnchorNode = parent;
+  }
+
+  mPhase = eAtStart;
+  return nullptr;
+}
+
 dom::AllChildrenIterator*
 TreeWalker::PopState()
 {
   size_t length = mStateStack.Length();
   mStateStack.RemoveElementAt(length - 1);
   return mStateStack.IsEmpty() ? nullptr : &mStateStack[mStateStack.Length() - 1];
 }
--- a/accessible/base/TreeWalker.h
+++ b/accessible/base/TreeWalker.h
@@ -45,53 +45,79 @@ public:
    * @param aAnchorNode [in] the node the search will be prepared relative to
    * @param aFlags   [in] flags (see enum above)
    */
   TreeWalker(Accessible* aContext, nsIContent* aAnchorNode, uint32_t aFlags = 0);
 
   ~TreeWalker();
 
   /**
-   * Return the next accessible.
+   * Clears the tree walker state and resets it to the given child within
+   * the anchor.
+   */
+  bool Seek(nsIContent* aChildNode);
+
+  /**
+   * Return the next/prev accessible.
    *
    * @note Returned accessible is bound to the document, if the accessible is
    *       rejected during tree creation then the caller should be unbind it
    *       from the document.
    */
   Accessible* Next();
+  Accessible* Prev();
+
+  Accessible* Context() const { return mContext; }
+  DocAccessible* Document() const { return mDoc; }
 
 private:
   TreeWalker();
   TreeWalker(const TreeWalker&);
   TreeWalker& operator =(const TreeWalker&);
 
   /**
-   * Create new state for the given node and push it on top of stack.
+   * Create new state for the given node and push it on top of stack / at bottom
+   * of stack.
    *
    * @note State stack is used to navigate up/down the DOM subtree during
    *        accessible children search.
    */
-  dom::AllChildrenIterator* PushState(nsIContent* aContent)
+  dom::AllChildrenIterator* PushState(nsIContent* aContent,
+                                      bool aStartAtBeginning)
   {
     return mStateStack.AppendElement(
-      dom::AllChildrenIterator(aContent, mChildFilter));
+      dom::AllChildrenIterator(aContent, mChildFilter, aStartAtBeginning));
+  }
+  dom::AllChildrenIterator* PrependState(nsIContent* aContent,
+                                         bool aStartAtBeginning)
+  {
+    return mStateStack.InsertElementAt(0,
+      dom::AllChildrenIterator(aContent, mChildFilter, aStartAtBeginning));
   }
 
   /**
    * Pop state from stack.
    */
   dom::AllChildrenIterator* PopState();
 
   DocAccessible* mDoc;
   Accessible* mContext;
   nsIContent* mAnchorNode;
 
   AutoTArray<dom::AllChildrenIterator, 20> mStateStack;
   uint32_t mARIAOwnsIdx;
 
   int32_t mChildFilter;
   uint32_t mFlags;
+
+  enum Phase {
+    eAtStart,
+    eAtDOM,
+    eAtARIAOwns,
+    eAtEnd
+  };
+  Phase mPhase;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_TreeWalker_h_
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -286,16 +286,21 @@ public:
   Accessible* ARIAOwnedAt(Accessible* aParent, uint32_t aIndex) const
   {
     nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(aParent);
     if (children) {
       return children->SafeElementAt(aIndex);
     }
     return nullptr;
   }
+  uint32_t ARIAOwnedCount(Accessible* aParent) const
+  {
+    nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(aParent);
+    return children ? children->Length() : 0;
+  }
 
   /**
    * Return true if the given ID is referred by relation attribute.
    *
    * @note Different elements may share the same ID if they are hosted inside
    *       XBL bindings. Be careful the result of this method may be  senseless
    *       while it's called for XUL elements (where XBL is used widely).
    */
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -21,17 +21,16 @@ builtin(include, build/autoconf/mozcommo
 builtin(include, build/autoconf/lto.m4)dnl
 builtin(include, build/autoconf/frameptr.m4)dnl
 builtin(include, build/autoconf/compiler-opts.m4)dnl
 builtin(include, build/autoconf/expandlibs.m4)dnl
 builtin(include, build/autoconf/arch.m4)dnl
 builtin(include, build/autoconf/android.m4)dnl
 builtin(include, build/autoconf/zlib.m4)dnl
 builtin(include, build/autoconf/linux.m4)dnl
-builtin(include, build/autoconf/python-virtualenv.m4)dnl
 builtin(include, build/autoconf/winsdk.m4)dnl
 builtin(include, build/autoconf/icu.m4)dnl
 builtin(include, build/autoconf/ffi.m4)dnl
 builtin(include, build/autoconf/clang-plugin.m4)dnl
 builtin(include, build/autoconf/alloc.m4)dnl
 builtin(include, build/autoconf/ios.m4)dnl
 builtin(include, build/autoconf/jemalloc.m4)dnl
 builtin(include, build/autoconf/rust.m4)dnl
--- a/b2g/dev/app.mozbuild
+++ b/b2g/dev/app.mozbuild
@@ -5,19 +5,16 @@
 
 include('/toolkit/toolkit.mozbuild')
 
 if CONFIG['MOZ_EXTENSIONS']:
     DIRS += ['/extensions']
 
 DIRS += ['/%s' % CONFIG['MOZ_BRANDING_DIRECTORY']]
 
-if CONFIG['MOZ_WEBAPP_RUNTIME']:
-    DIRS += ['/webapprt']
-
 DIRS += [
     '/b2g/chrome',
     '/b2g/components',
     '/b2g/dev/app',
 
     # Never add dirs after browser because they apparently won't get
     # packaged properly on Mac.
     '/browser',
--- a/b2g/dev/config/tooltool-manifests/linux64/releng.manifest
+++ b/b2g/dev/config/tooltool-manifests/linux64/releng.manifest
@@ -11,18 +11,18 @@
 "size": 12072532,
 "digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
 "algorithm": "sha512",
 "filename": "gtk3.tar.xz",
 "setup": "setup.sh",
 "unpack": true
 },
 {
-"size": 80164520,
-"digest": "26fd5301aaf6174a0e4f2ac3a8d19f39573f78a051aa78e876c065d60421b2b62207c11fbf1f20f92ba61acc4b9ce58d05409bf5af886943891b04c3d22f5e04",
+"size": 89319524,
+"digest": "5383d843c9f28abf0a6d254e9d975d96972d2c86d627ca836fa8e272a5d53230603b387d7d1499c49df7f84b1bb946946e800a85c88d968bdbe81c755fcb02e1",
 "algorithm": "sha512",
 "filename": "rustc.tar.xz",
 "unpack": true
 },
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
--- a/browser/app.mozbuild
+++ b/browser/app.mozbuild
@@ -5,14 +5,11 @@
 
 include('/toolkit/toolkit.mozbuild')
 
 if CONFIG['MOZ_EXTENSIONS']:
     DIRS += ['/extensions']
 
 DIRS += ['/%s' % CONFIG['MOZ_BRANDING_DIRECTORY']]
 
-if CONFIG['MOZ_WEBAPP_RUNTIME']:
-    DIRS += ['/webapprt']
-
 # Never add dirs after browser because they apparently won't get
 # packaged properly on Mac.
 DIRS += ['/browser']
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -673,18 +673,16 @@
                 <image id="addons-notification-icon" class="notification-anchor-icon" role="button"
                        aria-label="&urlbar.addonsNotificationAnchor.label;"/>
                 <image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"
                        aria-label="&urlbar.indexedDBNotificationAnchor.label;"/>
                 <image id="login-fill-notification-icon" class="notification-anchor-icon" role="button"
                        aria-label="&urlbar.loginFillNotificationAnchor.label;"/>
                 <image id="password-notification-icon" class="notification-anchor-icon" role="button"
                        aria-label="&urlbar.passwordNotificationAnchor.label;"/>
-                <image id="webapps-notification-icon" class="notification-anchor-icon" role="button"
-                       aria-label="&urlbar.webappsNotificationAnchor.label;"/>
                 <image id="plugins-notification-icon" class="notification-anchor-icon" role="button"
                        aria-label="&urlbar.pluginsNotificationAnchor.label;"/>
                 <image id="web-notifications-notification-icon" class="notification-anchor-icon" role="button"
                        aria-label="&urlbar.webNotsNotificationAnchor3.label;"/>
                 <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"
                        aria-label="&urlbar.webRTCShareDevicesNotificationAnchor.label;"/>
                 <image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"
                        aria-label="&urlbar.webRTCSharingDevicesNotificationAnchor.label;"/>
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -34,20 +34,16 @@
                accesskey="&getUserMedia.selectMicrophone.accesskey;"
                control="webRTC-selectMicrophone-menulist"/>
         <menulist id="webRTC-selectMicrophone-menulist">
           <menupopup id="webRTC-selectMicrophone-menupopup"/>
         </menulist>
       </popupnotificationcontent>
     </popupnotification>
 
-    <popupnotification id="webapps-install-progress-notification" hidden="true">
-      <popupnotificationcontent id="webapps-install-progress-content" orient="vertical" align="start"/>
-    </popupnotification>
-
     <popupnotification id="servicesInstall-notification" hidden="true">
       <popupnotificationcontent orient="vertical" align="start">
         <!-- XXX bug 974146, tests are looking for this, can't remove yet. -->
       </popupnotificationcontent>
     </popupnotification>
 
     <popupnotification id="pointerLock-notification" hidden="true">
       <popupnotificationcontent orient="vertical" align="start">
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -273,26 +273,49 @@ Sanitizer.prototype = {
           let mediaMgr = Components.classes["@mozilla.org/mediaManagerService;1"]
                                    .getService(Ci.nsIMediaManagerService);
           mediaMgr.sanitizeDeviceIds(range && range[0]);
         } catch (ex) {
           seenException = ex;
         }
 
         // Clear plugin data.
+        // As evidenced in bug 1253204, clearing plugin data can sometimes be
+        // very, very long, for mysterious reasons. Unfortunately, this is not
+        // something actionable by Mozilla, so crashing here serves no purpose.
+        //
+        // For this reason, instead of waiting for sanitization to always
+        // complete, we introduce a soft timeout. Once this timeout has
+        // elapsed, we proceed with the shutdown of Firefox.
+        let promiseClearPluginCookies;
         TelemetryStopwatch.start("FX_SANITIZE_PLUGINS", refObj);
         try {
-          yield this.promiseClearPluginCookies(range);
+          // We don't want to wait for this operation to complete...
+          promiseClearPluginCookies = this.promiseClearPluginCookies(range);
+
+          //... at least, not for more than 10 seconds.
+          yield Promise.race([
+            promiseClearPluginCookies,
+            new Promise(resolve => setTimeout(resolve, 10000 /* 10 seconds */))
+          ]);
         } catch (ex) {
           seenException = ex;
-        } finally {
-          TelemetryStopwatch.finish("FX_SANITIZE_PLUGINS", refObj);
         }
 
-        TelemetryStopwatch.finish("FX_SANITIZE_COOKIES", refObj);
+        // Detach waiting for plugin cookies to be cleared.
+        promiseClearPluginCookies.catch(() => {
+          // If this exception is raised before the soft timeout, it
+          // will appear in `seenException`. Otherwise, it's too late
+          // to do anything about it.
+        }).then(() => {
+          // Finally, update statistics.
+          TelemetryStopwatch.finish("FX_SANITIZE_PLUGINS", refObj);
+          TelemetryStopwatch.finish("FX_SANITIZE_COOKIES", refObj);
+        });
+
         if (seenException) {
           throw seenException;
         }
       }),
 
       promiseClearPluginCookies: Task.async(function* (range) {
         const FLAG_CLEAR_ALL = Ci.nsIPluginHost.FLAG_CLEAR_ALL;
         let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
--- a/browser/components/contextualidentity/test/browser/browser.ini
+++ b/browser/components/contextualidentity/test/browser/browser.ini
@@ -1,6 +1,8 @@
 [DEFAULT]
 skip-if = buildapp == "mulet"
 support-files =
+  empty_file.html
   file_reflect_cookie_into_title.html
 
 [browser_usercontext.js]
+[browser_windowName.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_windowName.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+const USER_CONTEXTS = [
+  "default",
+  "personal",
+  "work",
+];
+
+const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+  + "contextualidentity/test/browser/empty_file.html";
+
+add_task(function* setup() {
+  // make sure userContext is enabled.
+  SpecialPowers.pushPrefEnv({"set": [
+    ["privacy.userContext.enabled", true],
+    ["browser.link.open_newwindow", 3],
+  ]});
+});
+
+add_task(function* cleanup() {
+  // make sure we don't leave any prefs set for the next tests
+  registerCleanupFunction(function() {
+    SpecialPowers.popPrefEnv();
+  });
+});
+
+add_task(function* test() {
+  info("Creating first tab...");
+  let tab1 = gBrowser.addTab(BASE_URI + '?old', {userContextId: 1});
+  let browser1 = gBrowser.getBrowserForTab(tab1);
+  yield BrowserTestUtils.browserLoaded(browser1);
+  yield ContentTask.spawn(browser1, null, function(opts) {
+    content.window.name = 'tab-1';
+  });
+
+  info("Creating second tab...");
+  let tab2 = gBrowser.addTab(BASE_URI + '?old', {userContextId: 2});
+  let browser2 = gBrowser.getBrowserForTab(tab2);
+  yield BrowserTestUtils.browserLoaded(browser2);
+  yield ContentTask.spawn(browser2, null, function(opts) {
+    content.window.name = 'tab-2';
+  });
+
+  // Let's try to open a window from tab1 with a name 'tab-2'.
+  info("Opening a window from the first tab...");
+  yield ContentTask.spawn(browser1, { url: BASE_URI + '?new' }, function(opts) {
+    yield (new content.window.wrappedJSObject.Promise(resolve => {
+      let w = content.window.wrappedJSObject.open(opts.url, 'tab-2');
+      w.onload = function() { resolve(); }
+    }));
+  });
+
+  is(browser1.contentDocument.title, '?old', "Tab1 title must be 'old'");
+  is(browser1.contentDocument.nodePrincipal.userContextId, 1, "Tab1 UCI must be 1");
+
+  is(browser2.contentDocument.title, '?old', "Tab2 title must be 'old'");
+  is(browser2.contentDocument.nodePrincipal.userContextId, 2, "Tab2 UCI must be 2");
+
+  let found = false;
+  for (let i = 0; i < gBrowser.tabContainer.childNodes.length; ++i) {
+    let tab = gBrowser.tabContainer.childNodes[i];
+    let browser = gBrowser.getBrowserForTab(tab);
+    if (browser.contentDocument.title == '?new') {
+      is(browser.contentDocument.nodePrincipal.userContextId, 1, "Tab3 UCI must be 1");
+      isnot(browser, browser1, "Tab3 is not browser 1");
+      isnot(browser, browser2, "Tab3 is not browser 2");
+      gBrowser.removeTab(tab);
+      found = true;
+      break;
+    }
+  }
+
+  ok(found, "We have tab3");
+
+  gBrowser.removeTab(tab1);
+  gBrowser.removeTab(tab2);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/empty_file.html
@@ -0,0 +1,5 @@
+<html><body>
+<script>
+document.title = window.location.search;
+</script>
+</body></html>
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -47,19 +47,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/PlacesUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
                                   "resource://gre/modules/BookmarkHTMLUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
                                   "resource://gre/modules/BookmarkJSONUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "WebappManager",
-                                  "resource:///modules/WebappManager.jsm");
-
 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
                                   "resource://gre/modules/PageThumbs.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PdfJs",
                                   "resource://pdf.js/PdfJs.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ProcessHangMonitor",
                                   "resource:///modules/ProcessHangMonitor.jsm");
@@ -737,17 +734,16 @@ BrowserGlue.prototype = {
 
     // apply distribution customizations
     // prefs are applied in _onAppDefaults()
     this._distributionCustomizer.applyCustomizations();
 
     // handle any UI migration
     this._migrateUI();
 
-    WebappManager.init();
     PageThumbs.init();
     webrtcUI.init();
     AboutHome.init();
 
     DirectoryLinksProvider.init();
     NewTabUtils.init();
     NewTabUtils.links.addProvider(DirectoryLinksProvider);
     AboutNewTab.init();
@@ -1052,19 +1048,16 @@ BrowserGlue.prototype = {
       let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
                          .getService(Ci.nsIAppStartup);
       appStartup.trackStartupCrashEnd();
     } catch (e) {
       Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
     }
 
     SelfSupportBackend.uninit();
-
-    WebappManager.uninit();
-
     NewTabPrefsProvider.prefs.uninit();
     AboutNewTab.uninit();
     webrtcUI.uninit();
     FormValidationHandler.uninit();
     if (AppConstants.NIGHTLY_BUILD) {
       AddonWatcher.uninit();
     }
   },
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/win32/debug-static-analysis
@@ -0,0 +1,22 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_PACKAGE_TESTS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --enable-debug
+ac_add_options --enable-dmd
+
+ac_add_options --enable-clang-plugin
+
+. $topsrcdir/build/win32/mozconfig.vs2013-win64
+
+# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
+ac_add_options --enable-warnings-as-errors
+
+. "$topsrcdir/build/mozconfig.rust"
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
+. "$topsrcdir/build/mozconfig.clang-cl"
--- a/browser/config/tooltool-manifests/linux64/releng.manifest
+++ b/browser/config/tooltool-manifests/linux64/releng.manifest
@@ -10,18 +10,18 @@
 "size": 12072532,
 "digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
 "algorithm": "sha512",
 "filename": "gtk3.tar.xz",
 "setup": "setup.sh",
 "unpack": true
 },
 {
-"size": 80164520,
-"digest": "26fd5301aaf6174a0e4f2ac3a8d19f39573f78a051aa78e876c065d60421b2b62207c11fbf1f20f92ba61acc4b9ce58d05409bf5af886943891b04c3d22f5e04",
+"size": 89319524,
+"digest": "5383d843c9f28abf0a6d254e9d975d96972d2c86d627ca836fa8e272a5d53230603b387d7d1499c49df7f84b1bb946946e800a85c88d968bdbe81c755fcb02e1",
 "algorithm": "sha512",
 "filename": "rustc.tar.xz",
 "unpack": true
 },
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
--- a/browser/config/tooltool-manifests/win32/releng.manifest
+++ b/browser/config/tooltool-manifests/win32/releng.manifest
@@ -1,20 +1,20 @@
 [
 {
 "size": 266240,
 "digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
 "algorithm": "sha512",
 "filename": "mozmake.exe"
 },
 {
-"size": 80513610,
-"digest": "a388df6ce743be521ba688132d06ba86d225673b53f71f9c7c0d3189adf16f553088d8d359f583f958e886583de9583df53873c85c34abf33b2d55ee7d807206",
+"size": 78886322,
+"digest": "9c2c40637de27a0852aa1166f2a08159908b23f7a55855c933087c541461bbb2a1ec9e0522df0d2b9da2b2c343b673dbb5a2fa8d30216fe8acee1eb1383336ea",
 "algorithm": "sha512",
-"filename": "rustc-nightly-i686-pc-windows-msvc.tar.bz2",
+"filename": "rustc-beta-i686-pc-windows-msvc.tar.bz2",
 "unpack": true
 },
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
--- a/browser/config/tooltool-manifests/win64/releng.manifest
+++ b/browser/config/tooltool-manifests/win64/releng.manifest
@@ -1,18 +1,18 @@
 [
 {
 "size": 266240,
 "digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
 "algorithm": "sha512",
 "filename": "mozmake.exe"
 },
 {
-"size": 72442063,
-"digest": "899da5af9b322ba63ec04de06f92b5bb82a2700f9fe03001e75fdc6f678a435cd66a474190fd93863327456270aef5649d3788aa50d852121059ced99a6004db",
+"size": 80157273,
+"digest": "c4704dcc6774b9f3baaa9313192d26e36bfba2d4380d0518ee7cb89153d9adfe63f228f0ac29848f02948eb1ab7e6624ba71210f0121196d2b54ecebd640d1e6",
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "rustc.tar.bz2",
 "unpack": true
 },
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -48,17 +48,16 @@ MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3
 # This should usually be the same as the value MAR_CHANNEL_ID.
 # If more than one ID is needed, then you should use a comma separated list
 # of values.
 ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-central
 # The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t "
 MAR_CHANNEL_ID=firefox-mozilla-central
 MOZ_PROFILE_MIGRATOR=1
 MOZ_APP_STATIC_INI=1
-MOZ_WEBAPP_RUNTIME=1
 MOZ_MEDIA_NAVIGATOR=1
 MOZ_WEBGL_CONFORMANT=1
 # Enable navigator.mozPay
 MOZ_PAY=1
 # Enable activities. These are used for FxOS developers currently.
 MOZ_ACTIVITIES=1
 MOZ_JSDOWNLOADS=1
 MOZ_WEBM_ENCODER=1
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -831,41 +831,16 @@ bin/libfreebl_32int64_3.so
 #endif
 #endif
 @RESPATH@/browser/crashreporter-override.ini
 #ifdef MOZ_CRASHREPORTER_INJECTOR
 @BINPATH@/breakpadinjector.dll
 #endif
 #endif
 
-#ifdef MOZ_WEBAPP_RUNTIME
-[WebappRuntime]
-#ifdef XP_WIN
-@BINPATH@/webapp-uninstaller@BIN_SUFFIX@
-#endif
-@RESPATH@/webapprt-stub@BIN_SUFFIX@
-@RESPATH@/webapprt/webapprt.ini
-@RESPATH@/webapprt/chrome.manifest
-@RESPATH@/webapprt/chrome/webapprt@JAREXT@
-@RESPATH@/webapprt/chrome/webapprt.manifest
-@RESPATH@/webapprt/chrome/@AB_CD@@JAREXT@
-@RESPATH@/webapprt/chrome/@AB_CD@.manifest
-@RESPATH@/webapprt/components/CommandLineHandler.js
-@RESPATH@/webapprt/components/ContentPermission.js
-@RESPATH@/webapprt/components/DirectoryProvider.js
-@RESPATH@/webapprt/components/PaymentUIGlue.js
-@RESPATH@/webapprt/components/components.manifest
-@RESPATH@/webapprt/defaults/preferences/prefs.js
-@RESPATH@/webapprt/modules/DownloadView.jsm
-@RESPATH@/webapprt/modules/Startup.jsm
-@RESPATH@/webapprt/modules/WebappRT.jsm
-@RESPATH@/webapprt/modules/WebappManager.jsm
-@RESPATH@/webapprt/modules/WebRTCHandler.jsm
-#endif
-
 @RESPATH@/components/DataStore.manifest
 @RESPATH@/components/DataStoreImpl.js
 @RESPATH@/components/dom_datastore.xpt
 
 @RESPATH@/components/dom_audiochannel.xpt
 
 ; Shutdown Terminator
 @RESPATH@/components/nsTerminatorTelemetry.js
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -95,19 +95,16 @@ searchplugins:: $(list-txt)
 	$(NSINSTALL) -D $@
 
 DEFINES += -DBOOKMARKS_INCLUDE_DIR=$(dir $(call MERGE_FILE,profile/bookmarks.inc))
 
 libs-%:
 	$(NSINSTALL) -D $(DIST)/install
 	@$(MAKE) -C ../../toolkit/locales libs-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -C ../../services/sync/locales AB_CD=$* XPI_NAME=locale-$*
-ifdef MOZ_WEBAPP_RUNTIME
-	@$(MAKE) -C ../../webapprt/locales AB_CD=$* XPI_NAME=locale-$*
-endif
 	@$(MAKE) -C ../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../extensions/pocket/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../extensions/loop/chrome/locale AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../intl/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../devtools/client/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -B searchplugins AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR)
 	@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
@@ -133,24 +130,21 @@ repackage-zip-%: repackage-win32-install
 else
 repackage-win32-installer-%: ;
 endif
 
 
 clobber-zip:
 	$(RM) $(STAGEDIST)/chrome/$(AB_CD).jar \
 	  $(STAGEDIST)/chrome/$(AB_CD).manifest \
-	  $(STAGEDIST)/webapprt/chrome/$(AB_CD).jar \
-	  $(STAGEDIST)/webapprt/chrome/$(AB_CD).manifest \
 	  $(STAGEDIST)/$(PREF_DIR)/firefox-l10n.js
 	$(RM) -rf  $(STAGEDIST)/dictionaries \
 	  $(STAGEDIST)/hyphenation \
 	  $(STAGEDIST)/defaults/profile \
-	  $(STAGEDIST)/chrome/$(AB_CD) \
-	  $(STAGEDIST)/webapprt/chrome/$(AB_CD)
+	  $(STAGEDIST)/chrome/$(AB_CD)
 
 
 langpack: langpack-$(AB_CD)
 
 # This is a generic target that will make a langpack, repack ZIP (+tarball)
 # builds, and repack an installer if applicable. It is called from the
 # tinderbox scripts. Alter it with caution.
 
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -192,17 +192,16 @@ These should match what Safari and other
      used to provide accessible labels to users of assistive technology like screenreaders.
      It is not possible to see them visually in the UI. -->
 <!ENTITY urlbar.defaultNotificationAnchor.label         "View a notification">
 <!ENTITY urlbar.geolocationNotificationAnchor.label     "View the location request">
 <!ENTITY urlbar.addonsNotificationAnchor.label          "View the add-on install message">
 <!ENTITY urlbar.indexedDBNotificationAnchor.label       "View the app-offline storage message">
 <!ENTITY urlbar.loginFillNotificationAnchor.label       "Manage your login information">
 <!ENTITY urlbar.passwordNotificationAnchor.label        "Check if you want to save your password">
-<!ENTITY urlbar.webappsNotificationAnchor.label         "View the app install message">
 <!ENTITY urlbar.pluginsNotificationAnchor.label         "Manage plugin usage on this page">
 <!ENTITY urlbar.webNotsNotificationAnchor3.label        "Change whether you can receive notifications from the site">
 
 <!ENTITY urlbar.webRTCShareDevicesNotificationAnchor.label      "Manage sharing your camera and/or microphone with the site">
 <!ENTITY urlbar.webRTCSharingDevicesNotificationAnchor.label    "You are sharing your camera and/or microphone with the site">
 <!ENTITY urlbar.webRTCShareMicrophoneNotificationAnchor.label   "Manage sharing your microphone with the site">
 <!ENTITY urlbar.webRTCSharingMicrophoneNotificationAnchor.label "You are sharing your microphone with the site">
 <!ENTITY urlbar.webRTCShareScreenNotificationAnchor.label       "Manage sharing your windows or screen with the site">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -468,30 +468,16 @@ dataReportingNotification.button.accessK
 processHang.label = A web page is slowing down your browser. What would you like to do?
 processHang.button_stop.label = Stop It
 processHang.button_stop.accessKey = S
 processHang.button_wait.label = Wait
 processHang.button_wait.accessKey = W
 processHang.button_debug.label = Debug Script
 processHang.button_debug.accessKey = D
 
-# Webapps notification popup
-webapps.install = Install
-webapps.install.accesskey = I
-#LOCALIZATION NOTE (webapps.requestInstall2) %S is the web app name
-webapps.requestInstall2 = Do you want to install “%S” from this site?
-webapps.install.success = Application Installed
-webapps.install.inprogress = Installation in progress
-webapps.uninstall = Uninstall
-webapps.uninstall.accesskey = U
-webapps.doNotUninstall = Don't Uninstall
-webapps.doNotUninstall.accesskey = D
-#LOCALIZATION NOTE (webapps.requestUninstall) %1$S is the web app name
-webapps.requestUninstall = Do you want to uninstall “%1$S”?
-
 # LOCALIZATION NOTE (fullscreenButton.tooltip): %S is the keyboard shortcut for full screen
 fullscreenButton.tooltip=Display the window in full screen (%S)
 
 service.toolbarbutton.label=Services
 service.toolbarbutton.tooltiptext=Services
 
 # LOCALIZATION NOTE (social.install.description): %1$S is the hostname of the social provider, %2$S is brandShortName (e.g. Firefox)
 service.install.description=Would you like to enable services from %1$S to display in your %2$S toolbar and sidebar?
--- a/browser/locales/filter.py
+++ b/browser/locales/filter.py
@@ -2,17 +2,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/.
 
 def test(mod, path, entity = None):
   import re
   # ignore anything but Firefox
   if mod not in ("netwerk", "dom", "toolkit", "security/manager",
                  "devtools/client", "devtools/shared",
-                 "browser", "webapprt",
+                 "browser",
                  "extensions/reporter", "extensions/spellcheck",
                  "other-licenses/branding/firefox",
                  "browser/branding/official",
                  "services/sync",
                  "browser/extensions/pocket"):
     return "ignore"
   if mod not in ("browser", "extensions/spellcheck"):
     # we only have exceptions for browser and extensions/spellcheck
--- a/browser/locales/l10n.ini
+++ b/browser/locales/l10n.ini
@@ -14,12 +14,11 @@ dirs = browser
      devtools/client
      browser/extensions/pocket
 
 [includes]
 # non-central apps might want to use %(topsrcdir)s here, or other vars
 # RFE: that needs to be supported by compare-locales, too, though
 toolkit = toolkit/locales/l10n.ini
 services_sync = services/sync/locales/l10n.ini
-webapprt = webapprt/locales/l10n.ini
 
 [extras]
 dirs = extensions/spellcheck
deleted file mode 100644
--- a/browser/modules/WebappManager.jsm
+++ /dev/null
@@ -1,335 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-this.EXPORTED_SYMBOLS = ["WebappManager"];
-
-var Ci = Components.interfaces;
-var Cc = Components.classes;
-var Cu = Components.utils;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Webapps.jsm");
-Cu.import("resource://gre/modules/AppsUtils.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NativeApp",
-  "resource://gre/modules/NativeApp.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
-  "resource://gre/modules/WebappOSUtils.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
-                                   "@mozilla.org/childprocessmessagemanager;1",
-                                   "nsIMessageSender");
-
-this.WebappManager = {
-  // List of promises for in-progress installations
-  installations: {},
-
-  init: function() {
-    Services.obs.addObserver(this, "webapps-ask-install", false);
-    Services.obs.addObserver(this, "webapps-ask-uninstall", false);
-    Services.obs.addObserver(this, "webapps-launch", false);
-    Services.obs.addObserver(this, "webapps-uninstall", false);
-    cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
-                          { messages: ["Webapps:Install:Return:OK",
-                                       "Webapps:Install:Return:KO",
-                                       "Webapps:UpdateState"]});
-    cpmm.addMessageListener("Webapps:Install:Return:OK", this);
-    cpmm.addMessageListener("Webapps:Install:Return:KO", this);
-    cpmm.addMessageListener("Webapps:UpdateState", this);
-  },
-
-  uninit: function() {
-    Services.obs.removeObserver(this, "webapps-ask-install");
-    Services.obs.removeObserver(this, "webapps-ask-uninstall");
-    Services.obs.removeObserver(this, "webapps-launch");
-    Services.obs.removeObserver(this, "webapps-uninstall");
-    cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
-                          ["Webapps:Install:Return:OK",
-                           "Webapps:Install:Return:KO",
-                           "Webapps:UpdateState"]);
-    cpmm.removeMessageListener("Webapps:Install:Return:OK", this);
-    cpmm.removeMessageListener("Webapps:Install:Return:KO", this);
-    cpmm.removeMessageListener("Webapps:UpdateState", this);
-  },
-
-  receiveMessage: function(aMessage) {
-    let data = aMessage.data;
-
-    let manifestURL = data.manifestURL ||
-                      (data.app && data.app.manifestURL) ||
-                      data.manifest;
-
-    if (!this.installations[manifestURL]) {
-      return;
-    }
-
-    if (aMessage.name == "Webapps:UpdateState") {
-      if (data.error) {
-        this.installations[manifestURL].reject(data.error);
-      } else if (data.app.installState == "installed") {
-        this.installations[manifestURL].resolve();
-      }
-    } else if (aMessage.name == "Webapps:Install:Return:OK" &&
-               !data.isPackage) {
-      let manifest = new ManifestHelper(data.app.manifest,
-                                        data.app.origin,
-                                        data.app.manifestURL);
-      if (!manifest.appcache_path) {
-        this.installations[manifestURL].resolve();
-      }
-    } else if (aMessage.name == "Webapps:Install:Return:KO") {
-      this.installations[manifestURL].reject(data.error);
-    }
-  },
-
-  observe: function(aSubject, aTopic, aData) {
-    let data = JSON.parse(aData);
-    data.mm = aSubject;
-
-    let browser;
-    switch(aTopic) {
-      case "webapps-ask-install":
-        browser = this._getBrowserForId(data.topId);
-        if (browser) {
-          this.doInstall(data, browser);
-        }
-        break;
-      case "webapps-ask-uninstall":
-        browser = this._getBrowserForId(data.topId);
-        if (browser) {
-          this.doUninstall(data, browser);
-        }
-        break;
-      case "webapps-launch":
-        WebappOSUtils.launch(data);
-        break;
-      case "webapps-uninstall":
-        WebappOSUtils.uninstall(data);
-        break;
-    }
-  },
-
-  _getBrowserForId: function(aId) {
-    let windows = Services.wm.getEnumerator("navigator:browser");
-    while (windows.hasMoreElements()) {
-      let window = windows.getNext();
-      let tabbrowser = window.gBrowser;
-      let foundBrowser = tabbrowser.getBrowserForOuterWindowID(aId);
-      if (foundBrowser) {
-        return foundBrowser;
-      }
-    }
-    let foundWindow = Services.wm.getOuterWindowWithId(aId);
-    if (foundWindow) {
-      return foundWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIWebNavigation)
-                        .QueryInterface(Ci.nsIDocShell)
-                        .chromeEventHandler;
-    }
-    return null;
-  },
-
-  doInstall: function(aData, aBrowser) {
-    let chromeDoc = aBrowser.ownerDocument;
-    let chromeWin = chromeDoc.defaultView;
-    let popupProgressContent =
-      chromeDoc.getElementById("webapps-install-progress-content");
-
-    let bundle = chromeWin.gNavigatorBundle;
-
-    let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
-
-    let notification;
-
-    let mainAction = {
-      label: bundle.getString("webapps.install"),
-      accessKey: bundle.getString("webapps.install.accesskey"),
-      callback: () => {
-        notification.remove();
-
-        notification = chromeWin.PopupNotifications.
-                        show(aBrowser,
-                             "webapps-install-progress",
-                             bundle.getString("webapps.install.inprogress"),
-                             "webapps-notification-icon");
-
-        let progressMeter = chromeDoc.createElement("progressmeter");
-        progressMeter.setAttribute("mode", "undetermined");
-        popupProgressContent.appendChild(progressMeter);
-
-        let manifestURL = aData.app.manifestURL;
-
-        let nativeApp = new NativeApp(aData.app, jsonManifest,
-                                      aData.app.categories);
-
-        this.installations[manifestURL] = Promise.defer();
-        this.installations[manifestURL].promise.then(() => {
-          notifyInstallSuccess(aData.app, nativeApp, bundle,
-                               PrivateBrowsingUtils.isBrowserPrivate(aBrowser));
-        }, (error) => {
-          Cu.reportError("Error installing webapp: " + error);
-        }).then(() => {
-          popupProgressContent.removeChild(progressMeter);
-          delete this.installations[manifestURL];
-          if (Object.getOwnPropertyNames(this.installations).length == 0) {
-            notification.remove();
-          }
-        });
-
-        let localDir;
-        try {
-          localDir = nativeApp.createProfile();
-        } catch (ex) {
-          DOMApplicationRegistry.denyInstall(aData);
-          return;
-        }
-
-        DOMApplicationRegistry.confirmInstall(aData, localDir,
-          Task.async(function*(aApp, aManifest, aZipPath) {
-            try {
-              yield nativeApp.install(aApp, aManifest, aZipPath);
-            } catch (ex) {
-              Cu.reportError("Error installing webapp: " + ex);
-              throw ex;
-            }
-          })
-        );
-      }
-    };
-
-    let requestingURI = chromeWin.makeURI(aData.from);
-    let app = aData.app;
-    let manifest = new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
-
-    let options = {
-      displayURI: requestingURI,
-    };
-
-    let message = bundle.getFormattedString("webapps.requestInstall2",
-                                            [manifest.name]);
-
-    let gBrowser = chromeWin.gBrowser;
-    if (gBrowser) {
-      let windowID = aData.oid;
-
-      let listener = {
-        onLocationChange(webProgress) {
-          if (webProgress.DOMWindowID == windowID) {
-            notification.remove();
-          }
-        }
-      };
-
-      gBrowser.addProgressListener(listener);
-
-      options.eventCallback = event => {
-        if (event != "removed") {
-          return;
-        }
-        // The notification was removed, so we should
-        // remove our listener.
-        gBrowser.removeProgressListener(listener);
-      };
-    }
-
-    notification = chromeWin.PopupNotifications.show(aBrowser,
-                                                     "webapps-install",
-                                                     message,
-                                                     "webapps-notification-icon",
-                                                     mainAction, [],
-                                                     options);
-  },
-
-  doUninstall: function(aData, aBrowser) {
-    let chromeDoc = aBrowser.ownerDocument;
-    let chromeWin = chromeDoc.defaultView;
-
-    let bundle = chromeWin.gNavigatorBundle;
-    let jsonManifest = aData.app.manifest;
-
-    let notification;
-
-    let mainAction = {
-      label: bundle.getString("webapps.uninstall"),
-      accessKey: bundle.getString("webapps.uninstall.accesskey"),
-      callback: () => {
-        notification.remove();
-        DOMApplicationRegistry.confirmUninstall(aData);
-      }
-    };
-
-    let secondaryAction = {
-      label: bundle.getString("webapps.doNotUninstall"),
-      accessKey: bundle.getString("webapps.doNotUninstall.accesskey"),
-      callback: () => {
-        notification.remove();
-        DOMApplicationRegistry.denyUninstall(aData, "USER_DECLINED");
-      }
-    };
-
-    let manifest = new ManifestHelper(jsonManifest, aData.app.origin,
-                                      aData.app.manifestURL);
-
-    let message = bundle.getFormattedString("webapps.requestUninstall",
-                                            [manifest.name]);
-
-
-    let options = {};
-    let gBrowser = chromeWin.gBrowser;
-    if (gBrowser) {
-      let windowID = aData.oid;
-
-      let listener = {
-        onLocationChange(webProgress) {
-          if (webProgress.DOMWindowID == windowID) {
-            notification.remove();
-          }
-        }
-      };
-
-      gBrowser.addProgressListener(listener);
-
-      options.eventCallback = event => {
-        if (event != "removed") {
-          return;
-        }
-        // The notification was removed, so we should
-        // remove our listener.
-        gBrowser.removeProgressListener(listener);
-      };
-    }
-
-    notification = chromeWin.PopupNotifications.show(
-                     aBrowser, "webapps-uninstall", message,
-                     "webapps-notification-icon",
-                     mainAction, [secondaryAction],
-                     options);
-  }
-}
-
-function notifyInstallSuccess(aApp, aNativeApp, aBundle, aInPrivateBrowsing) {
-  let launcher = {
-    observe: function(aSubject, aTopic) {
-      if (aTopic == "alertclickcallback") {
-        WebappOSUtils.launch(aApp);
-      }
-    }
-  };
-
-  try {
-    let notifier = Cc["@mozilla.org/alerts-service;1"].
-                   getService(Ci.nsIAlertsService);
-
-    notifier.showAlertNotification(aNativeApp.iconURI.spec,
-                                   aBundle.getString("webapps.install.success"),
-                                   aNativeApp.appNameAsFilename,
-                                   true, null, launcher, "", "", "", "", null,
-                                   aInPrivateBrowsing);
-  } catch (ex) {}
-}
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -39,17 +39,16 @@ EXTRA_JS_MODULES += [
     'RemotePrompt.jsm',
     'Sanitizer.jsm',
     'SelfSupportBackend.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
     'TabGroupsMigrator.jsm',
     'TransientPrefs.jsm',
     'UserContextUI.jsm',
-    'WebappManager.jsm',
     'webrtcUI.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXTRA_JS_MODULES += [
         'Windows8WindowFrameColor.jsm',
         'WindowsJumpLists.jsm',
         'WindowsPreviewPerTab.jsm',
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -57,21 +57,16 @@
 .popup-notification-icon[popupid="offline-app-usage"] {
   list-style-image: url(chrome://global/skin/icons/question-64.png);
 }
 
 .popup-notification-icon[popupid="password"] {
   list-style-image: url(chrome://mozapps/skin/passwordmgr/key-64.png);
 }
 
-.popup-notification-icon[popupid="webapps-install-progress"],
-.popup-notification-icon[popupid="webapps-install"] {
-  list-style-image: url(chrome://global/skin/icons/webapps-64.png);
-}
-
 .popup-notification-icon[popupid="webRTC-sharingDevices"],
 .popup-notification-icon[popupid="webRTC-shareDevices"] {
   list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png);
 }
 
 .popup-notification-icon[popupid="webRTC-sharingMicrophone"],
 .popup-notification-icon[popupid="webRTC-shareMicrophone"] {
   list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-64.png);
@@ -154,21 +149,16 @@
 }
 
 #login-fill-notification-icon {
   /* Temporary icon until the capture and fill doorhangers are unified. */
   list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16.png);
   transform: scaleX(-1);
 }
 
-.webapps-notification-icon,
-#webapps-notification-icon {
-  list-style-image: url(chrome://global/skin/icons/webapps-16.png);
-}
-
 #plugins-notification-icon {
   list-style-image: url(chrome://browser/skin/notification-pluginNormal.png);
 }
 
 #plugins-notification-icon.plugin-hidden {
   list-style-image: url(chrome://browser/skin/notification-pluginAlert.png);
 }
 
--- a/build/autoconf/altoptions.m4
+++ b/build/autoconf/altoptions.m4
@@ -15,109 +15,81 @@ dnl MOZ_ARG_DISABLE_BOOL(          NAME,
 dnl MOZ_ARG_ENABLE_STRING(         NAME, HELP, IF-SET [, ELSE])
 dnl MOZ_ARG_ENABLE_BOOL_OR_STRING( NAME, HELP, IF-YES, IF-NO, IF-SET[, ELSE]]])
 dnl MOZ_ARG_WITH_BOOL(             NAME, HELP, IF-YES [, IF-NO [, ELSE])
 dnl MOZ_ARG_WITHOUT_BOOL(          NAME, HELP, IF-NO [, IF-YES [, ELSE])
 dnl MOZ_ARG_WITH_STRING(           NAME, HELP, IF-SET [, ELSE])
 dnl MOZ_ARG_HEADER(Comment)
 dnl MOZ_READ_MYCONFIG() - Read in 'myconfig.sh' file
 
+define([MOZ_DIVERSION_ARGS], 12)
+
+AC_DEFUN([MOZ_ARG],[dnl
+AC_DIVERT_PUSH(MOZ_DIVERSION_ARGS)dnl
+    '$1',
+AC_DIVERT_POP()dnl
+])
+AC_DEFUN([MOZ_AC_ARG_ENABLE],[MOZ_ARG([--enable-]translit([$1],[_],[-]))AC_ARG_ENABLE([$1], [$2], [$3], [$4])])
+AC_DEFUN([MOZ_AC_ARG_WITH],[MOZ_ARG([--with-]translit([$1],[_],[-]))AC_ARG_WITH([$1], [$2], [$3], [$4])])
 
 dnl MOZ_TWO_STRING_TEST(NAME, VAL, STR1, IF-STR1, STR2, IF-STR2 [, ELSE])
 AC_DEFUN([MOZ_TWO_STRING_TEST],
 [if test "[$2]" = "[$3]"; then
     ifelse([$4], , :, [$4])
   elif test "[$2]" = "[$5]"; then
     ifelse([$6], , :, [$6])
   else
     ifelse([$7], ,
       [AC_MSG_ERROR([Option, [$1], does not take an argument ([$2]).])],
       [$7])
   fi])
 
 dnl MOZ_ARG_ENABLE_BOOL(NAME, HELP, IF-YES [, IF-NO [, ELSE]])
 AC_DEFUN([MOZ_ARG_ENABLE_BOOL],
-[AC_ARG_ENABLE([$1], [$2], 
+[MOZ_AC_ARG_ENABLE([$1], [$2],
  [MOZ_TWO_STRING_TEST([$1], [$enableval], yes, [$3], no, [$4])],
  [$5])])
 
 dnl MOZ_ARG_DISABLE_BOOL(NAME, HELP, IF-NO [, IF-YES [, ELSE]])
 AC_DEFUN([MOZ_ARG_DISABLE_BOOL],
-[AC_ARG_ENABLE([$1], [$2],
+[MOZ_AC_ARG_ENABLE([$1], [$2],
  [MOZ_TWO_STRING_TEST([$1], [$enableval], no, [$3], yes, [$4])],
  [$5])])
 
 dnl MOZ_ARG_ENABLE_STRING(NAME, HELP, IF-SET [, ELSE])
 AC_DEFUN([MOZ_ARG_ENABLE_STRING],
-[AC_ARG_ENABLE([$1], [$2], [$3], [$4])])
+[MOZ_AC_ARG_ENABLE([$1], [$2], [$3], [$4])])
 
 dnl MOZ_ARG_ENABLE_BOOL_OR_STRING(NAME, HELP, IF-YES, IF-NO, IF-SET[, ELSE]]])
 AC_DEFUN([MOZ_ARG_ENABLE_BOOL_OR_STRING],
 [ifelse([$5], , 
  [errprint([Option, $1, needs an "IF-SET" argument.
 ])
   m4exit(1)],
- [AC_ARG_ENABLE([$1], [$2],
+ [MOZ_AC_ARG_ENABLE([$1], [$2],
   [MOZ_TWO_STRING_TEST([$1], [$enableval], yes, [$3], no, [$4], [$5])],
   [$6])])])
 
 dnl MOZ_ARG_WITH_BOOL(NAME, HELP, IF-YES [, IF-NO [, ELSE])
 AC_DEFUN([MOZ_ARG_WITH_BOOL],
-[AC_ARG_WITH([$1], [$2],
+[MOZ_AC_ARG_WITH([$1], [$2],
  [MOZ_TWO_STRING_TEST([$1], [$withval], yes, [$3], no, [$4])],
  [$5])])
 
 dnl MOZ_ARG_WITHOUT_BOOL(NAME, HELP, IF-NO [, IF-YES [, ELSE])
 AC_DEFUN([MOZ_ARG_WITHOUT_BOOL],
-[AC_ARG_WITH([$1], [$2],
+[MOZ_AC_ARG_WITH([$1], [$2],
  [MOZ_TWO_STRING_TEST([$1], [$withval], no, [$3], yes, [$4])],
  [$5])])
 
 dnl MOZ_ARG_WITH_STRING(NAME, HELP, IF-SET [, ELSE])
 AC_DEFUN([MOZ_ARG_WITH_STRING],
-[AC_ARG_WITH([$1], [$2], [$3], [$4])])
+[MOZ_AC_ARG_WITH([$1], [$2], [$3], [$4])])
 
 dnl MOZ_ARG_HEADER(Comment)
 dnl This is used by webconfig to group options
 define(MOZ_ARG_HEADER, [# $1])
 
 dnl MOZ_READ_MYCONFIG() - Read in 'myconfig.sh' file
 AC_DEFUN([MOZ_READ_MOZCONFIG],
 [AC_REQUIRE([AC_INIT_BINSH])dnl
-inserted=
-dnl Shell is hard, so here is what the following does:
-dnl - Reset $@ (command line arguments)
-dnl - Add the configure options from mozconfig to $@ one by one
-dnl - Add the original command line arguments after that, one by one
-dnl
-dnl There are several tricks involved:
-dnl - It is not possible to preserve the whitespaces in $@ by assigning to
-dnl   another variable, so the two first steps above need to happen in the first
-dnl   iteration of the third step.
-dnl - We always want the configure options to be added, so the loop must be
-dnl   iterated at least once, so we add a dummy argument first, and discard it.
-dnl - something | while read line ... makes the while run in a subshell, meaning
-dnl   that anything it does is not propagated to the main shell, so we can't do
-dnl   set -- foo there. As a consequence, what the while loop reading mach
-dnl   environment output does is output a set of shell commands for the main shell
-dnl   to eval.
-dnl - Extra care is due when lines from mach environment output contain special
-dnl   shell characters, so we use ' for quoting and ensure no ' end up in between
-dnl   the quoting mark unescaped.
-dnl Some of the above is directly done in mach environment --format=configure.
-failed_eval() {
-  echo "Failed eval'ing the following:"
-  $(dirname [$]0)/[$1]/mach environment --format=configure
-  exit 1
-}
-
-set -- dummy "[$]@"
-for ac_option
-do
-  if test -z "$inserted"; then
-    set --
-    eval "$($(dirname [$]0)/[$1]/mach environment --format=configure)" || failed_eval
-    inserted=1
-  else
-    set -- "[$]@" "$ac_option"
-  fi
-done
+. ./old-configure.vars
 ])
--- a/build/autoconf/compiler-opts.m4
+++ b/build/autoconf/compiler-opts.m4
@@ -505,16 +505,17 @@ AC_DEFUN([MOZ_SET_WARNINGS_CXXFLAGS],
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wignored-qualifiers"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Woverloaded-virtual"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wpointer-arith"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wsign-compare"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wtype-limits"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wwrite-strings"
 
     # -Wclass-varargs - catches objects passed by value to variadic functions.
+    # -Wimplicit-fallthrough - catches unintentional switch case fallthroughs
     # -Wloop-analysis - catches issues around loops
     # -Wnon-literal-null-conversion - catches expressions used as a null pointer constant
     # -Wthread-safety - catches inconsistent use of mutexes
     # -Wunreachable-code - catches some dead code
     # -Wunreachable-code-return - catches dead code after return call
     #
     # XXX: at the time of writing, the version of clang used on the OS X test
     # machines has a bug that causes it to reject some valid files if both
@@ -523,16 +524,17 @@ AC_DEFUN([MOZ_SET_WARNINGS_CXXFLAGS],
     # -Werror=non-literal-null-conversion, but we only do that when
     # --enable-warnings-as-errors is specified so that no unexpected fatal
     # warnings are produced.
     MOZ_CXX_SUPPORTS_WARNING(-W, c++11-compat-pedantic, ac_cxx_has_wcxx11_compat_pedantic)
     MOZ_CXX_SUPPORTS_WARNING(-W, c++14-compat, ac_cxx_has_wcxx14_compat)
     MOZ_CXX_SUPPORTS_WARNING(-W, c++14-compat-pedantic, ac_cxx_has_wcxx14_compat_pedantic)
     MOZ_CXX_SUPPORTS_WARNING(-W, c++1z-compat, ac_cxx_has_wcxx1z_compat)
     MOZ_CXX_SUPPORTS_WARNING(-W, class-varargs, ac_cxx_has_wclass_varargs)
+    MOZ_CXX_SUPPORTS_WARNING(-W, implicit-fallthrough, ac_cxx_has_wimplicit_fallthrough)
     MOZ_CXX_SUPPORTS_WARNING(-W, loop-analysis, ac_cxx_has_wloop_analysis)
 
     if test "$MOZ_ENABLE_WARNINGS_AS_ERRORS"; then
         MOZ_CXX_SUPPORTS_WARNING(-Werror=, non-literal-null-conversion, ac_cxx_has_non_literal_null_conversion)
     fi
 
     MOZ_CXX_SUPPORTS_WARNING(-W, thread-safety, ac_cxx_has_wthread_safety)
     MOZ_CXX_SUPPORTS_WARNING(-W, unreachable-code, ac_cxx_has_wunreachable_code)
--- a/build/autoconf/config.status.m4
+++ b/build/autoconf/config.status.m4
@@ -78,168 +78,105 @@ define([AC_DEFINE_UNQUOTED],
 EOF
 ifelse($#, 2, _MOZ_AC_DEFINE_UNQUOTED($1, $2), $#, 3, _MOZ_AC_DEFINE_UNQUOTED($1, $2, $3),_MOZ_AC_DEFINE_UNQUOTED($1))dnl
 ])
 
 dnl Replace AC_OUTPUT to create and call a python config.status
 define([MOZ_CREATE_CONFIG_STATUS],
 [dnl Top source directory in Windows format (as opposed to msys format).
 WIN_TOP_SRC=
-encoding=utf-8
 case "$host_os" in
 mingw*)
     WIN_TOP_SRC=`cd $srcdir; pwd -W`
-    encoding=mbcs
     ;;
 esac
 AC_SUBST(WIN_TOP_SRC)
 
 dnl Used in all Makefile.in files
 top_srcdir=$srcdir
 AC_SUBST(top_srcdir)
 
 dnl Picked from autoconf 2.13
 trap '' 1 2 15
 AC_CACHE_SAVE
 
 trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15
-: ${CONFIG_STATUS=./config.status}
+: ${CONFIG_STATUS=./config.data}
 
 dnl We're going to need [ ] for python syntax.
 changequote(<<<, >>>)dnl
 echo creating $CONFIG_STATUS
 
-extra_python_path=${COMM_BUILD:+"'mozilla', "}
-
 cat > $CONFIG_STATUS <<EOF
-#!${PYTHON}
-# coding=$encoding
-
-import os
-import types
-dnl topsrcdir is the top source directory in native form, as opposed to a
-dnl form suitable for make.
-topsrcdir = '''${WIN_TOP_SRC:-$srcdir}'''
-if not os.path.isabs(topsrcdir):
-    rel = os.path.join(os.path.dirname(<<<__file__>>>), topsrcdir)
-    topsrcdir = os.path.abspath(rel)
-topsrcdir = os.path.normpath(topsrcdir)
-
-topobjdir = os.path.abspath(os.path.dirname(<<<__file__>>>))
-
 def unique_list(l):
     result = []
     for i in l:
         if l not in result:
             result.append(i)
     return result
 
 dnl All defines and substs are stored with an additional space at the beginning
 dnl and at the end of the string, to avoid any problem with values starting or
 dnl ending with quotes.
-defines = [(name[1:-1], value[1:-1]) for name, value in [
+defines = [
 EOF
 
 dnl confdefs.pytmp contains AC_DEFINEs, in the expected format, but
 dnl lacks the final comma (see above).
 sed 's/$/,/' confdefs.pytmp >> $CONFIG_STATUS
 rm confdefs.pytmp confdefs.h
 
 cat >> $CONFIG_STATUS <<\EOF
-] ]
+]
 
-substs = [(name[1:-1], value[1:-1] if isinstance(value, types.StringTypes) else value) for name, value in [
+substs = [
 EOF
 
 dnl The MOZ_DIVERSION_SUBST output diversion contains AC_SUBSTs, in the
 dnl expected format, but lacks the final comma (see above).
 sed 's/$/,/' >> $CONFIG_STATUS <<EOF
 undivert(MOZ_DIVERSION_SUBST)dnl
 EOF
 
 dnl Add in the output from the subconfigure script
 for ac_subst_arg in $_subconfigure_ac_subst_args; do
   variable='$'$ac_subst_arg
   echo "    (''' $ac_subst_arg ''', r''' `eval echo $variable` ''')," >> $CONFIG_STATUS
 done
 
 cat >> $CONFIG_STATUS <<\EOF
-] ]
+]
 
 dnl List of AC_DEFINEs that aren't to be exposed in ALLDEFINES
 non_global_defines = [
 EOF
 
 if test -n "$_NON_GLOBAL_ACDEFINES"; then
   for var in $_NON_GLOBAL_ACDEFINES; do
     echo "    '$var'," >> $CONFIG_STATUS
   done
 fi
 
 cat >> $CONFIG_STATUS <<EOF
 ]
 
-__all__ = ['topobjdir', 'topsrcdir', 'defines', 'non_global_defines', 'substs']
-EOF
-
-# We don't want js/src/config.status to do anything in gecko builds.
-if test -z "$BUILDING_JS" -o -n "$JS_STANDALONE"; then
-
-    cat >> $CONFIG_STATUS <<EOF
-dnl Do the actual work
-if __name__ == '__main__':
-    args = dict([(name, globals()[name]) for name in __all__])
-    from mozbuild.config_status import config_status
-    config_status(**args)
+flags = [
+undivert(MOZ_DIVERSION_ARGS)dnl
+]
 EOF
 
-fi
-
 changequote([, ])
-
-chmod +x $CONFIG_STATUS
-])
-
-define([MOZ_RUN_CONFIG_STATUS],
-[
-
-MOZ_RUN_ALL_SUBCONFIGURES()
-
-rm -fr confdefs* $ac_clean_files
-dnl Execute config.status, unless --no-create was passed to configure.
-if test "$no_create" != yes && ! ${PYTHON} $CONFIG_STATUS; then
-    trap '' EXIT
-    exit 1
-fi
 ])
 
 define([m4_fatal],[
 errprint([$1
 ])
 m4exit(1)
 ])
 
 define([AC_OUTPUT], [ifelse($#_$1, 1_, [MOZ_CREATE_CONFIG_STATUS()
 MOZ_RUN_CONFIG_STATUS()],
 [m4_fatal([Use CONFIGURE_SUBST_FILES in moz.build files to create substituted files.])]
 )])
 
 define([AC_CONFIG_HEADER],
 [m4_fatal([Use CONFIGURE_DEFINE_FILES in moz.build files to produce header files.])
 ])
-
-define([MOZ_BUILD_BACKEND],
-[
-dnl For now, only enable the unified hybrid build system on artifact builds,
-dnl otherwise default to RecursiveMake /and/ FasterMake.
-if test -n "$MOZ_ARTIFACT_BUILDS"; then
-    BUILD_BACKENDS="FasterMake+RecursiveMake"
-else
-    BUILD_BACKENDS="RecursiveMake FasterMake"
-fi
-
-MOZ_ARG_ENABLE_STRING(build-backend,
-[  --enable-build-backend={$($(dirname ]$[0)/$1/mach python -c "from mozbuild.backend import backends; print ','.join(sorted(backends))")}
-                         Enable additional build backends],
-[ BUILD_BACKENDS="$BUILD_BACKENDS `echo $enableval | sed 's/,/ /g'`"])
-
-AC_SUBST_SET([BUILD_BACKENDS])
-])
deleted file mode 100644
--- a/build/autoconf/python-virtualenv.m4
+++ /dev/null
@@ -1,85 +0,0 @@
-dnl This Source Code Form is subject to the terms of the Mozilla Public
-dnl License, v. 2.0. If a copy of the MPL was not distributed with this
-dnl file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-AC_DEFUN([MOZ_PYTHON],
-[
-
-dnl We honor the Python path defined in an environment variable. This is used
-dnl to pass the virtualenv's Python from the main configure to SpiderMonkey's
-dnl configure, for example.
-if test -z "$PYTHON"; then
-  MOZ_PATH_PROGS(PYTHON, $PYTHON python2.7 python)
-  if test -z "$PYTHON"; then
-      AC_MSG_ERROR([python was not found in \$PATH])
-  fi
-else
-  AC_MSG_RESULT([Using Python from environment variable \$PYTHON])
-fi
-
-_virtualenv_topsrcdir=
-_virtualenv_populate_path=
-
-dnl If this is a mozilla-central, we'll find the virtualenv in the top
-dnl source directory. If this is a SpiderMonkey build, we assume we're at
-dnl js/src and try to find the virtualenv from the mozilla-central root.
-for base in $MOZILLA_CENTRAL_PATH $_topsrcdir $_topsrcdir/../..; do
-  possible=$base/python/mozbuild/mozbuild/virtualenv.py
-
-  if test -e $possible; then
-    _virtualenv_topsrcdir=$base
-    _virtualenv_populate_path=$possible
-    break
-  fi
-done
-
-if test -z $_virtualenv_populate_path; then
-    AC_MSG_ERROR([Unable to find Virtualenv population script. In order
-to build, you will need mozilla-central's virtualenv.
-
-If you are building from a mozilla-central checkout, you should never see this
-message. If you are building from a source archive, the source archive was
-likely not created properly (it is missing the virtualenv files).
-
-If you have a copy of mozilla-central available, define the
-MOZILLA_CENTRAL_PATH environment variable to the top source directory of
-mozilla-central and relaunch configure.])
-
-fi
-
-if test -z $DONT_POPULATE_VIRTUALENV; then
-  AC_MSG_RESULT([Creating Python environment])
-  dnl This verifies our Python version is sane and ensures the Python
-  dnl virtualenv is present and up to date. It sanitizes the environment
-  dnl for us, so we don't need to clean anything out.
-  $PYTHON $_virtualenv_populate_path \
-    $_virtualenv_topsrcdir $MOZ_BUILD_ROOT $MOZ_BUILD_ROOT/_virtualenv \
-    $_virtualenv_topsrcdir/build/virtualenv_packages.txt || exit 1
-
-  case "$host_os" in
-  mingw*)
-    PYTHON=`cd $MOZ_BUILD_ROOT && pwd -W`/_virtualenv/Scripts/python.exe
-    ;;
-  *)
-    PYTHON=$MOZ_BUILD_ROOT/_virtualenv/bin/python
-    ;;
-  esac
-fi
-
-AC_SUBST(PYTHON)
-
-AC_MSG_CHECKING([Python environment is Mozilla virtualenv])
-$PYTHON -c "import mozbuild.base"
-if test "$?" != 0; then
-    AC_MSG_ERROR([Python environment does not appear to be sane.])
-fi
-AC_MSG_RESULT([yes])
-
-PYTHON_SITE_PACKAGES=`$PYTHON -c "import distutils.sysconfig; print distutils.sysconfig.get_python_lib()"`
-if test -z "$PYTHON_SITE_PACKAGES"; then
-    AC_MSG_ERROR([Could not determine python site packages directory.])
-fi
-AC_SUBST([PYTHON_SITE_PACKAGES])
-
-])
-
--- a/build/docs/test_manifests.rst
+++ b/build/docs/test_manifests.rst
@@ -26,19 +26,16 @@ browser.ini
    For the *browser chrome* flavor of mochitests.
 
 a11y.ini
    For the *a11y* flavor of mochitests.
 
 xpcshell.ini
    For *xpcshell* tests.
 
-webapprt.ini
-   For the *chrome* flavor of webapp runtime mochitests.
-
 .. _manifestparser_manifests:
 
 ManifestParser Manifests
 ==========================
 
 ManifestParser manifests are essentially ini files that conform to a basic
 set of assumptions.
 
--- a/build/gen_test_packages_manifest.py
+++ b/build/gen_test_packages_manifest.py
@@ -7,17 +7,16 @@
 import json
 
 from argparse import ArgumentParser
 
 ALL_HARNESSES = [
     'common', # Harnesses without a specific package will look here.
     'mochitest',
     'reftest',
-    'webapprt',
     'xpcshell',
     'cppunittest',
     'jittest',
     'mozbase',
     'web-platform',
     'talos',
     'gtest',
 ]
new file mode 100644
--- /dev/null
+++ b/build/moz.configure/init.configure
@@ -0,0 +1,200 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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('util.configure')
+
+option(env='DIST', nargs=1, help='DIST directory')
+
+# Do not allow objdir == srcdir builds.
+# ==============================================================
+@depends('--help', 'DIST')
+def check_build_environment(help, dist):
+    topobjdir = os.path.realpath(os.path.abspath('.'))
+    topsrcdir = os.path.realpath(os.path.abspath(
+        os.path.join(os.path.dirname(__file__), '..', '..')))
+
+    set_config('TOPSRCDIR', topsrcdir)
+    set_config('TOPOBJDIR', topobjdir)
+    set_config('MOZ_BUILD_ROOT', topobjdir)
+    if dist:
+        set_config('DIST', normsep(dist[0]))
+    else:
+        set_config('DIST', os.path.join(topobjdir, 'dist'))
+
+    if help:
+        return
+
+    if topsrcdir == topobjdir:
+        error(
+            '  ***\n'
+            '  * Building directly in the main source directory is not allowed.\n'
+            '  *\n'
+            '  * To build, you must run configure from a separate directory\n'
+            '  * (referred to as an object directory).\n'
+            '  *\n'
+            '  * If you are building with a mozconfig, you will need to change your\n'
+            '  * mozconfig to point to a different object directory.\n'
+            '  ***'
+        )
+
+    # Check for a couple representative files in the source tree
+    conflict_files = [
+        '*         %s' % f for f in ('Makefile', 'config/autoconf.mk')
+        if os.path.exists(os.path.join(topsrcdir, f))
+    ]
+    if conflict_files:
+        error(
+            '  ***\n'
+            '  *   Your source tree contains these files:\n'
+            '  %s\n'
+            '  *   This indicates that you previously built in the source tree.\n'
+            '  *   A source tree build can confuse the separate objdir build.\n'
+            '  *\n'
+            '  *   To clean up the source tree:\n'
+            '  *     1. cd %s\n'
+            '  *     2. gmake distclean\n'
+            '  ***'
+            % ('\n  '.join(conflict_files), topsrcdir)
+        )
+
+
+option(env='MOZ_CURRENT_PROJECT', nargs=1, help='Current build project')
+option(env='MOZCONFIG', nargs=1, help='Mozconfig location')
+
+# Read user mozconfig
+# ==============================================================
+# Note: the dependency on --help is only there to always read the mozconfig,
+# even when --help is passed. Without this dependency, the function wouldn't
+# be called when --help is passed, and the mozconfig wouldn't be read.
+@depends('MOZ_CURRENT_PROJECT', 'MOZCONFIG', check_build_environment, '--help')
+@advanced
+def mozconfig(current_project, mozconfig,  build_env, help):
+    from mozbuild.mozconfig import MozconfigLoader
+
+    # Don't read the mozconfig for the js configure (yay backwards
+    # compatibility)
+    if build_env['TOPOBJDIR'].endswith('/js/src'):
+        return {'path': None}
+
+    loader = MozconfigLoader(build_env['TOPSRCDIR'])
+    current_project = current_project[0] if current_project else None
+    mozconfig = mozconfig[0] if mozconfig else None
+    mozconfig = loader.find_mozconfig(env={'MOZCONFIG': mozconfig})
+    mozconfig = loader.read_mozconfig(mozconfig, moz_build_app=current_project)
+
+    return mozconfig
+
+
+option(env='PYTHON', nargs=1, help='Python interpreter')
+
+# Setup python virtualenv
+# ==============================================================
+@depends('PYTHON', check_build_environment, mozconfig)
+@advanced
+def virtualenv_python(env_python, build_env, mozconfig):
+    import os
+    import sys
+    import subprocess
+    from mozbuild.virtualenv import (
+	VirtualenvManager,
+        verify_python_version,
+    )
+
+    python = env_python[0] if env_python else None
+
+    # Ideally we'd rely on the mozconfig injection from mozconfig_options,
+    # but we'd rather avoid the verbosity when we need to reexecute with
+    # a different python.
+    if mozconfig['path']:
+        if 'PYTHON' in mozconfig['env']['added']:
+            python = mozconfig['env']['added']['PYTHON']
+        elif 'PYTHON' in mozconfig['env']['modified']:
+            python = mozconfig['env']['modified']['PYTHON'][1]
+        elif 'PYTHON' in mozconfig['vars']['added']:
+            python = mozconfig['vars']['added']['PYTHON']
+        elif 'PYTHON' in mozconfig['vars']['modified']:
+            python = mozconfig['vars']['modified']['PYTHON'][1]
+
+    verify_python_version(sys.stderr)
+    topsrcdir, topobjdir = build_env['TOPSRCDIR'], build_env['TOPOBJDIR']
+    if topobjdir.endswith('/js/src'):
+        topobjdir = topobjdir[:-7]
+
+    manager = VirtualenvManager(
+        topsrcdir, topobjdir,
+        os.path.join(topobjdir, '_virtualenv'), sys.stdout,
+        os.path.join(topsrcdir, 'build', 'virtualenv_packages.txt'))
+
+    if python:
+        # If we're not in the virtualenv, we need the which module for
+        # find_program.
+        if normsep(sys.executable) != normsep(manager.python_path):
+            sys.path.append(os.path.join(topsrcdir, 'python', 'which'))
+        found_python = find_program(python)
+        if not found_python:
+            error('The PYTHON environment variable does not contain '
+                  'a valid path. Cannot find %s' % python)
+        python = found_python
+    else:
+        python = sys.executable
+
+    if not manager.up_to_date(python):
+        warn('Creating Python environment')
+        manager.build(python)
+
+    python = normsep(manager.python_path)
+
+    if python != normsep(sys.executable):
+        warn('Reexecuting in the virtualenv')
+        if env_python:
+            del os.environ['PYTHON']
+        # One would prefer to use os.execl, but that's completely borked on
+        # Windows.
+        sys.exit(subprocess.call([python] + sys.argv))
+
+    # We are now in the virtualenv
+    import distutils.sysconfig
+    if not distutils.sysconfig.get_python_lib():
+        error('Could not determine python site packages directory')
+
+    set_config('PYTHON', python)
+    return python
+
+
+# Inject mozconfig options
+# ==============================================================
+@template
+@advanced
+def command_line_helper():
+    # This escapes the sandbox. Don't copy this. This is only here because
+    # it is a one off and because the required functionality doesn't need
+    # to be exposed for other usecases.
+    return depends.__self__._helper
+
+
+@depends(mozconfig)
+def mozconfig_options(mozconfig):
+    if mozconfig['path']:
+        helper = command_line_helper()
+        warn('Adding configure options from %s' % mozconfig['path'])
+        for arg in mozconfig['configure_args']:
+            warn('  %s' % arg)
+            # We could be using imply_option() here, but it has other
+            # contraints that don't really apply to the command-line
+            # emulation that mozconfig provides.
+            helper.add(arg, origin='mozconfig', args=helper._args)
+
+        # Ideally we'd handle mozconfig['env'] and mozconfig['vars'] here,
+        # but at the moment, moz.configure has no knowledge of the options
+        # that may appear there. We'll opt-in when we move things from
+        # old-configure.in, which will be tedious but necessary until we
+        # can discriminate what old-configure.in supports.
+
+del command_line_helper
+
+
+option(env='MOZILLABUILD', nargs=1,
+       help='Path to Mozilla Build (Windows-only)')
new file mode 100644
--- /dev/null
+++ b/build/moz.configure/old.configure
@@ -0,0 +1,402 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+# It feels dirty replicating this from python/mozbuild/mozbuild/mozconfig.py,
+# but the end goal being that the configure script would go away...
+@depends('MOZILLABUILD')
+@advanced
+def shell(mozillabuild):
+    import sys
+
+    shell = 'sh'
+    if mozillabuild:
+        shell = mozillabuild[0] + '/msys/bin/sh'
+    if sys.platform == 'win32':
+        shell = shell + '.exe'
+    return shell
+
+
+option(env='AUTOCONF', nargs=1, help='Path to autoconf 2.13')
+
+@depends(mozconfig, 'AUTOCONF')
+@advanced
+def autoconf(mozconfig, autoconf):
+    import re
+
+    mozconfig_autoconf = None
+    if mozconfig['path']:
+        make_extra = mozconfig['make_extra']
+        if make_extra:
+            for assignment in make_extra:
+                m = re.match('(?:export\s+)?AUTOCONF\s*:?=\s*(.+)$',
+                             assignment)
+                if m:
+                    mozconfig_autoconf = m.group(1)
+
+    autoconf = autoconf[0] if autoconf else None
+
+    for ac in (mozconfig_autoconf, autoconf, 'autoconf-2.13', 'autoconf2.13',
+               'autoconf213'):
+        if ac:
+            autoconf = find_program(ac)
+            if autoconf:
+                break
+    else:
+        fink = find_program('fink')
+        if find:
+            autoconf = os.path.normpath(os.path.join(
+                fink, '..', '..', 'lib', 'autoconf2.13', 'bin', 'autoconf'))
+
+    if not autoconf:
+        error('Could not find autoconf 2.13')
+
+    set_config('AUTOCONF', autoconf)
+    return autoconf
+
+
+option(env='OLD_CONFIGURE', nargs=1, help='Path to the old configure script')
+
+@depends('OLD_CONFIGURE', mozconfig, autoconf, check_build_environment, shell,
+         virtualenv_python, compile_environment)
+@advanced
+def prepare_configure(old_configure, mozconfig, autoconf, build_env, shell,
+                      python, compile_env):
+    import glob
+    import itertools
+    import subprocess
+    import sys
+    # Import getmtime without overwriting the sandbox os.path.
+    from os.path import getmtime
+
+    from mozbuild.shellutil import quote
+
+    if not old_configure:
+        error('The OLD_CONFIGURE environment variable must be set')
+
+    # os.path.abspath in the sandbox will ensure forward slashes on Windows,
+    # which is actually necessary because this path actually ends up literally
+    # as $0, and backslashes there breaks autoconf's detection of the source
+    # directory.
+    old_configure = os.path.abspath(old_configure[0])
+
+    refresh = True
+    if os.path.exists(old_configure):
+        mtime = getmtime(old_configure)
+        aclocal = os.path.join(build_env['TOPSRCDIR'], 'build', 'autoconf',
+                               '*.m4')
+        for input in itertools.chain(
+            (old_configure + '.in',
+             os.path.join(os.path.dirname(old_configure), 'aclocal.m4')),
+            glob.iglob(aclocal),
+        ):
+            if getmtime(input) > mtime:
+                break
+        else:
+            refresh = False
+
+    if refresh:
+        warn('Refreshing %s with %s' % (old_configure, autoconf))
+        with open(old_configure, 'wb') as fh:
+            subprocess.check_call([
+                shell, autoconf,
+                '--localdir=%s' % os.path.dirname(old_configure),
+                old_configure + '.in'], stdout=fh)
+
+    cmd = [shell, old_configure] + sys.argv[1:]
+    with open('old-configure.vars', 'w') as out:
+        if mozconfig['path']:
+            if mozconfig['configure_args']:
+                cmd += mozconfig['configure_args']
+
+            for key, value in mozconfig['env']['added'].items():
+                print("export %s=%s" % (key, quote(value)), file=out)
+            for key, (old, value) in mozconfig['env']['modified'].items():
+                print("export %s=%s" % (key, quote(value)), file=out)
+            for key, value in mozconfig['vars']['added'].items():
+                print("%s=%s" % (key, quote(value)), file=out)
+            for key, (old, value) in mozconfig['vars']['modified'].items():
+                print("%s=%s" % (key, quote(value)), file=out)
+            for t in ('env', 'vars'):
+                for key in mozconfig[t]['removed'].keys():
+                    print("unset %s" % key, file=out)
+
+        print('PYTHON=%s' % quote(python), file=out)
+        if compile_env:
+            print('COMPILE_ENVIRONMENT=1', file=out)
+
+    return cmd
+
+
+@template
+def old_configure_options(*options):
+    for opt in options:
+        option(opt, nargs='*', help='Help missing for old configure options')
+
+    @depends('--help')
+    def all_options(help):
+        return set(options)
+
+    return depends(prepare_configure, all_options, *options)
+
+
+@old_configure_options(
+    '--cache-file',
+    '--enable-accessibility',
+    '--enable-address-sanitizer',
+    '--enable-alsa',
+    '--enable-android-apz',
+    '--enable-android-omx',
+    '--enable-android-resource-constrained',
+    '--enable-application',
+    '--enable-approximate-location',
+    '--enable-b2g-bt',
+    '--enable-b2g-camera',
+    '--enable-b2g-ril',
+    '--enable-bundled-fonts',
+    '--enable-callgrind',
+    '--enable-chrome-format',
+    '--enable-clang-plugin',
+    '--enable-content-sandbox',
+    '--enable-cookies',
+    '--enable-cpp-rtti',
+    '--enable-crashreporter',
+    '--enable-ctypes',
+    '--enable-dbm',
+    '--enable-dbus',
+    '--enable-debug',
+    '--enable-debug-js-modules',
+    '--enable-debug-symbols',
+    '--enable-default-toolkit',
+    '--enable-directshow',
+    '--enable-dmd',
+    '--enable-dtrace',
+    '--enable-dump-painting',
+    '--enable-elf-hack',
+    '--enable-eme',
+    '--enable-export-js',
+    '--enable-extensions',
+    '--enable-faststripe',
+    '--enable-feeds',
+    '--enable-ffmpeg',
+    '--enable-fmp4',
+    '--enable-gamepad',
+    '--enable-gc-trace',
+    '--enable-gconf',
+    '--enable-gczeal',
+    '--enable-gio',
+    '--enable-gnomeui',
+    '--enable-gold',
+    '--enable-gps-debug',
+    '--enable-hardware-aec-ns',
+    '--enable-icf',
+    '--enable-install-strip',
+    '--enable-instruments',
+    '--enable-ion',
+    '--enable-ios-target',
+    '--enable-ipdl-tests',
+    '--enable-jemalloc',
+    '--enable-jitspew',
+    '--enable-jprof',
+    '--enable-libjpeg-turbo',
+    '--enable-libproxy',
+    '--enable-llvm-hacks',
+    '--enable-logrefcnt',
+    '--enable-macos-target',
+    '--enable-maintenance-service',
+    '--enable-media-navigator',
+    '--enable-memory-sanitizer',
+    '--enable-mobile-optimize',
+    '--enable-more-deterministic',
+    '--enable-mozril-geoloc',
+    '--enable-necko-protocols',
+    '--enable-necko-wifi',
+    '--enable-negotiateauth',
+    '--enable-nfc',
+    '--enable-nspr-build',
+    '--enable-official-branding',
+    '--enable-omx-plugin',
+    '--enable-oom-breakpoint',
+    '--enable-optimize',
+    '--enable-parental-controls',
+    '--enable-perf',
+    '--enable-permissions',
+    '--enable-pie',
+    '--enable-png-arm-neon-support',
+    '--enable-posix-nspr-emulation',
+    '--enable-pref-extensions',
+    '--enable-printing',
+    '--enable-profilelocking',
+    '--enable-profiling',
+    '--enable-pulseaudio',
+    '--enable-raw',
+    '--enable-readline',
+    '--enable-reflow-perf',
+    '--enable-release',
+    '--enable-replace-malloc',
+    '--enable-require-all-d3dc-versions',
+    '--enable-rust',
+    '--enable-safe-browsing',
+    '--enable-sandbox',
+    '--enable-shared-js',
+    '--enable-signmar',
+    '--enable-simulator',
+    '--enable-skia',
+    '--enable-skia-gpu',
+    '--enable-small-chunk-size',
+    '--enable-startup-notification',
+    '--enable-startupcache',
+    '--enable-stdcxx-compat',
+    '--enable-strip',
+    '--enable-synth-pico',
+    '--enable-synth-speechd',
+    '--enable-system-cairo',
+    '--enable-system-extension-dirs',
+    '--enable-system-ffi',
+    '--enable-system-hunspell',
+    '--enable-system-pixman',
+    '--enable-system-sqlite',
+    '--enable-systrace',
+    '--enable-tasktracer',
+    '--enable-tests',
+    '--enable-thread-sanitizer',
+    '--enable-trace-logging',
+    '--enable-tree-freetype',
+    '--enable-ui-locale',
+    '--enable-universalchardet',
+    '--enable-update-channel',
+    '--enable-update-packaging',
+    '--enable-updater',
+    '--enable-url-classifier',
+    '--enable-valgrind',
+    '--enable-verify-mar',
+    '--enable-vtune',
+    '--enable-warnings-as-errors',
+    '--enable-webapp-runtime',
+    '--enable-webrtc',
+    '--enable-websms-backend',
+    '--enable-webspeech',
+    '--enable-webspeechtestbackend',
+    '--enable-wmf',
+    '--enable-xterm-updates',
+    '--enable-xul',
+    '--enable-zipwriter',
+    '--host',
+    '--no-create',
+    '--prefix',
+    '--target',
+    '--with-adjust-sdk-keyfile',
+    '--with-android-cxx-stl',
+    '--with-android-distribution-directory',
+    '--with-android-gnu-compiler-version',
+    '--with-android-max-sdk',
+    '--with-android-min-sdk',
+    '--with-android-ndk',
+    '--with-android-sdk',
+    '--with-android-toolchain',
+    '--with-android-version',
+    '--with-app-basename',
+    '--with-app-name',
+    '--with-arch',
+    '--with-arm-kuser',
+    '--with-bing-api-keyfile',
+    '--with-branding',
+    '--with-ccache',
+    '--with-compiler-wrapper',
+    '--with-crashreporter-enable-percent',
+    '--with-cross-lib',
+    '--with-debug-label',
+    '--with-default-mozilla-five-home',
+    '--with-distribution-id',
+    '--with-doc-include-dirs',
+    '--with-doc-input-dirs',
+    '--with-doc-output-dir',
+    '--with-external-source-dir',
+    '--with-float-abi',
+    '--with-fpu',
+    '--with-gl-provider',
+    '--with-gonk',
+    '--with-gonk-toolchain-prefix',
+    '--with-google-api-keyfile',
+    '--with-google-oauth-api-keyfile',
+    '--with-gradle',
+    '--with-intl-api',
+    '--with-ios-sdk',
+    '--with-java-bin-path',
+    '--with-jitreport-granularity',
+    '--with-l10n-base',
+    '--with-libxul-sdk',
+    '--with-linux-headers',
+    '--with-macbundlename-prefix',
+    '--with-macos-private-frameworks',
+    '--with-macos-sdk',
+    '--with-mozilla-api-keyfile',
+    '--with-nspr-cflags',
+    '--with-nspr-libs',
+    '--with-pthreads',
+    '--with-qemu-exe',
+    '--with-qtdir',
+    '--with-servo',
+    '--with-sixgill',
+    '--with-soft-float',
+    '--with-system-bz2',
+    '--with-system-icu',
+    '--with-system-jpeg',
+    '--with-system-libevent',
+    '--with-system-libvpx',
+    '--with-system-nspr',
+    '--with-system-nss',
+    '--with-system-png',
+    '--with-system-zlib',
+    '--with-thumb',
+    '--with-thumb-interwork',
+    '--with-unify-dist',
+    '--with-user-appdir',
+    '--with-windows-version',
+    '--with-x',
+    '--with-xulrunner-stub-name',
+    '--x-includes',
+    '--x-libraries',
+)
+@advanced
+def old_configure(prepare_configure, all_options, *options):
+    import codecs
+    import os
+    import subprocess
+    import sys
+    import types
+
+    ret = subprocess.call(prepare_configure)
+    if ret:
+        sys.exit(ret)
+
+    raw_config = {}
+    encoding = 'mbcs' if sys.platform == 'win32' else 'utf-8'
+    with codecs.open('config.data', 'r', encoding) as fh:
+	code = compile(fh.read(), 'config.data', 'exec')
+	# Every variation of the exec() function I tried led to:
+	# SyntaxError: unqualified exec is not allowed in function 'main' it
+	# contains a nested function with free variables
+	exec code in raw_config
+
+    # Ensure all the flags known to old-configure appear in the
+    # @old_configure_options above.
+    for flag in raw_config['flags']:
+        if flag not in all_options:
+            error('Missing option in `@old_configure_options` in %s: %s'
+                  % (__file__, flag))
+
+    # If the code execution above fails, we want to keep the file around for
+    # debugging.
+    os.remove('config.data')
+
+    config = {}
+    for k, v in raw_config['substs']:
+        set_config(k[1:-1], v[1:-1] if isinstance(v, types.StringTypes) else v)
+
+    for k, v in dict(raw_config['defines']).iteritems():
+        set_define(k[1:-1], v[1:-1])
+
+    set_config('non_global_defines', raw_config['non_global_defines'])
new file mode 100644
--- /dev/null
+++ b/build/moz.configure/util.configure
@@ -0,0 +1,78 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+@template
+@advanced
+def warn(*args):
+    import sys
+    print(*args, file=sys.stderr)
+    sys.stderr.flush()
+
+
+@template
+@advanced
+def error(*args):
+    import sys
+    print(*args, file=sys.stderr)
+    sys.stderr.flush()
+    sys.exit(1)
+
+
+@template
+@advanced
+def is_absolute_or_relative(path):
+    import os
+    if os.altsep and os.altsep in path:
+        return True
+    return os.sep in path
+
+
+@template
+@advanced
+def normsep(path):
+    import mozpack.path as mozpath
+    return mozpath.normsep(path)
+
+
+@template
+@advanced
+def find_program(file):
+    if is_absolute_or_relative(file):
+        return os.path.abspath(file) if os.path.isfile(file) else None
+    from which import which, WhichError
+    try:
+        return normsep(which(file))
+    except WhichError:
+        return None
+
+
+@depends('--help')
+def _defines(help):
+    ret = {}
+    set_config('DEFINES', ret)
+    return ret
+
+
+@template
+def set_define(name, value):
+    @depends(_defines)
+    @advanced
+    def _add_define(defines):
+        from mozbuild.configure import ConfigureError
+        if name in defines:
+            raise ConfigureError("'%s' is already defined" % name)
+        defines[name] = value
+
+del _defines
+
+
+@template
+def unique_list(l):
+    result = []
+    for i in l:
+        if l not in result:
+            result.append(i)
+    return result
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -1545,19 +1545,16 @@ FREEZE_VARIABLES = \
 
 CHECK_FROZEN_VARIABLES = $(foreach var,$(FREEZE_VARIABLES), \
   $(if $(subst $($(var)_FROZEN),,'$($(var))'),$(error Makefile variable '$(var)' changed value after including rules.mk. Was $($(var)_FROZEN), now $($(var)).)))
 
 libs export::
 	$(CHECK_FROZEN_VARIABLES)
 
 PURGECACHES_DIRS ?= $(DIST)/bin
-ifdef MOZ_WEBAPP_RUNTIME
-PURGECACHES_DIRS += $(DIST)/bin/webapprt
-endif
 
 PURGECACHES_FILES = $(addsuffix /.purgecaches,$(PURGECACHES_DIRS))
 
 default all:: $(PURGECACHES_FILES)
 
 $(PURGECACHES_FILES):
 	if test -d $(@D) ; then touch $@ ; fi
 
--- a/configure.py
+++ b/configure.py
@@ -1,123 +1,73 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import print_function, unicode_literals
 
-import glob
-import itertools
+import codecs
+import json
 import os
 import subprocess
 import sys
-import re
-
-base_dir = os.path.dirname(__file__)
-sys.path.append(os.path.join(base_dir, 'python', 'which'))
-sys.path.append(os.path.join(base_dir, 'python', 'mozbuild'))
-from which import which, WhichError
-from mozbuild.mozconfig import MozconfigLoader
 
 
-# If feel dirty replicating this from python/mozbuild/mozbuild/mozconfig.py,
-# but the end goal being that the configure script would go away...
-shell = 'sh'
-if 'MOZILLABUILD' in os.environ:
-    shell = os.environ['MOZILLABUILD'] + '/msys/bin/sh'
-if sys.platform == 'win32':
-    shell = shell + '.exe'
-
-
-def is_absolute_or_relative(path):
-    if os.altsep and os.altsep in path:
-        return True
-    return os.sep in path
-
-
-def find_program(file):
-    if is_absolute_or_relative(file):
-        return os.path.abspath(file) if os.path.isfile(file) else None
-    try:
-        return which(file)
-    except WhichError:
-        return None
+base_dir = os.path.abspath(os.path.dirname(__file__))
+sys.path.append(os.path.join(base_dir, 'python', 'mozbuild'))
+from mozbuild.configure import ConfigureSandbox
 
 
-def autoconf_refresh(configure):
-    if os.path.exists(configure):
-        mtime = os.path.getmtime(configure)
-        aclocal = os.path.join(base_dir, 'build', 'autoconf', '*.m4')
-        for input in itertools.chain(
-            (configure + '.in',
-             os.path.join(os.path.dirname(configure), 'aclocal.m4')),
-            glob.iglob(aclocal),
-        ):
-            if os.path.getmtime(input) > mtime:
-                break
-        else:
-            return
+def main(argv):
+    config = {}
+    sandbox = ConfigureSandbox(config, os.environ, argv)
+    sandbox.run(os.path.join(os.path.dirname(__file__), 'moz.configure'))
+
+    if sandbox._help:
+        return 0
 
-    mozconfig_autoconf = None
-    configure_dir = os.path.dirname(configure)
-    # Don't read the mozconfig for the js configure (yay backwards
-    # compatibility)
-    if not configure_dir.replace(os.sep, '/').endswith('/js/src'):
-        loader = MozconfigLoader(os.path.dirname(configure))
-        project = os.environ.get('MOZ_CURRENT_PROJECT')
-        mozconfig = loader.find_mozconfig(env=os.environ)
-        mozconfig = loader.read_mozconfig(mozconfig, moz_build_app=project)
-        make_extra = mozconfig['make_extra']
-        if make_extra:
-            for assignment in make_extra:
-                m = re.match('(?:export\s+)?AUTOCONF\s*:?=\s*(.+)$',
-                             assignment)
-                if m:
-                    mozconfig_autoconf = m.group(1)
+    # Sanitize config data to feed config.status
+    sanitized_config = {}
+    sanitized_config['substs'] = {
+        k: v for k, v in config.iteritems()
+        if k not in ('DEFINES', 'non_global_defines', 'TOPSRCDIR', 'TOPOBJDIR')
+    }
+    sanitized_config['defines'] = config['DEFINES']
+    sanitized_config['non_global_defines'] = config['non_global_defines']
+    sanitized_config['topsrcdir'] = config['TOPSRCDIR']
+    sanitized_config['topobjdir'] = config['TOPOBJDIR']
 
-    for ac in (mozconfig_autoconf, os.environ.get('AUTOCONF'), 'autoconf-2.13',
-               'autoconf2.13', 'autoconf213'):
-        if ac:
-            autoconf = find_program(ac)
-            if autoconf:
-                break
-    else:
-        fink = find_program('fink')
-        if fink:
-            autoconf = os.path.normpath(os.path.join(
-                fink, '..', '..', 'lib', 'autoconf2.13', 'bin', 'autoconf'))
-
-    if not autoconf:
-        raise RuntimeError('Could not find autoconf 2.13')
-
-    # Add or adjust AUTOCONF for subprocesses, especially the js/src configure
-    os.environ['AUTOCONF'] = autoconf
-
-    print('Refreshing %s with %s' % (configure, autoconf), file=sys.stderr)
+    # Create config.status. Eventually, we'll want to just do the work it does
+    # here, when we're able to skip configure tests/use cached results/not rely
+    # on autoconf.
+    print("Creating config.status", file=sys.stderr)
+    encoding = 'mbcs' if sys.platform == 'win32' else 'utf-8'
+    with codecs.open('config.status', 'w', encoding) as fh:
+        fh.write('#!%s\n' % config['PYTHON'])
+        fh.write('# coding=%s\n' % encoding)
+        for k, v in sanitized_config.iteritems():
+            fh.write('%s = ' % k)
+            json.dump(v, fh, sort_keys=True, indent=4, ensure_ascii=False)
+            fh.write('\n')
+        fh.write("__all__ = ['topobjdir', 'topsrcdir', 'defines', "
+                 "'non_global_defines', 'substs']")
 
-    with open(configure, 'wb') as fh:
-        subprocess.check_call([
-            shell, autoconf, '--localdir=%s' % os.path.dirname(configure),
-            configure + '.in'], stdout=fh)
-
-
-def main(args):
-    old_configure = os.environ.get('OLD_CONFIGURE')
-
-    if not old_configure:
-        raise Exception('The OLD_CONFIGURE environment variable must be set')
+        if not config.get('BUILDING_JS') or config.get('JS_STANDALONE'):
+            fh.write('''
+if __name__ == '__main__':
+    args = dict([(name, globals()[name]) for name in __all__])
+    from mozbuild.config_status import config_status
+    config_status(**args)
+''')
 
-    # We need to replace backslashes with forward slashes on Windows because
-    # this path actually ends up literally as $0, which breaks autoconf's
-    # detection of the source directory.
-    old_configure = os.path.abspath(old_configure).replace(os.sep, '/')
-
-    try:
-        autoconf_refresh(old_configure)
-    except RuntimeError as e:
-        print(e.message, file=sys.stderr)
-        return 1
-
-    return subprocess.call([shell, old_configure] + args)
-
+    # Other things than us are going to run this file, so we need to give it
+    # executable permissions.
+    os.chmod('config.status', 0755)
+    if not config.get('BUILDING_JS') or config.get('JS_STANDALONE'):
+        if not config.get('JS_STANDALONE'):
+            os.environ['WRITE_MOZINFO'] = '1'
+        # Until we have access to the virtualenv from this script, execute
+        # config.status externally, with the virtualenv python.
+        return subprocess.call([config['PYTHON'], 'config.status'])
+    return 0
 
 if __name__ == '__main__':
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv))
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -883,17 +883,17 @@ WebConsoleActor.prototype =
         let error = evalResult.throw;
         errorGrip = this.createValueGrip(error);
         // XXXworkers: Calling unsafeDereference() returns an object with no
         // toString method in workers. See Bug 1215120.
         let unsafeDereference = error && (typeof error === "object") &&
                                 error.unsafeDereference();
         errorMessage = unsafeDereference && unsafeDereference.toString
           ? unsafeDereference.toString()
-          : "" + error;
+          : String(error);
       }
     }
 
     // If a value is encountered that the debugger server doesn't support yet,
     // the console should remain functional.
     let resultGrip;
     try {
       resultGrip = this.createValueGrip(result);
--- a/devtools/shared/apps/tests/unit/head_apps.js
+++ b/devtools/shared/apps/tests/unit/head_apps.js
@@ -80,22 +80,16 @@ function setup() {
   Components.utils.import("resource://testing-common/AppInfo.jsm");
   updateAppInfo();
 
   // We have to toggle this flag in order to have apps being listed in getAll
   // as only launchable apps are returned
   Components.utils.import('resource://gre/modules/Webapps.jsm');
   DOMApplicationRegistry.allAppsLaunchable = true;
 
-  // Mock WebappOSUtils
-  Cu.import("resource://gre/modules/WebappOSUtils.jsm");
-  WebappOSUtils.getPackagePath = function(aApp) {
-    return aApp.basePath + "/" + aApp.id;
-  }
-
   // Enable launch/close method of the webapps actor
   let {WebappsActor} = require("devtools/server/actors/webapps");
   WebappsActor.prototype.supportsLaunch = true;
 }
 
 function do_get_webappsdir() {
   var webappsDir = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
   webappsDir.append("test_webapps");
--- a/devtools/shared/webconsole/test/test_throw.html
+++ b/devtools/shared/webconsole/test/test_throw.html
@@ -57,16 +57,31 @@ function onAttach(aState, aResponse)
       );
       is(aResponse.exceptionMessage.initial, shortedString,
         "exceptionMessage.initial for throw longString"
       );
       nextTest();
     });
   });
 
+  let symbolTestValues = [
+    ["Symbol.iterator", "Symbol(Symbol.iterator)"],
+    ["Symbol('foo')", "Symbol(foo)"],
+    ["Symbol()", "Symbol()"],
+  ];
+  symbolTestValues.forEach(function([expr, message]) {
+    tests.push(function() {
+      aState.client.evaluateJS("throw " + expr + ";", function(aResponse) {
+        is(aResponse.exceptionMessage, message,
+           "response.exception for throw " + expr);
+        nextTest();
+      });
+    });
+  });
+
   runTests(tests, endTest.bind(null, aState));
 }
 
 function endTest(aState)
 {
   closeDebugger(aState, function() {
     SimpleTest.finish();
   });
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3493,16 +3493,21 @@ nsDocShell::CanAccessItem(nsIDocShellTre
   }
 
   if (targetDS->GetIsInIsolatedMozBrowserElement() !=
         accessingDS->GetIsInIsolatedMozBrowserElement() ||
       targetDS->GetAppId() != accessingDS->GetAppId()) {
     return false;
   }
 
+  if (static_cast<nsDocShell*>(targetDS.get())->GetOriginAttributes().mUserContextId !=
+      static_cast<nsDocShell*>(accessingDS.get())->GetOriginAttributes().mUserContextId) {
+    return false;
+  }
+
   // A private document can't access a non-private one, and vice versa.
   if (static_cast<nsDocShell*>(targetDS.get())->UsePrivateBrowsing() !=
       static_cast<nsDocShell*>(accessingDS.get())->UsePrivateBrowsing()) {
     return false;
   }
 
 
   nsCOMPtr<nsIDocShellTreeItem> accessingRoot;
@@ -14186,17 +14191,19 @@ nsDocShell::ShouldPrepareForIntercept(ns
         return NS_OK;
       }
     }
   }
 
   if (aIsNonSubresourceRequest) {
     PrincipalOriginAttributes attrs;
     attrs.InheritFromDocShellToDoc(mOriginAttributes, aURI);
-    *aShouldIntercept = swm->IsAvailable(attrs, aURI);
+    nsCOMPtr<nsIPrincipal> principal =
+      BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
+    *aShouldIntercept = swm->IsAvailable(principal, aURI);
     return NS_OK;
   }
 
   nsCOMPtr<nsIDocument> doc = GetDocument();
   if (!doc) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
--- a/dom/animation/CSSPseudoElement.cpp
+++ b/dom/animation/CSSPseudoElement.cpp
@@ -63,19 +63,19 @@ CSSPseudoElement::GetAnimations(nsTArray
 
 already_AddRefed<Animation>
 CSSPseudoElement::Animate(
     JSContext* aContext,
     JS::Handle<JSObject*> aFrames,
     const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
     ErrorResult& aError)
 {
-  // Bug 1241784: Implement this API.
-  NS_NOTREACHED("CSSPseudoElement::Animate() is not implemented yet.");
-  return nullptr;
+  Nullable<ElementOrCSSPseudoElement> target;
+  target.SetValue().SetAsCSSPseudoElement() = this;
+  return Element::Animate(target, aContext, aFrames, aOptions, aError);
 }
 
 /* static */ already_AddRefed<CSSPseudoElement>
 CSSPseudoElement::GetCSSPseudoElement(Element* aElement,
                                       CSSPseudoElementType aType)
 {
   if (!aElement) {
     return nullptr;
--- a/dom/animation/EffectSet.h
+++ b/dom/animation/EffectSet.h
@@ -29,18 +29,18 @@ enum class CSSPseudoElementType : uint8_
 // A wrapper around a hashset of AnimationEffect objects to handle
 // storing the set as a property of an element.
 class EffectSet
 {
 public:
   EffectSet()
     : mCascadeNeedsUpdate(false)
     , mAnimationGeneration(0)
+#ifdef DEBUG
     , mActiveIterators(0)
-#ifdef DEBUG
     , mCalledPropertyDtor(false)
 #endif
   {
     MOZ_COUNT_CTOR(EffectSet);
   }
 
   ~EffectSet()
   {
@@ -82,38 +82,44 @@ public:
   class Iterator
   {
   public:
     explicit Iterator(EffectSet& aEffectSet)
       : mEffectSet(aEffectSet)
       , mHashIterator(mozilla::Move(aEffectSet.mEffects.Iter()))
       , mIsEndIterator(false)
     {
+#ifdef DEBUG
       mEffectSet.mActiveIterators++;
+#endif
     }
 
     Iterator(Iterator&& aOther)
       : mEffectSet(aOther.mEffectSet)
       , mHashIterator(mozilla::Move(aOther.mHashIterator))
       , mIsEndIterator(aOther.mIsEndIterator)
     {
+#ifdef DEBUG
       mEffectSet.mActiveIterators++;
+#endif
     }
 
     static Iterator EndIterator(EffectSet& aEffectSet)
     {
       Iterator result(aEffectSet);
       result.mIsEndIterator = true;
       return result;
     }
 
     ~Iterator()
     {
+#ifdef DEBUG
       MOZ_ASSERT(mEffectSet.mActiveIterators > 0);
       mEffectSet.mActiveIterators--;
+#endif
     }
 
     bool operator!=(const Iterator& aOther) const {
       if (Done() || aOther.Done()) {
         return Done() != aOther.Done();
       }
       return mHashIterator.Get() != aOther.mHashIterator.Get();
     }
@@ -216,20 +222,20 @@ private:
   // RestyleManager keeps track of the number of animation restyles.
   // 'mini-flushes' (see nsTransitionManager::UpdateAllThrottledStyles()).
   // mAnimationGeneration is the sequence number of the last flush where a
   // transition/animation changed.  We keep a similar count on the
   // corresponding layer so we can check that the layer is up to date with
   // the animation manager.
   uint64_t mAnimationGeneration;
 
+#ifdef DEBUG
   // Track how many iterators are referencing this effect set when we are
   // destroyed, we can assert that nothing is still pointing to us.
-  DebugOnly<uint64_t> mActiveIterators;
+  uint64_t mActiveIterators;
 
-#ifdef DEBUG
   bool mCalledPropertyDtor;
 #endif
 };
 
 } // namespace mozilla
 
 #endif // mozilla_EffectSet_h
--- a/dom/apps/AppsUtils.jsm
+++ b/dom/apps/AppsUtils.jsm
@@ -11,19 +11,16 @@ const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
   "resource://gre/modules/FileUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
-  "resource://gre/modules/WebappOSUtils.jsm");
-
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
   "resource://gre/modules/NetUtil.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
                                    "@mozilla.org/AppsService;1",
                                    "nsIAppsService");
 
 // Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
@@ -380,17 +377,17 @@ this.AppsUtils = {
     // from child process in mochitest.
     let prefName = "dom.mozApps.auto_confirm_install";
     if (Services.prefs.prefHasUserValue(prefName) &&
         Services.prefs.getBoolPref(prefName)) {
       return { "path": app.basePath + "/" + app.id,
                "isCoreApp": isCoreApp };
     }
 
-    return { "path": WebappOSUtils.getPackagePath(app),
+    return { "path": app.basePath + "/" + app.id,
              "isCoreApp": isCoreApp };
   },
 
   /**
     * Remove potential HTML tags from displayable fields in the manifest.
     * We check name, description, developer name, and permission description
     */
   sanitizeManifest: function(aManifest) {
--- a/dom/apps/Webapps.jsm
+++ b/dom/apps/Webapps.jsm
@@ -68,19 +68,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/PermissionsInstaller.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "OfflineCacheInstaller",
   "resource://gre/modules/OfflineCacheInstaller.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SystemMessagePermissionsChecker",
   "resource://gre/modules/SystemMessagePermissionsChecker.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
-  "resource://gre/modules/WebappOSUtils.jsm");
-
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
   "resource://gre/modules/NetUtil.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ScriptPreloader",
                                   "resource://gre/modules/ScriptPreloader.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Langpacks",
                                   "resource://gre/modules/Langpacks.jsm");
@@ -130,19 +127,17 @@ function supportUseCurrentProfile() {
 
 function supportSystemMessages() {
   return Services.prefs.getBoolPref("dom.sysmsg.enabled");
 }
 
 // Minimum delay between two progress events while downloading, in ms.
 const MIN_PROGRESS_EVENT_DELAY = 1500;
 
-const WEBAPP_RUNTIME = Services.appinfo.ID == "webapprt@mozilla.org";
-
-const chromeWindowType = WEBAPP_RUNTIME ? "webapprt:webapp" : "navigator:browser";
+const chromeWindowType = "navigator:browser";
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageBroadcaster");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
@@ -175,20 +170,18 @@ XPCOMUtils.defineLazyGetter(this, "permM
            .getService(Ci.nsIPermissionManager);
 });
 
 #ifdef MOZ_WIDGET_GONK
   const DIRECTORY_NAME = "webappsDir";
 #elifdef ANDROID
   const DIRECTORY_NAME = "webappsDir";
 #else
-  // If we're executing in the context of the webapp runtime, the data files
-  // are in a different directory (currently the Firefox profile that installed
-  // the webapp); otherwise, they're in the current profile.
-  const DIRECTORY_NAME = WEBAPP_RUNTIME ? "WebappRegD" : "ProfD";
+  // Mulet, B2G Desktop, etc.
+  const DIRECTORY_NAME = "ProfD";
 #endif
 
 // We'll use this to identify privileged apps that have been preinstalled
 // For those apps we'll set
 // STORE_ID_PENDING_PREFIX + installOrigin
 // as the storeID. This ensures it's unique and can't be set from a legit
 // store even by error.
 const STORE_ID_PENDING_PREFIX = "#unknownID#";
@@ -2993,17 +2986,17 @@ this.DOMApplicationRegistry = {
 
     if (aUpdateManifest && aUpdateManifest.size) {
       app.downloadSize = aUpdateManifest.size;
     }
 
     app.manifestHash = AppsUtils.computeHash(JSON.stringify(aUpdateManifest ||
                                                             aManifest));
 
-    let zipFile = WebappOSUtils.getPackagePath(app);
+    let zipFile = app.basePath + "/" + app.id;
     app.packageHash = yield this._computeFileHash(zipFile);
 
     app.role = aManifest.role || "";
     if (!AppsUtils.checkAppRole(app.role, app.appStatus)) {
       return;
     }
 
     app.redirects = this.sanitizeRedirects(aManifest.redirects);
@@ -4806,20 +4799,17 @@ this.DOMApplicationRegistry = {
   updateDataStoreEntriesFromLocalId: function(aLocalId) {
     let app = appsService.getAppByLocalId(aLocalId);
     if (app) {
       this.updateDataStoreForApp(app.id);
     }
   },
 
   _isLaunchable: function(aApp) {
-    if (this.allAppsLaunchable)
-      return true;
-
-    return WebappOSUtils.isLaunchable(aApp);
+    return true;
   },
 
   _notifyCategoryAndObservers: function(subject, topic, data,  msg) {
     const serviceMarker = "service,";
 
     // First create observers from the category manager.
     let cm =
       Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
--- a/dom/apps/tests/test_bug_945152.html
+++ b/dom/apps/tests/test_bug_945152.html
@@ -14,29 +14,16 @@ https://bugzilla.mozilla.org/show_bug.cg
   const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
   SimpleTest.waitForExplicitFinish();
 
   const gBaseURL = 'http://test/chrome/dom/apps/tests/';
   const gSJS = gBaseURL + 'file_bug_945152.sjs';
   var gGenerator = runTest();
 
-  // When using SpecialPowers.autoConfirmAppInstall, it skips the local
-  // installation, and we need this mock to get correct package path.
-  Cu.import("resource://gre/modules/Services.jsm");
-  Cu.import("resource://gre/modules/WebappOSUtils.jsm");
-  var oldWebappOSUtils = WebappOSUtils;
-  WebappOSUtils.getPackagePath = function(aApp) {
-    return aApp.basePath + "/" + aApp.id;
-  }
-
-  SimpleTest.registerCleanupFunction(() => {
-    WebappOSUtils = oldWebappOSUtils;
-  });
-
   function go() {
      gGenerator.next();
   }
 
   function continueTest() {
     try { gGenerator.next(); }
     catch (e) { dump("Got exception: " + e + "\n"); }
   }
--- a/dom/apps/tests/test_packaged_app_asmjs.html
+++ b/dom/apps/tests/test_packaged_app_asmjs.html
@@ -18,30 +18,22 @@ const { classes: Cc, interfaces: Ci, uti
 
 SimpleTest.waitForExplicitFinish();
 
 const gBaseURL = 'http://test/chrome/dom/apps/tests/asmjs/';
 const gSJS = gBaseURL + 'asmjs_app.sjs';
 const gManifestURL = gSJS + '?getManifest=true';
 let gGenerator = runTest();
 
-// Mock WebappOSUtils
-Cu.import("resource://gre/modules/WebappOSUtils.jsm");
-let oldWebappOSUtils = WebappOSUtils;
-WebappOSUtils.getPackagePath = function(aApp) {
-  return aApp.basePath + "/" + aApp.id;
-}
-
 // Enable the ScriptPreloader module
 Cu.import("resource://gre/modules/ScriptPreloader.jsm");
 let oldScriptPreloaderEnabled = ScriptPreloader._enabled;
 ScriptPreloader._enabled = true;
 
 SimpleTest.registerCleanupFunction(() => {
-  WebappOSUtils = oldWebappOSUtils;
   ScriptPreloader._enabled = oldScriptPreloaderEnabled;
 });
 
 function go() {
   gGenerator.next();
 }
 
 function continueTest() {
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -132,23 +132,25 @@
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/NodeListBinding.h"
 
 #include "nsStyledElement.h"
 #include "nsXBLService.h"
 #include "nsITextControlElement.h"
 #include "nsITextControlFrame.h"
 #include "nsISupportsImpl.h"
+#include "mozilla/dom/CSSPseudoElement.h"
 #include "mozilla/dom/DocumentFragment.h"
-#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/dom/KeyframeEffectBinding.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/dom/ElementBinding.h"
 #include "mozilla/dom/VRDevice.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Preferences.h"
 #include "nsComputedDOMStyle.h"
-#include "mozilla/Preferences.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsIAtom*
 nsIContent::DoGetID() const
 {
   MOZ_ASSERT(HasID(), "Unexpected call");
@@ -3302,17 +3304,41 @@ Element::MozRequestPointerLock()
 }
 
 already_AddRefed<Animation>
 Element::Animate(JSContext* aContext,
                  JS::Handle<JSObject*> aFrames,
                  const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
                  ErrorResult& aError)
 {
-  nsCOMPtr<nsIGlobalObject> ownerGlobal = GetOwnerGlobal();
+  Nullable<ElementOrCSSPseudoElement> target;
+  target.SetValue().SetAsElement() = this;
+  return Animate(target, aContext, aFrames, aOptions, aError);
+}
+
+/* static */ already_AddRefed<Animation>
+Element::Animate(const Nullable<ElementOrCSSPseudoElement>& aTarget,
+                 JSContext* aContext,
+                 JS::Handle<JSObject*> aFrames,
+                 const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+                 ErrorResult& aError)
+{
+  MOZ_ASSERT(!aTarget.IsNull() &&
+             (aTarget.Value().IsElement() ||
+              aTarget.Value().IsCSSPseudoElement()),
+             "aTarget should be initialized");
+
+  RefPtr<Element> referenceElement;
+  if (aTarget.Value().IsElement()) {
+    referenceElement = &aTarget.Value().GetAsElement();
+  } else {
+    referenceElement = aTarget.Value().GetAsCSSPseudoElement().ParentElement();
+  }
+
+  nsCOMPtr<nsIGlobalObject> ownerGlobal = referenceElement->GetOwnerGlobal();
   if (!ownerGlobal) {
     aError.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
   GlobalObject global(aContext, ownerGlobal->GetGlobalJSObject());
   MOZ_ASSERT(!global.Failed());
 
   // Wrap the aFrames object for the cross-compartment case.
@@ -3322,27 +3348,26 @@ Element::Animate(JSContext* aContext,
   if (js::GetContextCompartment(aContext) !=
       js::GetObjectCompartment(ownerGlobal->GetGlobalJSObject())) {
     ac.emplace(aContext, ownerGlobal->GetGlobalJSObject());
     if (!JS_WrapObject(aContext, &frames)) {
       return nullptr;
     }
   }
 
-  Nullable<ElementOrCSSPseudoElement> target;
-  target.SetValue().SetAsElement() = this;
   RefPtr<KeyframeEffect> effect =
-    KeyframeEffect::Constructor(global, target, frames,
-      TimingParams::FromOptionsUnion(aOptions, target), aError);
+    KeyframeEffect::Constructor(global, aTarget, frames,
+      TimingParams::FromOptionsUnion(aOptions, aTarget), aError);
   if (aError.Failed()) {
     return nullptr;
   }
 
   RefPtr<Animation> animation =
-    Animation::Constructor(global, effect, OwnerDoc()->Timeline(), aError);
+    Animation::Constructor(global, effect,
+                           referenceElement->OwnerDoc()->Timeline(), aError);
   if (aError.Failed()) {
     return nullptr;
   }
 
   if (aOptions.IsKeyframeAnimationOptions()) {
     animation->SetId(aOptions.GetAsKeyframeAnimationOptions().mId);
   }
 
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -31,16 +31,17 @@
 #include "mozilla/dom/Attr.h"
 #include "nsISMILAttr.h"
 #include "mozilla/dom/DOMRect.h"
 #include "nsAttrValue.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/Nullable.h"
 #include "Units.h"
 
 class nsIFrame;
 class nsIDOMMozNamedAttrMap;
 class nsIURI;
 class nsIScrollableFrame;
 class nsAttrValueOrString;
 class nsContentList;
@@ -51,16 +52,17 @@ class nsGlobalWindow;
 class nsICSSDeclaration;
 class nsISMILAttr;
 class nsDocument;
 
 namespace mozilla {
 namespace dom {
   struct ScrollIntoViewOptions;
   struct ScrollToOptions;
+  class ElementOrCSSPseudoElement;
   class UnrestrictedDoubleOrKeyframeAnimationOptions;
 } // namespace dom
 } // namespace mozilla
 
 
 already_AddRefed<nsContentList>
 NS_GetContentList(nsINode* aRootNode,
                   int32_t  aMatchNameSpaceId,
@@ -821,21 +823,30 @@ public:
   {
     return false;
   }
 
   virtual void SetUndoScope(bool aUndoScope, ErrorResult& aError)
   {
   }
 
-  already_AddRefed<Animation> Animate(
-    JSContext* aContext,
-    JS::Handle<JSObject*> aFrames,
-    const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
-    ErrorResult& aError);
+  already_AddRefed<Animation>
+  Animate(JSContext* aContext,
+          JS::Handle<JSObject*> aFrames,
+          const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+          ErrorResult& aError);
+
+  // A helper method that factors out the common functionality needed by
+  // Element::Animate and CSSPseudoElement::Animate
+  static already_AddRefed<Animation>
+  Animate(const Nullable<ElementOrCSSPseudoElement>& aTarget,
+          JSContext* aContext,
+          JS::Handle<JSObject*> aFrames,
+          const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+          ErrorResult& aError);
 
   // Note: GetAnimations will flush style while GetAnimationsUnsorted won't.
   void GetAnimations(nsTArray<RefPtr<Animation>>& aAnimations);
   static void GetAnimationsUnsorted(Element* aElement,
                                     CSSPseudoElementType aPseudoType,
                                     nsTArray<RefPtr<Animation>>& aAnimations);
 
   NS_IMETHOD GetInnerHTML(nsAString& aInnerHTML);
--- a/dom/base/SubtleCrypto.cpp
+++ b/dom/base/SubtleCrypto.cpp
@@ -99,45 +99,46 @@ already_AddRefed<Promise>
 SubtleCrypto::ImportKey(JSContext* cx,
                         const nsAString& format,
                         JS::Handle<JSObject*> keyData,
                         const ObjectOrString& algorithm,
                         bool extractable,
                         const Sequence<nsString>& keyUsages,
                         ErrorResult& aRv)
 {
-  SUBTLECRYPTO_METHOD_BODY(ImportKey, aRv, cx, format, keyData, algorithm,
-                           extractable, keyUsages)
+  SUBTLECRYPTO_METHOD_BODY(ImportKey, aRv, mParent, cx, format, keyData,
+                           algorithm, extractable, keyUsages)
 }
 
 already_AddRefed<Promise>
 SubtleCrypto::ExportKey(const nsAString& format,
                         CryptoKey& key,
                         ErrorResult& aRv)
 {
   SUBTLECRYPTO_METHOD_BODY(ExportKey, aRv, format, key)
 }
 
 already_AddRefed<Promise>
 SubtleCrypto::GenerateKey(JSContext* cx, const ObjectOrString& algorithm,
                           bool extractable, const Sequence<nsString>& keyUsages,
                           ErrorResult& aRv)
 {
-  SUBTLECRYPTO_METHOD_BODY(GenerateKey, aRv, cx, algorithm, extractable, keyUsages)
+  SUBTLECRYPTO_METHOD_BODY(GenerateKey, aRv, mParent, cx, algorithm,
+                           extractable, keyUsages)
 }
 
 already_AddRefed<Promise>
 SubtleCrypto::DeriveKey(JSContext* cx,
                         const ObjectOrString& algorithm,
                         CryptoKey& baseKey,
                         const ObjectOrString& derivedKeyType,
                         bool extractable, const Sequence<nsString>& keyUsages,
                         ErrorResult& aRv)
 {
-  SUBTLECRYPTO_METHOD_BODY(DeriveKey, aRv, cx, algorithm, baseKey,
+  SUBTLECRYPTO_METHOD_BODY(DeriveKey, aRv, mParent, cx, algorithm, baseKey,
                            derivedKeyType, extractable, keyUsages)
 }
 
 already_AddRefed<Promise>
 SubtleCrypto::DeriveBits(JSContext* cx,
                          const ObjectOrString& algorithm,
                          CryptoKey& baseKey,
                          uint32_t length,
@@ -163,15 +164,15 @@ SubtleCrypto::UnwrapKey(JSContext* cx,
                         const ArrayBufferViewOrArrayBuffer& wrappedKey,
                         CryptoKey& unwrappingKey,
                         const ObjectOrString& unwrapAlgorithm,
                         const ObjectOrString& unwrappedKeyAlgorithm,
                         bool extractable,
                         const Sequence<nsString>& keyUsages,
                         ErrorResult& aRv)
 {
-  SUBTLECRYPTO_METHOD_BODY(UnwrapKey, aRv, cx, format, wrappedKey, unwrappingKey,
-                           unwrapAlgorithm, unwrappedKeyAlgorithm,
-                           extractable, keyUsages)
+  SUBTLECRYPTO_METHOD_BODY(UnwrapKey, aRv, mParent, cx, format, wrappedKey,
+                           unwrappingKey, unwrapAlgorithm,
+                           unwrappedKeyAlgorithm, extractable, keyUsages)
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -481,17 +481,17 @@ public:
    *
    * Note that in the case that this node is a document node this method
    * will return |this|.  That is different to the Node.ownerDocument DOM
    * attribute (implemented by nsINode::GetOwnerDocument) which is specified to
    * be null in that case:
    *
    * https://dom.spec.whatwg.org/#dom-node-ownerdocument
    *
-   * For all other cases GetOwnerDoc and GetOwnerDocument behave identically.
+   * For all other cases OwnerDoc and GetOwnerDocument behave identically.
    */
   nsIDocument *OwnerDoc() const
   {
     return mNodeInfo->GetDocument();
   }
 
   /**
    * Return the "owner document" of this node as an nsINode*.  Implemented
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -256,16 +256,17 @@ support-files =
   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
+  websocket_tests.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]
--- a/dom/base/test/test_websocket1.html
+++ b/dom/base/test/test_websocket1.html
@@ -1,251 +1,21 @@
 <!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>
+  <script type="text/javascript" src="websocket_tests.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
@@ -254,15 +24,19 @@ var tests = [
   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();
 }
 
+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>
 
 <div id="feedback">
 </div>
 
 </body>
 </html>
--- a/dom/base/test/test_websocket2.html
+++ b/dom/base/test/test_websocket2.html
@@ -1,253 +1,21 @@
 <!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>
+  <script type="text/javascript" src="websocket_tests.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;
@@ -258,15 +26,19 @@ var tests = [
           // connection;
   test20, // see bug 572975 - only on error and onclose event listeners set
 ];
 
 function testWebSocket() {
   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();
+
 </script>
 
 <div id="feedback">
 </div>
 
 </body>
 </html>
--- a/dom/base/test/test_websocket3.html
+++ b/dom/base/test/test_websocket3.html
@@ -1,206 +1,21 @@
 <!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>
+  <script type="text/javascript" src="websocket_tests.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
@@ -211,15 +26,19 @@ var tests = [
  test30, // ctor using valid 1 element sub-protocol array with element server
          // will reject
 ];
 
 function testWebSocket() {
   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();
+
 </script>
 
 <div id="feedback">
 </div>
 
 </body>
 </html>
--- a/dom/base/test/test_websocket4.html
+++ b/dom/base/test/test_websocket4.html
@@ -1,273 +1,21 @@
 <!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>
+  <script type="text/javascript" src="websocket_tests.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
@@ -276,15 +24,19 @@ var tests = [
   test39, // a basic wss:// connectivity test
   test40, // negative test for wss:// with no cert
 ];
 
 function testWebSocket() {
   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();
+
 </script>
 
 <div id="feedback">
 </div>
 
 </body>
 </html>
--- a/dom/base/test/test_websocket5.html
+++ b/dom/base/test/test_websocket5.html
@@ -1,293 +1,38 @@
 <!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>
+  <script type="text/javascript" src="websocket_tests.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();
 }
 
+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>
 
 <div id="feedback">
 </div>
 
 </body>
 </html>
--- a/dom/base/test/websocket_helpers.js
+++ b/dom/base/test/websocket_helpers.js
@@ -42,21 +42,25 @@ function CreateTestWS(ws_location, ws_pr
   return ws;
 }
 
 function forcegc() {
   SpecialPowers.forceGC();
   SpecialPowers.gc();
 }
 
+function feedback() {
+  $("feedback").innerHTML = "executing test: " + (current_test+1) + " of " + tests.length + " tests.";
+}
+
+function finish() {
+  SimpleTest.finish();
+}
+
 function doTest() {
   if (current_test >= tests.length) {
-    SimpleTest.finish();
+    finish();
     return;
   }
 
-  $("feedback").innerHTML = "executing test: " + (current_test+1) + " of " + tests.length + " tests.";
+  feedback();
   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();
new file mode 100644
--- /dev/null
+++ b/dom/base/test/websocket_tests.js
@@ -0,0 +1,1215 @@
+// test1: client tries to connect to a http scheme location;
+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();
+  });
+}
+
+// test2: assure serialization of the connections;
+// 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();
+    }
+  });
+}
+
+// test3: client tries to connect to an non-existent ws server;
+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();
+    }
+  });
+}
+
+// test4: client tries to connect using a relative url;
+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();
+  });
+}
+
+// test5: client uses an invalid protocol value;
+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();
+  });
+}
+
+// test6: counter and encoding check;
+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();
+    }
+  });
+}
+
+// test7: onmessage event origin property check
+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();
+    }
+  });
+}
+
+// test8: client calls close() and the server sends the close frame (with no
+//        code or reason) in acknowledgement;
+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();
+    }
+  });
+}
+
+// test9: client closes the connection before the ws connection is established;
+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();
+  });
+}
+
+// test10: client sends a message before the ws connection is established;
+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();
+    }
+  });
+}
+
+// test11: a simple hello echo;
+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();
+    }
+  });
+}
+
+// test12: client sends a message containing unpaired surrogates
+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();
+    }
+  });
+}
+
+// test13: server sends an invalid message;
+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();
+    }
+  });
+}
+
+// test14: server sends the close frame, it doesn't close the tcp connection
+//         and it keeps sending normal ws messages;
+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();
+    };
+  });
+}
+
+// test15: server closes the tcp connection, but it doesn't send the close
+//         frame;
+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() {
+    }
+  });
+}
+
+// test16: client calls close() and tries to send a message;
+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();
+    }
+  });
+}
+
+// test17: see bug 572975 - all event listeners set
+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.
+
+// test18: client tries to connect to an http resource;
+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();
+    }
+  });
+}
+
+// test19: server closes the tcp connection before establishing the ws
+//         connection;
+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();
+    }
+  });
+}
+
+// test20: see bug 572975 - only on error and onclose event listeners set
+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();
+  });
+}
+
+// test21: see bug 572975 - same as test 17, but delete strong event listeners
+//         when receiving the message event;
+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();
+  });
+}
+
+// test22: server takes too long to establish the ws connection;
+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);
+  });
+}
+
+// test23: should detect WebSocket on window object;
+function test23() {
+  return new Promise(function(resolve, reject) {
+    ok("WebSocket" in window, "WebSocket should be available on window object");
+    resolve();
+  });
+}
+
+// test24: server rejects sub-protocol string
+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() {
+    }
+  });
+}
+
+// test25: ctor with valid empty sub-protocol array
+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();
+    }
+  });
+}
+
+// test26: ctor with invalid sub-protocol array containing 1 empty element
+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();
+  });
+}
+
+// test27: ctor with invalid sub-protocol array containing an empty element in
+//         list
+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();
+  });
+}
+
+// test28: ctor using valid 1 element sub-protocol array
+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();
+    }
+  });
+}
+
+// test29: ctor using all valid 5 element sub-protocol array
+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();
+    }
+  });
+}
+
+// test30: ctor using valid 1 element sub-protocol array with element server
+//         will reject
+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() {
+    }
+  });
+}
+
+// test31: ctor using valid 2 element sub-protocol array with 1 element server
+//         will reject and one server will accept
+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();
+    }
+  });
+}
+
+// test32: ctor using invalid sub-protocol array that contains duplicate items
+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();
+  });
+}
+
+// test33: test for sending/receiving custom close code (but no close reason)
+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();
+    }
+  });
+}
+
+// test34: test for receiving custom close code and reason
+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();
+    }
+  });
+}
+
+// test35: test for sending custom close code and reason
+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();
+      }
+    }
+  });
+}
+
+// test36: negative test for sending out of range close code
+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();
+    }
+  });
+}
+
+// test37: negative test for too long of a close reason
+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();
+        }
+      }
+    }
+  });
+}
+
+// test38: ensure extensions attribute is defined
+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();
+    }
+  });
+}
+
+// test39: a basic wss:// connectivity test
+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();
+    }
+  });
+}
+
+// test40: negative test for wss:// with no cert
+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();
+    }
+  });
+}
+
+// test41: HSTS
+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();
+         }
+       }
+    }
+  });
+}
+
+// test42: non-char utf-8 sequences
+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();
+    }
+  });
+}
+
+// test43: Test setting binaryType attribute
+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();
+    }
+  });
+}
+
+// test44: Test sending/receving binary ArrayBuffer
+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();
+    }
+  });
+}
+
+// test45: Test sending/receving binary Blob
+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();
+    });
+  });
+}
+
+// test46: Test that we don't dispatch incoming msgs once in CLOSING state
+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();
+    }
+  });
+}
+
+// test47: Make sure onerror/onclose aren't called during close()
+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);
+  });
+}
--- a/dom/battery/BatteryManager.cpp
+++ b/dom/battery/BatteryManager.cpp
@@ -135,25 +135,28 @@ BatteryManager::UpdateFromBatteryInfo(co
 
   // Round to the nearest ten percent for non-chrome and non-certified apps
   nsIDocument* doc = GetOwner()->GetDoc();
   uint16_t status = nsIPrincipal::APP_STATUS_NOT_INSTALLED;
   if (doc) {
     doc->NodePrincipal()->GetAppStatus(&status);
   }
 
+  mCharging = aBatteryInfo.charging();
+  mRemainingTime = aBatteryInfo.remainingTime();
+
   if (!nsContentUtils::IsChromeDoc(doc) &&
       status != nsIPrincipal::APP_STATUS_CERTIFIED)
   {
     mLevel = lround(mLevel * 10.0) / 10.0;
+    if (mLevel == 1.0) {
+      mRemainingTime = mCharging ? kDefaultRemainingTime : kUnknownRemainingTime;
+    }
   }
 
-  mCharging = aBatteryInfo.charging();
-  mRemainingTime = aBatteryInfo.remainingTime();
-
   // Add some guards to make sure the values are coherent.
   if (mLevel == 1.0 && mCharging == true &&
       mRemainingTime != kDefaultRemainingTime) {
     mRemainingTime = kDefaultRemainingTime;
     NS_ERROR("Battery API: When charging and level at 1.0, remaining time "
              "should be 0. Please fix your backend!");
   }
 }
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3494,23 +3494,19 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
                                      mDrawTarget,
                                      mAppUnitsPerDevPixel,
                                      flags,
                                      mMissingFonts);
   }
 
   virtual nscoord GetWidth()
   {
-    gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0,
-                                                               mTextRun->GetLength(),
-                                                               mDoMeasureBoundingBox ?
-                                                                 gfxFont::TIGHT_INK_EXTENTS :
-                                                                 gfxFont::LOOSE_INK_EXTENTS,
-                                                               mDrawTarget,
-                                                               nullptr);
+    gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(
+        mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS
+                              : gfxFont::LOOSE_INK_EXTENTS, mDrawTarget);
 
     // this only measures the height; the total width is gotten from the
     // the return value of ProcessText.
     if (mDoMeasureBoundingBox) {
       textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel);
       mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox);
     }
 
@@ -3530,23 +3526,20 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
     // offset is given in terms of left side of string
     if (rtl) {
       // Bug 581092 - don't use rounded pixel width to advance to
       // right-hand end of run, because this will cause different
       // glyph positioning for LTR vs RTL drawing of the same
       // glyph string on OS X and DWrite where textrun widths may
       // involve fractional pixels.
       gfxTextRun::Metrics textRunMetrics =
-        mTextRun->MeasureText(0,
-                              mTextRun->GetLength(),
-                              mDoMeasureBoundingBox ?
-                                  gfxFont::TIGHT_INK_EXTENTS :
-                                  gfxFont::LOOSE_INK_EXTENTS,
-                              mDrawTarget,
-                              nullptr);
+        mTextRun->MeasureText(mDoMeasureBoundingBox ?
+                                gfxFont::TIGHT_INK_EXTENTS :
+                                gfxFont::LOOSE_INK_EXTENTS,
+                              mDrawTarget);
       inlineCoord += textRunMetrics.mAdvanceWidth;
       // old code was:
       //   point.x += width * mAppUnitsPerDevPixel;
       // TODO: restore this if/when we move to fractional coords
       // throughout the text layout process
     }
 
     uint32_t numRuns;
--- a/dom/canvas/WebGLContextLossHandler.h
+++ b/dom/canvas/WebGLContextLossHandler.h
@@ -21,17 +21,19 @@ class WebGLContext;
 class WebGLContextLossHandler : public dom::workers::WorkerFeature
 {
     WeakPtr<WebGLContext> mWeakWebGL;
     nsCOMPtr<nsITimer> mTimer;
     bool mIsTimerRunning;
     bool mShouldRunTimerAgain;
     bool mIsDisabled;
     bool mFeatureAdded;
-    DebugOnly<nsIThread*> mThread;
+#ifdef DEBUG
+    nsIThread* mThread;
+#endif
 
 public:
     NS_INLINE_DECL_REFCOUNTING(WebGLContextLossHandler)
 
     explicit WebGLContextLossHandler(WebGLContext* webgl);
 
     void RunTimer();
     void DisableTimer();
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -1327,34 +1327,26 @@ private:
 
     return rv;
   }
 };
 
 class ImportKeyTask : public WebCryptoTask
 {
 public:
-  void Init(JSContext* aCx,
-      const nsAString& aFormat,
-      const ObjectOrString& aAlgorithm, bool aExtractable,
-      const Sequence<nsString>& aKeyUsages)
+  void Init(nsIGlobalObject* aGlobal, JSContext* aCx,
+      const nsAString& aFormat, const ObjectOrString& aAlgorithm,
+      bool aExtractable, const Sequence<nsString>& aKeyUsages)
   {
     mFormat = aFormat;
     mDataIsSet = false;
     mDataIsJwk = false;
 
-    // Get the current global object from the context
-    nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
-    if (!global) {
-      mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR;
-      return;
-    }
-
     // This stuff pretty much always happens, so we'll do it here
-    mKey = new CryptoKey(global);
+    mKey = new CryptoKey(aGlobal);
     mKey->SetExtractable(aExtractable);
     mKey->ClearUsages();
     for (uint32_t i = 0; i < aKeyUsages.Length(); ++i) {
       mEarlyRv = mKey->AddUsage(aKeyUsages[i]);
       if (NS_FAILED(mEarlyRv)) {
         return;
       }
     }
@@ -1467,48 +1459,47 @@ private:
     mKey = nullptr;
   }
 };
 
 
 class ImportSymmetricKeyTask : public ImportKeyTask
 {
 public:
-  ImportSymmetricKeyTask(JSContext* aCx,
+  ImportSymmetricKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
       const nsAString& aFormat,
       const ObjectOrString& aAlgorithm, bool aExtractable,
       const Sequence<nsString>& aKeyUsages)
   {
-    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
   }
 
-  ImportSymmetricKeyTask(JSContext* aCx,
+  ImportSymmetricKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
       const nsAString& aFormat, const JS::Handle<JSObject*> aKeyData,
       const ObjectOrString& aAlgorithm, bool aExtractable,
       const Sequence<nsString>& aKeyUsages)
   {
-    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
     SetKeyData(aCx, aKeyData);
     NS_ENSURE_SUCCESS_VOID(mEarlyRv);
     if (mDataIsJwk && !mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
       mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
       return;
     }
   }
 
-  void Init(JSContext* aCx,
-      const nsAString& aFormat,
+  void Init(nsIGlobalObject* aGlobal, JSContext* aCx, const nsAString& aFormat,
       const ObjectOrString& aAlgorithm, bool aExtractable,
       const Sequence<nsString>& aKeyUsages)
   {
-    ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    ImportKeyTask::Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
     // This task only supports raw and JWK format.
     if (!mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
         !mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
       mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
@@ -1625,48 +1616,48 @@ public:
 
 private:
   nsString mHashName;
 };
 
 class ImportRsaKeyTask : public ImportKeyTask
 {
 public:
-  ImportRsaKeyTask(JSContext* aCx,
+  ImportRsaKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
       const nsAString& aFormat,
       const ObjectOrString& aAlgorithm, bool aExtractable,
       const Sequence<nsString>& aKeyUsages)
   {
-    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
   }
 
-  ImportRsaKeyTask(JSContext* aCx,
+  ImportRsaKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
       const nsAString& aFormat, JS::Handle<JSObject*> aKeyData,
       const ObjectOrString& aAlgorithm, bool aExtractable,
       const Sequence<nsString>& aKeyUsages)
   {
-    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
     SetKeyData(aCx, aKeyData);
     NS_ENSURE_SUCCESS_VOID(mEarlyRv);
     if (mDataIsJwk && !mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
       mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
       return;
     }
   }
 
-  void Init(JSContext* aCx,
+  void Init(nsIGlobalObject* aGlobal, JSContext* aCx,
       const nsAString& aFormat,
       const ObjectOrString& aAlgorithm, bool aExtractable,
       const Sequence<nsString>& aKeyUsages)
   {
-    ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    ImportKeyTask::Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
     // If this is RSA with a hash, cache the hash name
     if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
         mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
         mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
@@ -1793,42 +1784,42 @@ private:
 
     return NS_OK;
   }
 };
 
 class ImportEcKeyTask : public ImportKeyTask
 {
 public:
-  ImportEcKeyTask(JSContext* aCx, const nsAString& aFormat,
+  ImportEcKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+                  const nsAString& aFormat, const ObjectOrString& aAlgorithm,
+                  bool aExtractable, const Sequence<nsString>& aKeyUsages)
+  {
+    Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+  }
+
+  ImportEcKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+                  const nsAString& aFormat, JS::Handle<JSObject*> aKeyData,
                   const ObjectOrString& aAlgorithm, bool aExtractable,
                   const Sequence<nsString>& aKeyUsages)
   {
-    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
-  }
-
-  ImportEcKeyTask(JSContext* aCx, const nsAString& aFormat,
-                  JS::Handle<JSObject*> aKeyData,
-                  const ObjectOrString& aAlgorithm, bool aExtractable,
-                  const Sequence<nsString>& aKeyUsages)
-  {
-    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
     SetKeyData(aCx, aKeyData);
     NS_ENSURE_SUCCESS_VOID(mEarlyRv);
   }
 
-  void Init(JSContext* aCx, const nsAString& aFormat,
+  void Init(nsIGlobalObject* aGlobal, JSContext* aCx, const nsAString& aFormat,
             const ObjectOrString& aAlgorithm, bool aExtractable,
             const Sequence<nsString>& aKeyUsages)
   {
-    ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    ImportKeyTask::Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
     if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
       RootedDictionary<EcKeyImportParams> params(aCx);
       mEarlyRv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(mEarlyRv) || !params.mNamedCurve.WasPassed()) {
@@ -1946,40 +1937,40 @@ private:
 
     return NS_OK;
   }
 };
 
 class ImportDhKeyTask : public ImportKeyTask
 {
 public:
-  ImportDhKeyTask(JSContext* aCx, const nsAString& aFormat,
+  ImportDhKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+                  const nsAString& aFormat, const ObjectOrString& aAlgorithm,
+                  bool aExtractable, const Sequence<nsString>& aKeyUsages)
+  {
+    Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+  }
+
+  ImportDhKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+                  const nsAString& aFormat, JS::Handle<JSObject*> aKeyData,
                   const ObjectOrString& aAlgorithm, bool aExtractable,
                   const Sequence<nsString>& aKeyUsages)
   {
-    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
-  }
-
-  ImportDhKeyTask(JSContext* aCx, const nsAString& aFormat,
-                  JS::Handle<JSObject*> aKeyData,
-                  const ObjectOrString& aAlgorithm, bool aExtractable,
-                  const Sequence<nsString>& aKeyUsages)
-  {
-    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_SUCCEEDED(mEarlyRv)) {
       SetKeyData(aCx, aKeyData);
       NS_ENSURE_SUCCESS_VOID(mEarlyRv);
     }
   }
 
-  void Init(JSContext* aCx, const nsAString& aFormat,
+  void Init(nsIGlobalObject* aGlobal, JSContext* aCx, const nsAString& aFormat,
             const ObjectOrString& aAlgorithm, bool aExtractable,
             const Sequence<nsString>& aKeyUsages)
   {
-    ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    ImportKeyTask::Init(aGlobal, aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
     if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
       RootedDictionary<DhImportKeyParams> params(aCx);
       mEarlyRv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(mEarlyRv)) {
@@ -2192,28 +2183,22 @@ private:
     TypedArrayCreator<ArrayBuffer> ret(mResult);
     mResultPromise->MaybeResolve(ret);
   }
 };
 
 class GenerateSymmetricKeyTask : public WebCryptoTask
 {
 public:
-  GenerateSymmetricKeyTask(JSContext* aCx,
+  GenerateSymmetricKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
       const ObjectOrString& aAlgorithm, bool aExtractable,
       const Sequence<nsString>& aKeyUsages)
   {
-    nsIGlobalObject* global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
-    if (!global) {
-      mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR;
-      return;
-    }
-
     // Create an empty key and set easy attributes
-    mKey = new CryptoKey(global);
+    mKey = new CryptoKey(aGlobal);
     mKey->SetExtractable(aExtractable);
     mKey->SetType(CryptoKey::SECRET);
 
     // Extract algorithm name
     nsString algName;
     mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
     if (NS_FAILED(mEarlyRv)) {
       mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
@@ -2321,35 +2306,29 @@ private:
 
   virtual void Cleanup() override
   {
     mKey = nullptr;
   }
 };
 
 GenerateAsymmetricKeyTask::GenerateAsymmetricKeyTask(
-    JSContext* aCx, const ObjectOrString& aAlgorithm, bool aExtractable,
-    const Sequence<nsString>& aKeyUsages)
+    nsIGlobalObject* aGlobal, JSContext* aCx, const ObjectOrString& aAlgorithm,
+    bool aExtractable, const Sequence<nsString>& aKeyUsages)
   : mKeyPair(new CryptoKeyPair())
 {
-  nsIGlobalObject* global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
-  if (!global) {
-    mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR;
-    return;
-  }
-
   mArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
   if (!mArena) {
     mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR;
     return;
   }
 
   // Create an empty key pair and set easy attributes
-  mKeyPair->mPrivateKey = new CryptoKey(global);
-  mKeyPair->mPublicKey = new CryptoKey(global);
+  mKeyPair->mPrivateKey = new CryptoKey(aGlobal);
+  mKeyPair->mPublicKey = new CryptoKey(aGlobal);
 
   // Extract algorithm name
   mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, mAlgName);
   if (NS_FAILED(mEarlyRv)) {
     mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
     return;
   }
 
@@ -2848,29 +2827,29 @@ private:
     return NS_OK;
   }
 };
 
 template<class DeriveBitsTask>
 class DeriveKeyTask : public DeriveBitsTask
 {
 public:
-  DeriveKeyTask(JSContext* aCx,
+  DeriveKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
                 const ObjectOrString& aAlgorithm, CryptoKey& aBaseKey,
                 const ObjectOrString& aDerivedKeyType, bool aExtractable,
                 const Sequence<nsString>& aKeyUsages)
     : DeriveBitsTask(aCx, aAlgorithm, aBaseKey, aDerivedKeyType)
     , mResolved(false)
   {
     if (NS_FAILED(this->mEarlyRv)) {
       return;
     }
 
     NS_NAMED_LITERAL_STRING(format, WEBCRYPTO_KEY_FORMAT_RAW);
-    mTask = new ImportSymmetricKeyTask(aCx, format, aDerivedKeyType,
+    mTask = new ImportSymmetricKeyTask(aGlobal, aCx, format, aDerivedKeyType,
                                        aExtractable, aKeyUsages);
   }
 
 protected:
   RefPtr<ImportSymmetricKeyTask> mTask;
   bool mResolved;
 
 private:
@@ -3290,17 +3269,18 @@ WebCryptoTask::CreateDigestTask(JSContex
       algName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
     return new DigestTask(aCx, aAlgorithm, aData);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
-WebCryptoTask::CreateImportKeyTask(JSContext* aCx,
+WebCryptoTask::CreateImportKeyTask(nsIGlobalObject* aGlobal,
+                                   JSContext* aCx,
                                    const nsAString& aFormat,
                                    JS::Handle<JSObject*> aKeyData,
                                    const ObjectOrString& aAlgorithm,
                                    bool aExtractable,
                                    const Sequence<nsString>& aKeyUsages)
 {
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_IMPORTKEY);
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_IMPORT, aExtractable);
@@ -3328,29 +3308,29 @@ WebCryptoTask::CreateImportKeyTask(JSCon
   // However, the spec should be updated to allow it.
   if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_HKDF) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
-    return new ImportSymmetricKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
-                                      aExtractable, aKeyUsages);
+    return new ImportSymmetricKeyTask(aGlobal, aCx, aFormat, aKeyData,
+                                      aAlgorithm, aExtractable, aKeyUsages);
   } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
              algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
              algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
-    return new ImportRsaKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
+    return new ImportRsaKeyTask(aGlobal, aCx, aFormat, aKeyData, aAlgorithm,
                                 aExtractable, aKeyUsages);
   } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
              algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
-    return new ImportEcKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
+    return new ImportEcKeyTask(aGlobal, aCx, aFormat, aKeyData, aAlgorithm,
                                aExtractable, aKeyUsages);
   } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
-    return new ImportDhKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
+    return new ImportDhKeyTask(aGlobal, aCx, aFormat, aKeyData, aAlgorithm,
                                aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 }
 
 WebCryptoTask*
 WebCryptoTask::CreateExportKeyTask(const nsAString& aFormat,
@@ -3389,17 +3369,18 @@ WebCryptoTask::CreateExportKeyTask(const
       algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
     return new ExportKeyTask(aFormat, aKey);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
-WebCryptoTask::CreateGenerateKeyTask(JSContext* aCx,
+WebCryptoTask::CreateGenerateKeyTask(nsIGlobalObject* aGlobal,
+                                     JSContext* aCx,
                                      const ObjectOrString& aAlgorithm,
                                      bool aExtractable,
                                      const Sequence<nsString>& aKeyUsages)
 {
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_GENERATEKEY);
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_EXTRACTABLE_GENERATE, aExtractable);
 
   // Verify that aKeyUsages does not contain an unrecognized value
@@ -3415,31 +3396,34 @@ WebCryptoTask::CreateGenerateKeyTask(JSC
     return new FailureTask(rv);
   }
 
   if (algName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_AES_KW) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_HMAC)) {
-    return new GenerateSymmetricKeyTask(aCx, aAlgorithm, aExtractable, aKeyUsages);
+    return new GenerateSymmetricKeyTask(aGlobal, aCx, aAlgorithm, aExtractable,
+                                        aKeyUsages);
   } else if (algName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
              algName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP) ||
              algName.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS) ||
              algName.EqualsASCII(WEBCRYPTO_ALG_ECDH) ||
              algName.EqualsASCII(WEBCRYPTO_ALG_ECDSA) ||
              algName.EqualsASCII(WEBCRYPTO_ALG_DH)) {
-    return new GenerateAsymmetricKeyTask(aCx, aAlgorithm, aExtractable, aKeyUsages);
+    return new GenerateAsymmetricKeyTask(aGlobal, aCx, aAlgorithm, aExtractable,
+                                         aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 }
 
 WebCryptoTask*
-WebCryptoTask::CreateDeriveKeyTask(JSContext* aCx,
+WebCryptoTask::CreateDeriveKeyTask(nsIGlobalObject* aGlobal,
+                                   JSContext* aCx,
                                    const ObjectOrString& aAlgorithm,
                                    CryptoKey& aBaseKey,
                                    const ObjectOrString& aDerivedKeyType,
                                    bool aExtractable,
                                    const Sequence<nsString>& aKeyUsages)
 {
   Telemetry::Accumulate(Telemetry::WEBCRYPTO_METHOD, TM_DERIVEKEY);
 
@@ -3455,31 +3439,31 @@ WebCryptoTask::CreateDeriveKeyTask(JSCon
 
   nsString algName;
   nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
   if (NS_FAILED(rv)) {
     return new FailureTask(rv);
   }
 
   if (algName.EqualsASCII(WEBCRYPTO_ALG_HKDF)) {
-    return new DeriveKeyTask<DeriveHkdfBitsTask>(aCx, aAlgorithm, aBaseKey,
-                                                 aDerivedKeyType, aExtractable,
-                                                 aKeyUsages);
+    return new DeriveKeyTask<DeriveHkdfBitsTask>(aGlobal, aCx, aAlgorithm,
+                                                 aBaseKey, aDerivedKeyType,
+                                                 aExtractable, aKeyUsages);
   }
 
   if (algName.EqualsASCII(WEBCRYPTO_ALG_PBKDF2)) {
-    return new DeriveKeyTask<DerivePbkdfBitsTask>(aCx, aAlgorithm, aBaseKey,
-                                                  aDerivedKeyType, aExtractable,
-                                                  aKeyUsages);
+    return new DeriveKeyTask<DerivePbkdfBitsTask>(aGlobal, aCx, aAlgorithm,
+                                                  aBaseKey, aDerivedKeyType,
+                                                  aExtractable, aKeyUsages);
   }
 
   if (algName.EqualsASCII(WEBCRYPTO_ALG_ECDH)) {
-    return new DeriveKeyTask<DeriveEcdhBitsTask>(aCx, aAlgorithm, aBaseKey,
-                                                 aDerivedKeyType, aExtractable,
-                                                 aKeyUsages);
+    return new DeriveKeyTask<DeriveEcdhBitsTask>(aGlobal, aCx, aAlgorithm,
+                                                 aBaseKey, aDerivedKeyType,
+                                                 aExtractable, aKeyUsages);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
 WebCryptoTask::CreateDeriveBitsTask(JSContext* aCx,
                                     const ObjectOrString& aAlgorithm,
@@ -3563,17 +3547,18 @@ WebCryptoTask::CreateWrapKeyTask(JSConte
     return new WrapKeyTask<RsaOaepTask>(aCx, aFormat, aKey,
                                         aWrappingKey, aWrapAlgorithm);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
-WebCryptoTask::CreateUnwrapKeyTask(JSContext* aCx,
+WebCryptoTask::CreateUnwrapKeyTask(nsIGlobalObject* aGlobal,
+                                   JSContext* aCx,
                                    const nsAString& aFormat,
                                    const ArrayBufferViewOrArrayBuffer& aWrappedKey,
                                    CryptoKey& aUnwrappingKey,
                                    const ObjectOrString& aUnwrapAlgorithm,
                                    const ObjectOrString& aUnwrappedKeyAlgorithm,
                                    bool aExtractable,
                                    const Sequence<nsString>& aKeyUsages)
 {
@@ -3597,23 +3582,23 @@ WebCryptoTask::CreateUnwrapKeyTask(JSCon
 
   CryptoOperationData dummy;
   RefPtr<ImportKeyTask> importTask;
   if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
       keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
       keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
       keyAlgName.EqualsASCII(WEBCRYPTO_ALG_HKDF) ||
       keyAlgName.EqualsASCII(WEBCRYPTO_ALG_HMAC)) {
-    importTask = new ImportSymmetricKeyTask(aCx, aFormat,
+    importTask = new ImportSymmetricKeyTask(aGlobal, aCx, aFormat,
                                             aUnwrappedKeyAlgorithm,
                                             aExtractable, aKeyUsages);
   } else if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
              keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP) ||
              keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS)) {
-    importTask = new ImportRsaKeyTask(aCx, aFormat,
+    importTask = new ImportRsaKeyTask(aGlobal, aCx, aFormat,
                                       aUnwrappedKeyAlgorithm,
                                       aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 
   nsString unwrapAlgName;
   rv = GetAlgorithmName(aCx, aUnwrapAlgorithm, unwrapAlgName);
--- a/dom/crypto/WebCryptoTask.h
+++ b/dom/crypto/WebCryptoTask.h
@@ -117,46 +117,50 @@ public:
   {
     return CreateSignVerifyTask(aCx, aAlgorithm, aKey, aSignature, aData, false);
   }
 
   static WebCryptoTask* CreateDigestTask(JSContext* aCx,
                           const ObjectOrString& aAlgorithm,
                           const CryptoOperationData& aData);
 
-  static WebCryptoTask* CreateImportKeyTask(JSContext* aCx,
+  static WebCryptoTask* CreateImportKeyTask(nsIGlobalObject* aGlobal,
+                          JSContext* aCx,
                           const nsAString& aFormat,
                           JS::Handle<JSObject*> aKeyData,
                           const ObjectOrString& aAlgorithm,
                           bool aExtractable,
                           const Sequence<nsString>& aKeyUsages);
   static WebCryptoTask* CreateExportKeyTask(const nsAString& aFormat,
                           CryptoKey& aKey);
-  static WebCryptoTask* CreateGenerateKeyTask(JSContext* aCx,
+  static WebCryptoTask* CreateGenerateKeyTask(nsIGlobalObject* aGlobal,
+                          JSContext* aCx,
                           const ObjectOrString& aAlgorithm,
                           bool aExtractable,
                           const Sequence<nsString>& aKeyUsages);
 
-  static WebCryptoTask* CreateDeriveKeyTask(JSContext* aCx,
+  static WebCryptoTask* CreateDeriveKeyTask(nsIGlobalObject* aGlobal,
+                          JSContext* aCx,
                           const ObjectOrString& aAlgorithm,
                           CryptoKey& aBaseKey,
                           const ObjectOrString& aDerivedKeyType,
                           bool extractable,
                           const Sequence<nsString>& aKeyUsages);
   static WebCryptoTask* CreateDeriveBitsTask(JSContext* aCx,
                           const ObjectOrString& aAlgorithm,
                           CryptoKey& aKey,
                           uint32_t aLength);
 
   static WebCryptoTask* CreateWrapKeyTask(JSContext* aCx,
                           const nsAString& aFormat,
                           CryptoKey& aKey,
                           CryptoKey& aWrappingKey,
                           const ObjectOrString& aWrapAlgorithm);
-  static WebCryptoTask* CreateUnwrapKeyTask(JSContext* aCx,
+  static WebCryptoTask* CreateUnwrapKeyTask(nsIGlobalObject* aGlobal,
+                          JSContext* aCx,
                           const nsAString& aFormat,
                           const ArrayBufferViewOrArrayBuffer& aWrappedKey,
                           CryptoKey& aUnwrappingKey,
                           const ObjectOrString& aUnwrapAlgorithm,
                           const ObjectOrString& aUnwrappedKeyAlgorithm,
                           bool aExtractable,
                           const Sequence<nsString>& aKeyUsages);
 
@@ -223,17 +227,17 @@ private:
   bool mReleasedNSSResources;
   nsresult mRv;
 };
 
 // XXX This class is declared here (unlike others) to enable reuse by WebRTC.
 class GenerateAsymmetricKeyTask : public WebCryptoTask
 {
 public:
-  GenerateAsymmetricKeyTask(JSContext* aCx,
+  GenerateAsymmetricKeyTask(nsIGlobalObject* aGlobal, JSContext* aCx,
                             const ObjectOrString& aAlgorithm, bool aExtractable,
                             const Sequence<nsString>& aKeyUsages);
 protected:
   ScopedPLArenaPool mArena;
   UniquePtr<CryptoKeyPair> mKeyPair;
   nsString mAlgName;
   CK_MECHANISM_TYPE mMechanism;
   PK11RSAGenParams mRsaParams;
--- a/dom/crypto/test/test_WebCrypto.html
+++ b/dom/crypto/test/test_WebCrypto.html
@@ -982,26 +982,73 @@ TestArray.addTest(
       }
 
       return crypto.subtle.generateKey(alg, false, ["sign"]).then(doSign);
     }
 
     doCheckRSASSA().then(error(that), complete(that));
   }
 );
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Test that we're using the right globals when creating objects",
+  function() {
+    var that = this;
+    var data = crypto.getRandomValues(new Uint8Array(10));
+    var hmacAlg = {name: "HMAC", length: 256, hash: "SHA-1"};
+
+    var rsaAlg = {
+      name: "RSA-PSS",
+      hash: "SHA-1",
+      modulusLength: 1024,
+      publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+    };
+
+    function checkPrototypes(obj, type) {
+      return obj.__proto__ != window[type].prototype &&
+             obj.__proto__ == frames[0][type].prototype
+    }
+
+    var p1 = crypto.subtle.importKey.call(
+      frames[0].crypto.subtle, "raw", data, hmacAlg, false, ["sign", "verify"]);
+    var p2 = crypto.subtle.generateKey.call(
+      frames[0].crypto.subtle, hmacAlg, false, ["sign", "verify"]);
+    var p3 = crypto.subtle.generateKey.call(
+      frames[0].crypto.subtle, rsaAlg, false, ["sign", "verify"]);
+
+    if (!checkPrototypes(p1, "Promise") ||
+        !checkPrototypes(p2, "Promise") ||
+        !checkPrototypes(p3, "Promise")) {
+      error(that)();
+    }
+
+    Promise.all([p1, p2, p3]).then(complete(that, keys => {
+      return keys.every(key => {
+        if (key instanceof CryptoKey) {
+          return checkPrototypes(key, "CryptoKey");
+        }
+
+        return checkPrototypes(key.publicKey, "CryptoKey") &&
+               checkPrototypes(key.privateKey, "CryptoKey");
+      });
+    }), error(that));
+  }
+);
 /*]]>*/</script>
 </head>
 
 <body>
 
 <div id="content">
 	<div id="head">
 		<b>Web</b>Crypto<br>
 	</div>
 
+    <iframe style="display: none;"></iframe>
     <div id="start" onclick="start();">RUN ALL</div>
 
     <div id="resultDiv" class="content">
     Summary:
     <span class="pass"><span id="passN">0</span> passed, </span>
     <span class="fail"><span id="failN">0</span> failed, </span>
     <span class="pending"><span id="pendingN">0</span> pending.</span>
     <br/>
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -653,20 +653,19 @@ ContentEventHandler::AppendFontRanges(Fo
       next = static_cast<nsTextFrame*>(curr->GetNextContinuation());
       while (next && next->GetTextRun(nsTextFrame::eInflated) == textRun) {
         frameXPEnd = std::min(next->GetContentEnd(), aXPEndOffset);
         next = frameXPEnd < aXPEndOffset ?
           static_cast<nsTextFrame*>(next->GetNextContinuation()) : nullptr;
       }
     }
 
-    uint32_t skipStart = iter.ConvertOriginalToSkipped(frameXPStart);
-    uint32_t skipEnd = iter.ConvertOriginalToSkipped(frameXPEnd);
-    gfxTextRun::GlyphRunIterator runIter(
-      textRun, skipStart, skipEnd - skipStart);
+    gfxTextRun::Range skipRange(iter.ConvertOriginalToSkipped(frameXPStart),
+                                iter.ConvertOriginalToSkipped(frameXPEnd));
+    gfxTextRun::GlyphRunIterator runIter(textRun, skipRange);
     int32_t lastXPEndOffset = frameXPStart;
     while (runIter.NextRun()) {
       gfxFont* font = runIter.GetGlyphRun()->mFont.get();
       int32_t startXPOffset =
         iter.ConvertSkippedToOriginal(runIter.GetStringStart());
       // It is possible that the first glyph run has exceeded the frame,
       // because the whole frame is filled by skipped chars.
       if (startXPOffset >= frameXPEnd) {
--- a/dom/events/test/test_dom_wheel_event.html
+++ b/dom/events/test/test_dom_wheel_event.html
@@ -34,68 +34,69 @@
     Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br>
     Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br>
     Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br>
     Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br>
     Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text. Tere is a lot of text.<br>
   </div>
 </div>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(runTests, window);
+SimpleTest.waitForFocus(runTest, window);
 
 var gScrollableElement = document.getElementById("scrollable");
 var gScrolledElement = document.getElementById("scrolled");
 
 var gLineHeight = 0;
 var gHorizontalLine = 0;
 var gPageHeight = 0;
 var gPageWidth  = 0;
 
-function prepareScrollUnits()
+function* prepareScrollUnits()
 {
   var result = -1;
   function handler(aEvent)
   {
     result = aEvent.detail;
     aEvent.preventDefault();
+    setTimeout(continueTest, 0);
   }
   window.addEventListener("MozMousePixelScroll", handler, true);
 
-  synthesizeWheel(gScrollableElement, 10, 10,
-                  { deltaMode: WheelEvent.DOM_DELTA_LINE,
-                    deltaY: 1.0, lineOrPageDeltaY: 1 });
+  yield synthesizeWheel(gScrollableElement, 10, 10,
+                        { deltaMode: WheelEvent.DOM_DELTA_LINE,
+                          deltaY: 1.0, lineOrPageDeltaY: 1 });
   gLineHeight = result;
   ok(gLineHeight > 10 && gLineHeight < 25, "prepareScrollUnits: gLineHeight may be illegal value, got " + gLineHeight);
 
   result = -1;
-  synthesizeWheel(gScrollableElement, 10, 10,
-                  { deltaMode: WheelEvent.DOM_DELTA_LINE,
-                    deltaX: 1.0, lineOrPageDeltaX: 1 });
+  yield synthesizeWheel(gScrollableElement, 10, 10,
+                        { deltaMode: WheelEvent.DOM_DELTA_LINE,
+                          deltaX: 1.0, lineOrPageDeltaX: 1 });
   gHorizontalLine = result;
   ok(gHorizontalLine > 5 && gHorizontalLine < 16, "prepareScrollUnits: gHorizontalLine may be illegal value, got " + gHorizontalLine);
 
   result = -1;
-  synthesizeWheel(gScrollableElement, 10, 10,
-                  { deltaMode: WheelEvent.DOM_DELTA_PAGE,
-                    deltaY: 1.0, lineOrPageDeltaY: 1 });
+  yield synthesizeWheel(gScrollableElement, 10, 10,
+                        { deltaMode: WheelEvent.DOM_DELTA_PAGE,
+                          deltaY: 1.0, lineOrPageDeltaY: 1 });
   gPageHeight = result;
   // XXX Cannot we know the actual scroll port size?
   ok(gPageHeight >= 150 && gPageHeight <= 200,
      "prepareScrollUnits: gPageHeight is strange value, got " + gPageHeight);
 
   result = -1;
-  synthesizeWheel(gScrollableElement, 10, 10,
-                  { deltaMode: WheelEvent.DOM_DELTA_PAGE,
-                    deltaX: 1.0, lineOrPageDeltaX: 1 });
+  yield synthesizeWheel(gScrollableElement, 10, 10,
+                        { deltaMode: WheelEvent.DOM_DELTA_PAGE,
+                          deltaX: 1.0, lineOrPageDeltaX: 1 });
   gPageWidth = result;
   ok(gPageWidth >= 150 && gPageWidth <= 200,
      "prepareScrollUnits: gPageWidth is strange value, got " + gPageWidth);
 
   window.removeEventListener("MozMousePixelScroll", handler, true);
 }
 
 function testMakingUntrustedEvent()
@@ -118,17 +119,17 @@ function testMakingUntrustedEvent()
   ok(wheelEvent instanceof WheelEvent,
      "new WheelEvent() should create an instance of WheelEvent");
   ok(typeof(wheelEvent.initWheelEvent) != "function",
      "WheelEvent must not have initWheelEvent()");
 }
 
 // delta_multiplier prefs should cause changing delta values of trusted events only.
 // And also legacy events' detail value should be changed too.
-function testDeltaMultiplierPrefs()
+function* testDeltaMultiplierPrefs()
 {
   const kModifierAlt     = 0x01;
   const kModifierControl = 0x02;
   const kModifierMeta    = 0x04;
   const kModifierShift   = 0x08;
   const kModifierWin     = 0x10;
 
   const kTests = [
@@ -185,16 +186,17 @@ function testDeltaMultiplierPrefs()
     "delta_multiplier_x", "delta_multiplier_y", "delta_multiplier_z"
   ];
 
   const kPrefValues = [
     200, 50, 0, -50, -150
   ];
 
   var currentTest, currentModifiers, currentEvent, currentPref, currentMultiplier, testingExpected;
+  var expectedHandlerCalls;
   var description;
   var calledHandlers = { wheel: false,
                          DOMMouseScroll: { horizontal: false, vertical: false },
                          MozMousePixelScroll: { horizontal: false, vertical: false } };
 
   function wheelEventHandler(aEvent) {
     calledHandlers.wheel = true;
 
@@ -210,19 +212,23 @@ function testDeltaMultiplierPrefs()
         case "y":
           expectedDeltaY *= currentMultiplier;
           break;
         case "z":
           expectedDeltaZ *= currentMultiplier;
           break;
       }
     }
-    is(aEvent.deltaX, expectedDeltaX, description + "deltaX (" + currentEvent.deltaX + ") was invaild");
-    is(aEvent.deltaY, expectedDeltaY, description + "deltaY (" + currentEvent.deltaY + ") was invaild");
-    is(aEvent.deltaZ, expectedDeltaZ, description + "deltaZ (" + currentEvent.deltaZ + ") was invaild");
+    is(aEvent.deltaX, expectedDeltaX, description + "deltaX (" + currentEvent.deltaX + ") was invalid");
+    is(aEvent.deltaY, expectedDeltaY, description + "deltaY (" + currentEvent.deltaY + ") was invalid");
+    is(aEvent.deltaZ, expectedDeltaZ, description + "deltaZ (" + currentEvent.deltaZ + ") was invalid");
+
+    if (--expectedHandlerCalls == 0) {
+      setTimeout(continueTest, 0);
+    }
   }
 
   function legacyEventHandler(aEvent) {
     var isHorizontal = (aEvent.axis == MouseScrollEvent.HORIZONTAL_AXIS);
     var isScrollEvent = (aEvent.type == "DOMMouseScroll");
     if (isScrollEvent) {
       if (isHorizontal) {
         calledHandlers.DOMMouseScroll.horizontal = true;
@@ -271,23 +277,27 @@ function testDeltaMultiplierPrefs()
           expectedDetail *= currentMultiplier;
           expectedDetail = expectedDetail < 0 ? Math.ceil(expectedDetail) : Math.floor(expectedDetail);
         }
       }
     }
     is(aEvent.detail, expectedDetail, description + eventName + "detail was invalid");
 
     aEvent.preventDefault();
+
+    if (--expectedHandlerCalls == 0) {
+      setTimeout(continueTest, 0);
+    }
   }
 
   window.addEventListener("wheel", wheelEventHandler, true);
   window.addEventListener("DOMMouseScroll", legacyEventHandler, true);
   window.addEventListener("MozMousePixelScroll", legacyEventHandler, true);
 
-  function dispatchEvent(aIsExpected) {
+  function* dispatchEvent(aIsExpected) {
     for (var i = 0; i < kEvents.length; i++) {
       currentEvent = kEvents[i];
       currentEvent.shiftKey = (currentModifiers & kModifierShift) != 0;
       currentEvent.ctrlKey  = (currentModifiers & kModifierControl) != 0;
       currentEvent.altKey   = (currentModifiers & kModifierAlt) != 0;
       currentEvent.metaKey  = (currentModifiers & kModifierMeta) != 0;
       currentEvent.osKey    = (currentModifiers & kModifierWin) != 0;
       var modifierList = "";
@@ -307,49 +317,62 @@ function testDeltaMultiplierPrefs()
         modifierList += "Win ";
       }
 
       for (var j = 0; j < kPrefValues.length; j++) {
         currentMultiplier = kPrefValues[j] / 100;
         for (var k = 0; k < kDeltaMultiplierPrefs.length; k++) {
           currentPref = "mousewheel." + currentTest.name + "." + kDeltaMultiplierPrefs[k];
 
-          SpecialPowers.setIntPref(currentPref, kPrefValues[j]);
+          yield SpecialPowers.pushPrefEnv({"set": [[currentPref, kPrefValues[j]]]}, continueTest);
 
           gScrollableElement.scrollTop = gScrollableElement.scrollBottom = 1000;
 
           // trusted event's delta valuses should be reverted by the pref.
           testingExpected = aIsExpected;
 
-          description = "testDeltaMultiplierPrefs, pref: " + currentPref + "=" + kPrefValues[j] +
-            ", deltaMode: " + currentEvent.deltaMode + ", modifiers: \"" + modifierList + "\", (trusted event): ";
-          synthesizeWheel(gScrollableElement, 10, 10, currentEvent);
-
           var expectedProps = {
             deltaX: currentEvent.deltaX * currentMultiplier,
             deltaY: currentEvent.deltaY * currentMultiplier,
             dletaZ: currentEvent.deltaZ * currentMultiplier,
             lineOrPageDeltaX: currentEvent.lineOrPageDeltaX * currentMultiplier,
             lineOrPageDeltaY: currentEvent.lineOrPageDeltaY * currentMultiplier,
           };
 
+          var expectedWheel = expectedProps.deltaX != 0 || expectedProps.deltaY != 0 || expectedProps.deltaZ != 0;
+          var expectedDOMMouseX = expectedProps.lineOrPageDeltaX >= 1 || expectedProps.lineOrPageDeltaX <= -1;
+          var expectedDOMMouseY = expectedProps.lineOrPageDeltaY >= 1 || expectedProps.lineOrPageDeltaY <= -1;
+          var expectedMozMouseX = expectedProps.deltaX >= 1 || expectedProps.deltaX <= -1;
+          var expectedMozMouseY = expectedProps.deltaY >= 1 || expectedProps.deltaY <= -1;
+
+          expectedHandlerCalls = 0;
+          if (expectedWheel) ++expectedHandlerCalls;
+          if (expectedDOMMouseX) ++expectedHandlerCalls;
+          if (expectedDOMMouseY) ++expectedHandlerCalls;
+          if (expectedMozMouseX) ++expectedHandlerCalls;
+          if (expectedMozMouseY) ++expectedHandlerCalls;
+
+          description = "testDeltaMultiplierPrefs, pref: " + currentPref + "=" + kPrefValues[j] +
+            ", deltaMode: " + currentEvent.deltaMode + ", modifiers: \"" + modifierList + "\", (trusted event): ";
+          yield synthesizeWheel(gScrollableElement, 10, 10, currentEvent);
+
           is(calledHandlers.wheel,
-             expectedProps.deltaX != 0 || expectedProps.deltaY != 0 || expectedProps.deltaZ != 0,
+             expectedWheel,
              description + "wheel event was (not) fired");
           is(calledHandlers.DOMMouseScroll.horizontal,
-             expectedProps.lineOrPageDeltaX >= 1 || expectedProps.lineOrPageDeltaX <= -1,
+             expectedDOMMouseX,
              description + "Horizontal DOMMouseScroll event was (not) fired");
           is(calledHandlers.DOMMouseScroll.vertical,
-             expectedProps.lineOrPageDeltaY >= 1 || expectedProps.lineOrPageDeltaY <= -1,
+             expectedDOMMouseY,
              description + "Vertical DOMMouseScroll event was (not) fired");
           is(calledHandlers.MozMousePixelScroll.horizontal,
-             expectedProps.deltaY >= 1 || expectedProps.deltaY <= -1,
+             expectedMozMouseX,
              description + "Horizontal MozMousePixelScroll event was (not) fired");
           is(calledHandlers.MozMousePixelScroll.vertical,
-             expectedProps.deltaY >= 1 || expectedProps.deltaY <= -1,
+             expectedMozMouseY,
              description + "Vertical MozMousePixelScroll event was (not) fired");
 
           calledHandlers = { wheel: false,
                              DOMMouseScroll: { horizontal: false, vertical: false },
                              MozMousePixelScroll: { horizontal: false, vertical: false } };
 
           // untrusted event's delta values shouldn't be reverted by the pref.
           testingExpected = false;
@@ -376,17 +399,17 @@ function testDeltaMultiplierPrefs()
              description + "Horizontal DOMMouseScroll event was fired for untrusted event");
           ok(!calledHandlers.DOMMouseScroll.vertical,
              description + "Vertical DOMMouseScroll event was fired for untrusted event");
           ok(!calledHandlers.MozMousePixelScroll.horizontal,
              description + "Horizontal MozMousePixelScroll event was fired for untrusted event");
           ok(!calledHandlers.MozMousePixelScroll.vertical,
              description + "Vertical MozMousePixelScroll event was fired for untrusted event");
 
-          SpecialPowers.setIntPref(currentPref, 100);
+          yield SpecialPowers.pushPrefEnv({"set": [[currentPref, 100]]}, continueTest);
 
           calledHandlers = { wheel: false,
                              DOMMouseScroll: { horizontal: false, vertical: false },
                              MozMousePixelScroll: { horizontal: false, vertical: false } };
 
         }
         // We should skip other value tests if testing with modifier key.
         // If we didn't do so, it would test too many times, but we don't need to do so.
@@ -396,21 +419,21 @@ function testDeltaMultiplierPrefs()
       }
     }
   }
 
   for (var i = 0; i < kTests.length; i++) {
     currentTest = kTests[i];
     for (var j = 0; j < currentTest.expected.length; j++) {
       currentModifiers = currentTest.expected[j];
-      dispatchEvent(true);
+      yield* dispatchEvent(true);
     }
     for (var k = 0; k < currentTest.unexpected.length; k++) {
       currentModifiers = currentTest.unexpected[k];
-      dispatchEvent(false);
+      yield* dispatchEvent(false);
     }
   }
 
   window.removeEventListener("wheel", wheelEventHandler, true);
   window.removeEventListener("DOMMouseScroll", legacyEventHandler, true);
   window.removeEventListener("MozMousePixelScroll", legacyEventHandler, true);
 }
 
@@ -463,17 +486,17 @@ function testDispatchingUntrustEvent()
   gScrolledElement.dispatchEvent(untrustedPageEvent);
   ok(wheelEventFired, description + "wheel event wasn't fired");
 
   window.removeEventListener("wheel", wheelEventHandler, true);
   window.removeEventListener("DOMMouseScroll", legacyEventHandler, true);
   window.removeEventListener("MozMousePixelScroll", legacyEventHandler, true);
 }
 
-function testEventOrder()
+function* testEventOrder()
 {
   const kWheelEvent                         = 0x0001;
   const kDOMMouseScrollEvent                = 0x0002;
   const kMozMousePixelScrollEvent           = 0x0004;
   const kVerticalScrollEvent                = 0x0010;
   const kHorizontalScrollEvent              = 0x0020;
   const kInSystemGroup                      = 0x0100;
   const kDefaultPrevented                   = 0x1000;
@@ -610,45 +633,48 @@ function testEventOrder()
     }
     if (aIsSystemGroup) {
       event |= kInSystemGroup;
     }
     if (aEvent.defaultPrevented) {
       event |= kDefaultPrevented;
     }
     currentTest.resultEvents.push(event);
-    return event;
+
+    if (event == currentTest.doPreventDefaultAt) {
+      aEvent.preventDefault();
+    }
+
+    if (currentTest.resultEvents.length == currentTest.expectedEvents.length) {
+      setTimeout(continueTest, 0);
+    }
   }
 
   function handler(aEvent)
   {
-    if (pushEvent(aEvent, false) == currentTest.doPreventDefaultAt) {
-      aEvent.preventDefault();
-    }
+    pushEvent(aEvent, false);
   }
 
   function systemHandler(aEvent)
   {
-    if (pushEvent(aEvent, true) == currentTest.doPreventDefaultAt) {
-      aEvent.preventDefault();
-    }
+    pushEvent(aEvent, true);
   }
 
   window.addEventListener("wheel", handler, true);
   window.addEventListener("DOMMouseScroll", handler, true);
   window.addEventListener("MozMousePixelScroll", handler, true);
 
   SpecialPowers.addSystemEventListener(window, "wheel", systemHandler, true);
   SpecialPowers.addSystemEventListener(window, "DOMMouseScroll", systemHandler, true);
   SpecialPowers.addSystemEventListener(window, "MozMousePixelScroll", systemHandler, true);
 
   for (var i = 0; i < kTests.length; i++) {
     currentTest = kTests[i];
-    synthesizeWheel(gScrollableElement, 10, 10,
-                    { deltaMode: WheelEvent.DOM_DELTA_LINE, deltaX: 1.0, deltaY: 1.0 });
+    yield synthesizeWheel(gScrollableElement, 10, 10,
+                          { deltaMode: WheelEvent.DOM_DELTA_LINE, deltaX: 1.0, deltaY: 1.0 });
 
     for (var j = 0; j < currentTest.expectedEvents.length; j++) {
       if (currentTest.resultEvents.length == j) {
         ok(false, currentTest.description + ": " +
            getEventDescription(currentTest.expectedEvents[j]) + " wasn't fired");
         break;
       }
       is(getEventDescription(currentTest.resultEvents[j]),
@@ -667,98 +693,114 @@ function testEventOrder()
   window.removeEventListener("MozMousePixelScroll", handler, true);
 
   SpecialPowers.removeSystemEventListener(window, "wheel", systemHandler, true);
   SpecialPowers.removeSystemEventListener(window, "DOMMouseScroll", systemHandler, true);
   SpecialPowers.removeSystemEventListener(window, "MozMousePixelScroll", systemHandler, true);
 }
 
 var gOnWheelAttrHandled = new Array;
+var gOnWheelAttrCount = 0;
 
-function testOnWheelAttr()
+function* testOnWheelAttr()
 {
-  document.documentElement.setAttribute("onwheel", "gOnWheelAttrHandled['html'] = true;");
-  document.body.setAttribute("onwheel", "gOnWheelAttrHandled['body'] = true;");
-  gScrollableElement.setAttribute("onwheel", "gOnWheelAttrHandled['div'] = true;");
+  function onWheelHandledString(attr) {
+    return `gOnWheelAttrHandled['${attr}'] = true;
+            ++gOnWheelAttrCount;
+            if (gOnWheelAttrCount == 3) {
+              setTimeout(continueTest, 0);
+            };`;
+  }
+
+  document.documentElement.setAttribute("onwheel", onWheelHandledString("html"));
+  document.body.setAttribute("onwheel", onWheelHandledString("body"));
+  gScrollableElement.setAttribute("onwheel", onWheelHandledString("div"));
   var target = document.getElementById("onwheel");
-  synthesizeWheel(gScrollableElement, 10, 10,
-                  { deltaMode: WheelEvent.DOM_DELTA_LINE,
-                    deltaX: 1.0, deltaY: 2.0 });
+  yield synthesizeWheel(gScrollableElement, 10, 10,
+                        { deltaMode: WheelEvent.DOM_DELTA_LINE,
+                        deltaX: 1.0, deltaY: 2.0 });
   ok(gOnWheelAttrHandled['html'], "html element's onwheel attribute isn't performed");
   ok(gOnWheelAttrHandled['body'], "body element's onwheel attribute isn't performed");
   ok(gOnWheelAttrHandled['div'], "div element's onwheel attribute isn't performed");
 }
 
 var gOnWheelPropHandled = new Array;
+var gOnWheelPropCount = 0;
 
-function testOnWheelProperty()
+function* testOnWheelProperty()
 {
-  window.onwheel = function (e) { gOnWheelPropHandled["window"] = true; }
-  document.onwheel = function (e) { gOnWheelPropHandled["document"] = true; }
-  document.documentElement.onwheel = function (e) { gOnWheelPropHandled["html"] = true; };
-  document.body.onwheel = function (e) { gOnWheelPropHandled["body"] = true; };
-  gScrollableElement.onwheel = function (e) { gOnWheelPropHandled["div"] = true; };
+  const handleOnWheelProp = prop => e => {
+    gOnWheelPropHandled[prop] = true;
+    ++gOnWheelPropCount;
+    if (gOnWheelPropCount == 5) {
+      setTimeout(continueTest, 0);
+    }
+  }
+
+  window.onwheel = handleOnWheelProp('window');
+  document.onwheel = handleOnWheelProp('document');
+  document.documentElement.onwheel = handleOnWheelProp('html');
+  document.body.onwheel = handleOnWheelProp('body');
+  gScrollableElement.onwheel = handleOnWheelProp('div');
+
   var target = document.getElementById("onwheel");
-  synthesizeWheel(gScrollableElement, 10, 10,
-                  { deltaMode: WheelEvent.DOM_DELTA_LINE,
-                    deltaX: 1.0, deltaY: 2.0 });
+  yield synthesizeWheel(gScrollableElement, 10, 10,
+                        { deltaMode: WheelEvent.DOM_DELTA_LINE,
+                          deltaX: 1.0, deltaY: 2.0 });
+
   ok(gOnWheelPropHandled['window'], "window's onwheel property isn't performed");
   ok(gOnWheelPropHandled['document'], "document's onwheel property isn't performed");
   ok(gOnWheelPropHandled['html'], "html element's onwheel property isn't performed");
   ok(gOnWheelPropHandled['body'], "body element's onwheel property isn't performed");
   ok(gOnWheelPropHandled['div'], "div element's onwheel property isn't performed");
 }
 
-function runTests()
+function* testBody()
 {
-  SpecialPowers.setIntPref("mousewheel.default.delta_multiplier_x", 100);
-  SpecialPowers.setIntPref("mousewheel.default.delta_multiplier_y", 100);
-  SpecialPowers.setIntPref("mousewheel.default.delta_multiplier_z", 100);
-  SpecialPowers.setIntPref("mousewheel.with_alt.delta_multiplier_x", 100);
-  SpecialPowers.setIntPref("mousewheel.with_alt.delta_multiplier_y", 100);
-  SpecialPowers.setIntPref("mousewheel.with_alt.delta_multiplier_z", 100);
-  SpecialPowers.setIntPref("mousewheel.with_control.delta_multiplier_x", 100);
-  SpecialPowers.setIntPref("mousewheel.with_control.delta_multiplier_y", 100);
-  SpecialPowers.setIntPref("mousewheel.with_control.delta_multiplier_z", 100);
-  SpecialPowers.setIntPref("mousewheel.with_meta.delta_multiplier_x", 100);
-  SpecialPowers.setIntPref("mousewheel.with_meta.delta_multiplier_y", 100);
-  SpecialPowers.setIntPref("mousewheel.with_meta.delta_multiplier_z", 100);
-  SpecialPowers.setIntPref("mousewheel.with_shift.delta_multiplier_x", 100);
-  SpecialPowers.setIntPref("mousewheel.with_shift.delta_multiplier_y", 100);
-  SpecialPowers.setIntPref("mousewheel.with_shift.delta_multiplier_z", 100);
-  SpecialPowers.setIntPref("mousewheel.with_win.delta_multiplier_x", 100);
-  SpecialPowers.setIntPref("mousewheel.with_win.delta_multiplier_y", 100);
-  SpecialPowers.setIntPref("mousewheel.with_win.delta_multiplier_z", 100);
-
-  prepareScrollUnits();
+  yield* prepareScrollUnits();
   testMakingUntrustedEvent();
-  testDeltaMultiplierPrefs();
+  yield* testDeltaMultiplierPrefs();
   testDispatchingUntrustEvent();
-  testEventOrder();
-  testOnWheelAttr();
-  testOnWheelProperty();
+  yield* testEventOrder();
+  yield* testOnWheelAttr();
+  yield* testOnWheelProperty();
+}
+
+var gTestContinuation = null;
 
-  SpecialPowers.clearUserPref("mousewheel.default.delta_multiplier_x");
-  SpecialPowers.clearUserPref("mousewheel.default.delta_multiplier_y");
-  SpecialPowers.clearUserPref("mousewheel.default.delta_multiplier_z");
-  SpecialPowers.clearUserPref("mousewheel.with_alt.delta_multiplier_x");
-  SpecialPowers.clearUserPref("mousewheel.with_alt.delta_multiplier_y");
-  SpecialPowers.clearUserPref("mousewheel.with_alt.delta_multiplier_z");
-  SpecialPowers.clearUserPref("mousewheel.with_control.delta_multiplier_x");
-  SpecialPowers.clearUserPref("mousewheel.with_control.delta_multiplier_y");
-  SpecialPowers.clearUserPref("mousewheel.with_control.delta_multiplier_z");
-  SpecialPowers.clearUserPref("mousewheel.with_meta.delta_multiplier_x");
-  SpecialPowers.clearUserPref("mousewheel.with_meta.delta_multiplier_y");
-  SpecialPowers.clearUserPref("mousewheel.with_meta.delta_multiplier_z");
-  SpecialPowers.clearUserPref("mousewheel.with_shift.delta_multiplier_x");
-  SpecialPowers.clearUserPref("mousewheel.with_shift.delta_multiplier_y");
-  SpecialPowers.clearUserPref("mousewheel.with_shift.delta_multiplier_z");
-  SpecialPowers.clearUserPref("mousewheel.with_win.delta_multiplier_x");
-  SpecialPowers.clearUserPref("mousewheel.with_win.delta_multiplier_y");
-  SpecialPowers.clearUserPref("mousewheel.with_win.delta_multiplier_z");
-
-  SimpleTest.finish();
+function continueTest()
+{
+  if (!gTestContinuation) {
+    gTestContinuation = testBody();
+  }
+  var ret = gTestContinuation.next();
+  if (ret.done) {
+    SimpleTest.finish();
+  }
 }
 
+function runTest()
+{
+  SpecialPowers.pushPrefEnv({"set": [
+    ["mousewheel.default.delta_multiplier_x", 100],
+    ["mousewheel.default.delta_multiplier_y", 100],
+    ["mousewheel.default.delta_multiplier_z", 100],
+    ["mousewheel.with_alt.delta_multiplier_x", 100],
+    ["mousewheel.with_alt.delta_multiplier_y", 100],
+    ["mousewheel.with_alt.delta_multiplier_z", 100],
+    ["mousewheel.with_control.delta_multiplier_x", 100],
+    ["mousewheel.with_control.delta_multiplier_y", 100],
+    ["mousewheel.with_control.delta_multiplier_z", 100],
+    ["mousewheel.with_meta.delta_multiplier_x", 100],
+    ["mousewheel.with_meta.delta_multiplier_y", 100],
+    ["mousewheel.with_meta.delta_multiplier_z", 100],
+    ["mousewheel.with_shift.delta_multiplier_x", 100],
+    ["mousewheel.with_shift.delta_multiplier_y", 100],
+    ["mousewheel.with_shift.delta_multiplier_z", 100],
+    ["mousewheel.with_win.delta_multiplier_x", 100],
+    ["mousewheel.with_win.delta_multiplier_y", 100],
+    ["mousewheel.with_win.delta_multiplier_z", 100]]},
+    continueTest);
+}
 </script>
 </pre>
 </body>
 </html>
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -750,17 +750,19 @@ public:
     return true;
   }
 };
 
 template <class Derived>
 FetchBody<Derived>::FetchBody()
   : mFeature(nullptr)
   , mBodyUsed(false)
+#ifdef DEBUG
   , mReadDone(false)
+#endif
 {
   if (!NS_IsMainThread()) {
     mWorkerPrivate = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(mWorkerPrivate);
   } else {
     mWorkerPrivate = nullptr;
   }
 }
@@ -935,17 +937,19 @@ void
 FetchBody<Derived>::ContinueConsumeBody(nsresult aStatus, uint32_t aResultLength, uint8_t* aResult)
 {
   AssertIsOnTargetThread();
   // Just a precaution to ensure ContinueConsumeBody is not called out of
   // sync with a body read.
   MOZ_ASSERT(mBodyUsed);
   MOZ_ASSERT(!mReadDone);
   MOZ_ASSERT_IF(mWorkerPrivate, mFeature);
+#ifdef DEBUG
   mReadDone = true;
+#endif
 
   AutoFreeBuffer autoFree(aResult);
 
   MOZ_ASSERT(mConsumePromise);
   RefPtr<Promise> localPromise = mConsumePromise.forget();
 
   RefPtr<Derived> kungfuDeathGrip = DerivedClass();
   ReleaseObject();
--- a/dom/fetch/Fetch.h
+++ b/dom/fetch/Fetch.h
@@ -209,17 +209,19 @@ private:
 
   // Only ever set once, always on target thread.
   bool mBodyUsed;
   nsCString mMimeType;
 
   // Only touched on target thread.
   ConsumeType mConsumeType;
   RefPtr<Promise> mConsumePromise;
-  DebugOnly<bool> mReadDone;
+#ifdef DEBUG
+  bool mReadDone;
+#endif
 
   nsMainThreadPtrHandle<nsIInputStreamPump> mConsumeBodyPump;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Fetch_h
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -45,34 +45,38 @@ NS_IMPL_ISUPPORTS(FetchDriver,
                   nsIStreamListener, nsIChannelEventSink, nsIInterfaceRequestor,
                   nsIThreadRetargetableStreamListener)
 
 FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal,
                          nsILoadGroup* aLoadGroup)
   : mPrincipal(aPrincipal)
   , mLoadGroup(aLoadGroup)
   , mRequest(aRequest)
+#ifdef DEBUG
   , mResponseAvailableCalled(false)
   , mFetchCalled(false)
+#endif
 {
 }
 
 FetchDriver::~FetchDriver()
 {
   // We assert this since even on failures, we should call
   // FailWithNetworkError().
   MOZ_ASSERT(mResponseAvailableCalled);
 }
 
 nsresult
 FetchDriver::Fetch(FetchDriverObserver* aObserver)
 {
   workers::AssertIsOnMainThread();
+#ifdef DEBUG
   MOZ_ASSERT(!mFetchCalled);
   mFetchCalled = true;
+#endif
 
   mObserver = aObserver;
 
   Telemetry::Accumulate(Telemetry::SERVICE_WORKER_REQUEST_PASSTHROUGH,
                         mRequest->WasCreatedByFetchEvent());
 
   // FIXME(nsm): Deal with HSTS.
 
@@ -407,28 +411,32 @@ FetchDriver::BeginAndGetFilteredResponse
       default:
         MOZ_CRASH("Unexpected case");
     }
   }
 
   MOZ_ASSERT(filteredResponse);
   MOZ_ASSERT(mObserver);
   mObserver->OnResponseAvailable(filteredResponse);
+#ifdef DEBUG
   mResponseAvailableCalled = true;
+#endif
   return filteredResponse.forget();
 }
 
 void
 FetchDriver::FailWithNetworkError()
 {
   workers::AssertIsOnMainThread();
   RefPtr<InternalResponse> error = InternalResponse::NetworkError();
   if (mObserver) {
     mObserver->OnResponseAvailable(error);
+#ifdef DEBUG
     mResponseAvailableCalled = true;
+#endif
     mObserver->OnResponseEnd();
     mObserver = nullptr;
   }
 }
 
 namespace {
 class FillResponseHeaders final : public nsIHttpHeaderVisitor {
   InternalResponse* mResponse;
--- a/dom/fetch/FetchDriver.h
+++ b/dom/fetch/FetchDriver.h
@@ -78,18 +78,20 @@ private:
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsILoadGroup> mLoadGroup;
   RefPtr<InternalRequest> mRequest;
   RefPtr<InternalResponse> mResponse;
   nsCOMPtr<nsIOutputStream> mPipeOutputStream;
   RefPtr<FetchDriverObserver> mObserver;
   nsCOMPtr<nsIDocument> mDocument;
 
-  DebugOnly<bool> mResponseAvailableCalled;
-  DebugOnly<bool> mFetchCalled;
+#ifdef DEBUG
+  bool mResponseAvailableCalled;
+  bool mFetchCalled;
+#endif
 
   FetchDriver() = delete;
   FetchDriver(const FetchDriver&) = delete;
   FetchDriver& operator=(const FetchDriver&) = delete;
   ~FetchDriver();
 
   nsresult ContinueFetch();
   nsresult HttpFetch();
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -5648,17 +5648,19 @@ struct ConnectionPool::TransactionInfo f
   const int64_t mLoggingSerialNumber;
   const nsTArray<nsString> mObjectStoreNames;
   nsTHashtable<nsPtrHashKey<TransactionInfo>> mBlockedOn;
   nsTHashtable<nsPtrHashKey<TransactionInfo>> mBlocking;
   nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
   const bool mIsWriteTransaction;
   bool mRunning;
 
-  DebugOnly<bool> mFinished;
+#ifdef DEBUG
+  bool mFinished;
+#endif
 
   TransactionInfo(DatabaseInfo* aDatabaseInfo,
                   const nsID& aBackgroundChildLoggingId,
                   const nsACString& aDatabaseId,
                   uint64_t aTransactionId,
                   int64_t aLoggingSerialNumber,
                   const nsTArray<nsString>& aObjectStoreNames,
                   bool aIsWriteTransaction,
@@ -5908,17 +5910,19 @@ private:
 
   // Not to be overridden by subclasses.
   NS_DECL_MOZISTORAGEPROGRESSHANDLER
 };
 
 class MOZ_STACK_CLASS DatabaseOperationBase::AutoSetProgressHandler final
 {
   mozIStorageConnection* mConnection;
-  DebugOnly<DatabaseOperationBase*> mDEBUGDatabaseOp;
+#ifdef DEBUG
+  DatabaseOperationBase* mDEBUGDatabaseOp;
+#endif
 
 public:
   AutoSetProgressHandler();
 
   ~AutoSetProgressHandler();
 
   nsresult
   Register(mozIStorageConnection* aConnection,
@@ -6009,17 +6013,19 @@ class Factory final
   : public PBackgroundIDBFactoryParent
 {
   // Counts the number of "live" Factory instances that have not yet had
   // ActorDestroy called.
   static uint64_t sFactoryInstanceCount;
 
   RefPtr<DatabaseLoggingInfo> mLoggingInfo;
 
-  DebugOnly<bool> mActorDestroyed;
+#ifdef DEBUG
+  bool mActorDestroyed;
+#endif
 
 public:
   static already_AddRefed<Factory>
   Create(const LoggingInfo& aLoggingInfo);
 
   DatabaseLoggingInfo*
   GetLoggingInfo() const
   {
@@ -7919,26 +7925,30 @@ private:
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 };
 
 class NormalTransactionOp
   : public TransactionDatabaseOperationBase
   , public PBackgroundIDBRequestParent
 {
-  DebugOnly<bool> mResponseSent;
+#ifdef DEBUG
+  bool mResponseSent;
+#endif
 
 public:
   virtual void
   Cleanup() override;
 
 protected:
   explicit NormalTransactionOp(TransactionBase* aTransaction)
     : TransactionDatabaseOperationBase(aTransaction)
+#ifdef DEBUG
     , mResponseSent(false)
+#endif
   { }
 
   virtual
   ~NormalTransactionOp()
   { }
 
   // An overload of DatabaseOperationBase's function that can avoid doing extra
   // work on non-versionchange transactions.
@@ -8387,23 +8397,27 @@ class Cursor::CursorOpBase
   : public TransactionDatabaseOperationBase
 {
 protected:
   RefPtr<Cursor> mCursor;
   nsTArray<FallibleTArray<StructuredCloneFile>> mFiles;
 
   CursorResponse mResponse;
 
-  DebugOnly<bool> mResponseSent;
+#ifdef DEBUG
+  bool mResponseSent;
+#endif
 
 protected:
   explicit CursorOpBase(Cursor* aCursor)
     : TransactionDatabaseOperationBase(aCursor->mTransaction)
     , mCursor(aCursor)
+#ifdef DEBUG
     , mResponseSent(false)
+#endif
   {
     AssertIsOnBackgroundThread();
     MOZ_ASSERT(aCursor);
   }
 
   virtual
   ~CursorOpBase()
   { }
@@ -9240,26 +9254,30 @@ private:
 class MOZ_STACK_CLASS DatabaseMaintenance::AutoProgressHandler final
   : public mozIStorageProgressHandler
 {
   Maintenance* mMaintenance;
   mozIStorageConnection* mConnection;
 
   NS_DECL_OWNINGTHREAD
 
+#ifdef DEBUG
   // This class is stack-based so we never actually allow AddRef/Release to do
   // anything. But we need to know if any consumer *thinks* that they have a
   // reference to this object so we track the reference countin DEBUG builds.
-  DebugOnly<nsrefcnt> mDEBUGRefCnt;
+  nsrefcnt mDEBUGRefCnt;
+#endif
 
 public:
   explicit AutoProgressHandler(Maintenance* aMaintenance)
     : mMaintenance(aMaintenance)
     , mConnection(nullptr)
+#ifdef DEBUG
     , mDEBUGRefCnt(0)
+#endif
   {
     MOZ_ASSERT(!NS_IsMainThread());
     MOZ_ASSERT(!IsOnBackgroundThread());
     NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
     MOZ_ASSERT(aMaintenance);
   }
 
   ~AutoProgressHandler()
@@ -12637,17 +12655,19 @@ TransactionInfo::TransactionInfo(
   : mDatabaseInfo(aDatabaseInfo)
   , mBackgroundChildLoggingId(aBackgroundChildLoggingId)
   , mDatabaseId(aDatabaseId)
   , mTransactionId(aTransactionId)
   , mLoggingSerialNumber(aLoggingSerialNumber)
   , mObjectStoreNames(aObjectStoreNames)
   , mIsWriteTransaction(aIsWriteTransaction)
   , mRunning(false)
+#ifdef DEBUG
   , mFinished(false)
+#endif
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabaseInfo);
   aDatabaseInfo->mConnectionPool->AssertIsOnOwningThread();
 
   MOZ_COUNT_CTOR(ConnectionPool::TransactionInfo);
 
   if (aTransactionOp) {
@@ -12788,17 +12808,19 @@ DatabaseLoggingInfo::~DatabaseLoggingInf
 /*******************************************************************************
  * Factory
  ******************************************************************************/
 
 uint64_t Factory::sFactoryInstanceCount = 0;
 
 Factory::Factory(already_AddRefed<DatabaseLoggingInfo> aLoggingInfo)
   : mLoggingInfo(Move(aLoggingInfo))
+#ifdef DEBUG
   , mActorDestroyed(false)
+#endif
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
 }
 
 Factory::~Factory()
 {
   MOZ_ASSERT(mActorDestroyed);
@@ -12877,17 +12899,19 @@ Factory::Create(const LoggingInfo& aLogg
 }
 
 void
 Factory::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mActorDestroyed);
 
+#ifdef DEBUG
   mActorDestroyed = true;
+#endif
 
   // Clean up if there are no more instances.
   if (!(--sFactoryInstanceCount)) {
     MOZ_ASSERT(gLoggingInfoHashtable);
     gLoggingInfoHashtable = nullptr;
 
     MOZ_ASSERT(gLiveDatabaseHashtable);
     MOZ_ASSERT(!gLiveDatabaseHashtable->Count());
@@ -18200,27 +18224,31 @@ AutoProgressHandler::Unregister()
 }
 
 NS_IMETHODIMP_(MozExternalRefCountType)
 DatabaseMaintenance::
 AutoProgressHandler::AddRef()
 {
   NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
 
+#ifdef DEBUG
   mDEBUGRefCnt++;
+#endif
   return 2;
 }
 
 NS_IMETHODIMP_(MozExternalRefCountType)
 DatabaseMaintenance::
 AutoProgressHandler::Release()
 {
   NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
 
+#ifdef DEBUG
   mDEBUGRefCnt--;
+#endif
   return 1;
 }
 
 NS_IMPL_QUERY_INTERFACE(DatabaseMaintenance::AutoProgressHandler,
                         mozIStorageProgressHandler)
 
 NS_IMETHODIMP
 DatabaseMaintenance::
@@ -19198,17 +19226,19 @@ DatabaseOperationBase::OnProgress(mozISt
   // This is intentionally racy.
   *_retval = !OperationMayProceed();
   return NS_OK;
 }
 
 DatabaseOperationBase::
 AutoSetProgressHandler::AutoSetProgressHandler()
   : mConnection(nullptr)
+#ifdef DEBUG
   , mDEBUGDatabaseOp(nullptr)
+#endif
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
 }
 
 DatabaseOperationBase::
 AutoSetProgressHandler::~AutoSetProgressHandler()
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
@@ -19239,17 +19269,19 @@ AutoSetProgressHandler::Register(mozISto
                                     getter_AddRefs(oldProgressHandler));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   MOZ_ASSERT(!oldProgressHandler);
 
   mConnection = aConnection;
+#ifdef DEBUG
   mDEBUGDatabaseOp = aDatabaseOp;
+#endif
 
   return NS_OK;
 }
 
 MutableFile::MutableFile(nsIFile* aFile,
                          Database* aDatabase,
                          FileInfo* aFileInfo)
   : BackgroundMutableFileParentBase(FILE_HANDLE_STORAGE_IDB,
@@ -24347,17 +24379,19 @@ NormalTransactionOp::SendSuccessResult()
 
     if (NS_WARN_IF(!PBackgroundIDBRequestParent::Send__delete__(this,
                                                                 response))) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
   }
 
+#ifdef DEBUG
   mResponseSent = true;
+#endif
 
   return NS_OK;
 }
 
 bool
 NormalTransactionOp::SendFailureResult(nsresult aResultCode)
 {
   AssertIsOnOwningThread();
@@ -24366,17 +24400,19 @@ NormalTransactionOp::SendFailureResult(n
   bool result = false;
 
   if (!IsActorDestroyed()) {
     result =
       PBackgroundIDBRequestParent::Send__delete__(this,
                                                   ClampResultCode(aResultCode));
   }
 
+#ifdef DEBUG
   mResponseSent = true;
+#endif
 
   return result;
 }
 
 void
 NormalTransactionOp::Cleanup()
 {
   AssertIsOnOwningThread();
@@ -26057,17 +26093,19 @@ CursorOpBase::SendFailureResult(nsresult
   MOZ_ASSERT(!mResponseSent);
 
   if (!IsActorDestroyed()) {
     mResponse = ClampResultCode(aResultCode);
 
     mCursor->SendResponseInternal(mResponse, mFiles);
   }
 
+#ifdef DEBUG
   mResponseSent = true;
+#endif
   return false;
 }
 
 void
 Cursor::
 CursorOpBase::Cleanup()
 {
   AssertIsOnOwningThread();
@@ -27067,17 +27105,19 @@ OpenOp::SendSuccessResult()
                 mCursor->mObjectKey.IsUnset());
 
   if (IsActorDestroyed()) {
     return NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
   }
 
   mCursor->SendResponseInternal(mResponse, mFiles);
 
+#ifdef DEBUG
   mResponseSent = true;
+#endif
   return NS_OK;
 }
 
 nsresult
 Cursor::
 ContinueOp::DoDatabaseWork(DatabaseConnection* aConnection)
 {
   MOZ_ASSERT(aConnection);
@@ -27243,17 +27283,19 @@ ContinueOp::SendSuccessResult()
                 mCursor->mObjectKey.IsUnset());
 
   if (IsActorDestroyed()) {
     return NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
   }
 
   mCursor->SendResponseInternal(mResponse, mFiles);
 
+#ifdef DEBUG
   mResponseSent = true;
+#endif
   return NS_OK;
 }
 
 Utils::Utils()
 #ifdef DEBUG
   : mActorDestroyed(false)
 #endif
 {
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -292,17 +292,19 @@ TabParent::TabParent(nsIContentParent* a
   , mDragAreaY(0)
   , mInitedByParent(false)
   , mTabId(aTabId)
   , mCreatingWindow(false)
   , mNeedLayerTreeReadyNotification(false)
   , mCursor(nsCursor(-1))
   , mTabSetsCursor(false)
   , mHasContentOpener(false)
+#ifdef DEBUG
   , mActiveSupressDisplayportCount(0)
+#endif
 {
   MOZ_ASSERT(aManager);
 }
 
 TabParent::~TabParent()
 {
 }
 
@@ -2830,23 +2832,24 @@ TabParent::SetDocShellIsActiveAndForegro
 
 NS_IMETHODIMP
 TabParent::SuppressDisplayport(bool aEnabled)
 {
   if (IsDestroyed()) {
     return NS_OK;
   }
 
+#ifdef DEBUG
   if (aEnabled) {
     mActiveSupressDisplayportCount++;
   } else {
     mActiveSupressDisplayportCount--;
   }
-
   MOZ_ASSERT(mActiveSupressDisplayportCount >= 0);
+#endif
 
   Unused << SendSuppressDisplayport(aEnabled);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::GetTabId(uint64_t* aId)
 {
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -715,17 +715,19 @@ private:
   // True if the cursor changes from the TabChild should change the widget
   // cursor.  This happens whenever the cursor is in the tab's region.
   bool mTabSetsCursor;
 
   RefPtr<nsIPresShell> mPresShellWithRefreshListener;
 
   bool mHasContentOpener;
 
-  DebugOnly<int32_t> mActiveSupressDisplayportCount;
+#ifdef DEBUG
+  int32_t mActiveSupressDisplayportCount;
+#endif
 
   ShowInfo GetShowInfo();
 
 private:
   // This is used when APZ needs to find the TabParent associated with a layer
   // to dispatch events.
   typedef nsDataHashtable<nsUint64HashKey, TabParent*> LayerToTabParentTable;
   static LayerToTabParentTable* sLayerToTabParentTable;
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -4,16 +4,18 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <MediaStreamGraphImpl.h>
 #include "mozilla/dom/AudioContext.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "CubebUtils.h"
 
+#include "webrtc/MediaEngineWebRTC.h"
+
 #ifdef XP_MACOSX
 #include <sys/sysctl.h>
 #endif
 
 extern mozilla::LazyLogModule gMediaStreamGraphLog;
 #define STREAM_LOG(type, msg) MOZ_LOG(gMediaStreamGraphLog, type, msg)
 
 // We don't use NSPR log here because we want this interleaved with adb logcat
@@ -601,25 +603,32 @@ AudioCallbackDriver::Init()
   if (cubeb_get_min_latency(CubebUtils::GetCubebContext(), output, &latency) != CUBEB_OK) {
     NS_WARNING("Could not get minimal latency from cubeb.");
     return;
   }
 
   input = output;
   input.channels = mInputChannels; // change to support optional stereo capture
 
-  cubeb_stream* stream;
-  // XXX Only pass input input if we have an input listener.  Always
-  // set up output because it's easier, and it will just get silence.
-  // XXX Add support for adding/removing an input listener later.
-  if (cubeb_stream_init(CubebUtils::GetCubebContext(), &stream,
+  cubeb_stream* stream = nullptr;
+  CubebUtils::AudioDeviceID input_id = nullptr, output_id = nullptr;
+  // We have to translate the deviceID values to cubeb devid's since those can be
+  // freed whenever enumerate is called.
+  if ((!mGraphImpl->mInputWanted ||
+       AudioInputCubeb::GetDeviceID(mGraphImpl->mInputDeviceID, input_id)) &&
+      (mGraphImpl->mOutputDeviceID == -1 || // pass nullptr for ID for default output
+       AudioInputCubeb::GetDeviceID(mGraphImpl->mOutputDeviceID, output_id)) &&
+      // XXX Only pass input input if we have an input listener.  Always
+      // set up output because it's easier, and it will just get silence.
+      // XXX Add support for adding/removing an input listener later.
+      cubeb_stream_init(CubebUtils::GetCubebContext(), &stream,
                         "AudioCallbackDriver",
-                        mGraphImpl->mInputDeviceID,
+                        input_id,
                         mGraphImpl->mInputWanted ? &input : nullptr,
-                        mGraphImpl->mOutputDeviceID,
+                        output_id,
                         mGraphImpl->mOutputWanted ? &output : nullptr, latency,
                         DataCallback_s, StateCallback_s, this) == CUBEB_OK) {
     mAudioStream.own(stream);
   } else {
     NS_WARNING("Could not create a cubeb stream for MediaStreamGraph, falling back to a SystemClockDriver");
     // Fall back to a driver using a normal thread.
     MonitorAutoLock lock(GraphImpl()->GetMonitor());
     SetNextDriver(new SystemClockDriver(GraphImpl()));
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -691,32 +691,16 @@ MediaDecoderStateMachine::Push(MediaData
   } else {
     // TODO: Handle MediaRawData, determine which queue should be pushed.
   }
   UpdateNextFrameStatus();
   DispatchDecodeTasksIfNeeded();
 }
 
 void
-MediaDecoderStateMachine::PushFront(MediaData* aSample, MediaData::Type aSampleType)
-{
-  MOZ_ASSERT(OnTaskQueue());
-  MOZ_ASSERT(aSample);
-  if (aSample->mType == MediaData::AUDIO_DATA) {
-    AudioQueue().PushFront(aSample);
-  } else if (aSample->mType == MediaData::VIDEO_DATA) {
-    aSample->As<VideoData>()->mFrameID = ++mCurrentFrameID;
-    VideoQueue().PushFront(aSample);
-  } else {
-    // TODO: Handle MediaRawData, determine which queue should be pushed.
-  }
-  UpdateNextFrameStatus();
-}
-
-void
 MediaDecoderStateMachine::CheckIsAudible(const MediaData* aSample)
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(aSample->mType == MediaData::AUDIO_DATA);
 
   const AudioData* data = aSample->As<AudioData>();
   bool isAudible = data->IsAudible();
   if (isAudible && !mIsAudioDataAudible) {
@@ -880,17 +864,18 @@ MediaDecoderStateMachine::MaybeFinishDec
   // We can now complete the pending seek.
   mPendingSeek.Steal(mQueuedSeek);
   SetState(DECODER_STATE_SEEKING);
   ScheduleStateMachine();
   return true;
 }
 
 void
-MediaDecoderStateMachine::OnVideoDecoded(MediaData* aVideoSample)
+MediaDecoderStateMachine::OnVideoDecoded(MediaData* aVideoSample,
+                                         TimeStamp aDecodeStartTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   RefPtr<MediaData> video(aVideoSample);
   MOZ_ASSERT(video);
   mVideoDataRequest.Complete();
   aVideoSample->AdjustForStartTime(StartTime());
 
   // Handle abnormal or negative timestamps.
@@ -926,17 +911,17 @@ MediaDecoderStateMachine::OnVideoDecoded
       // For non async readers, if the requested video sample was slow to
       // arrive, increase the amount of audio we buffer to ensure that we
       // don't run out of audio. This is unnecessary for async readers,
       // since they decode audio and video on different threads so they
       // are unlikely to run out of decoded audio.
       if (mReader->IsAsync()) {
         return;
       }
-      TimeDuration decodeTime = TimeStamp::Now() - mVideoDecodeStartTime;
+      TimeDuration decodeTime = TimeStamp::Now() - aDecodeStartTime;
       if (!IsDecodingFirstFrame() &&
           THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
           !HasLowUndecodedData())
       {
         mLowAudioThresholdUsecs =
           std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), mAmpleAudioThresholdUsecs);
         mAmpleAudioThresholdUsecs = std::max(THRESHOLD_FACTOR * mLowAudioThresholdUsecs,
                                               mAmpleAudioThresholdUsecs);
@@ -1763,46 +1748,58 @@ MediaDecoderStateMachine::EnsureVideoDec
 void
 MediaDecoderStateMachine::RequestVideoData()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   // Time the video decode, so that if it's slow, we can increase our low
   // audio threshold to reduce the chance of an audio underrun while we're
   // waiting for a video decode to complete.
-  mVideoDecodeStartTime = TimeStamp::Now();
+  TimeStamp videoDecodeStartTime = TimeStamp::Now();
 
   bool skipToNextKeyFrame = mSentFirstFrameLoadedEvent &&
     NeedToSkipToNextKeyframe();
-  int64_t currentTime = mState == DECODER_STATE_SEEKING ? 0 : GetMediaTime();
+
+  int64_t currentTime =
+    mState == DECODER_STATE_SEEKING || !mSentFirstFrameLoadedEvent
+      ? 0 : GetMediaTime() + StartTime();
 
   SAMPLE_LOG("Queueing video task - queued=%i, decoder-queued=%o, skip=%i, time=%lld",
              VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames(), skipToNextKeyFrame,
              currentTime);
 
+  RefPtr<MediaDecoderStateMachine> self = this;
   if (mSentFirstFrameLoadedEvent) {
     mVideoDataRequest.Begin(
       InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
                   &MediaDecoderReader::RequestVideoData,
                   skipToNextKeyFrame, currentTime)
-      ->Then(OwnerThread(), __func__, this,
-             &MediaDecoderStateMachine::OnVideoDecoded,
-             &MediaDecoderStateMachine::OnVideoNotDecoded));
+      ->Then(OwnerThread(), __func__,
+             [self, videoDecodeStartTime] (MediaData* aVideoSample) {
+               self->OnVideoDecoded(aVideoSample, videoDecodeStartTime);
+             },
+             [self] (MediaDecoderReader::NotDecodedReason aReason) {
+               self->OnVideoNotDecoded(aReason);
+             }));
   } else {
     mVideoDataRequest.Begin(
       InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
                   &MediaDecoderReader::RequestVideoData,
                   skipToNextKeyFrame, currentTime)
       ->Then(OwnerThread(), __func__, mStartTimeRendezvous.get(),
              &StartTimeRendezvous::ProcessFirstSample<VideoDataPromise, MediaData::VIDEO_DATA>,
              &StartTimeRendezvous::FirstSampleRejected<MediaData::VIDEO_DATA>)
       ->CompletionPromise()
-      ->Then(OwnerThread(), __func__, this,
-             &MediaDecoderStateMachine::OnVideoDecoded,
-             &MediaDecoderStateMachine::OnVideoNotDecoded));
+      ->Then(OwnerThread(), __func__,
+             [self, videoDecodeStartTime] (MediaData* aVideoSample) {
+               self->OnVideoDecoded(aVideoSample, videoDecodeStartTime);
+             },
+             [self] (MediaDecoderReader::NotDecodedReason aReason) {
+               self->OnVideoNotDecoded(aReason);
+             }));
   }
 }
 
 void
 MediaDecoderStateMachine::StartMediaSink()
 {
   MOZ_ASSERT(OnTaskQueue());
   if (!mMediaSink->IsStarted()) {
@@ -2519,17 +2516,18 @@ MediaDecoderStateMachine::DropVideoUpToS
       RefPtr<VideoData> temp = VideoData::ShallowCopyUpdateTimestamp(video, target);
       video = temp;
     }
     mFirstVideoFrameAfterSeek = nullptr;
 
     DECODER_LOG("DropVideoUpToSeekTarget() found video frame [%lld, %lld] containing target=%lld",
                 video->mTime, video->GetEndTime(), target);
 
-    PushFront(video, MediaData::VIDEO_DATA);
+    MOZ_ASSERT(VideoQueue().GetSize() == 0, "Should be the 1st sample after seeking");
+    Push(video, MediaData::VIDEO_DATA);
   }
 
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::DropAudioUpToSeekTarget(MediaData* aSample)
 {
@@ -2595,17 +2593,18 @@ MediaDecoderStateMachine::DropAudioUpToS
   }
   RefPtr<AudioData> data(new AudioData(audio->mOffset,
                                        mCurrentSeek.mTarget.GetTime().ToMicroseconds(),
                                        duration.value(),
                                        frames,
                                        Move(audioData),
                                        channels,
                                        audio->mRate));
-  PushFront(data, MediaData::AUDIO_DATA);
+  MOZ_ASSERT(AudioQueue().GetSize() == 0, "Should be the 1st sample after seeking");
+  Push(data, MediaData::AUDIO_DATA);
 
   return NS_OK;
 }
 
 void MediaDecoderStateMachine::UpdateNextFrameStatus()
 {
   MOZ_ASSERT(OnTaskQueue());
 
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -359,17 +359,17 @@ private:
 
   // Returns true if we're currently playing. The decoder monitor must
   // be held.
   bool IsPlaying() const;
 
   // TODO: Those callback function may receive demuxed-only data.
   // Need to figure out a suitable API name for this case.
   void OnAudioDecoded(MediaData* aAudioSample);
-  void OnVideoDecoded(MediaData* aVideoSample);
+  void OnVideoDecoded(MediaData* aVideoSample, TimeStamp aDecodeStartTime);
   void OnNotDecoded(MediaData::Type aType, MediaDecoderReader::NotDecodedReason aReason);
   void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
   {
     MOZ_ASSERT(OnTaskQueue());
     OnNotDecoded(MediaData::AUDIO_DATA, aReason);
   }
   void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
   {
@@ -387,17 +387,16 @@ protected:
   void SetState(State aState);
 
   void BufferedRangeUpdated();
 
   // Inserts MediaData* samples into their respective MediaQueues.
   // aSample must not be null.
 
   void Push(MediaData* aSample, MediaData::Type aSampleType);
-  void PushFront(MediaData* aSample, MediaData::Type aSampleType);
 
   void OnAudioPopped(const RefPtr<MediaData>& aSample);
   void OnVideoPopped(const RefPtr<MediaData>& aSample);
 
   void CheckIsAudible(const MediaData* aSample);
   void VolumeChanged();
   void LogicalPlaybackRateChanged();
   void PreservesPitchChanged();
@@ -784,21 +783,16 @@ private:
     Maybe<int64_t> mAudioStartTime;
     Maybe<int64_t> mVideoStartTime;
   };
   RefPtr<StartTimeRendezvous> mStartTimeRendezvous;
 
   bool HaveStartTime() { return mStartTimeRendezvous && mStartTimeRendezvous->HaveStartTime(); }
   int64_t StartTime() { return mStartTimeRendezvous->StartTime(); }
 
-  // Time at which the last video sample was requested. If it takes too long
-  // before the sample arrives, we will increase the amount of audio we buffer.
-  // This is necessary for legacy synchronous decoders to prevent underruns.
-  TimeStamp mVideoDecodeStartTime;
-
   // Queue of audio frames. This queue is threadsafe, and is accessed from
   // the audio, decoder, state machine, and main threads.
   MediaQueue<MediaData> mAudioQueue;
   // Queue of video frames. This queue is threadsafe, and is accessed from
   // the decoder, state machine, and main threads.
   MediaQueue<MediaData> mVideoQueue;
 
   // The decoder monitor must be obtained before modifying this state.
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -18,17 +18,16 @@
 
 namespace mozilla {
 
 class CDMProxy;
 
 class MediaFormatReader final : public MediaDecoderReader
 {
   typedef TrackInfo::TrackType TrackType;
-  typedef media::Interval<int64_t> ByteInterval;
 
 public:
   MediaFormatReader(AbstractMediaDecoder* aDecoder,
                     MediaDataDemuxer* aDemuxer,
                     VideoFrameContainer* aVideoFrameContainer = nullptr,
                     layers::LayersBackend aLayersBackend = layers::LayersBackend::LAYERS_NONE);
 
   virtual ~MediaFormatReader();
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1505,21 +1505,21 @@ MediaManager::IsInMediaThread()
 // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
 // from MediaManager thread.
 
 // Guaranteed never to return nullptr.
 /* static */  MediaManager*
 MediaManager::Get() {
   if (!sSingleton) {
     MOZ_ASSERT(NS_IsMainThread());
-#ifdef DEBUG
+
     static int timesCreated = 0;
     timesCreated++;
-    MOZ_ASSERT(timesCreated == 1);
-#endif
+    MOZ_RELEASE_ASSERT(timesCreated == 1);
+
     sSingleton = new MediaManager();
 
     sSingleton->mMediaThread = new base::Thread("MediaManager");
     base::Thread::Options options;
 #if defined(_WIN32)
     options.message_loop_type = MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD;
 #else
     options.message_loop_type = MessageLoop::TYPE_MOZILLA_NONMAINTHREAD;
@@ -3286,18 +3286,22 @@ GetUserMediaCallbackMediaStreamListener:
 
 void
 GetUserMediaCallbackMediaStreamListener::NotifyFinished()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mFinished = true;
   Stop(); // we know it's been activated
 
-  RefPtr<MediaManager> manager(MediaManager::GetInstance());
-  manager->RemoveFromWindowList(mWindowID, this);
+  RefPtr<MediaManager> manager(MediaManager::GetIfExists());
+  if (manager) {
+    manager->RemoveFromWindowList(mWindowID, this);
+  } else {
+    NS_WARNING("Late NotifyFinished after MediaManager shutdown");
+  }
 }
 
 // Called from the MediaStreamGraph thread
 void
 GetUserMediaCallbackMediaStreamListener::NotifyDirectListeners(MediaStreamGraph* aGraph,
                                                                bool aHasListeners)
 {
   MediaManager::PostTask(FROM_HERE,
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -99,17 +99,18 @@ void
 MediaStreamGraphImpl::AddStreamGraphThread(MediaStream* aStream)
 {
   aStream->mBufferStartTime = mProcessedTime;
   if (aStream->IsSuspended()) {
     mSuspendedStreams.AppendElement(aStream);
     STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to the graph, in the suspended stream array", aStream));
   } else {
     mStreams.AppendElement(aStream);
-    STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to the graph", aStream));
+    STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to graph %p, count %lu", aStream, this, mStreams.Length()));
+    LIFECYCLE_LOG("Adding media stream %p to graph %p, count %lu", aStream, this, mStreams.Length());
   }
 
   SetStreamOrderDirty();
 }
 
 void
 MediaStreamGraphImpl::RemoveStreamGraphThread(MediaStream* aStream)
 {
@@ -129,19 +130,22 @@ MediaStreamGraphImpl::RemoveStreamGraphT
   SetStreamOrderDirty();
 
   if (aStream->IsSuspended()) {
     mSuspendedStreams.RemoveElement(aStream);
   } else {
     mStreams.RemoveElement(aStream);
   }
 
+  STREAM_LOG(LogLevel::Debug, ("Removed media stream %p from graph %p, count %lu",
+                               aStream, this, mStreams.Length()))
+  LIFECYCLE_LOG("Removed media stream %p from graph %p, count %lu",
+                aStream, this, mStreams.Length());
+
   NS_RELEASE(aStream); // probably destroying it
-
-  STREAM_LOG(LogLevel::Debug, ("Removing media stream %p from the graph", aStream));
 }
 
 void
 MediaStreamGraphImpl::ExtractPendingInput(SourceMediaStream* aStream,
                                           GraphTime aDesiredUpToTime,
                                           bool* aEnsureNextIteration)
 {
   bool finished;
@@ -331,48 +335,80 @@ MediaStreamGraphImpl::WillUnderrun(Media
 
 namespace {
   // Value of mCycleMarker for unvisited streams in cycle detection.
   const uint32_t NOT_VISITED = UINT32_MAX;
   // Value of mCycleMarker for ordered streams in muted cycles.
   const uint32_t IN_MUTED_CYCLE = 1;
 } // namespace
 
-void
-MediaStreamGraphImpl::UpdateStreamOrder()
+bool
+MediaStreamGraphImpl::AudioTrackPresent(bool& aNeedsAEC)
 {
 #ifdef MOZ_WEBRTC
   bool shouldAEC = false;
 #endif
   bool audioTrackPresent = false;
-  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
+  for (uint32_t i = 0; i < mStreams.Length() && audioTrackPresent == false; ++i) {
     MediaStream* stream = mStreams[i];
+    SourceMediaStream* source = stream->AsSourceStream();
 #ifdef MOZ_WEBRTC
-    if (stream->AsSourceStream() &&
-        stream->AsSourceStream()->NeedsMixing()) {
-      shouldAEC = true;
+    if (source && source->NeedsMixing()) {
+      aNeedsAEC = true;
     }
 #endif
     // If this is a AudioNodeStream, force a AudioCallbackDriver.
     if (stream->AsAudioNodeStream()) {
       audioTrackPresent = true;
     } else {
       for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer(), MediaSegment::AUDIO);
            !tracks.IsEnded(); tracks.Next()) {
         audioTrackPresent = true;
       }
     }
+    if (source) {
+      for (auto& data : source->mPendingTracks) {
+        if (data.mData->GetType() == MediaSegment::AUDIO) {
+          audioTrackPresent = true;
+          break;
+        }
+      }
+    }
   }
+
+  // XXX For some reason, there are race conditions when starting an audio input where
+  // we find no active audio tracks.  In any case, if we have an active audio input we
+  // should not allow a switch back to a SystemClockDriver
+  if (!audioTrackPresent && mInputDeviceUsers.Count() != 0) {
+    NS_WARNING("No audio tracks, but full-duplex audio is enabled!!!!!");
+    audioTrackPresent = true;
+    shouldAEC = true;
+  }
+
+#ifdef MOZ_WEBRTC
+  aNeedsAEC = shouldAEC;
+#endif
+  return audioTrackPresent;
+}
+
+void
+MediaStreamGraphImpl::UpdateStreamOrder()
+{
+  bool shouldAEC = false;
+  bool audioTrackPresent = AudioTrackPresent(shouldAEC);
+
   // Note that this looks for any audio streams, input or output, and switches to a
-  // SystemClockDriver if there are none
+  // SystemClockDriver if there are none.  However, if another is already pending, let that
+  // switch happen.
 
   if (!audioTrackPresent && mRealtime &&
       CurrentDriver()->AsAudioCallbackDriver()) {
     MonitorAutoLock mon(mMonitor);
-    if (CurrentDriver()->AsAudioCallbackDriver()->IsStarted()) {
+    if (CurrentDriver()->AsAudioCallbackDriver()->IsStarted() &&
+        !(CurrentDriver()->Switching())) {
       if (mLifecycleState == LIFECYCLE_RUNNING) {
         SystemClockDriver* driver = new SystemClockDriver(this);
         CurrentDriver()->SwitchAtNextIteration(driver);
       }
     }
   }
 
   bool switching = false;
@@ -930,17 +966,17 @@ MediaStreamGraphImpl::PlayVideo(MediaStr
   // If the stream has finished and the timestamps of all frames have expired
   // then no more updates are required.
   if (aStream->mFinished && !haveMultipleImages) {
     aStream->mLastPlayedVideoFrame.SetNull();
   }
 }
 
 void
-MediaStreamGraphImpl::OpenAudioInputImpl(CubebUtils::AudioDeviceID aID,
+MediaStreamGraphImpl::OpenAudioInputImpl(int aID,
                                          AudioDataListener *aListener)
 {
   // Bug 1238038 Need support for multiple mics at once
   if (mInputDeviceUsers.Count() > 0 &&
       !mInputDeviceUsers.Get(aListener, nullptr)) {
     NS_ASSERTION(false, "Input from multiple mics not yet supported; bug 1238038");
     // Need to support separate input-only AudioCallback drivers; they'll
     // call us back on "other" threads.  We will need to echo-cancel them, though.
@@ -952,56 +988,60 @@ MediaStreamGraphImpl::OpenAudioInputImpl
   // XXX Since we can't rely on IDs staying valid (ugh), use the listener as
   // a stand-in for the ID.  Fix as part of support for multiple-captures
   // (Bug 1238038)
   uint32_t count = 0;
   mInputDeviceUsers.Get(aListener, &count); // ok if this fails
   count++;
   mInputDeviceUsers.Put(aListener, count); // creates a new entry in the hash if needed
 
-  // aID is a cubeb_devid, and we assume that opaque ptr is valid until
-  // we close cubeb.
-  mInputDeviceID = aID;
   if (count == 1) { // first open for this listener
+    // aID is a cubeb_devid, and we assume that opaque ptr is valid until
+    // we close cubeb.
+    mInputDeviceID = aID;
     mAudioInputs.AppendElement(aListener); // always monitor speaker data
-  }
-
-  // Switch Drivers since we're adding input (to input-only or full-duplex)
-  MonitorAutoLock mon(mMonitor);
-  if (mLifecycleState == LIFECYCLE_RUNNING) {
-    AudioCallbackDriver* driver = new AudioCallbackDriver(this);
-    driver->SetInputListener(aListener);
-    CurrentDriver()->SwitchAtNextIteration(driver);
+
+    // Switch Drivers since we're adding input (to input-only or full-duplex)
+    MonitorAutoLock mon(mMonitor);
+    if (mLifecycleState == LIFECYCLE_RUNNING) {
+      AudioCallbackDriver* driver = new AudioCallbackDriver(this);
+      STREAM_LOG(LogLevel::Debug, ("OpenAudioInput: starting new AudioCallbackDriver(input) %p", driver));
+      LIFECYCLE_LOG("OpenAudioInput: starting new AudioCallbackDriver(input) %p", driver);
+      driver->SetInputListener(aListener);
+      CurrentDriver()->SwitchAtNextIteration(driver);
+   } else {
+      STREAM_LOG(LogLevel::Error, ("OpenAudioInput in shutdown!"));
+      LIFECYCLE_LOG("OpenAudioInput in shutdown!");
+      NS_ASSERTION(false, "Can't open cubeb inputs in shutdown");
+    }
   }
 }
 
 nsresult
-MediaStreamGraphImpl::OpenAudioInput(CubebUtils::AudioDeviceID aID,
+MediaStreamGraphImpl::OpenAudioInput(int aID,
                                      AudioDataListener *aListener)
 {
   // So, so, so annoying.  Can't AppendMessage except on Mainthread
   if (!NS_IsMainThread()) {
     NS_DispatchToMainThread(WrapRunnable(this,
                                          &MediaStreamGraphImpl::OpenAudioInput,
                                          aID, aListener));
     return NS_OK;
   }
   class Message : public ControlMessage {
   public:
-    Message(MediaStreamGraphImpl *aGraph, CubebUtils::AudioDeviceID aID,
+    Message(MediaStreamGraphImpl *aGraph, int aID,
             AudioDataListener *aListener) :
       ControlMessage(nullptr), mGraph(aGraph), mID(aID), mListener(aListener) {}
     virtual void Run()
     {
       mGraph->OpenAudioInputImpl(mID, mListener);
     }
     MediaStreamGraphImpl *mGraph;
-    // aID is a cubeb_devid, and we assume that opaque ptr is valid until
-    // we close cubeb.
-    CubebUtils::AudioDeviceID mID;
+    int mID;
     RefPtr<AudioDataListener> mListener;
   };
   // XXX Check not destroyed!
   this->AppendMessage(MakeUnique<Message>(this, aID, aListener));
   return NS_OK;
 }
 
 void
@@ -1010,39 +1050,27 @@ MediaStreamGraphImpl::CloseAudioInputImp
   uint32_t count;
   DebugOnly<bool> result = mInputDeviceUsers.Get(aListener, &count);
   MOZ_ASSERT(result);
   if (--count > 0) {
     mInputDeviceUsers.Put(aListener, count);
     return; // still in use
   }
   mInputDeviceUsers.Remove(aListener);
-  mInputDeviceID = nullptr;
+  mInputDeviceID = -1;
   mInputWanted = false;
   AudioCallbackDriver *driver = CurrentDriver()->AsAudioCallbackDriver();
   if (driver) {
     driver->RemoveInputListener(aListener);
   }
   mAudioInputs.RemoveElement(aListener);
 
   // Switch Drivers since we're adding or removing an input (to nothing/system or output only)
-  bool audioTrackPresent = false;
-  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-    MediaStream* stream = mStreams[i];
-    // If this is a AudioNodeStream, force a AudioCallbackDriver.
-    if (stream->AsAudioNodeStream()) {
-      audioTrackPresent = true;
-    } else if (CurrentDriver()->AsAudioCallbackDriver()) {
-      // only if there's a real switch!
-      for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer(), MediaSegment::AUDIO);
-           !tracks.IsEnded(); tracks.Next()) {
-        audioTrackPresent = true;
-      }
-    }
-  }
+  bool shouldAEC = false;
+  bool audioTrackPresent = AudioTrackPresent(shouldAEC);
 
   MonitorAutoLock mon(mMonitor);
   if (mLifecycleState == LIFECYCLE_RUNNING) {
     GraphDriver* driver;
     if (audioTrackPresent) {
       // We still have audio output
       STREAM_LOG(LogLevel::Debug, ("CloseInput: output present (AudioCallback)"));
 
@@ -2317,17 +2345,17 @@ MediaStream::AddMainThreadListener(MainT
     RefPtr<MediaStream> mStream;
   };
 
   RefPtr<nsRunnable> runnable = new NotifyRunnable(this);
   NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(runnable.forget())));
 }
 
 nsresult
-SourceMediaStream::OpenAudioInput(CubebUtils::AudioDeviceID aID,
+SourceMediaStream::OpenAudioInput(int aID,
                                   AudioDataListener *aListener)
 {
   if (GraphImpl()) {
     mInputListener = aListener;
     return GraphImpl()->OpenAudioInput(aID, aListener);
   }
   return NS_ERROR_FAILURE;
 }
@@ -2366,16 +2394,17 @@ SourceMediaStream::SetPullEnabled(bool a
 void
 SourceMediaStream::AddTrackInternal(TrackID aID, TrackRate aRate, StreamTime aStart,
                                     MediaSegment* aSegment, uint32_t aFlags)
 {
   MutexAutoLock lock(mMutex);
   nsTArray<TrackData> *track_data = (aFlags & ADDTRACK_QUEUED) ?
                                     &mPendingTracks : &mUpdateTracks;
   TrackData* data = track_data->AppendElement();
+  LIFECYCLE_LOG("AddTrackInternal: %lu/%lu", mPendingTracks.Length(), mUpdateTracks.Length());
   data->mID = aID;
   data->mInputRate = aRate;
   data->mResamplerChannelCount = 0;
   data->mStart = aStart;
   data->mEndOfFlushedData = aStart;
   data->mCommands = TRACK_CREATE;
   data->mData = aSegment;
   if (!(aFlags & ADDTRACK_QUEUED) && GraphImpl()) {
@@ -2383,16 +2412,17 @@ SourceMediaStream::AddTrackInternal(Trac
   }
 }
 
 void
 SourceMediaStream::FinishAddTracks()
 {
   MutexAutoLock lock(mMutex);
   mUpdateTracks.AppendElements(Move(mPendingTracks));
+  LIFECYCLE_LOG("FinishAddTracks: %lu/%lu", mPendingTracks.Length(), mUpdateTracks.Length());
   if (GraphImpl()) {
     GraphImpl()->EnsureNextIteration();
   }
 }
 
 StreamBuffer::Track*
 SourceMediaStream::FindTrack(TrackID aID)
 {
@@ -2803,19 +2833,19 @@ ProcessedMediaStream::DestroyImpl()
 }
 
 MediaStreamGraphImpl::MediaStreamGraphImpl(GraphDriverType aDriverRequested,
                                            TrackRate aSampleRate,
                                            dom::AudioChannel aChannel)
   : MediaStreamGraph(aSampleRate)
   , mPortCount(0)
   , mInputWanted(false)
-  , mInputDeviceID(nullptr)
+  , mInputDeviceID(-1)
   , mOutputWanted(true)
-  , mOutputDeviceID(nullptr)
+  , mOutputDeviceID(-1)
   , mNeedAnotherIteration(false)
   , mGraphDriverAsleep(false)
   , mMonitor("MediaStreamGraphImpl")
   , mLifecycleState(LIFECYCLE_THREAD_NOT_STARTED)
   , mEndTime(GRAPH_TIME_MAX)
   , mForceShutDown(false)
   , mPostedRunInStableStateEvent(false)
   , mDetectedNotRunning(false)
@@ -3266,27 +3296,19 @@ MediaStreamGraphImpl::ApplyAudioContextO
     }
   }
   // Close, suspend: check if we are going to switch to a
   // SystemAudioCallbackDriver, and pass the promise to the AudioCallbackDriver
   // if that's the case, so it can notify the content.
   // This is the same logic as in UpdateStreamOrder, but it's simpler to have it
   // here as well so we don't have to store the Promise(s) on the Graph.
   if (aOperation != AudioContextOperation::Resume) {
-    bool audioTrackPresent = false;
-    for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-      MediaStream* stream = mStreams[i];
-      if (stream->AsAudioNodeStream()) {
-        audioTrackPresent = true;
-      }
-      for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer(), MediaSegment::AUDIO);
-          !tracks.IsEnded(); tracks.Next()) {
-        audioTrackPresent = true;
-      }
-    }
+    bool shouldAEC = false;
+    bool audioTrackPresent = AudioTrackPresent(shouldAEC);
+
     if (!audioTrackPresent && CurrentDriver()->AsAudioCallbackDriver()) {
       CurrentDriver()->AsAudioCallbackDriver()->
         EnqueueStreamAndPromiseForOperation(aDestinationStream, aPromise,
                                             aOperation);
 
       SystemClockDriver* driver;
       if (nextDriver) {
         MOZ_ASSERT(!nextDriver->AsAudioCallbackDriver());
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -734,17 +734,17 @@ public:
   SourceMediaStream* AsSourceStream() override { return this; }
 
   // Media graph thread only
 
   // Users of audio inputs go through the stream so it can track when the
   // last stream referencing an input goes away, so it can close the cubeb
   // input.  Also note: callable on any thread (though it bounces through
   // MainThread to set the command if needed).
-  nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID,
+  nsresult OpenAudioInput(int aID,
                           AudioDataListener *aListener);
   // Note: also implied when Destroy() happens
   void CloseAudioInput();
 
   void DestroyImpl() override;
 
   // Call these on any thread.
   /**
@@ -1219,17 +1219,17 @@ public:
   };
   // Main thread only
   static MediaStreamGraph* GetInstance(GraphDriverType aGraphDriverRequested,
                                        dom::AudioChannel aChannel);
   static MediaStreamGraph* CreateNonRealtimeInstance(TrackRate aSampleRate);
   // Idempotent
   static void DestroyNonRealtimeInstance(MediaStreamGraph* aGraph);
 
-  virtual nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID,
+  virtual nsresult OpenAudioInput(int aID,
                                   AudioDataListener *aListener) {
     return NS_ERROR_FAILURE;
   }
   virtual void CloseAudioInput(AudioDataListener *aListener) {}
 
   // Control API.
   /**
    * Create a stream that a media decoder (or some other source of
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -180,20 +180,16 @@ public:
 
   /**
    * Make this MediaStreamGraph enter forced-shutdown state. This state
    * will be noticed by the media graph thread, which will shut down all streams
    * and other state controlled by the media graph thread.
    * This is called during application shutdown.
    */
   void ForceShutDown(ShutdownTicket* aShutdownTicket);
-  /**
-   * Shutdown() this MediaStreamGraph's threads and return when they've shut down.
-   */
-  void ShutdownThreads();
 
   /**
    * Called before the thread runs.
    */
   void Init();
   // The following methods run on the graph thread (or possibly the main thread if
   // mLifecycleState > LIFECYCLE_RUNNING)
   void AssertOnGraphThreadOrNotRunning() const
@@ -329,16 +325,22 @@ public:
   /*
    * Move streams from the mStreams to mSuspendedStream if suspending/closing an
    * AudioContext, or the inverse when resuming an AudioContext.
    */
   void SuspendOrResumeStreams(dom::AudioContextOperation aAudioContextOperation,
                               const nsTArray<MediaStream*>& aStreamSet);
 
   /**
+   * Determine if we have any audio tracks, or are about to add any audiotracks.
+   * Also checks if we'll need the AEC running (i.e. microphone input tracks)
+   */
+  bool AudioTrackPresent(bool& aNeedsAEC);
+
+  /**
    * Sort mStreams so that every stream not in a cycle is after any streams
    * it depends on, and every stream in a cycle is marked as being in a cycle.
    * Also sets mIsConsumed on every stream.
    */
   void UpdateStreamOrder();
 
   /**
    * Returns smallest value of t such that t is a multiple of
@@ -384,19 +386,19 @@ public:
    * Set the correct current video frame for stream aStream.
    */
   void PlayVideo(MediaStream* aStream);
   /**
    * No more data will be forthcoming for aStream. The stream will end
    * at the current buffer end point. The StreamBuffer's tracks must be
    * explicitly set to finished by the caller.
    */
-  void OpenAudioInputImpl(CubebUtils::AudioDeviceID aID,
+  void OpenAudioInputImpl(int aID,
                           AudioDataListener *aListener);
-  virtual nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID,
+  virtual nsresult OpenAudioInput(int aID,
                                   AudioDataListener *aListener) override;
   void CloseAudioInputImpl(AudioDataListener *aListener);
   virtual void CloseAudioInput(AudioDataListener *aListener) override;
 
   void FinishStream(MediaStream* aStream);
   /**
    * Compute how much stream data we would like to buffer for aStream.
    */
@@ -623,19 +625,19 @@ public:
    */
   int32_t mPortCount;
 
   /**
    * Devices to use for cubeb input & output, or NULL for no input (void*),
    * and boolean to control if we want input/output
    */
   bool mInputWanted;
-  CubebUtils::AudioDeviceID mInputDeviceID;
+  int mInputDeviceID;
   bool mOutputWanted;
-  CubebUtils::AudioDeviceID mOutputDeviceID;
+  int mOutputDeviceID;
   // Maps AudioDataListeners to a usecount of streams using the listener
   // so we can know when it's no longer in use.
   nsDataHashtable<nsPtrHashKey<AudioDataListener>, uint32_t> mInputDeviceUsers;
 
   // True if the graph needs another iteration after the current iteration.
   Atomic<bool> mNeedAnotherIteration;
   // GraphDriver may need a WakeUp() if something changes
   Atomic<bool> mGraphDriverAsleep;