Merge m-c to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 23 Oct 2015 11:51:27 +0200
changeset 304327 308fb512df21952364853a202809848d9024653f
parent 304326 b0d8d94d792b3635adc9ab13e972561434be37f5 (current diff)
parent 304313 0625c68c0abcfe4d10880d15d8fe7d06df3369c9 (diff)
child 304328 62b2b335cc85c7ef6ecb338a4a97127ce8f6c98c
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to fx-team
devtools/client/webconsole/test/browser.ini
mobile/android/base/GeckoInputConnection.java
testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-html-imports.https.html.ini
testing/web-platform/mozilla/meta/service-workers/service-worker/referer.https.html.ini
testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-html-imports.https.html
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-html-imports-iframe.sub.html
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-html-imports-worker.sub.js
--- a/accessible/base/DocManager.cpp
+++ b/accessible/base/DocManager.cpp
@@ -1,9 +1,10 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DocManager.h"
 
 #include "ApplicationAccessible.h"
 #include "ARIAMap.h"
@@ -66,23 +67,29 @@ DocManager::GetDocAccessible(nsIDocument
     return docAcc;
 
   return CreateDocOrRootAccessible(aDocument);
 }
 
 Accessible*
 DocManager::FindAccessibleInCache(nsINode* aNode) const
 {
-  nsSearchAccessibleInCacheArg arg;
-  arg.mNode = aNode;
+  for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
+    DocAccessible* docAccessible = iter.UserData();
+    NS_ASSERTION(docAccessible,
+                 "No doc accessible for the object in doc accessible cache!");
 
-  mDocAccessibleCache.EnumerateRead(SearchAccessibleInDocCache,
-                                    static_cast<void*>(&arg));
-
-  return arg.mAccessible;
+    if (docAccessible) {
+      Accessible* accessible = docAccessible->GetAccessible(aNode);
+      if (accessible) {
+        return accessible;
+      }
+    }
+  }
+  return nullptr;
 }
 
 void
 DocManager::NotifyOfDocumentShutdown(DocAccessible* aDocument,
                                      nsIDocument* aDOMDocument)
 {
   xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
   if (xpcDoc) {
@@ -107,21 +114,27 @@ DocManager::GetXPCDocument(DocAccessible
   }
   return xpcDoc;
 }
 
 #ifdef DEBUG
 bool
 DocManager::IsProcessingRefreshDriverNotification() const
 {
-  bool isDocRefreshing = false;
-  mDocAccessibleCache.EnumerateRead(SearchIfDocIsRefreshing,
-                                    static_cast<void*>(&isDocRefreshing));
+  for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
+    DocAccessible* docAccessible = iter.UserData();
+    NS_ASSERTION(docAccessible,
+                 "No doc accessible for the object in doc accessible cache!");
 
-  return isDocRefreshing;
+    if (docAccessible && docAccessible->mNotificationController &&
+        docAccessible->mNotificationController->IsUpdating()) {
+      return true;
+    }
+  }
+  return false;
 }
 #endif
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // DocManager protected
 
 bool
@@ -483,76 +496,34 @@ DocManager::CreateDocOrRootAccessible(ns
 
   AddListeners(aDocument, isRootDoc);
   return docAcc;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // DocManager static
 
-PLDHashOperator
-DocManager::GetFirstEntryInDocCache(const nsIDocument* aKey,
-                                    DocAccessible* aDocAccessible,
-                                    void* aUserArg)
-{
-  NS_ASSERTION(aDocAccessible,
-               "No doc accessible for the object in doc accessible cache!");
-  *reinterpret_cast<DocAccessible**>(aUserArg) = aDocAccessible;
-
-  return PL_DHASH_STOP;
-}
-
 void
 DocManager::ClearDocCache()
 {
-  DocAccessible* docAcc = nullptr;
-  while (mDocAccessibleCache.EnumerateRead(GetFirstEntryInDocCache, static_cast<void*>(&docAcc))) {
-    if (docAcc)
+  // This unusual do-one-element-per-iterator approach is required because each
+  // DocAccessible is removed elsewhere upon its Shutdown() method being
+  // called, which invalidates the existing iterator.
+  while (mDocAccessibleCache.Count() > 0) {
+    auto iter = mDocAccessibleCache.Iter();
+    MOZ_ASSERT(!iter.Done());
+    DocAccessible* docAcc = iter.UserData();
+    NS_ASSERTION(docAcc,
+                 "No doc accessible for the object in doc accessible cache!");
+    if (docAcc) {
       docAcc->Shutdown();
+    }
   }
 }
 
-PLDHashOperator
-DocManager::SearchAccessibleInDocCache(const nsIDocument* aKey,
-                                       DocAccessible* aDocAccessible,
-                                       void* aUserArg)
-{
-  NS_ASSERTION(aDocAccessible,
-               "No doc accessible for the object in doc accessible cache!");
-
-  if (aDocAccessible) {
-    nsSearchAccessibleInCacheArg* arg =
-      static_cast<nsSearchAccessibleInCacheArg*>(aUserArg);
-    arg->mAccessible = aDocAccessible->GetAccessible(arg->mNode);
-    if (arg->mAccessible)
-      return PL_DHASH_STOP;
-  }
-
-  return PL_DHASH_NEXT;
-}
-
-#ifdef DEBUG
-PLDHashOperator
-DocManager::SearchIfDocIsRefreshing(const nsIDocument* aKey,
-                                    DocAccessible* aDocAccessible,
-                                    void* aUserArg)
-{
-  NS_ASSERTION(aDocAccessible,
-               "No doc accessible for the object in doc accessible cache!");
-
-  if (aDocAccessible && aDocAccessible->mNotificationController &&
-      aDocAccessible->mNotificationController->IsUpdating()) {
-    *(static_cast<bool*>(aUserArg)) = true;
-    return PL_DHASH_STOP;
-  }
-
-  return PL_DHASH_NEXT;
-}
-#endif
-
 void
 DocManager::RemoteDocAdded(DocAccessibleParent* aDoc)
 {
   if (!sRemoteDocuments) {
     sRemoteDocuments = new nsTArray<DocAccessibleParent*>;
     ClearOnShutdown(&sRemoteDocuments);
   }
 
--- a/accessible/base/DocManager.h
+++ b/accessible/base/DocManager.h
@@ -131,45 +131,20 @@ private:
   void RemoveListeners(nsIDocument* aDocument);
 
   /**
    * Create document or root accessible.
    */
   DocAccessible* CreateDocOrRootAccessible(nsIDocument* aDocument);
 
   /**
-   * Get first entry of the document accessible from cache.
-   */
-  static PLDHashOperator
-    GetFirstEntryInDocCache(const nsIDocument* aKey,
-                            DocAccessible* aDocAccessible,
-                            void* aUserArg);
-
-  /**
    * Clear the cache and shutdown the document accessibles.
    */
   void ClearDocCache();
 
-  struct nsSearchAccessibleInCacheArg
-  {
-    Accessible* mAccessible;
-    nsINode* mNode;
-  };
-
-  static PLDHashOperator
-    SearchAccessibleInDocCache(const nsIDocument* aKey,
-                               DocAccessible* aDocAccessible,
-                               void* aUserArg);
-
-#ifdef DEBUG
-  static PLDHashOperator
-    SearchIfDocIsRefreshing(const nsIDocument* aKey,
-                            DocAccessible* aDocAccessible, void* aUserArg);
-#endif
-
   typedef nsRefPtrHashtable<nsPtrHashKey<const nsIDocument>, DocAccessible>
     DocAccessibleHashtable;
   DocAccessibleHashtable mDocAccessibleCache;
 
   typedef nsRefPtrHashtable<nsPtrHashKey<const DocAccessible>, xpcAccessibleDocument>
     XPCDocumentHashtable;
   XPCDocumentHashtable mXPCDocumentCache;
 
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1,9 +1,10 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Accessible-inl.h"
 #include "AccIterator.h"
 #include "DocAccessible-inl.h"
 #include "DocAccessibleChild.h"
@@ -108,17 +109,30 @@ DocAccessible::~DocAccessible()
 // nsISupports
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
-  tmp->mDependentIDsHash.EnumerateRead(CycleCollectorTraverseDepIDsEntry, &cb);
+  for (auto iter = tmp->mDependentIDsHash.Iter(); !iter.Done(); iter.Next()) {
+    AttrRelProviderArray* providers = iter.UserData();
+
+    for (int32_t jdx = providers->Length() - 1; jdx >= 0; jdx--) {
+      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
+        cb, "content of dependent ids hash entry of document accessible");
+
+      AttrRelProvider* provider = (*providers)[jdx];
+      cb.NoteXPCOMChild(provider->mContent);
+
+      NS_ASSERTION(provider->mContent->IsInDoc(),
+                   "Referred content is not in document!");
+    }
+  }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
   for (uint32_t i = 0; i < tmp->mARIAOwnsInvalidationList.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mARIAOwnsInvalidationList[i].mOwner)
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mARIAOwnsInvalidationList[i].mChild)
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
@@ -2212,30 +2226,8 @@ DocAccessible::IsLoadEventTarget() const
     DocAccessible* parentDoc = ParentDocument();
     return parentDoc && parentDoc->HasLoadState(eCompletelyLoaded);
   }
 
   // It's content (not chrome) root document.
   return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent);
 }
 
-PLDHashOperator
-DocAccessible::CycleCollectorTraverseDepIDsEntry(const nsAString& aKey,
-                                                 AttrRelProviderArray* aProviders,
-                                                 void* aUserArg)
-{
-  nsCycleCollectionTraversalCallback* cb =
-    static_cast<nsCycleCollectionTraversalCallback*>(aUserArg);
-
-  for (int32_t jdx = aProviders->Length() - 1; jdx >= 0; jdx--) {
-    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb,
-                                       "content of dependent ids hash entry of document accessible");
-
-    AttrRelProvider* provider = (*aProviders)[jdx];
-    cb->NoteXPCOMChild(provider->mContent);
-
-    NS_ASSERTION(provider->mContent->IsInDoc(),
-                 "Referred content is not in document!");
-  }
-
-  return PL_DHASH_NEXT;
-}
-
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -653,21 +653,16 @@ protected:
   typedef nsClassHashtable<nsStringHashKey, AttrRelProviderArray>
     DependentIDsHashtable;
 
   /**
    * The cache of IDs pointed by relation attributes.
    */
   DependentIDsHashtable mDependentIDsHash;
 
-  static PLDHashOperator
-    CycleCollectorTraverseDepIDsEntry(const nsAString& aKey,
-                                      AttrRelProviderArray* aProviders,
-                                      void* aUserArg);
-
   friend class RelatedAccIterator;
 
   /**
    * Used for our caching algorithm. We store the list of nodes that should be
    * invalidated.
    *
    * @see ProcessInvalidationList
    */
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f4c7fbf7a3ae24315b7937c77cdaf7cdb127ff86"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="410e91ddabc7ba82a9b43b3711a3fdf2cb8de309"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c1bbb66f52f9e2d76ce97e7b3aa0cb29957cd7d8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f4c7fbf7a3ae24315b7937c77cdaf7cdb127ff86"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="410e91ddabc7ba82a9b43b3711a3fdf2cb8de309"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c1bbb66f52f9e2d76ce97e7b3aa0cb29957cd7d8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f4c7fbf7a3ae24315b7937c77cdaf7cdb127ff86"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="410e91ddabc7ba82a9b43b3711a3fdf2cb8de309"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c1bbb66f52f9e2d76ce97e7b3aa0cb29957cd7d8"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c72c9278ddc2f442d193474993d36e7f2cfb08c4"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f4c7fbf7a3ae24315b7937c77cdaf7cdb127ff86"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="410e91ddabc7ba82a9b43b3711a3fdf2cb8de309"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c1bbb66f52f9e2d76ce97e7b3aa0cb29957cd7d8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f4c7fbf7a3ae24315b7937c77cdaf7cdb127ff86"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="410e91ddabc7ba82a9b43b3711a3fdf2cb8de309"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c1bbb66f52f9e2d76ce97e7b3aa0cb29957cd7d8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f4c7fbf7a3ae24315b7937c77cdaf7cdb127ff86"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="410e91ddabc7ba82a9b43b3711a3fdf2cb8de309"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c1bbb66f52f9e2d76ce97e7b3aa0cb29957cd7d8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f4c7fbf7a3ae24315b7937c77cdaf7cdb127ff86"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="410e91ddabc7ba82a9b43b3711a3fdf2cb8de309"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c1bbb66f52f9e2d76ce97e7b3aa0cb29957cd7d8"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c72c9278ddc2f442d193474993d36e7f2cfb08c4"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f4c7fbf7a3ae24315b7937c77cdaf7cdb127ff86"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="410e91ddabc7ba82a9b43b3711a3fdf2cb8de309"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c1bbb66f52f9e2d76ce97e7b3aa0cb29957cd7d8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "f4c7fbf7a3ae24315b7937c77cdaf7cdb127ff86", 
+        "git_revision": "410e91ddabc7ba82a9b43b3711a3fdf2cb8de309", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "4112549b51a4790f29bfb983c1f04fc5f4cc569e", 
+    "revision": "1b985049f337d592c0de868cbe3e34e49ea560bd", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4-kk/sources.xml
+++ b/b2g/config/nexus-4-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f4c7fbf7a3ae24315b7937c77cdaf7cdb127ff86"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="410e91ddabc7ba82a9b43b3711a3fdf2cb8de309"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c1bbb66f52f9e2d76ce97e7b3aa0cb29957cd7d8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -13,17 +13,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f4c7fbf7a3ae24315b7937c77cdaf7cdb127ff86"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="410e91ddabc7ba82a9b43b3711a3fdf2cb8de309"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c1bbb66f52f9e2d76ce97e7b3aa0cb29957cd7d8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="f4c7fbf7a3ae24315b7937c77cdaf7cdb127ff86"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="410e91ddabc7ba82a9b43b3711a3fdf2cb8de309"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="c1bbb66f52f9e2d76ce97e7b3aa0cb29957cd7d8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
--- a/build/annotationProcessors/CodeGenerator.java
+++ b/build/annotationProcessors/CodeGenerator.java
@@ -72,17 +72,17 @@ public class CodeGenerator {
      *
      * Return null if the given type does not match any class searched.
      */
     private String getMatchingClassType(final Class<?> type) {
         Class<?> cls = this.cls;
         String clsName = this.clsName;
 
         while (cls != null) {
-            if (type == cls) {
+            if (type.equals(cls)) {
                 return clsName;
             }
             cls = cls.getDeclaringClass();
             clsName = clsName.substring(0, Math.max(0, clsName.lastIndexOf("::")));
         }
         return null;
     }
 
@@ -176,17 +176,17 @@ public class CodeGenerator {
         for (Class<?> argType : argTypes) {
             proto.append(getNativeParameterType(argType, info));
             if (includeArgName) {
                 proto.append(" a").append(argIndex++);
             }
             proto.append(", ");
         }
 
-        if (info.catchException && returnType != void.class) {
+        if (info.catchException && !returnType.equals(void.class)) {
             proto.append(getNativeReturnType(returnType, info)).append('*');
             if (includeArgName) {
                 proto.append(" a").append(argIndex++);
             }
             proto.append(", ");
         }
 
         if (proto.substring(proto.length() - 2).equals(", ")) {
@@ -234,17 +234,17 @@ public class CodeGenerator {
                                   /* isConst */ !isStatic));
         def.append("\n{\n");
 
 
         // Generate code to handle the return value, if needed.
         // We initialize rv to NS_OK instead of NS_ERROR_* because loading NS_OK (0) uses
         // fewer instructions. We are guaranteed to set rv to the correct value later.
 
-        if (info.catchException && returnType == void.class) {
+        if (info.catchException && returnType.equals(void.class)) {
             def.append(
                     "    nsresult rv = NS_OK;\n" +
                     "    ");
 
         } else if (info.catchException) {
             // Non-void return type
             final String resultArg = "a" + argTypes.length;
             def.append(
@@ -345,24 +345,24 @@ public class CodeGenerator {
                 "        mozilla::jni::MakeNativeMethod<" + traits + ">(\n" +
                 "                mozilla::jni::NativeStub<" + traits + ", Impl>\n" +
                 "                ::template Wrap<&Impl::" + info.wrapperName + ">)");
     }
 
     private String getLiteral(Object val, AnnotationInfo info) {
         final Class<?> type = val.getClass();
 
-        if (type == char.class || type == Character.class) {
+        if (type.equals(char.class) || type.equals(Character.class)) {
             final char c = (char) val;
             if (c >= 0x20 && c < 0x7F) {
                 return "'" + c + '\'';
             }
             return "u'\\u" + Integer.toHexString(0x10000 | (int) c).substring(1) + '\'';
 
-        } else if (type == CharSequence.class || type == String.class) {
+        } else if (type.equals(CharSequence.class) || type.equals(String.class)) {
             final CharSequence str = (CharSequence) val;
             final StringBuilder out = new StringBuilder(info.narrowChars ? "u8\"" : "u\"");
             for (int i = 0; i < str.length(); i++) {
                 final char c = str.charAt(i);
                 if (c >= 0x20 && c < 0x7F) {
                     out.append(c);
                 } else {
                     out.append("\\u").append(Integer.toHexString(0x10000 | (int) c).substring(1));
@@ -384,34 +384,34 @@ public class CodeGenerator {
         // It just gets in the way and stops our code from compiling.
         if (field.isSynthetic() || field.getName().equals("$VALUES")) {
             return;
         }
 
         final boolean isStatic = Utils.isStatic(field);
         final boolean isFinal = Utils.isFinal(field);
 
-        if (isStatic && isFinal && (type.isPrimitive() || type == String.class)) {
+        if (isStatic && isFinal && (type.isPrimitive() || type.equals(String.class))) {
             Object val = null;
             try {
                 field.setAccessible(true);
                 val = field.get(null);
             } catch (final IllegalAccessException e) {
             }
 
             if (val != null && type.isPrimitive()) {
                 // For static final primitive fields, we can use a "static const" declaration.
                 header.append(
                     "public:\n" +
                     "    static const " + Utils.getNativeReturnType(type, info) +
                             ' ' + info.wrapperName + " = " + getLiteral(val, info) + ";\n" +
                     "\n");
                 return;
 
-            } else if (val != null && type == String.class) {
+            } else if (val != null && type.equals(String.class)) {
                 final String nativeType = info.narrowChars ? "char" : "char16_t";
 
                 header.append(
                     "public:\n" +
                     "    static const " + nativeType + ' ' + info.wrapperName + "[];\n" +
                     "\n");
 
                 cpp.append(
--- a/build/annotationProcessors/SDKProcessor.java
+++ b/build/annotationProcessors/SDKProcessor.java
@@ -157,16 +157,30 @@ public class SDKProcessor {
                 version = version2;
             }
             if (version > sMaxSdkVersion) {
                 System.out.println("Skipping " + m.getDeclaringClass().getName() + "." + m.getName() +
                     ", version " + version + " > " + sMaxSdkVersion);
                 continue;
             }
 
+            // Sometimes (e.g. KeyEvent) a field can appear in both a class and a superclass. In
+            // that case we want to filter out the version that appears in the superclass, or
+            // we'll have bindings with duplicate names.
+            try {
+                if (m instanceof Field && !m.equals(cls.getField(m.getName()))) {
+                    // m is a field in a superclass that has been hidden by
+                    // a field with the same name in a subclass.
+                    System.out.println("Skipping " + m.getName() +
+                                       " from " + m.getDeclaringClass());
+                    continue;
+                }
+            } catch (final NoSuchFieldException e) {
+            }
+
             list.add(m);
         }
 
         return list.toArray(new Member[list.size()]);
     }
 
     private static void generateClass(Class<?> clazz,
                                       StringBuilder implementationFile,
--- a/build/annotationProcessors/utils/Utils.java
+++ b/build/annotationProcessors/utils/Utils.java
@@ -76,27 +76,27 @@ public class Utils {
         if (type.isArray()) {
             final String compName = type.getComponentType().getName();
             if (NATIVE_ARRAY_TYPES.containsKey(compName)) {
                 return NATIVE_ARRAY_TYPES.get(compName) + "::Param";
             }
             return "mozilla::jni::ObjectArray::Param";
         }
 
-        if (type == String.class || type == CharSequence.class) {
+        if (type.equals(String.class) || type.equals(CharSequence.class)) {
             return "mozilla::jni::String::Param";
         }
 
-        if (type == Class.class) {
+        if (type.equals(Class.class)) {
             // You're doing reflection on Java objects from inside C, returning Class objects
             // to C, generating the corresponding code using this Java program. Really?!
             return "mozilla::jni::ClassObject::Param";
         }
 
-        if (type == Throwable.class) {
+        if (type.equals(Throwable.class)) {
             return "mozilla::jni::Throwable::Param";
         }
 
         return "mozilla::jni::Object::Param";
     }
 
     public static String getNativeReturnType(Class<?> type, AnnotationInfo info) {
         final String name = type.getName().replace('.', '/');
@@ -108,27 +108,27 @@ public class Utils {
         if (type.isArray()) {
             final String compName = type.getComponentType().getName();
             if (NATIVE_ARRAY_TYPES.containsKey(compName)) {
                 return NATIVE_ARRAY_TYPES.get(compName) + "::LocalRef";
             }
             return "mozilla::jni::ObjectArray::LocalRef";
         }
 
-        if (type == String.class) {
+        if (type.equals(String.class)) {
             return "mozilla::jni::String::LocalRef";
         }
 
-        if (type == Class.class) {
+        if (type.equals(Class.class)) {
             // You're doing reflection on Java objects from inside C, returning Class objects
             // to C, generating the corresponding code using this Java program. Really?!
             return "mozilla::jni::ClassObject::LocalRef";
         }
 
-        if (type == Throwable.class) {
+        if (type.equals(Throwable.class)) {
             return "mozilla::jni::Throwable::LocalRef";
         }
 
         return "mozilla::jni::Object::LocalRef";
     }
 
     /**
      * Get the JNI class descriptor corresponding to the provided type parameter.
--- a/devtools/client/webconsole/test/browser.ini
+++ b/devtools/client/webconsole/test/browser.ini
@@ -271,16 +271,17 @@ skip-if = e10s # Bug 1042253 - webconsol
 [browser_webconsole_bug_658368_time_methods.js]
 [browser_webconsole_bug_659907_console_dir.js]
 [browser_webconsole_bug_660806_history_nav.js]
 [browser_webconsole_bug_664131_console_group.js]
 [browser_webconsole_bug_686937_autocomplete_JSTerm_helpers.js]
 [browser_webconsole_bug_704295.js]
 [browser_webconsole_bug_734061_No_input_change_and_Tab_key_pressed.js]
 [browser_webconsole_bug_737873_mixedcontent.js]
+tags = mcb
 [browser_webconsole_bug_752559_ineffective_iframe_sandbox_warning.js]
 skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
 [browser_webconsole_bug_762593_insecure_passwords_about_blank_web_console_warning.js]
 skip-if = buildapp == 'mulet'
 [browser_webconsole_bug_762593_insecure_passwords_web_console_warning.js]
 skip-if = true # Bug 1110500 - mouse event failure in test
 [browser_webconsole_bug_764572_output_open_url.js]
 skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
--- a/dom/base/nsXMLHttpRequest.cpp
+++ b/dom/base/nsXMLHttpRequest.cpp
@@ -1917,16 +1917,22 @@ nsXMLHttpRequest::OnStartRequest(nsIRequ
     mRequestObserver->OnStartRequest(request, ctxt);
   }
 
   if (request != mChannel) {
     // Can this still happen?
     return NS_OK;
   }
 
+  // Always treat tainted channels as cross-origin.
+  nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+  if (loadInfo && loadInfo->GetTainting() != LoadTainting::Basic) {
+    mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
+  }
+
   // Don't do anything if we have been aborted
   if (mState & XML_HTTP_REQUEST_UNSENT)
     return NS_OK;
 
   /* Apparently, Abort() should set XML_HTTP_REQUEST_UNSENT.  See bug 361773.
      XHR2 spec says this is correct. */
   if (mState & XML_HTTP_REQUEST_ABORTED) {
     NS_ERROR("Ugh, still getting data on an aborted XMLHttpRequest!");
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -46,16 +46,17 @@ NS_IMPL_ISUPPORTS(FetchDriver,
                   nsIThreadRetargetableStreamListener)
 
 FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal,
                          nsILoadGroup* aLoadGroup)
   : mPrincipal(aPrincipal)
   , mLoadGroup(aLoadGroup)
   , mRequest(aRequest)
   , mHasBeenCrossSite(false)
+  , mFoundOpaqueRedirect(false)
   , mResponseAvailableCalled(false)
   , mFetchCalled(false)
 {
 }
 
 FetchDriver::~FetchDriver()
 {
   // We assert this since even on failures, we should call
@@ -123,24 +124,24 @@ FetchDriver::SetTainting()
 
   // request's mode is "same-origin"
   if (mRequest->Mode() == RequestMode::Same_origin) {
     return NS_ERROR_DOM_BAD_URI;
   }
 
   // request's mode is "no-cors"
   if (mRequest->Mode() == RequestMode::No_cors) {
-    mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUE);
+    mRequest->MaybeIncreaseResponseTainting(LoadTainting::Opaque);
     // What the spec calls "basic fetch" is handled within our necko channel
     // code.  Therefore everything goes through HTTP Fetch
     return NS_OK;
   }
 
   // Otherwise
-  mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS);
+  mRequest->MaybeIncreaseResponseTainting(LoadTainting::CORS);
 
   return NS_OK;
 }
 
 nsresult
 FetchDriver::ContinueFetch()
 {
   workers::AssertIsOnMainThread();
@@ -439,34 +440,33 @@ FetchDriver::BeginAndGetFilteredResponse
   if (aFinalURI) {
     aFinalURI->GetSpec(reqURL);
   } else {
     mRequest->GetURL(reqURL);
   }
   DebugOnly<nsresult> rv = aResponse->StripFragmentAndSetUrl(reqURL);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
-  // FIXME(nsm): Handle mixed content check, step 7 of fetch.
-
   RefPtr<InternalResponse> filteredResponse;
-  switch (mRequest->GetResponseTainting()) {
-    case InternalRequest::RESPONSETAINT_BASIC:
-      filteredResponse = aResponse->BasicResponse();
-      break;
-    case InternalRequest::RESPONSETAINT_CORS:
-      filteredResponse = aResponse->CORSResponse();
-      break;
-    case InternalRequest::RESPONSETAINT_OPAQUE:
-      filteredResponse = aResponse->OpaqueResponse();
-      break;
-    case InternalRequest::RESPONSETAINT_OPAQUEREDIRECT:
-      filteredResponse = aResponse->OpaqueRedirectResponse();
-      break;
-    default:
-      MOZ_CRASH("Unexpected case");
+  if (mFoundOpaqueRedirect) {
+    filteredResponse = aResponse->OpaqueRedirectResponse();
+  } else {
+    switch (mRequest->GetResponseTainting()) {
+      case LoadTainting::Basic:
+        filteredResponse = aResponse->BasicResponse();
+        break;
+      case LoadTainting::CORS:
+        filteredResponse = aResponse->CORSResponse();
+        break;
+      case LoadTainting::Opaque:
+        filteredResponse = aResponse->OpaqueResponse();
+        break;
+      default:
+        MOZ_CRASH("Unexpected case");
+    }
   }
 
   MOZ_ASSERT(filteredResponse);
   MOZ_ASSERT(mObserver);
   mObserver->OnResponseAvailable(filteredResponse);
   mResponseAvailableCalled = true;
   return filteredResponse.forget();
 }
@@ -625,16 +625,36 @@ FetchDriver::OnStartRequest(nsIRequest* 
   nsCOMPtr<nsIURI> channelURI;
   rv = channel->GetURI(getter_AddRefs(channelURI));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailWithNetworkError();
     // Cancel request.
     return rv;
   }
 
+  nsCOMPtr<nsILoadInfo> loadInfo;
+  rv = channel->GetLoadInfo(getter_AddRefs(loadInfo));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    FailWithNetworkError();
+    return rv;
+  }
+
+  LoadTainting channelTainting = LoadTainting::Basic;
+  if (loadInfo) {
+    channelTainting = loadInfo->GetTainting();
+  }
+
+  // Propagate any tainting from the channel back to our response here.  This
+  // step is not reflected in the spec because the spec is written such that
+  // FetchEvent.respondWith() just passes the already-tainted Response back to
+  // the outer fetch().  In gecko, however, we serialize the Response through
+  // the channel and must regenerate the tainting from the channel in the
+  // interception case.
+  mRequest->MaybeIncreaseResponseTainting(channelTainting);
+
   // Resolves fetch() promise which may trigger code running in a worker.  Make
   // sure the Response is fully initialized before calling this.
   mResponse = BeginAndGetFilteredResponse(response, channelURI);
 
   nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailWithNetworkError();
     // Cancel request.
@@ -744,17 +764,18 @@ FetchDriver::AsyncOnChannelRedirect(nsIC
     // The HTTP cache will also error on vetoed redirects when the
     // redirect has been previously cached.
     //
     // Therefore simulate the completion of the channel to produce the
     // opaqueredirect Response and then cancel the original channel.  This
     // will result in OnStartRequest() getting called twice, but the second
     // time will be with an error response (from the Cancel) which will
     // be ignored.
-    mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUEREDIRECT);
+    MOZ_ASSERT(!mFoundOpaqueRedirect);
+    mFoundOpaqueRedirect = true;
     unused << OnStartRequest(aOldChannel, nullptr);
     unused << OnStopRequest(aOldChannel, nullptr, NS_OK);
 
     aOldChannel->Cancel(NS_BINDING_FAILED);
 
     return NS_BINDING_FAILED;
   }
 
--- a/dom/fetch/FetchDriver.h
+++ b/dom/fetch/FetchDriver.h
@@ -78,16 +78,17 @@ private:
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsILoadGroup> mLoadGroup;
   RefPtr<InternalRequest> mRequest;
   RefPtr<InternalResponse> mResponse;
   nsCOMPtr<nsIOutputStream> mPipeOutputStream;
   RefPtr<FetchDriverObserver> mObserver;
   nsCOMPtr<nsIDocument> mDocument;
   bool mHasBeenCrossSite;
+  bool mFoundOpaqueRedirect;
 
   DebugOnly<bool> mResponseAvailableCalled;
   DebugOnly<bool> mFetchCalled;
 
   FetchDriver() = delete;
   FetchDriver(const FetchDriver&) = delete;
   FetchDriver& operator=(const FetchDriver&) = delete;
   ~FetchDriver();
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_InternalRequest_h
 #define mozilla_dom_InternalRequest_h
 
 #include "mozilla/dom/HeadersBinding.h"
 #include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/RequestBinding.h"
+#include "mozilla/LoadTainting.h"
 
 #include "nsIContentPolicy.h"
 #include "nsIInputStream.h"
 #include "nsISupportsImpl.h"
 #ifdef DEBUG
 #include "nsIURLParser.h"
 #include "nsNetCID.h"
 #include "nsServiceManagerUtils.h"
@@ -82,31 +83,23 @@ class Request;
 
 class InternalRequest final
 {
   friend class Request;
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalRequest)
 
-  enum ResponseTainting
-  {
-    RESPONSETAINT_BASIC,
-    RESPONSETAINT_CORS,
-    RESPONSETAINT_OPAQUE,
-    RESPONSETAINT_OPAQUEREDIRECT,
-  };
-
   explicit InternalRequest()
     : mMethod("GET")
     , mHeaders(new InternalHeaders(HeadersGuardEnum::None))
     , mReferrer(NS_LITERAL_STRING(kFETCH_CLIENT_REFERRER_STR))
     , mMode(RequestMode::No_cors)
     , mCredentialsMode(RequestCredentials::Omit)
-    , mResponseTainting(RESPONSETAINT_BASIC)
+    , mResponseTainting(LoadTainting::Basic)
     , mCacheMode(RequestCache::Default)
     , mRedirectMode(RequestRedirect::Follow)
     , mAuthenticationFlag(false)
     , mForceOriginHeader(false)
     , mPreserveContentCodings(false)
       // FIXME(nsm): This should be false by default, but will lead to the
       // algorithm never loading data: URLs right now. See Bug 1018872 about
       // how certain contexts will override it to set it to true. Fetch
@@ -237,26 +230,28 @@ public:
   }
 
   void
   SetCredentialsMode(RequestCredentials aCredentialsMode)
   {
     mCredentialsMode = aCredentialsMode;
   }
 
-  ResponseTainting
+  LoadTainting
   GetResponseTainting() const
   {
     return mResponseTainting;
   }
 
   void
-  SetResponseTainting(ResponseTainting aTainting)
+  MaybeIncreaseResponseTainting(LoadTainting aTainting)
   {
-    mResponseTainting = aTainting;
+    if (aTainting > mResponseTainting) {
+      mResponseTainting = aTainting;
+    }
   }
 
   RequestCache
   GetCacheMode() const
   {
     return mCacheMode;
   }
 
@@ -405,17 +400,17 @@ private:
 
   // Empty string: no-referrer
   // "about:client": client (default)
   // URL: an URL
   nsString mReferrer;
 
   RequestMode mMode;
   RequestCredentials mCredentialsMode;
-  ResponseTainting mResponseTainting;
+  LoadTainting mResponseTainting;
   RequestCache mCacheMode;
   RequestRedirect mRedirectMode;
 
   bool mAuthenticationFlag;
   bool mForceOriginHeader;
   bool mPreserveContentCodings;
   bool mSameOriginDataURL;
   bool mSandboxedStorageAreaURLs;
--- a/dom/fetch/InternalResponse.cpp
+++ b/dom/fetch/InternalResponse.cpp
@@ -113,16 +113,29 @@ InternalResponse::StripFragmentAndSetUrl
   if(NS_WARN_IF(NS_FAILED(rv))){
     return rv;
   }
 
   SetUrl(spec);
   return NS_OK;
 }
 
+LoadTainting
+InternalResponse::GetTainting() const
+{
+  switch (mType) {
+    case ResponseType::Cors:
+      return LoadTainting::CORS;
+    case ResponseType::Opaque:
+      return LoadTainting::Opaque;
+    default:
+      return LoadTainting::Basic;
+  }
+}
+
 already_AddRefed<InternalResponse>
 InternalResponse::OpaqueResponse()
 {
   MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueResponse a already wrapped response");
   RefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString());
   response->mType = ResponseType::Opaque;
   response->mTerminationReason = mTerminationReason;
   response->mChannelInfo = mChannelInfo;
--- a/dom/fetch/InternalResponse.h
+++ b/dom/fetch/InternalResponse.h
@@ -213,16 +213,19 @@ public:
 
   // Takes ownership of the principal info.
   void
   SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo);
 
   nsresult
   StripFragmentAndSetUrl(const nsACString& aUrl);
 
+  LoadTainting
+  GetTainting() const;
+
 private:
   ~InternalResponse();
 
   explicit InternalResponse(const InternalResponse& aOther) = delete;
   InternalResponse& operator=(const InternalResponse&) = delete;
 
   // Returns an instance of InternalResponse which is a copy of this
   // InternalResponse, except headers, body and wrapped response (if any) which
--- a/dom/tests/mochitest/fetch/test_fetch_cors.js
+++ b/dom/tests/mochitest/fetch/test_fetch_cors.js
@@ -1387,16 +1387,17 @@ function testRedirects() {
     var request = new Request(req.url, { method: req.method,
                                          headers: req.headers,
                                          body: req.body });
     fetches.push((function(request, test) {
       return fetch(request).then(function(res) {
         ok(test.pass, "Expected test to pass for " + test.toSource());
         is(res.status, 200, "wrong status in test for " + test.toSource());
         is(res.statusText, "OK", "wrong status text for " + test.toSource());
+        is(res.type, 'cors', 'wrong response type for ' + test.toSource());
         var reqHost = (new URL(req.url)).host;
         // If there is a service worker present, the redirections will be
         // transparent, assuming that the original request is to the current
         // site and would be intercepted.
         if (isSWPresent) {
           if (reqHost === location.host) {
             is((new URL(res.url)).host, reqHost, "Response URL should be original URL with a SW present");
           }
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -125,72 +125,77 @@ public:
   {
   }
 
   NS_IMETHOD
   Run()
   {
     AssertIsOnMainThread();
 
-    if (!CSPPermitsResponse()) {
+    nsCOMPtr<nsIChannel> underlyingChannel;
+    nsresult rv = mChannel->GetChannel(getter_AddRefs(underlyingChannel));
+    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_TRUE(underlyingChannel, NS_ERROR_UNEXPECTED);
+    nsCOMPtr<nsILoadInfo> loadInfo = underlyingChannel->GetLoadInfo();
+
+    if (!CSPPermitsResponse(loadInfo)) {
       mChannel->Cancel(NS_ERROR_CONTENT_BLOCKED);
       return NS_OK;
     }
 
     ChannelInfo channelInfo;
     if (mInternalResponse->GetChannelInfo().IsInitialized()) {
       channelInfo = mInternalResponse->GetChannelInfo();
     } else {
       // We are dealing with a synthesized response here, so fall back to the
       // channel info for the worker script.
       channelInfo = mWorkerChannelInfo;
     }
-    nsresult rv = mChannel->SetChannelInfo(&channelInfo);
+    rv = mChannel->SetChannelInfo(&channelInfo);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     mChannel->SynthesizeStatus(mInternalResponse->GetUnfilteredStatus(),
                                mInternalResponse->GetUnfilteredStatusText());
 
     nsAutoTArray<InternalHeaders::Entry, 5> entries;
     mInternalResponse->UnfilteredHeaders()->GetEntries(entries);
     for (uint32_t i = 0; i < entries.Length(); ++i) {
        mChannel->SynthesizeHeader(entries[i].mName, entries[i].mValue);
     }
 
+    loadInfo->MaybeIncreaseTainting(mInternalResponse->GetTainting());
+
     rv = mChannel->FinishSynthesizedResponse(mResponseURLSpec);
     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to finish synthesized response");
     return rv;
   }
 
-  bool CSPPermitsResponse()
+  bool CSPPermitsResponse(nsILoadInfo* aLoadInfo)
   {
     AssertIsOnMainThread();
+    MOZ_ASSERT(aLoadInfo);
 
     nsresult rv;
     nsCOMPtr<nsIURI> uri;
     nsAutoCString url;
     mInternalResponse->GetUnfilteredUrl(url);
     if (url.IsEmpty()) {
       // Synthetic response. The buck stops at the worker script.
       url = mScriptSpec;
     }
     rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, nullptr);
     NS_ENSURE_SUCCESS(rv, false);
 
-    nsCOMPtr<nsIChannel> underlyingChannel;
-    rv = mChannel->GetChannel(getter_AddRefs(underlyingChannel));
-    NS_ENSURE_SUCCESS(rv, false);
-    NS_ENSURE_TRUE(underlyingChannel, false);
-    nsCOMPtr<nsILoadInfo> loadInfo = underlyingChannel->GetLoadInfo();
-
     int16_t decision = nsIContentPolicy::ACCEPT;
-    rv = NS_CheckContentLoadPolicy(loadInfo->InternalContentPolicyType(), uri, loadInfo->LoadingPrincipal(),
-                                   loadInfo->LoadingNode(), EmptyCString(), nullptr, &decision);
+    rv = NS_CheckContentLoadPolicy(aLoadInfo->InternalContentPolicyType(), uri,
+                                   aLoadInfo->LoadingPrincipal(),
+                                   aLoadInfo->LoadingNode(), EmptyCString(),
+                                   nullptr, &decision);
     NS_ENSURE_SUCCESS(rv, false);
     return decision == nsIContentPolicy::ACCEPT;
   }
 };
 
 class RespondWithHandler final : public PromiseNativeHandler
 {
   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
--- a/dom/workers/ServiceWorkerScriptCache.cpp
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -135,16 +135,20 @@ public:
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
     if (httpChannel) {
       // Spec says no redirects allowed for SW scripts.
       httpChannel->SetRedirectionLimit(0);
+
+      httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Service-Worker"),
+                                    NS_LITERAL_CSTRING("script"),
+                                    /* merge */ false);
     }
 
     nsCOMPtr<nsIStreamLoader> loader;
     rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
--- a/dom/workers/test/serviceworkers/fetch/fetch_tests.js
+++ b/dom/workers/test/serviceworkers/fetch/fetch_tests.js
@@ -1,8 +1,10 @@
+var origin = 'http://mochi.test:8888';
+
 function fetchXHRWithMethod(name, method, onload, onerror, headers) {
   expectAsyncResult();
 
   onload = onload || function() {
     my_ok(false, "XHR load should not complete successfully");
     finish();
   };
   onerror = onerror || function() {
@@ -16,16 +18,24 @@ function fetchXHRWithMethod(name, method
   x.onerror = function() { onerror(x) };
   headers = headers || [];
   headers.forEach(function(header) {
     x.setRequestHeader(header[0], header[1]);
   });
   x.send();
 }
 
+var corsServerPath = '/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs';
+var corsServerURL = 'http://example.com' + corsServerPath;
+
+function redirectURL(hops) {
+  return hops[0].server + corsServerPath + "?hop=1&hops=" +
+         encodeURIComponent(hops.toSource());
+}
+
 function fetchXHR(name, onload, onerror, headers) {
   return fetchXHRWithMethod(name, 'GET', onload, onerror, headers);
 }
 
 fetchXHR('bare-synthesized.txt', function(xhr) {
   my_ok(xhr.status == 200, "load should be successful");
   my_ok(xhr.responseText == "synthesized response body", "load should have synthesized response");
   finish();
@@ -138,35 +148,45 @@ fetchXHR('hello.gz', function(xhr) {
 fetchXHR('hello-after-extracting.gz', function(xhr) {
   my_ok(xhr.status == 200, "gzip load after extracting should be successful");
   my_ok(xhr.responseText == expectedUncompressedResponse, "gzip load after extracting should have synthesized response.");
   my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding after extracting should be gzip.");
   my_ok(xhr.getResponseHeader("Content-Length") == "35", "Content-Length after extracting should be of original gzipped file.");
   finish();
 });
 
-fetchXHR('http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*', function(xhr) {
+fetchXHR(corsServerURL + '?status=200&allowOrigin=*', function(xhr) {
+  my_ok(xhr.status == 200, "cross origin load with correct headers should be successful");
+  my_ok(xhr.getResponseHeader("access-control-allow-origin") == null, "cors headers should be filtered out");
+  finish();
+});
+
+// Verify that XHR is considered CORS tainted even when original URL is same-origin
+// redirected to cross-origin.
+fetchXHR(redirectURL([{ server: origin },
+                      { server: 'http://example.org',
+                        allowOrigin: origin }]), function(xhr) {
   my_ok(xhr.status == 200, "cross origin load with correct headers should be successful");
   my_ok(xhr.getResponseHeader("access-control-allow-origin") == null, "cors headers should be filtered out");
   finish();
 });
 
 // Test that CORS preflight requests cannot be intercepted. Performs a
 // cross-origin XHR that the SW chooses not to intercept. This requires a
 // preflight request, which the SW must not be allowed to intercept.
-fetchXHR('http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*', null, function(xhr) {
+fetchXHR(corsServerURL + '?status=200&allowOrigin=*', null, function(xhr) {
   my_ok(xhr.status == 0, "cross origin load with incorrect headers should be a failure");
   finish();
 }, [["X-Unsafe", "unsafe"]]);
 
 // Test that CORS preflight requests cannot be intercepted. Performs a
 // cross-origin XHR that the SW chooses to intercept and respond with a
 // cross-origin fetch. This requires a preflight request, which the SW must not
 // be allowed to intercept.
-fetchXHR('http://example.org/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*', null, function(xhr) {
+fetchXHR('http://example.org' + corsServerPath + '?status=200&allowOrigin=*', null, function(xhr) {
   my_ok(xhr.status == 0, "cross origin load with incorrect headers should be a failure");
   finish();
 }, [["X-Unsafe", "unsafe"]]);
 
 // Test that when the page fetches a url the controlling SW forces a redirect to
 // another location. This other location fetch should also be intercepted by
 // the SW.
 fetchXHR('something.txt', function(xhr) {
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/header_checker.sjs
@@ -0,0 +1,9 @@
+function handleRequest(request, response) {
+  if (request.getHeader("Service-Worker") === "script") {
+    response.setStatusLine("1.1", 200, "OK");
+    response.setHeader("Content-Type", "text/javascript");
+    response.write("// empty");
+  } else {
+    response.setStatusLine("1.1", 404, "Not Found");
+  }
+}
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -178,16 +178,17 @@ support-files =
   fetch/plugin/worker.js
   fetch/plugin/plugins.html
   eventsource/*
   sw_clients/file_blob_upload_frame.html
   redirect_post.sjs
   xslt_worker.js
   xslt/*
   unresolved_fetch_worker.js
+  header_checker.sjs
 
 [test_app_protocol.html]
 skip-if = release_build
 [test_bug1151916.html]
 [test_claim.html]
 [test_claim_fetch.html]
 [test_claim_oninstall.html]
 [test_close.html]
@@ -276,8 +277,9 @@ skip-if = toolkit == "android" || toolki
 [test_eventsource_intercept.html]
 [test_not_intercept_plugin.html]
 [test_file_blob_upload.html]
 [test_unresolved_fetch_interception.html]
 [test_hsts_upgrade_intercept.html]
 skip-if = e10s # Bug 1214305
 [test_csp_upgrade-insecure_intercept.html]
 skip-if = e10s # Bug 1214305
+[test_serviceworker_header.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworker_header.html
@@ -0,0 +1,41 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test that service worker scripts are fetched with a Service-Worker: script header</title>
+  <script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css" />
+  <base href="https://mozilla.org/">
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  function runTest() {
+    navigator.serviceWorker.register("http://mochi.test:8888/tests/dom/workers/test/serviceworkers/header_checker.sjs")
+      .then(reg => {
+        ok(true, "Register should succeed");
+        reg.unregister().then(() => SimpleTest.finish());
+      }, err => {
+        ok(false, "Register should not fail");
+        SimpleTest.finish();
+      });
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  onload = function() {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.testing.enabled", true],
+      ["dom.serviceWorkers.enabled", true],
+    ]}, runTest);
+  };
+</script>
+</body>
+</html>
--- a/gfx/thebes/gfxBlur.cpp
+++ b/gfx/thebes/gfxBlur.cpp
@@ -810,18 +810,37 @@ ComputeRectsForInsetBoxShadow(IntSize aB
   // Create the inner rect to be the smallest possible size based on
   // blur / spread / corner radii
   IntMargin innerMargin = IntMargin(ceil(cornerHeight) + rectBufferSize.height,
                                     ceil(cornerWidth) + rectBufferSize.width,
                                     ceil(cornerHeight) + rectBufferSize.height,
                                     ceil(cornerWidth) + rectBufferSize.width);
   aOutPathMargins = innerMargin;
 
-  IntSize minInnerSize(innerMargin.LeftRight() + 1,
-                       innerMargin.TopBottom() + 1);
+  // If we have a negative spread radius, we would not have enough
+  // size to actually do the blur. So the min size must be the abs() of the blur
+  // and spread radius.
+  IntSize minBlurSize(std::abs(aSpreadRadius.width) + std::abs(aBlurRadius.width),
+                      std::abs(aSpreadRadius.height) + std::abs(aBlurRadius.height));
+
+  IntMargin minInnerMargins = IntMargin(ceil(cornerHeight) + minBlurSize.height,
+                                        ceil(cornerWidth) + minBlurSize.width,
+                                        ceil(cornerHeight) + minBlurSize.height,
+                                        ceil(cornerWidth) + minBlurSize.width);
+
+  IntSize minInnerSize(minInnerMargins.LeftRight() + 1,
+                       minInnerMargins.TopBottom() + 1);
+
+  if (aShadowClipRect.height < minInnerSize.height) {
+    minInnerSize.height = aShadowClipRect.height;
+  }
+
+  if (aShadowClipRect.width < minInnerSize.width) {
+    minInnerSize.width = aShadowClipRect.width;
+  }
 
   // Then expand the outer rect based on the size between the inner/outer rects
   IntSize minOuterSize(minInnerSize);
   IntMargin outerRectMargin(rectBufferSize.height, rectBufferSize.width,
                             rectBufferSize.height, rectBufferSize.width);
   minOuterSize.width += outerRectMargin.LeftRight();
   minOuterSize.height += outerRectMargin.TopBottom();
 
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -135,21 +135,23 @@ js::ExecuteRegExpLegacy(JSContext* cx, R
         /* Forbid an array, as an optimization. */
         rval.setBoolean(true);
         return true;
     }
 
     return CreateRegExpMatchResult(cx, input, matches, rval);
 }
 
-/* ES6 draft rc4 21.2.3.2.2. */
-bool
-RegExpInitialize(JSContext* cx, RegExpObjectBuilder& builder,
-                 HandleValue patternValue, HandleValue flagsValue,
-                 RegExpStaticsUse staticsUse, MutableHandleObject result)
+/*
+ * ES6 21.2.3.2.2.  Because this function only ever returns |obj| in the spec,
+ * provided by the user, we omit it and just return the usual success/failure.
+ */
+static bool
+RegExpInitialize(JSContext* cx, Handle<RegExpObject*> obj, HandleValue patternValue,
+                 HandleValue flagsValue, RegExpStaticsUse staticsUse)
 {
     RootedAtom pattern(cx);
     if (patternValue.isUndefined()) {
         /* Step 1. */
         pattern = cx->runtime()->emptyString;
     } else {
         /* Steps 2-3. */
         pattern = ToAtom<CanGC>(cx, patternValue);
@@ -178,158 +180,20 @@ RegExpInitialize(JSContext* cx, RegExpOb
     if (staticsUse == UseRegExpStatics) {
         RegExpStatics* res = cx->global()->getRegExpStatics(cx);
         if (!res)
             return false;
         flags = RegExpFlag(flags | res->getFlags());
     }
 
     /* Steps 11-15. */
-    RootedObject reobj(cx, builder.build(pattern, flags));
-    if (!reobj)
+    if (!InitializeRegExp(cx, obj, pattern, flags))
         return false;
 
     /* Step 16. */
-    result.set(reobj);
-    return true;
-}
-
-/*
- * ES6 draft rc4 21.2.3.1 steps 5-10.
- * ES6 draft rc4 B.2.5.1 steps 3-5.
- * Compile a new |RegExpShared| for the |RegExpObject|.
- */
-static bool
-CompileRegExpObject(JSContext* cx, RegExpObjectBuilder& builder, const CallArgs& args,
-                    RegExpCreationMode creationMode, bool patternIsRegExp=false)
-{
-    if (args.length() == 0) {
-        /*
-         * 21.2.3.1 step 10.
-         * B.2.5.1 step 5.
-         */
-        RegExpStatics* res = cx->global()->getRegExpStatics(cx);
-        if (!res)
-            return false;
-
-        RootedAtom empty(cx, cx->runtime()->emptyString);
-        RegExpObject* reobj = builder.build(empty, res->getFlags());
-        if (!reobj)
-            return false;
-
-        args.rval().setObject(*reobj);
-        return true;
-    }
-
-    RootedValue patternValue(cx, args.get(0));
-
-    /*
-     * 21.2.3.1 step 5
-     * B.2.5.1 step 3.
-     */
-    ESClassValue cls;
-    if (!GetClassOfValue(cx, patternValue, &cls))
-        return false;
-    if (cls == ESClass_RegExp) {
-        /*
-         * B.2.5.1 step 3.a.
-         */
-        if (args.hasDefined(1) && creationMode == CreateForCompile) {
-            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NEWREGEXP_FLAGGED);
-            return false;
-        }
-
-        /*
-         * Beware, patternObj may be a (transparent) proxy to a RegExp, so only
-         * use generic (proxyable) operations on patternObj that do not assume
-         * patternObj.is<RegExpObject>().
-         */
-        RootedObject patternObj(cx, &patternValue.toObject());
-
-        RootedAtom sourceAtom(cx);
-        RegExpFlag flags;
-        {
-            /*
-             * 21.2.3.1 step 5.a.
-             * B.2.5.1 step 3.a.
-             * Extract the 'source' from patternObj; do not reuse the
-             * RegExpShared since it may be from a different compartment.
-             */
-            RegExpGuard g(cx);
-            if (!RegExpToShared(cx, patternObj, &g))
-                return false;
-            sourceAtom = g->getSource();
-
-            if (args.hasDefined(1)) {
-                /* 21.2.3.1 step 5.c. */
-                flags = RegExpFlag(0);
-                RootedString flagStr(cx, ToString<CanGC>(cx, args[1]));
-                if (!flagStr)
-                    return false;
-                if (!ParseRegExpFlags(cx, flagStr, &flags))
-                    return false;
-            } else {
-                /*
-                 * 21.2.3.1 step 5.b.
-                 * B.2.5.1 step 3.c.
-                 */
-                flags = g->getFlags();
-            }
-        }
-
-        /*
-         * 21.2.3.1 steps 8-10.
-         * B.2.5.1 step 5.
-         */
-        RegExpObject* reobj = builder.build(sourceAtom, flags);
-        if (!reobj)
-            return false;
-
-        args.rval().setObject(*reobj);
-        return true;
-    }
-
-    RootedValue P(cx);
-    RootedValue F(cx);
-    /* 21.2.3.1 step 6. */
-    if (patternIsRegExp) {
-        MOZ_ASSERT(creationMode == CreateForConstruct);
-        RootedObject patternObj(cx, &patternValue.toObject());
-
-        /* 21.2.3.1 steps 6.a-b. */
-        if (!GetProperty(cx, patternObj, patternObj, cx->names().source, &P))
-            return false;
-
-        /* 21.2.3.1 step 6.c. */
-        if (!args.hasDefined(1)) {
-            /* 21.2.3.1 steps 6.c.i-ii. */
-            if (!GetProperty(cx, patternObj, patternObj, cx->names().flags, &F))
-                return false;
-        } else {
-            /* 21.2.3.1 steps 6.d. */
-            F = args[1];
-        }
-    } else {
-        /*
-         * 21.2.3.1 steps 7.a-b.
-         * B.2.5.1 steps 4.a-b.
-         */
-        P = patternValue;
-        F = args.get(1);
-    }
-
-    /*
-     * 21.2.3.1 steps 8-10.
-     * B.2.5.1 step 5.
-     */
-    RootedObject reobj(cx);
-    if (!RegExpInitialize(cx, builder, P, F, UseRegExpStatics, &reobj))
-        return false;
-
-    args.rval().setObject(*reobj);
     return true;
 }
 
 MOZ_ALWAYS_INLINE bool
 IsRegExpObject(HandleValue v)
 {
     return v.isObject() && v.toObject().is<RegExpObject>();
 }
@@ -361,90 +225,223 @@ js::IsRegExp(JSContext* cx, HandleValue 
     ESClassValue cls;
     if (!GetClassOfValue(cx, value, &cls))
         return false;
 
     *result = cls == ESClass_RegExp;
     return true;
 }
 
-/* ES6 draft rc4 B.2.5.1. */
+/* ES6 B.2.5.1. */
 MOZ_ALWAYS_INLINE bool
 regexp_compile_impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(IsRegExpObject(args.thisv()));
 
-    /* Steps 3-5. */
-    RegExpObjectBuilder builder(cx, &args.thisv().toObject().as<RegExpObject>());
-    return CompileRegExpObject(cx, builder, args, CreateForCompile);
+    Rooted<RegExpObject*> regexp(cx, &args.thisv().toObject().as<RegExpObject>());
+
+    // Step 3.
+    RootedValue patternValue(cx, args.get(0));
+    ESClassValue cls;
+    if (!GetClassOfValue(cx, patternValue, &cls))
+        return false;
+    if (cls == ESClass_RegExp) {
+        // Step 3a.
+        if (args.hasDefined(1)) {
+            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NEWREGEXP_FLAGGED);
+            return false;
+        }
+
+        // Beware!  |patternObj| might be a proxy into another compartment, so
+        // don't assume |patternObj.is<RegExpObject>()|.  For the same reason,
+        // don't reuse the RegExpShared below.
+        RootedObject patternObj(cx, &patternValue.toObject());
+
+        RootedAtom sourceAtom(cx);
+        RegExpFlag flags;
+        {
+            // Step 3b.
+            RegExpGuard g(cx);
+            if (!RegExpToShared(cx, patternObj, &g))
+                return false;
+
+            sourceAtom = g->getSource();
+            flags = g->getFlags();
+        }
+
+        // Step 5.
+        if (!InitializeRegExp(cx, regexp, sourceAtom, flags))
+            return false;
+
+        args.rval().setObject(*regexp);
+        return true;
+    }
+
+    // Step 4.
+    RootedValue P(cx, patternValue);
+    RootedValue F(cx, args.get(1));
+
+    // Step 5.
+    if (!RegExpInitialize(cx, regexp, P, F, UseRegExpStatics))
+        return false;
+
+    args.rval().setObject(*regexp);
+    return true;
 }
 
 static bool
 regexp_compile(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     /* Steps 1-2. */
     return CallNonGenericMethod<IsRegExpObject, regexp_compile_impl>(cx, args);
 }
 
-/* ES6 draft rc4 21.2.3.1. */
+/* ES6 21.2.3.1. */
 bool
 js::regexp_construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    /* Steps 1-2. */
+    // Steps 1-2.
     bool patternIsRegExp;
     if (!IsRegExp(cx, args.get(0), &patternIsRegExp))
         return false;
 
-    /* Step 4. */
-    if (!args.isConstructing()) {
-        /* Step 4.b. */
+    if (args.isConstructing()) {
+        // XXX Step 3!
+    } else {
+        // XXX Step 4a
+
+        // Step 4b.
         if (patternIsRegExp && !args.hasDefined(1)) {
             RootedObject patternObj(cx, &args[0].toObject());
 
-            /* Steps 4.b.i-ii. */
+            // Steps 4b.i-ii.
             RootedValue patternConstructor(cx);
             if (!GetProperty(cx, patternObj, patternObj, cx->names().constructor, &patternConstructor))
                 return false;
 
-            /* Step 4.b.iii. */
+            // Step 4b.iii.
             if (patternConstructor.isObject() && patternConstructor.toObject() == args.callee()) {
                 args.rval().set(args[0]);
                 return true;
             }
         }
     }
 
-    /* Steps 5-10. */
-    RegExpObjectBuilder builder(cx);
-    return CompileRegExpObject(cx, builder, args, CreateForConstruct, patternIsRegExp);
+    RootedValue patternValue(cx, args.get(0));
+
+    // Step 5.
+    ESClassValue cls;
+    if (!GetClassOfValue(cx, patternValue, &cls))
+        return false;
+    if (cls == ESClass_RegExp) {
+        // Beware!  |patternObj| might be a proxy into another compartment, so
+        // don't assume |patternObj.is<RegExpObject>()|.  For the same reason,
+        // don't reuse the RegExpShared below.
+        RootedObject patternObj(cx, &patternValue.toObject());
+
+        RootedAtom sourceAtom(cx);
+        RegExpFlag flags;
+        {
+            // Step 5.a.
+            RegExpGuard g(cx);
+            if (!RegExpToShared(cx, patternObj, &g))
+                return false;
+            sourceAtom = g->getSource();
+
+            if (!args.hasDefined(1)) {
+                // Step 5b.
+                flags = g->getFlags();
+            } else {
+                // Step 5c.
+                // XXX We shouldn't be converting to string yet!  This must
+                //     come *after* the .constructor access in step 8.
+                flags = RegExpFlag(0);
+                RootedString flagStr(cx, ToString<CanGC>(cx, args[1]));
+                if (!flagStr)
+                    return false;
+                if (!ParseRegExpFlags(cx, flagStr, &flags))
+                    return false;
+            }
+        }
+
+        // Steps 8-9.
+        // XXX Note bug in step 5c, with respect to step 8.
+        Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx));
+        if (!regexp)
+            return false;
+
+        // Step 10.
+        if (!InitializeRegExp(cx, regexp, sourceAtom, flags))
+            return false;
+
+        args.rval().setObject(*regexp);
+        return true;
+    }
+
+    RootedValue P(cx);
+    RootedValue F(cx);
+
+    // Step 6.
+    if (patternIsRegExp) {
+        RootedObject patternObj(cx, &patternValue.toObject());
+
+        // Steps 6a-b.
+        if (!GetProperty(cx, patternObj, patternObj, cx->names().source, &P))
+            return false;
+
+        // Steps 6c-d.
+        F = args.get(1);
+        if (F.isUndefined()) {
+            if (!GetProperty(cx, patternObj, patternObj, cx->names().flags, &F))
+                return false;
+        }
+    } else {
+        // Steps 7a-b.
+        P = patternValue;
+        F = args.get(1);
+    }
+
+    // Steps 8-9.
+    Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx));
+    if (!regexp)
+        return false;
+
+    // Step 10.
+    if (!RegExpInitialize(cx, regexp, P, F, UseRegExpStatics))
+        return false;
+
+    args.rval().setObject(*regexp);
+    return true;
 }
 
 bool
 js::regexp_construct_no_statics(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     MOZ_ASSERT(args.length() == 1 || args.length() == 2);
     MOZ_ASSERT(args[0].isString());
     MOZ_ASSERT_IF(args.length() == 2, args[1].isString());
     MOZ_ASSERT(!args.isConstructing());
 
     /* Steps 1-6 are not required since pattern is always string. */
 
     /* Steps 7-10. */
-    RegExpObjectBuilder builder(cx);
-    RootedObject reobj(cx);
-    if (!RegExpInitialize(cx, builder, args[0], args.get(1), DontUseRegExpStatics, &reobj))
+    Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx));
+    if (!regexp)
         return false;
 
-    args.rval().setObject(*reobj);
+    if (!RegExpInitialize(cx, regexp, args[0], args.get(1), DontUseRegExpStatics))
+        return false;
+
+    args.rval().setObject(*regexp);
     return true;
 }
 
 /* ES6 draft rev32 21.2.5.4. */
 MOZ_ALWAYS_INLINE bool
 regexp_global_impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(IsRegExpObject(args.thisv()));
@@ -698,22 +695,18 @@ js::CreateRegExpPrototype(JSContext* cx,
 {
     MOZ_ASSERT(key == JSProto_RegExp);
 
     Rooted<RegExpObject*> proto(cx, cx->global()->createBlankPrototype<RegExpObject>(cx));
     if (!proto)
         return nullptr;
     proto->NativeObject::setPrivate(nullptr);
 
-    HandlePropertyName empty = cx->names().empty;
-    RegExpObjectBuilder builder(cx, proto);
-    if (!builder.build(empty, RegExpFlag(0)))
-        return nullptr;
-
-    return proto;
+    RootedAtom source(cx, cx->names().empty);
+    return InitializeRegExp(cx, proto, source, RegExpFlag(0));
 }
 
 static bool
 ReportLastIndexNonwritable(JSContext* cx)
 {
     JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_READ_ONLY, "\"lastIndex\"");
     return false;
 }
--- a/js/src/builtin/RegExp.h
+++ b/js/src/builtin/RegExp.h
@@ -21,19 +21,16 @@ InitRegExpClass(JSContext* cx, HandleObj
 
 // Whether RegExp statics should be updated with the input and results of a
 // regular expression execution.
 enum RegExpStaticsUpdate { UpdateRegExpStatics, DontUpdateRegExpStatics };
 
 // Whether RegExp statics should be used to create a RegExp instance.
 enum RegExpStaticsUse { UseRegExpStatics, DontUseRegExpStatics };
 
-// This enum is used to indicate whether 'CompileRegExpObject' is called from 'regexp_compile'.
-enum RegExpCreationMode { CreateForCompile, CreateForConstruct };
-
 RegExpRunStatus
 ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string,
               MatchPairs* matches, RegExpStaticsUpdate staticsUpdate);
 
 /*
  * Legacy behavior of ExecuteRegExp(), which is baked into the JSAPI.
  *
  * |res| may be nullptr if the RegExpStatics are not to be updated.
@@ -94,17 +91,15 @@ extern bool
 regexp_construct_no_statics(JSContext* cx, unsigned argc, Value* vp);
 
 extern bool
 IsRegExp(JSContext* cx, HandleValue value, bool* result);
 
 // RegExp ClassSpec members used in RegExpObject.cpp.
 extern bool
 regexp_construct(JSContext* cx, unsigned argc, Value* vp);
-extern JSObject*
-CreateRegExpPrototype(JSContext* cx, JSProtoKey key);
 extern const JSPropertySpec regexp_static_props[];
 extern const JSPropertySpec regexp_properties[];
 extern const JSFunctionSpec regexp_methods[];
 
 } /* namespace js */
 
 #endif /* builtin_RegExp_h */
--- a/js/src/jit/arm64/Assembler-arm64.cpp
+++ b/js/src/jit/arm64/Assembler-arm64.cpp
@@ -119,26 +119,29 @@ Assembler::emitExtendedJumpTable()
         //   [Patchable 8-byte constant low bits]
         //   [Patchable 8-byte constant high bits]
         DebugOnly<size_t> preOffset = size_t(armbuffer_.nextOffset().getOffset());
 
         ldr(vixl::ip0, ptrdiff_t(8 / vixl::kInstructionSize));
         br(vixl::ip0);
 
         DebugOnly<size_t> prePointer = size_t(armbuffer_.nextOffset().getOffset());
-        MOZ_ASSERT(prePointer - preOffset == OffsetOfJumpTableEntryPointer);
+        MOZ_ASSERT_IF(!oom(), prePointer - preOffset == OffsetOfJumpTableEntryPointer);
 
         brk(0x0);
         brk(0x0);
 
         DebugOnly<size_t> postOffset = size_t(armbuffer_.nextOffset().getOffset());
 
-        MOZ_ASSERT(postOffset - preOffset == SizeOfJumpTableEntry);
+        MOZ_ASSERT_IF(!oom(), postOffset - preOffset == SizeOfJumpTableEntry);
     }
 
+    if (oom())
+        return BufferOffset();
+
     return tableOffset;
 }
 
 void
 Assembler::executableCopy(uint8_t* buffer)
 {
     // Copy the code and all constant pools into the output buffer.
     armbuffer_.executableCopy(buffer);
@@ -216,17 +219,20 @@ Assembler::fImmPool32(ARMFPRegister dest
 {
     return fImmPool(dest, (uint8_t*)&value, vixl::LDR_s_lit);
 }
 
 void
 Assembler::bind(Label* label, BufferOffset targetOffset)
 {
     // Nothing has seen the label yet: just mark the location.
-    if (!label->used()) {
+    // If we've run out of memory, don't attempt to modify the buffer which may
+    // not be there. Just mark the label as bound to the (possibly bogus)
+    // targetOffset.
+    if (!label->used() || oom()) {
         label->bind(targetOffset.getOffset());
         return;
     }
 
     // Get the most recent instruction that used the label, as stored in the label.
     // This instruction is the head of an implicit linked list of label uses.
     uint32_t branchOffset = label->offset();
 
@@ -254,17 +260,19 @@ Assembler::bind(Label* label, BufferOffs
     // Bind the label, so that future uses may encode the offset immediately.
     label->bind(targetOffset.getOffset());
 }
 
 void
 Assembler::bind(RepatchLabel* label)
 {
     // Nothing has seen the label yet: just mark the location.
-    if (!label->used()) {
+    // If we've run out of memory, don't attempt to modify the buffer which may
+    // not be there. Just mark the label as bound to nextOffset().
+    if (!label->used() || oom()) {
         label->bind(nextOffset().getOffset());
         return;
     }
     int branchOffset = label->offset();
     Instruction* inst = getInstructionAt(BufferOffset(branchOffset));
     inst->SetImmPCOffsetTarget(inst + nextOffset().getOffset() - branchOffset);
 }
 
--- a/js/src/jit/arm64/Assembler-arm64.h
+++ b/js/src/jit/arm64/Assembler-arm64.h
@@ -59,19 +59,16 @@ static constexpr Register PreBarrierReg 
 
 static constexpr Register ReturnReg = { Registers::x0 };
 static constexpr Register JSReturnReg = { Registers::x2 };
 static constexpr Register FramePointer = { Registers::fp };
 static constexpr Register ZeroRegister = { Registers::sp };
 static constexpr ARMRegister ZeroRegister64 = { Registers::sp, 64 };
 static constexpr ARMRegister ZeroRegister32 = { Registers::sp, 32 };
 
-static constexpr FloatRegister ReturnFloatReg = { FloatRegisters::d0, FloatRegisters::Single };
-static constexpr FloatRegister ScratchFloatReg = { FloatRegisters::d31, FloatRegisters::Single };
-
 static constexpr FloatRegister ReturnSimdReg = InvalidFloatReg;
 static constexpr FloatRegister ScratchSimdReg = InvalidFloatReg;
 
 // StackPointer is intentionally undefined on ARM64 to prevent misuse:
 //  using sp as a base register is only valid if sp % 16 == 0.
 static constexpr Register RealStackPointer = { Registers::sp };
 
 static constexpr Register PseudoStackPointer = { Registers::x28 };
--- a/js/src/jit/arm64/SharedICRegisters-arm64.h
+++ b/js/src/jit/arm64/SharedICRegisters-arm64.h
@@ -44,16 +44,15 @@ static constexpr Register ExtractTemp0 =
 static constexpr Register ExtractTemp1 = r25;
 
 // R7 - R9 are generally available for use within stubcode.
 
 // Note that BaselineTailCallReg is actually just the link
 // register.  In ARM code emission, we do not clobber BaselineTailCallReg
 // since we keep the return address for calls there.
 
-// FloatReg0 must be equal to ReturnFloatReg.
-static constexpr FloatRegister FloatReg0 = { FloatRegisters::v0, FloatRegisters::Single };
-static constexpr FloatRegister FloatReg1 = { FloatRegisters::v1, FloatRegisters::Single };
+static constexpr FloatRegister FloatReg0 = { FloatRegisters::d0, FloatRegisters::Double };
+static constexpr FloatRegister FloatReg1 = { FloatRegisters::d1, FloatRegisters::Double };
 
 } // namespace jit
 } // namespace js
 
 #endif // jit_arm64_SharedICRegisters_arm64_h
--- a/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
@@ -337,21 +337,20 @@ class Redirection
     Redirection* current = sim->redirection();
     for (; current != nullptr; current = current->next_) {
       if (current->nativeFunction_ == nativeFunction) {
         VIXL_ASSERT(current->type() == type);
         return current;
       }
     }
 
+    js::AutoEnterOOMUnsafeRegion oomUnsafe;
     Redirection* redir = (Redirection*)js_malloc(sizeof(Redirection));
-    if (!redir) {
-      MOZ_ReportAssertionFailure("[unhandlable oom] Simulator redirection", __FILE__, __LINE__);
-      MOZ_CRASH();
-    }
+    if (!redir)
+        oomUnsafe.crash("Simulator redirection");
     new(redir) Redirection(nativeFunction, type, sim);
     return redir;
   }
 
   static const Redirection* FromSvcInstruction(const Instruction* svcInstruction) {
     const uint8_t* addrOfSvc = reinterpret_cast<const uint8_t*>(svcInstruction);
     const uint8_t* addrOfRedirection = addrOfSvc - offsetof(Redirection, svcInstruction_);
     return reinterpret_cast<const Redirection*>(addrOfRedirection);
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -541,122 +541,98 @@ Str(JSContext* cx, const Value& v, Strin
 
     bool isArray;
     if (!IsArray(cx, obj, &isArray))
         return false;
 
     return isArray ? JA(cx, obj, scx) : JO(cx, obj, scx);
 }
 
-/* ES5 15.12.3. */
+/* ES6 24.3.2. */
 bool
 js::Stringify(JSContext* cx, MutableHandleValue vp, JSObject* replacer_, Value space_,
               StringBuffer& sb)
 {
     RootedObject replacer(cx, replacer_);
     RootedValue space(cx, space_);
 
     /* Step 4. */
     AutoIdVector propertyList(cx);
     if (replacer) {
         bool isArray;
         if (replacer->isCallable()) {
             /* Step 4a(i): use replacer to transform values.  */
         } else if (!IsArray(cx, replacer, &isArray)) {
             return false;
         } else if (isArray) {
-            /*
-             * Step 4b: The spec algorithm is unhelpfully vague about the exact
-             * steps taken when the replacer is an array, regarding the exact
-             * sequence of [[Get]] calls for the array's elements, when its
-             * overall length is calculated, whether own or own plus inherited
-             * properties are considered, and so on.  A rewrite was proposed in
-             * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>,
-             * whose steps are copied below, and which are implemented here.
-             *
-             * i.   Let PropertyList be an empty internal List.
-             * ii.  Let len be the result of calling the [[Get]] internal
-             *      method of replacer with the argument "length".
-             * iii. Let i be 0.
-             * iv.  While i < len:
-             *      1. Let item be undefined.
-             *      2. Let v be the result of calling the [[Get]] internal
-             *         method of replacer with the argument ToString(i).
-             *      3. If Type(v) is String then let item be v.
-             *      4. Else if Type(v) is Number then let item be ToString(v).
-             *      5. Else if Type(v) is Object then
-             *         a. If the [[Class]] internal property of v is "String"
-             *            or "Number" then let item be ToString(v).
-             *      6. If item is not undefined and item is not currently an
-             *         element of PropertyList then,
-             *         a. Append item to the end of PropertyList.
-             *      7. Let i be i + 1.
-             */
+            /* Step 4b(iii). */
 
-            /* Step 4b(ii). */
+            /* Step 4b(iii)(2-3). */
             uint32_t len;
             if (!GetLengthProperty(cx, replacer, &len))
                 return false;
-            if (replacer->is<ArrayObject>() && !replacer->isIndexed())
-                len = Min(len, replacer->as<ArrayObject>().getDenseInitializedLength());
 
             // Cap the initial size to a moderately small value.  This avoids
             // ridiculous over-allocation if an array with bogusly-huge length
             // is passed in.  If we end up having to add elements past this
             // size, the set will naturally resize to accommodate them.
-            const uint32_t MaxInitialSize = 1024;
+            const uint32_t MaxInitialSize = 32;
             HashSet<jsid, JsidHasher> idSet(cx);
             if (!idSet.init(Min(len, MaxInitialSize)))
                 return false;
 
-            /* Step 4b(iii). */
-            uint32_t i = 0;
+            /* Step 4b(iii)(4). */
+            uint32_t k = 0;
 
-            /* Step 4b(iv). */
-            RootedValue v(cx);
-            for (; i < len; i++) {
+            /* Step 4b(iii)(5). */
+            RootedValue item(cx);
+            for (; k < len; k++) {
                 if (!CheckForInterrupt(cx))
                     return false;
 
-                /* Step 4b(iv)(2). */
-                if (!GetElement(cx, replacer, replacer, i, &v))
+                /* Step 4b(iii)(5)(a-b). */
+                if (!GetElement(cx, replacer, replacer, k, &item))
                     return false;
 
                 RootedId id(cx);
-                if (v.isNumber()) {
-                    /* Step 4b(iv)(4). */
+
+                /* Step 4b(iii)(5)(c-f). */
+                if (item.isNumber()) {
+                    /* Step 4b(iii)(5)(e). */
                     int32_t n;
-                    if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) {
+                    if (ValueFitsInInt32(item, &n) && INT_FITS_IN_JSID(n)) {
                         id = INT_TO_JSID(n);
                     } else {
-                        if (!ValueToId<CanGC>(cx, v, &id))
+                        if (!ValueToId<CanGC>(cx, item, &id))
                             return false;
                     }
                 } else {
-                    bool shouldAdd = v.isString();
+                    bool shouldAdd = item.isString();
                     if (!shouldAdd) {
                         ESClassValue cls;
-                        if (!GetClassOfValue(cx, v, &cls))
+                        if (!GetClassOfValue(cx, item, &cls))
                             return false;
+
                         shouldAdd = cls == ESClass_String || cls == ESClass_Number;
                     }
 
                     if (shouldAdd) {
-                        /* Step 4b(iv)(3), 4b(iv)(5). */
-                        if (!ValueToId<CanGC>(cx, v, &id))
+                        /* Step 4b(iii)(5)(f). */
+                        if (!ValueToId<CanGC>(cx, item, &id))
                             return false;
                     } else {
+                        /* Step 4b(iii)(5)(g). */
                         continue;
                     }
                 }
 
-                /* Step 4b(iv)(6). */
-                HashSet<jsid, JsidHasher>::AddPtr p = idSet.lookupForAdd(id);
+                /* Step 4b(iii)(5)(g). */
+                auto p = idSet.lookupForAdd(id);
                 if (!p) {
-                    /* Step 4b(iv)(6)(a). */
+                    /* Step 4b(iii)(5)(g)(i). */
                     if (!idSet.add(p, id) || !propertyList.append(id))
                         return false;
                 }
             }
         } else {
             replacer = nullptr;
         }
     }
@@ -682,17 +658,17 @@ js::Stringify(JSContext* cx, MutableHand
         }
     }
 
     StringBuffer gap(cx);
 
     if (space.isNumber()) {
         /* Step 6. */
         double d;
-        JS_ALWAYS_TRUE(ToInteger(cx, space, &d));
+        MOZ_ALWAYS_TRUE(ToInteger(cx, space, &d));
         d = Min(10.0, d);
         if (d >= 1 && !gap.appendN(' ', uint32_t(d)))
             return false;
     } else if (space.isString()) {
         /* Step 7. */
         JSLinearString* str = space.toString()->ensureLinear(cx);
         if (!str)
             return false;
@@ -704,22 +680,22 @@ js::Stringify(JSContext* cx, MutableHand
         MOZ_ASSERT(gap.empty());
     }
 
     /* Step 9. */
     RootedPlainObject wrapper(cx, NewBuiltinClassInstance<PlainObject>(cx));
     if (!wrapper)
         return false;
 
-    /* Step 10. */
+    /* Steps 10-11. */
     RootedId emptyId(cx, NameToId(cx->names().empty));
     if (!NativeDefineProperty(cx, wrapper, emptyId, vp, nullptr, nullptr, JSPROP_ENUMERATE))
         return false;
 
-    /* Step 11. */
+    /* Step 12. */
     StringifyContext scx(cx, sb, gap, replacer, propertyList);
     if (!scx.init())
         return false;
     if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx))
         return false;
     if (IsFilteredValue(vp))
         return true;
 
@@ -898,21 +874,22 @@ json_parse(JSContext* cx, unsigned argc,
     HandleValue reviver = args.get(1);
 
     /* Steps 2-5. */
     return linearChars.isLatin1()
            ? ParseJSONWithReviver(cx, linearChars.latin1Range(), reviver, args.rval())
            : ParseJSONWithReviver(cx, linearChars.twoByteRange(), reviver, args.rval());
 }
 
-/* ES5 15.12.3. */
+/* ES6 24.3.2. */
 bool
 json_stringify(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
+
     RootedObject replacer(cx, args.get(1).isObject() ? &args[1].toObject() : nullptr);
     RootedValue value(cx, args.get(0));
     RootedValue space(cx, args.get(2));
 
     StringBuffer sb(cx);
     if (!Stringify(cx, &value, replacer, space, sb))
         return false;
 
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/JSON/stringify-replacer-array-trailing-holes.js
@@ -0,0 +1,49 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+var gTestfile = "stringify-replacer-array-trailing-holes.js";
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1217069;
+var summary =
+  "Better/more correct handling for replacer arrays with trailing holes " +
+  "through which inherited elements might appear";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var obj = { 0: "hi", 1: "n-nao", 2: "run away!", 3: "bye" };
+
+var s;
+
+var replacer = [0, /* 1 */, /* 2 */, /* 3 */, ];
+
+assertEq(JSON.stringify(obj, replacer),
+         '{"0":"hi"}');
+
+var nobj = new Number(0);
+nobj.toString = () => { replacer[1] = 1; return 0; };
+replacer[0] = nobj;
+
+assertEq(JSON.stringify(obj, replacer),
+         '{"0":"hi","1":"n-nao"}');
+
+delete replacer[1];
+replacer[0] = 0;
+
+Object.prototype[0] = 0;
+Object.prototype[1] = 1;
+Object.prototype[2] = 2;
+Object.prototype[3] = 3;
+
+assertEq(JSON.stringify(obj, replacer),
+         '{"0":"hi","1":"n-nao","2":"run away!","3":"bye"}');
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -35,106 +35,34 @@ using js::frontend::TokenStream;
 
 using JS::AutoCheckCannotGC;
 
 JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
 JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
 JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
 JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
 
-/* RegExpObjectBuilder */
-
-RegExpObjectBuilder::RegExpObjectBuilder(ExclusiveContext* cx, RegExpObject* reobj)
-  : cx(cx), reobj_(cx, reobj)
-{}
-
-bool
-RegExpObjectBuilder::getOrCreate()
+RegExpObject*
+js::RegExpAlloc(ExclusiveContext* cx)
 {
-    if (reobj_)
-        return true;
-
     // Note: RegExp objects are always allocated in the tenured heap. This is
     // not strictly required, but simplifies embedding them in jitcode.
-    reobj_ = NewBuiltinClassInstance<RegExpObject>(cx, TenuredObject);
-    if (!reobj_)
-        return false;
-    reobj_->initPrivate(nullptr);
-
-    return true;
-}
+    RegExpObject* regexp = NewBuiltinClassInstance<RegExpObject>(cx, TenuredObject);
+    if (!regexp)
+        return nullptr;
 
-bool
-RegExpObjectBuilder::getOrCreateClone(HandleObjectGroup group)
-{
-    MOZ_ASSERT(!reobj_);
-    MOZ_ASSERT(group->clasp() == &RegExpObject::class_);
-
-    // Note: RegExp objects are always allocated in the tenured heap. This is
-    // not strictly required, but simplifies embedding them in jitcode.
-    reobj_ = NewObjectWithGroup<RegExpObject>(cx->asJSContext(), group, TenuredObject);
-    if (!reobj_)
-        return false;
-    reobj_->initPrivate(nullptr);
-
-    return true;
+    regexp->initPrivate(nullptr);
+    return regexp;
 }
 
 RegExpObject*
-RegExpObjectBuilder::build(HandleAtom source, RegExpShared& shared)
-{
-    if (!getOrCreate())
-        return nullptr;
-
-    if (!reobj_->init(cx, source, shared.getFlags()))
-        return nullptr;
-
-    reobj_->setShared(shared);
-    return reobj_;
-}
-
-RegExpObject*
-RegExpObjectBuilder::build(HandleAtom source, RegExpFlag flags)
-{
-    if (!getOrCreate())
-        return nullptr;
-
-    return reobj_->init(cx, source, flags) ? reobj_.get() : nullptr;
-}
-
-RegExpObject*
-RegExpObjectBuilder::clone(Handle<RegExpObject*> other)
+js::InitializeRegExp(ExclusiveContext* cx, Handle<RegExpObject*> regexp, HandleAtom source,
+                     RegExpFlag flags)
 {
-    RootedObjectGroup group(cx, other->group());
-    if (!getOrCreateClone(group))
-        return nullptr;
-
-    /*
-     * Check that the RegExpShared for the original is okay to use in
-     * the clone -- if the |RegExpStatics| provides more flags we'll
-     * need a different |RegExpShared|.
-     */
-    RegExpStatics* res = other->getProto()->global().getRegExpStatics(cx);
-    if (!res)
-        return nullptr;
-
-    RegExpFlag origFlags = other->getFlags();
-    RegExpFlag staticsFlags = res->getFlags();
-    if ((origFlags & staticsFlags) != staticsFlags) {
-        RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags);
-        Rooted<JSAtom*> source(cx, other->getSource());
-        return build(source, newFlags);
-    }
-
-    RegExpGuard g(cx);
-    if (!other->getShared(cx->asJSContext(), &g))
-        return nullptr;
-
-    Rooted<JSAtom*> source(cx, other->getSource());
-    return build(source, *g);
+    return regexp->init(cx, source, flags) ? regexp : nullptr;
 }
 
 /* MatchPairs */
 
 bool
 MatchPairs::initArrayFrom(MatchPairs& copyFrom)
 {
     MOZ_ASSERT(copyFrom.pairCount() > 0);
@@ -287,18 +215,21 @@ RegExpObject::createNoStatics(ExclusiveC
                                    (const char16_t*) nullptr, 0,
                                    (frontend::StrictModeGetter*) nullptr);
         tokenStream = dummyTokenStream.ptr();
     }
 
     if (!irregexp::ParsePatternSyntax(*tokenStream, alloc, source))
         return nullptr;
 
-    RegExpObjectBuilder builder(cx);
-    return builder.build(source, flags);
+    Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx));
+    if (!regexp)
+        return nullptr;
+
+    return InitializeRegExp(cx, regexp, source, flags);
 }
 
 bool
 RegExpObject::createShared(JSContext* cx, RegExpGuard* g)
 {
     Rooted<RegExpObject*> self(cx, this);
 
     MOZ_ASSERT(!maybeShared());
@@ -940,21 +871,56 @@ RegExpCompartment::sizeOfExcludingThis(m
     return n;
 }
 
 /* Functions */
 
 JSObject*
 js::CloneRegExpObject(JSContext* cx, JSObject* obj_)
 {
-    RegExpObjectBuilder builder(cx);
     Rooted<RegExpObject*> regex(cx, &obj_->as<RegExpObject>());
-    JSObject* res = builder.clone(regex);
-    MOZ_ASSERT_IF(res, res->group() == regex->group());
-    return res;
+
+    // Check that the RegExpShared for |regex| is okay to reuse in the clone.
+    // If the |RegExpStatics| provides additional flags, we'll need a new
+    // |RegExpShared|.
+    RegExpStatics* currentStatics = regex->getProto()->global().getRegExpStatics(cx);
+    if (!currentStatics)
+        return nullptr;
+
+    Rooted<JSAtom*> source(cx, regex->getSource());
+
+    RegExpFlag origFlags = regex->getFlags();
+    RegExpFlag staticsFlags = currentStatics->getFlags();
+    if ((origFlags & staticsFlags) != staticsFlags) {
+        Rooted<RegExpObject*> clone(cx, RegExpAlloc(cx));
+        if (!clone)
+            return nullptr;
+
+        return InitializeRegExp(cx, clone, source, RegExpFlag(origFlags | staticsFlags));
+    }
+
+    // Otherwise, the clone can use |regexp|'s RegExpShared.
+    RootedObjectGroup group(cx, regex->group());
+
+    // Note: RegExp objects are always allocated in the tenured heap. This is
+    // not strictly required, but it simplifies embedding them in jitcode.
+    Rooted<RegExpObject*> clone(cx, NewObjectWithGroup<RegExpObject>(cx, group, TenuredObject));
+    if (!clone)
+        return nullptr;
+    clone->initPrivate(nullptr);
+
+    RegExpGuard g(cx);
+    if (!regex->getShared(cx, &g))
+        return nullptr;
+
+    if (!InitializeRegExp(cx, clone, source, g->getFlags()))
+        return nullptr;
+
+    clone->setShared(*g.re());
+    return clone;
 }
 
 static bool
 HandleRegExpFlag(RegExpFlag flag, RegExpFlag* flags)
 {
     if (*flags & flag)
         return false;
     *flags = RegExpFlag(*flags | flag);
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -58,38 +58,29 @@ enum RegExpFlag
 
 enum RegExpRunStatus
 {
     RegExpRunStatus_Error,
     RegExpRunStatus_Success,
     RegExpRunStatus_Success_NotFound
 };
 
-class RegExpObjectBuilder
-{
-    ExclusiveContext* cx;
-    Rooted<RegExpObject*> reobj_;
+extern RegExpObject*
+RegExpAlloc(ExclusiveContext* cx);
 
-    bool getOrCreate();
-    bool getOrCreateClone(HandleObjectGroup group);
-
-  public:
-    explicit RegExpObjectBuilder(ExclusiveContext* cx, RegExpObject* reobj = nullptr);
+extern RegExpObject*
+InitializeRegExp(ExclusiveContext* cx, Handle<RegExpObject*> regexp, HandleAtom source,
+                 RegExpFlag flags);
 
-    RegExpObject* reobj() { return reobj_; }
-
-    RegExpObject* build(HandleAtom source, RegExpFlag flags);
-    RegExpObject* build(HandleAtom source, RegExpShared& shared);
+// |regexp| is under-typed because this function's used in the JIT.
+extern JSObject*
+CloneRegExpObject(JSContext* cx, JSObject* regexp);
 
-    /* Perform a VM-internal clone. */
-    RegExpObject* clone(Handle<RegExpObject*> other);
-};
-
-JSObject*
-CloneRegExpObject(JSContext* cx, JSObject* obj);
+extern JSObject*
+CreateRegExpPrototype(JSContext* cx, JSProtoKey key);
 
 /*
  * A RegExpShared is the compiled representation of a regexp. A RegExpShared is
  * potentially pointed to by multiple RegExpObjects. Additionally, C++ code may
  * have pointers to RegExpShareds on the stack. The RegExpShareds are kept in a
  * table so that they can be reused when compiling the same regex string.
  *
  * During a GC, RegExpShared instances are marked and swept like GC things.
@@ -452,17 +443,19 @@ class RegExpObject : public NativeObject
     void setShared(RegExpShared& shared) {
         MOZ_ASSERT(!maybeShared());
         NativeObject::setPrivate(&shared);
     }
 
     static void trace(JSTracer* trc, JSObject* obj);
 
   private:
-    friend class RegExpObjectBuilder;
+    friend RegExpObject*
+    InitializeRegExp(ExclusiveContext* cx, Handle<RegExpObject*> regexp, HandleAtom source,
+                     RegExpFlag flags);
 
     bool init(ExclusiveContext* cx, HandleAtom source, RegExpFlag flags);
 
     /*
      * Precondition: the syntax for |source| has already been validated.
      * Side effect: sets the private field.
      */
     bool createShared(JSContext* cx, RegExpGuard* g);
--- a/layout/generic/WritingModes.h
+++ b/layout/generic/WritingModes.h
@@ -451,40 +451,49 @@ public:
   { }
 
   /**
    * Construct writing mode based on a style context
    */
   explicit WritingMode(nsStyleContext* aStyleContext)
   {
     NS_ASSERTION(aStyleContext, "we need an nsStyleContext here");
+    InitFromStyleVisibility(aStyleContext->StyleVisibility());
+  }
 
-    const nsStyleVisibility* styleVisibility = aStyleContext->StyleVisibility();
+  explicit WritingMode(const nsStyleVisibility* aStyleVisibility)
+  {
+    NS_ASSERTION(aStyleVisibility, "we need an nsStyleVisibility here");
+    InitFromStyleVisibility(aStyleVisibility);
+  }
 
-    switch (styleVisibility->mWritingMode) {
+private:
+  void InitFromStyleVisibility(const nsStyleVisibility* aStyleVisibility)
+  {
+    switch (aStyleVisibility->mWritingMode) {
       case NS_STYLE_WRITING_MODE_HORIZONTAL_TB:
         mWritingMode = 0;
         break;
 
       case NS_STYLE_WRITING_MODE_VERTICAL_LR:
       {
         mWritingMode = eBlockFlowMask |
                        eLineOrientMask |
                        eOrientationMask;
-        uint8_t textOrientation = styleVisibility->mTextOrientation;
+        uint8_t textOrientation = aStyleVisibility->mTextOrientation;
         if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS) {
           mWritingMode |= eSidewaysMask;
         }
         break;
       }
 
       case NS_STYLE_WRITING_MODE_VERTICAL_RL:
       {
         mWritingMode = eOrientationMask;
-        uint8_t textOrientation = styleVisibility->mTextOrientation;
+        uint8_t textOrientation = aStyleVisibility->mTextOrientation;
         if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS) {
           mWritingMode |= eSidewaysMask;
         }
         break;
       }
 
       case NS_STYLE_WRITING_MODE_SIDEWAYS_LR:
         mWritingMode = eBlockFlowMask |
@@ -499,20 +508,21 @@ public:
         break;
 
       default:
         NS_NOTREACHED("unknown writing mode!");
         mWritingMode = 0;
         break;
     }
 
-    if (NS_STYLE_DIRECTION_RTL == styleVisibility->mDirection) {
+    if (NS_STYLE_DIRECTION_RTL == aStyleVisibility->mDirection) {
       mWritingMode ^= eInlineFlowMask | eBidiMask;
     }
   }
+public:
 
   /**
    * This function performs fixup for elements with 'unicode-bidi: plaintext',
    * where inline directionality is derived from the Unicode bidi categories
    * of the element's content, and not the CSS 'direction' property.
    *
    * The WritingMode constructor will have already incorporated the 'direction'
    * property into our flag bits, so such elements need to use this method
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -1613,17 +1613,17 @@ static nscoord
 WordSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr)
 {
   if (aFrame->IsSVGText()) {
     return 0;
   }
   if (!aStyleText) {
     aStyleText = aFrame->StyleText();
   }
-  return aStyleText->mWordSpacing;
+  return StyleToCoord(aStyleText->mWordSpacing);
 }
 
 bool
 BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2)
 {
   // We don't need to check font size inflation, since
   // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
   // ensures that text runs never cross block boundaries.  This means
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -9399,16 +9399,41 @@ CSSParserImpl::ParseColorStop(nsCSSValue
     if (stop->mIsInterpolationHint) {
       return false;
     }
     stop->mLocation.SetNoneValue();
   }
   return true;
 }
 
+// Helper for ParseLinearGradient -- returns true iff aPosition represents a
+// box-position value which was parsed with only edge keywords.
+// e.g. "left top", or "bottom", but not "left 10px"
+//
+// (NOTE: Even though callers may want to exclude explicit "center", we still
+// need to allow for _CENTER here, because omitted position-values (e.g. the
+// x-component of a value like "top") will have been parsed as being *implicit*
+// center. The correct way to disallow *explicit* center is to pass "false" for
+// ParseBoxPositionValues()'s "aAllowExplicitCenter" parameter, before you
+// call this function.)
+static bool
+IsBoxPositionStrictlyEdgeKeywords(nsCSSValuePair& aPosition)
+{
+  const nsCSSValue& xValue = aPosition.mXValue;
+  const nsCSSValue& yValue = aPosition.mYValue;
+  return (xValue.GetUnit() == eCSSUnit_Enumerated &&
+          (xValue.GetIntValue() & (NS_STYLE_BG_POSITION_LEFT |
+                                   NS_STYLE_BG_POSITION_CENTER |
+                                   NS_STYLE_BG_POSITION_RIGHT)) &&
+          yValue.GetUnit() == eCSSUnit_Enumerated &&
+          (yValue.GetIntValue() & (NS_STYLE_BG_POSITION_TOP |
+                                   NS_STYLE_BG_POSITION_CENTER |
+                                   NS_STYLE_BG_POSITION_BOTTOM)));
+}
+
 // <gradient>
 //    : linear-gradient( <linear-gradient-line>? <color-stops> ')'
 //    | radial-gradient( <radial-gradient-line>? <color-stops> ')'
 //
 // <linear-gradient-line> : [ to [left | right] || [top | bottom] ] ,
 //                        | <legacy-gradient-line>
 // <radial-gradient-line> : [ <shape> || <size> ] [ at <position> ]? ,
 //                        | [ at <position> ] ,
@@ -9430,83 +9455,97 @@ CSSParserImpl::ParseLinearGradient(nsCSS
 {
   RefPtr<nsCSSValueGradient> cssGradient
     = new nsCSSValueGradient(false, aFlags & eGradient_Repeating);
 
   if (!GetToken(true)) {
     return false;
   }
 
-  if (mToken.mType == eCSSToken_Ident &&
+  // Check for "to" syntax (but not if parsing a -webkit-linear-gradient)
+  if (!(aFlags & eGradient_WebkitLegacy) &&
+      mToken.mType == eCSSToken_Ident &&
       mToken.mIdent.LowerCaseEqualsLiteral("to")) {
 
     // "to" syntax doesn't allow explicit "center"
     if (!ParseBoxPositionValues(cssGradient->mBgPos, false, false)) {
       SkipUntil(')');
       return false;
     }
 
     // [ to [left | right] || [top | bottom] ] ,
-    const nsCSSValue& xValue = cssGradient->mBgPos.mXValue;
-    const nsCSSValue& yValue = cssGradient->mBgPos.mYValue;
-    if (xValue.GetUnit() != eCSSUnit_Enumerated ||
-        !(xValue.GetIntValue() & (NS_STYLE_BG_POSITION_LEFT |
-                                  NS_STYLE_BG_POSITION_CENTER |
-                                  NS_STYLE_BG_POSITION_RIGHT)) ||
-        yValue.GetUnit() != eCSSUnit_Enumerated ||
-        !(yValue.GetIntValue() & (NS_STYLE_BG_POSITION_TOP |
-                                  NS_STYLE_BG_POSITION_CENTER |
-                                  NS_STYLE_BG_POSITION_BOTTOM))) {
+    if (!IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) {
       SkipUntil(')');
       return false;
     }
 
     if (!ExpectSymbol(',', true)) {
       SkipUntil(')');
       return false;
     }
 
     return ParseGradientColorStops(cssGradient, aValue);
   }
 
-  if (!(aFlags & eGradient_MozLegacy)) {
+  if (!(aFlags & eGradient_AnyLegacy)) {
+    // We're parsing an unprefixed linear-gradient, and we tried & failed to
+    // parse a 'to' token above. Put the token back & try to re-parse our
+    // expression as <angle>? <color-stop-list>
     UngetToken();
 
     // <angle> ,
     if (ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr) &&
         !ExpectSymbol(',', true)) {
       SkipUntil(')');
       return false;
     }
 
     return ParseGradientColorStops(cssGradient, aValue);
   }
 
-  nsCSSTokenType ty = mToken.mType;
-  nsString id = mToken.mIdent;
+  // If we get here, we're parsing a prefixed linear-gradient expression.  Put
+  // back the first token (which we may have checked for "to" above) and try to
+  // parse expression as <legacy-gradient-line>? <color-stop-list>
+  bool haveGradientLine = IsLegacyGradientLine(mToken.mType, mToken.mIdent);
   UngetToken();
 
-  // <legacy-gradient-line>
-  bool haveGradientLine = IsLegacyGradientLine(ty, id);
   if (haveGradientLine) {
+    // Parse a <legacy-gradient-line>
     cssGradient->mIsLegacySyntax = true;
     bool haveAngle =
       ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr);
 
     // if we got an angle, we might now have a comma, ending the gradient-line
-    if (!haveAngle || !ExpectSymbol(',', true)) {
-      if (!ParseBoxPositionValues(cssGradient->mBgPos, false)) {
+    bool haveAngleComma = haveAngle && ExpectSymbol(',', true);
+
+    // If we're webkit-prefixed & didn't get an angle,
+    // OR if we're moz-prefixed & didn't get an angle+comma,
+    // then proceed to parse a box-position.
+    if (((aFlags & eGradient_WebkitLegacy) && !haveAngle) ||
+        ((aFlags & eGradient_MozLegacy) && !haveAngleComma)) {
+      // (Note: 3rd arg controls whether the "center" keyword is allowed.
+      // -moz-linear-gradient allows it; -webkit-linear-gradient does not.)
+      if (!ParseBoxPositionValues(cssGradient->mBgPos, false,
+                                  (aFlags & eGradient_MozLegacy))) {
+        SkipUntil(')');
+        return false;
+      }
+
+      // -webkit-linear-gradient only supports edge keywords here.
+      if ((aFlags & eGradient_WebkitLegacy) &&
+          !IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) {
         SkipUntil(')');
         return false;
       }
 
       if (!ExpectSymbol(',', true) &&
-          // if we didn't already get an angle, we might have one now,
-          // otherwise it's an error
+          // If we didn't already get an angle, and we're not -webkit prefixed,
+          // we can parse an angle+comma now.  Otherwise it's an error.
           (haveAngle ||
+           (aFlags & eGradient_WebkitLegacy) ||
            !ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE,
                                     nullptr) ||
            // now we better have a comma
            !ExpectSymbol(',', true))) {
         SkipUntil(')');
         return false;
       }
     }
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -3603,17 +3603,17 @@ CSS_PROP_TEXT(
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
         CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
     "",
     VARIANT_HL | VARIANT_NORMAL | VARIANT_CALC,
     nullptr,
     offsetof(nsStyleText, mWordSpacing),
-    eStyleAnimType_nscoord)
+    eStyleAnimType_Coord)
 CSS_PROP_TEXT(
     word-wrap,
     word_wrap,
     WordWrap,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kWordWrapKTable,
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -3517,17 +3517,17 @@ nsComputedDOMStyle::DoGetLetterSpacing()
   SetValueToCoord(val, StyleText()->mLetterSpacing, false);
   return val;
 }
 
 CSSValue*
 nsComputedDOMStyle::DoGetWordSpacing()
 {
   nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue;
-  val->SetAppUnits(StyleText()->mWordSpacing);
+  SetValueToCoord(val, StyleText()->mWordSpacing, false);
   return val;
 }
 
 CSSValue*
 nsComputedDOMStyle::DoGetWhiteSpace()
 {
   nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue;
   val->SetIdent(
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -61,16 +61,40 @@
 #include <alloca.h>
 #endif
 
 using std::max;
 using std::min;
 using namespace mozilla;
 using namespace mozilla::dom;
 
+void*
+nsConditionalResetStyleData::GetConditionalStyleData(nsStyleStructID aSID,
+                               nsStyleContext* aStyleContext) const
+{
+  Entry* e = static_cast<Entry*>(mEntries[aSID]);
+  MOZ_ASSERT(e, "if mConditionalBits bit is set, we must have at least one "
+                "conditional style struct");
+  do {
+    if (e->mConditions.Matches(aStyleContext)) {
+      void* data = e->mStyleStruct;
+
+      // For reset structs with conditions, we cache the data on the
+      // style context.
+      // Tell the style context that it doesn't own the data
+      aStyleContext->AddStyleBit(GetBitForSID(aSID));
+      aStyleContext->SetStyle(aSID, data);
+
+      return data;
+    }
+    e = e->mNext;
+  } while (e);
+  return nullptr;
+}
+
 #define NS_SET_IMAGE_REQUEST(method_, context_, request_)                     \
   if ((context_)->PresContext()->IsDynamic()) {                               \
     method_(request_);                                                        \
   } else {                                                                    \
     RefPtr<imgRequestProxy> req = nsContentUtils::GetStaticRequest(request_); \
     method_(req);                                                             \
   }
 
@@ -2343,22 +2367,21 @@ nsRuleNode::WalkRuleTree(const nsStyleSt
 
   if (detail == eRuleNone && startStruct) {
     // We specified absolutely no rule information, but a parent rule in the tree
     // specified all the rule information.  We set a bit along the branch from our
     // node in the tree to the node that specified the data that tells nodes on that
     // branch that they never need to examine their rules for this particular struct type
     // ever again.
     PropagateDependentBit(aSID, ruleNode, startStruct);
-    // With this same bit set, we do two different things:
-    // For reset structs, record that we have asked for this struct on
-    // this context, but it is not cached on the context.
     // For inherited structs, mark the struct (which will be set on
     // the context by our caller) as not being owned by the context.
-    aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID));
+    if (!isReset) {
+      aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID));
+    }
     return startStruct;
   }
   if ((!startStruct && !isReset &&
        (detail == eRuleNone || detail == eRulePartialInherited)) ||
       detail == eRuleFullInherited) {
     // We specified no non-inherited information and neither did any of
     // our parent rules.
 
@@ -2753,19 +2776,16 @@ nsRuleNode::SetDefaultOnRoot(const nsSty
     }                                                                         \
     NS_ASSERTION(!aHighestNode->mStyleData.mResetData->                       \
                    GetStyleData(eStyleStruct_##type_),                        \
                  "Going to leak style data");                                 \
     aHighestNode->mStyleData.mResetData->                                     \
       SetStyleData(eStyleStruct_##type_, data_);                              \
     /* Propagate the bit down. */                                             \
     PropagateDependentBit(eStyleStruct_##type_, aHighestNode, data_);         \
-    /* Tell the context that we've gotten the data (separate meaning */       \
-    /* of mBits when the cached data pointer is null) */                      \
-    aContext->AddStyleBit(NS_STYLE_INHERIT_BIT(type_));                       \
   } else if (conditions.Cacheable()) {                                        \
     if (!mStyleData.mResetData) {                                             \
       mStyleData.mResetData = new (mPresContext) nsConditionalResetStyleData; \
     }                                                                         \
     mStyleData.mResetData->                                                   \
       SetStyleData(eStyleStruct_##type_, mPresContext, data_, conditions);    \
     /* Tell the style context that it doesn't own the data */                 \
     aContext->AddStyleBit(NS_STYLE_INHERIT_BIT(type_));                       \
@@ -4347,34 +4367,26 @@ nsRuleNode::ComputeTextData(void* aStart
 
   // word-break: enum, inherit, initial
   SetDiscrete(*aRuleData->ValueForWordBreak(), text->mWordBreak, conditions,
               SETDSC_ENUMERATED | SETDSC_UNSET_INHERIT,
               parentText->mWordBreak,
               NS_STYLE_WORDBREAK_NORMAL, 0, 0, 0, 0);
 
   // word-spacing: normal, length, inherit
-  nsStyleCoord tempCoord;
   const nsCSSValue* wordSpacingValue = aRuleData->ValueForWordSpacing();
-  if (SetCoord(*wordSpacingValue, tempCoord,
-               nsStyleCoord(parentText->mWordSpacing,
-                            nsStyleCoord::CoordConstructor),
-               SETCOORD_LH | SETCOORD_NORMAL | SETCOORD_INITIAL_NORMAL |
-                 SETCOORD_CALC_LENGTH_ONLY | SETCOORD_UNSET_INHERIT,
-               aContext, mPresContext, conditions)) {
-    if (tempCoord.GetUnit() == eStyleUnit_Coord) {
-      text->mWordSpacing = tempCoord.GetCoordValue();
-    } else if (tempCoord.GetUnit() == eStyleUnit_Normal) {
-      text->mWordSpacing = 0;
-    } else {
-      NS_NOTREACHED("unexpected unit");
-    }
+  if (wordSpacingValue->GetUnit() == eCSSUnit_Normal) {
+    // Do this so that "normal" computes to 0px, as the CSS 2.1 spec requires.
+    text->mWordSpacing.SetCoordValue(0);
   } else {
-    NS_ASSERTION(wordSpacingValue->GetUnit() == eCSSUnit_Null,
-                 "unexpected unit");
+    SetCoord(*aRuleData->ValueForWordSpacing(),
+             text->mWordSpacing, parentText->mWordSpacing,
+             SETCOORD_LH | SETCOORD_INITIAL_ZERO |
+               SETCOORD_CALC_LENGTH_ONLY | SETCOORD_UNSET_INHERIT,
+             aContext, mPresContext, conditions);
   }
 
   // word-wrap: enum, inherit, initial
   SetDiscrete(*aRuleData->ValueForWordWrap(), text->mWordWrap, conditions,
               SETDSC_ENUMERATED | SETDSC_UNSET_INHERIT,
               parentText->mWordWrap,
               NS_STYLE_WORDWRAP_NORMAL, 0, 0, 0, 0);
 
@@ -9413,24 +9425,23 @@ nsRuleNode::GetStyleData(nsStyleStructID
   MOZ_ASSERT(!aContext->GetCachedStyleData(aSID),
              "style context should not have cached data for struct");
 
   const void *data;
 
   // Never use cached data for animated style inside a pseudo-element;
   // see comment on cacheability in AnimValuesStyleRule::MapRuleInfoInto.
   if (!(HasAnimationData() && ParentHasPseudoElementData(aContext))) {
-    data = mStyleData.GetStyleData(aSID, aContext);
+    data = mStyleData.GetStyleData(aSID, aContext, aComputeData);
     if (MOZ_LIKELY(data != nullptr)) {
-      // With this same bit set, we do two different things:
-      // For reset structs, mark the struct as having been retrieved for
-      // this context.
       // For inherited structs, mark the struct (which will be set on
       // the context by our caller) as not being owned by the context.
-      aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID));
+      if (!nsCachedStyleData::IsReset(aSID)) {
+        aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID));
+      }
       return data; // We have a fully specified struct. Just return it.
     }
   }
 
   if (MOZ_UNLIKELY(!aComputeData))
     return nullptr;
 
   // Nothing is cached.  We'll have to delve further and examine our rules.
--- a/layout/style/nsRuleNode.h
+++ b/layout/style/nsRuleNode.h
@@ -155,32 +155,39 @@ struct nsConditionalResetStyleData
   void* GetStyleData(nsStyleStructID aSID) const {
     if (mConditionalBits & GetBitForSID(aSID)) {
       return nullptr;
     }
     return mEntries[aSID];
   }
 
   void* GetStyleData(nsStyleStructID aSID,
-                     nsStyleContext* aStyleContext) const {
+                     nsStyleContext* aStyleContext,
+                     bool aCanComputeData) const {
     if (!(mConditionalBits & GetBitForSID(aSID))) {
       return mEntries[aSID];
     }
-    Entry* e = static_cast<Entry*>(mEntries[aSID]);
-    MOZ_ASSERT(e, "if mConditionalBits bit is set, we must have at least one "
-                  "conditional style struct");
-    do {
-      if (e->mConditions.Matches(aStyleContext)) {
-        return e->mStyleStruct;
-      }
-      e = e->mNext;
-    } while (e);
-    return nullptr;
+    if (!aCanComputeData) {
+      // If aCanComputeData is false, then any previously-computed data
+      // would have been cached on the style context.  Therefore it's
+      // unnecessary to check the conditional data.  It's also
+      // incorrect, because calling e->mConditions.Matches() below could
+      // cause additional structs to be computed, which is incorrect
+      // during CalcStyleDifference.
+      return nullptr;
+    }
+    return GetConditionalStyleData(aSID, aStyleContext);
   }
 
+private:
+  // non-inline helper for GetStyleData
+  void* GetConditionalStyleData(nsStyleStructID aSID,
+                                nsStyleContext* aStyleContext) const;
+
+public:
   void SetStyleData(nsStyleStructID aSID, void* aStyleStruct) {
     MOZ_ASSERT(!(mConditionalBits & GetBitForSID(aSID)),
                "rule node should not have unconditional and conditional style "
                "data for a given struct");
     mEntries[aSID] = aStyleStruct;
   }
 
   void SetStyleData(nsStyleStructID aSID,
@@ -269,20 +276,21 @@ struct nsCachedStyleData
       if (mInheritedData) {
         return mInheritedData->mStyleStructs[aSID];
       }
     }
     return nullptr;
   }
 
   void* NS_FASTCALL GetStyleData(const nsStyleStructID aSID,
-                                 nsStyleContext* aStyleContext) {
+                                 nsStyleContext* aStyleContext,
+                                 bool aCanComputeData) {
     if (IsReset(aSID)) {
       if (mResetData) {
-        return mResetData->GetStyleData(aSID, aStyleContext);
+        return mResetData->GetStyleData(aSID, aStyleContext, aCanComputeData);
       }
     } else {
       if (mInheritedData) {
         return mInheritedData->mStyleStructs[aSID];
       }
     }
     return nullptr;
   }
@@ -304,19 +312,21 @@ struct nsCachedStyleData
 
   // Typesafe and faster versions of the above
   #define STYLE_STRUCT_INHERITED(name_, checkdata_cb_)                         \
     nsStyle##name_ * NS_FASTCALL GetStyle##name_ () {                          \
       return mInheritedData ? static_cast<nsStyle##name_*>(                    \
         mInheritedData->mStyleStructs[eStyleStruct_##name_]) : nullptr;        \
     }
   #define STYLE_STRUCT_RESET(name_, checkdata_cb_)                             \
-    nsStyle##name_ * NS_FASTCALL GetStyle##name_ (nsStyleContext* aContext) {  \
+    nsStyle##name_ * NS_FASTCALL GetStyle##name_ (nsStyleContext* aContext,    \
+                                                  bool aCanComputeData) {         \
       return mResetData ? static_cast<nsStyle##name_*>(                        \
-        mResetData->GetStyleData(eStyleStruct_##name_, aContext)) : nullptr;   \
+        mResetData->GetStyleData(eStyleStruct_##name_, aContext, aCanComputeData))\
+                        : nullptr;                                             \
     }
   #include "nsStyleStructList.h"
   #undef STYLE_STRUCT_RESET
   #undef STYLE_STRUCT_INHERITED
 
   void Destroy(uint64_t aBits, nsPresContext* aContext) {
     if (mResetData)
       mResetData->Destroy(aBits, aContext);
@@ -917,36 +927,32 @@ public:
                                                                               \
     MOZ_ASSERT(data, "should have aborted on out-of-memory");                 \
     return data;                                                              \
   }
 
   #define STYLE_STRUCT_RESET(name_, checkdata_cb_)                            \
   template<bool aComputeData>                                                 \
   const nsStyle##name_*                                                       \
-  GetStyle##name_(nsStyleContext* aContext, uint64_t& aContextStyleBits)      \
+  GetStyle##name_(nsStyleContext* aContext)                                   \
   {                                                                           \
     NS_ASSERTION(IsUsedDirectly(),                                            \
                  "if we ever call this on rule nodes that aren't used "       \
                  "directly, we should adjust handling of mDependentBits "     \
                  "in some way.");                                             \
     MOZ_ASSERT(!ContextHasCachedData(aContext, eStyleStruct_##name_),         \
                "style context should not have cached data for struct");       \
                                                                               \
     const nsStyle##name_ *data;                                               \
                                                                               \
     /* Never use cached data for animated style inside a pseudo-element; */   \
     /* see comment on cacheability in AnimValuesStyleRule::MapRuleInfoInto */ \
     if (!(HasAnimationData() && ParentHasPseudoElementData(aContext))) {      \
-      data = mStyleData.GetStyle##name_(aContext);                            \
+      data = mStyleData.GetStyle##name_(aContext, aComputeData);              \
       if (MOZ_LIKELY(data != nullptr)) {                                      \
-        /* Mark the struct as having been retrieved for this context. */      \
-        /* Normally this would be aContext->AddStyleBit(), but aContext is */ \
-        /* an incomplete type here, so we work around that with a param. */   \
-        aContextStyleBits |= NS_STYLE_INHERIT_BIT(name_);                     \
         return data;                                                          \
       }                                                                       \
     }                                                                         \
                                                                               \
     if (!aComputeData)                                                        \
       return nullptr;                                                         \
                                                                               \
     data = static_cast<const nsStyle##name_ *>                                \
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -917,17 +917,17 @@ nsStyleContext::CalcStyleDifference(nsSt
   DO_STRUCT_DIFFERENCE(Table);
   DO_STRUCT_DIFFERENCE(UIReset);
   DO_STRUCT_DIFFERENCE(Text);
   DO_STRUCT_DIFFERENCE(List);
   DO_STRUCT_DIFFERENCE(Quotes);
   DO_STRUCT_DIFFERENCE(SVGReset);
   DO_STRUCT_DIFFERENCE(SVG);
 #undef EXTRA_DIFF_ARGS
-#define EXTRA_DIFF_ARGS , this
+#define EXTRA_DIFF_ARGS , PeekStyleVisibility()
   DO_STRUCT_DIFFERENCE(Position);
 #undef EXTRA_DIFF_ARGS
 #define EXTRA_DIFF_ARGS /* nothing */
   DO_STRUCT_DIFFERENCE(Font);
   DO_STRUCT_DIFFERENCE(Margin);
   DO_STRUCT_DIFFERENCE(Padding);
   DO_STRUCT_DIFFERENCE(Border);
   DO_STRUCT_DIFFERENCE(TextReset);
@@ -1372,17 +1372,17 @@ nsStyleContext::SwapStyleData(nsStyleCon
        i < nsStyleStructID_Inherited_Start + nsStyleStructID_Inherited_Count;
        i = nsStyleStructID(i + 1)) {
     uint32_t bit = nsCachedStyleData::GetBitForSID(i);
     if (!(aStructs & bit)) {
       continue;
     }
     void*& thisData = mCachedInheritedData.mStyleStructs[i];
     void*& otherData = aNewContext->mCachedInheritedData.mStyleStructs[i];
-    if ((mBits & bit) && thisData) {
+    if (mBits & bit) {
       if (thisData == otherData) {
         thisData = nullptr;
       }
     } else if (!(aNewContext->mBits & bit) && thisData && otherData) {
       std::swap(thisData, otherData);
     }
   }
 
@@ -1397,17 +1397,17 @@ nsStyleContext::SwapStyleData(nsStyleCon
       mCachedResetData = new (mRuleNode->PresContext()) nsResetStyleData;
     }
     if (!aNewContext->mCachedResetData) {
       aNewContext->mCachedResetData =
         new (mRuleNode->PresContext()) nsResetStyleData;
     }
     void*& thisData = mCachedResetData->mStyleStructs[i];
     void*& otherData = aNewContext->mCachedResetData->mStyleStructs[i];
-    if ((mBits & bit) && thisData) {
+    if (mBits & bit) {
       if (thisData == otherData) {
         thisData = nullptr;
       }
     } else if (!(aNewContext->mBits & bit) && thisData && otherData) {
       std::swap(thisData, otherData);
     }
   }
 }
--- a/layout/style/nsStyleContext.h
+++ b/layout/style/nsStyleContext.h
@@ -283,18 +283,17 @@ public:
 
   /**
    * Returns whether this style context has cached style data for a
    * given style struct and it does NOT own that struct.  This can
    * happen because it was inherited from the parent style context, or
    * because it was stored conditionally on the rule node.
    */
   bool HasCachedDependentStyleData(nsStyleStructID aSID) {
-    return (mBits & nsCachedStyleData::GetBitForSID(aSID)) &&
-           GetCachedStyleData(aSID);
+    return mBits & nsCachedStyleData::GetBitForSID(aSID);
   }
 
   nsRuleNode* RuleNode() { return mRuleNode; }
   void AddStyleBit(const uint64_t& aBit) { mBits |= aBit; }
 
   /*
    * Mark this style context's rule node (and its ancestors) to prevent
    * it from being garbage collected.
@@ -553,24 +552,19 @@ private:
     const nsStyle##name_ * DoGetStyle##name_() {                        \
       if (mCachedResetData) {                                           \
         const nsStyle##name_ * cachedData =                             \
           static_cast<nsStyle##name_*>(                                 \
             mCachedResetData->mStyleStructs[eStyleStruct_##name_]);     \
         if (cachedData) /* Have it cached already, yay */               \
           return cachedData;                                            \
       }                                                                 \
-      if (!aComputeData && !(mBits & NS_STYLE_INHERIT_BIT(name_))) {    \
-        /* When we compute reset structs, we either cache them on */    \
-        /* the style context or set the bit in mBits. */                \
-        return nullptr;                                                 \
-      }                                                                 \
       /* Have the rulenode deal */                                      \
       AUTO_CHECK_DEPENDENCY(eStyleStruct_##name_);                      \
-      return mRuleNode->GetStyle##name_<aComputeData>(this, mBits);     \
+      return mRuleNode->GetStyle##name_<aComputeData>(this);            \
     }
   #include "nsStyleStructList.h"
   #undef STYLE_STRUCT_RESET
   #undef STYLE_STRUCT_INHERITED
 
   // Helper for ClearCachedInheritedStyleDataOnDescendants.
   void DoClearCachedInheritedStyleDataOnDescendants(uint32_t aStructs);
 
@@ -617,33 +611,28 @@ private:
   // root of the rule tree, and the most specific rule matched is the
   // |mRule| member of |mRuleNode|.
   nsRuleNode* const       mRuleNode;
 
   // mCachedInheritedData and mCachedResetData point to both structs that
   // are owned by this style context and structs that are owned by one of
   // this style context's ancestors (which are indirectly owned since this
   // style context owns a reference to its parent).  If the bit in |mBits|
-  // is set for a non-null struct, that means that the pointer for that
-  // struct is owned by an ancestor or by mRuleNode rather than by this
-  // style context.  Since style contexts typically have some inherited
-  // data but only sometimes have reset data, we always allocate the
-  // mCachedInheritedData, but only sometimes allocate the
-  // mCachedResetData.
+  // is set for a struct, that means that the pointer for that struct is
+  // owned by an ancestor or by mRuleNode rather than by this style context.
+  // Since style contexts typically have some inherited data but only sometimes
+  // have reset data, we always allocate the mCachedInheritedData, but only
+  // sometimes allocate the mCachedResetData.
   nsResetStyleData*       mCachedResetData; // Cached reset style data.
   nsInheritedStyleData    mCachedInheritedData; // Cached inherited style data
 
   // mBits stores a number of things:
-  //  - For all structs, when they are non-null in the style context's
-  //    storage, it records (using the style struct bits) which structs
-  //    are inherited from the parent context or owned by mRuleNode.
-  //  - For reset (non-inherited) style structs, when they are null in
-  //    the style context's storage, it records that we have been asked
-  //    for that struct.  (We don't need this for inherited structs
-  //    since we always cache them in mCachedInheritedData.)
+  //  - It records (using the style struct bits) which structs are
+  //    inherited from the parent context or owned by mRuleNode (i.e.,
+  //    not owned by the style context).
   //  - It also stores the additional bits listed at the top of
   //    nsStyleStruct.h.
   uint64_t                mBits;
 
   uint32_t                mRefCnt;
 
 #ifdef DEBUG
   uint32_t                mFrameRefCnt; // number of frames that use this
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1501,17 +1501,17 @@ IsAutonessEqual(const nsStyleSides& aSid
       return false;
     }
   }
   return true;
 }
 
 nsChangeHint
 nsStylePosition::CalcDifference(const nsStylePosition& aOther,
-                                nsStyleContext* aContext) const
+                                const nsStyleVisibility* aOldStyleVisibility) const
 {
   nsChangeHint hint = nsChangeHint(0);
 
   // Changes to "z-index" require a repaint.
   if (mZIndex != aOther.mZIndex) {
     NS_UpdateHint(hint, nsChangeHint_RepaintFrame);
   }
 
@@ -1598,33 +1598,53 @@ nsStylePosition::CalcDifference(const ns
   }
 
   bool widthChanged = mWidth != aOther.mWidth ||
                       mMinWidth != aOther.mMinWidth ||
                       mMaxWidth != aOther.mMaxWidth;
   bool heightChanged = mHeight != aOther.mHeight ||
                        mMinHeight != aOther.mMinHeight ||
                        mMaxHeight != aOther.mMaxHeight;
-  bool isVertical = WritingMode(aContext).IsVertical();
-  if (isVertical ? widthChanged : heightChanged) {
-    // Block-size changes can affect descendant intrinsic sizes due to replaced
-    // elements with percentage bsizes in descendants which also have
-    // percentage bsizes. This is handled via nsChangeHint_UpdateComputedBSize
-    // which clears intrinsic sizes for frames that have such replaced elements.
-    NS_UpdateHint(hint, nsChangeHint_NeedReflow |
-        nsChangeHint_UpdateComputedBSize |
-        nsChangeHint_ReflowChangesSizeOrPosition);
-  }
-
-  if (isVertical ? heightChanged : widthChanged) {
-    // None of our inline-size differences can affect descendant intrinsic
-    // sizes and none of them need to force children to reflow.
-    NS_UpdateHint(hint, NS_SubtractHint(nsChangeHint_AllReflowHints,
-                                        NS_CombineHint(nsChangeHint_ClearDescendantIntrinsics,
-                                                       nsChangeHint_NeedDirtyReflow)));
+
+  // If aOldStyleVisibility is null, we don't need to bother with any of
+  // these tests, since we know that the element never had its
+  // nsStyleVisibility accessed, which means it couldn't have done
+  // layout.
+  // Note that we pass an nsStyleVisibility here because we don't want
+  // to cause a new struct to be computed during
+  // nsStyleContext::CalcStyleDifference, which can lead to incorrect
+  // style data.
+  // It doesn't matter whether we're looking at the old or new
+  // visibility struct, since a change between vertical and horizontal
+  // writing-mode will cause a reframe, and it's easier to pass the old.
+  if (aOldStyleVisibility) {
+    bool isVertical = WritingMode(aOldStyleVisibility).IsVertical();
+    if (isVertical ? widthChanged : heightChanged) {
+      // Block-size changes can affect descendant intrinsic sizes due to
+      // replaced elements with percentage bsizes in descendants which
+      // also have percentage bsizes. This is handled via
+      // nsChangeHint_UpdateComputedBSize which clears intrinsic sizes
+      // for frames that have such replaced elements.
+      NS_UpdateHint(hint, nsChangeHint_NeedReflow |
+          nsChangeHint_UpdateComputedBSize |
+          nsChangeHint_ReflowChangesSizeOrPosition);
+    }
+
+    if (isVertical ? heightChanged : widthChanged) {
+      // None of our inline-size differences can affect descendant
+      // intrinsic sizes and none of them need to force children to
+      // reflow.
+      NS_UpdateHint(hint, NS_SubtractHint(nsChangeHint_AllReflowHints,
+                                          NS_CombineHint(nsChangeHint_ClearDescendantIntrinsics,
+                                                         nsChangeHint_NeedDirtyReflow)));
+    }
+  } else {
+    if (widthChanged || heightChanged) {
+      NS_UpdateHint(hint, nsChangeHint_NeutralChange);
+    }
   }
 
   // If any of the offsets have changed, then return the respective hints
   // so that we would hopefully be able to avoid reflowing.
   // Note that it is possible that we'll need to reflow when processing
   // restyles, but we don't have enough information to make a good decision
   // right now.
   // Don't try to handle changes between "auto" and non-auto efficiently;
@@ -3425,20 +3445,20 @@ nsStyleText::nsStyleText(void)
   mWordWrap = NS_STYLE_WORDWRAP_NORMAL;
   mHyphens = NS_STYLE_HYPHENS_MANUAL;
   mRubyAlign = NS_STYLE_RUBY_ALIGN_SPACE_AROUND;
   mRubyPosition = NS_STYLE_RUBY_POSITION_OVER;
   mTextSizeAdjust = NS_STYLE_TEXT_SIZE_ADJUST_AUTO;
   mTextCombineUpright = NS_STYLE_TEXT_COMBINE_UPRIGHT_NONE;
   mControlCharacterVisibility = nsCSSParser::ControlCharVisibilityDefault();
 
+  mWordSpacing.SetCoordValue(0);
   mLetterSpacing.SetNormalValue();
   mLineHeight.SetNormalValue();
   mTextIndent.SetCoordValue(0);
-  mWordSpacing = 0;
 
   mTextShadow = nullptr;
   mTabSize = NS_STYLE_TABSIZE_INITIAL;
 }
 
 nsStyleText::nsStyleText(const nsStyleText& aSource)
   : mTextAlign(aSource.mTextAlign),
     mTextAlignLast(aSource.mTextAlignLast),
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -31,16 +31,17 @@
 #include "Orientation.h"
 #include "CounterStyleManager.h"
 
 class nsIFrame;
 class nsIURI;
 class nsStyleContext;
 class nsTextFrame;
 class imgIContainer;
+struct nsStyleVisibility;
 
 // Includes nsStyleStructID.
 #include "nsStyleStructFwd.h"
 
 // Bits for each struct.
 // NS_STYLE_INHERIT_BIT defined in nsStyleStructFwd.h
 #define NS_STYLE_INHERIT_MASK              0x000ffffff
 
@@ -1371,17 +1372,17 @@ struct nsStylePosition {
   }
   void Destroy(nsPresContext* aContext) {
     this->~nsStylePosition();
     aContext->PresShell()->
       FreeByObjectID(mozilla::eArenaObjectID_nsStylePosition, this);
   }
 
   nsChangeHint CalcDifference(const nsStylePosition& aOther,
-                              nsStyleContext* aContext) const;
+                              const nsStyleVisibility* aOldStyleVisibility) const;
   static nsChangeHint MaxDifference() {
     return NS_CombineHint(NS_STYLE_HINT_REFLOW,
                           nsChangeHint(nsChangeHint_RecomputePosition |
                                        nsChangeHint_UpdateParentOverflow |
                                        nsChangeHint_UpdateComputedBSize));
   }
   static nsChangeHint DifferenceAlwaysHandledForDescendants() {
     // CalcDifference can return all of the reflow hints that are
@@ -1681,20 +1682,20 @@ struct nsStyleText {
   uint8_t mHyphens;                     // [inherited] see nsStyleConsts.h
   uint8_t mRubyAlign;                   // [inherited] see nsStyleConsts.h
   uint8_t mRubyPosition;                // [inherited] see nsStyleConsts.h
   uint8_t mTextSizeAdjust;              // [inherited] see nsStyleConsts.h
   uint8_t mTextCombineUpright;          // [inherited] see nsStyleConsts.h
   uint8_t mControlCharacterVisibility;  // [inherited] see nsStyleConsts.h
   int32_t mTabSize;                     // [inherited] see nsStyleConsts.h
 
-  nscoord mWordSpacing;                 // [inherited]
-  nsStyleCoord  mLetterSpacing;         // [inherited] coord, normal
-  nsStyleCoord  mLineHeight;            // [inherited] coord, factor, normal
-  nsStyleCoord  mTextIndent;            // [inherited] coord, percent, calc
+  nsStyleCoord mWordSpacing;            // [inherited] coord
+  nsStyleCoord mLetterSpacing;          // [inherited] coord, normal
+  nsStyleCoord mLineHeight;             // [inherited] coord, factor, normal
+  nsStyleCoord mTextIndent;             // [inherited] coord, percent, calc
 
   RefPtr<nsCSSShadowArray> mTextShadow; // [inherited] nullptr in case of a zero-length
 
   bool WhiteSpaceIsSignificant() const {
     return mWhiteSpace == NS_STYLE_WHITESPACE_PRE ||
            mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
            mWhiteSpace == NS_STYLE_WHITESPACE_PRE_SPACE;
   }
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -642,16 +642,19 @@ if (IsCSSPropertyPrefEnabled("layout.css
 
     // Contain/cover keywords (valid only for -moz/-webkit prefixed):
     "-webkit-radial-gradient(cover, red, blue)",
     "-webkit-radial-gradient(cover circle, red, blue)",
     "-webkit-radial-gradient(contain, red, blue)",
     "-webkit-radial-gradient(contain ellipse, red, blue)",
 
     // Initial side/corner/point (valid only for -moz/-webkit prefixed):
+    "-webkit-linear-gradient(left, red, blue)",
+    "-webkit-linear-gradient(right top, red, blue)",
+    "-webkit-linear-gradient(top right, red, blue)",
     "-webkit-radial-gradient(right, red, blue)",
     "-webkit-radial-gradient(left bottom, red, blue)",
     "-webkit-radial-gradient(bottom left, red, blue)",
     "-webkit-radial-gradient(center, red, blue)",
     "-webkit-radial-gradient(center right, red, blue)",
     "-webkit-radial-gradient(center center, red, blue)",
     "-webkit-radial-gradient(center top, red, blue)",
     "-webkit-radial-gradient(left 50%, red, blue)",
@@ -661,36 +664,50 @@ if (IsCSSPropertyPrefEnabled("layout.css
     // Point + keyword-sized shape (valid only for -moz/-webkit prefixed):
     "-webkit-radial-gradient(center, circle closest-corner, red, blue)",
     "-webkit-radial-gradient(10px 20px, cover circle, red, blue)",
     "-webkit-radial-gradient(5em 50%, ellipse contain, red, blue)",
 
     // Repeating examples:
     "-webkit-repeating-linear-gradient(red 10%, blue 30%)",
     "-webkit-repeating-linear-gradient(30deg, pink 20px, orange 70px)",
+    "-webkit-repeating-linear-gradient(left, red, blue)",
+    "-webkit-repeating-linear-gradient(left, red 10%, blue 30%)",
     "-webkit-repeating-radial-gradient(circle, red, blue 10%, red 20%)",
     "-webkit-repeating-radial-gradient(circle farthest-corner, gray 10px, yellow 20px)",
     "-webkit-repeating-radial-gradient(top left, circle, red, blue 4%, red 8%)"
   );
 
   invalidGradientAndElementValues.push(
     // Syntax that's invalid for all types of gradients:
     // * empty gradient expressions:
     "-webkit-linear-gradient()",
     "-webkit-radial-gradient()",
     "-webkit-repeating-linear-gradient()",
     "-webkit-repeating-radial-gradient()",
 
-    // Syntax that's invalid for both -webkit & -moz, but valid for unprefixed:
-    // XXXdholbert (populated in a later patch)
-
     // Linear syntax that's invalid for both -webkit & unprefixed, but valid
     // for -moz:
-    // * initial length
+    // * initial <legacy-gradient-line> which includes a length:
     "-webkit-linear-gradient(10px, red, blue)",
+    "-webkit-linear-gradient(10px top, red, blue)",
+    // * initial <legacy-gradient-line> which includes a side *and* an angle:
+    "-webkit-linear-gradient(bottom 30deg, red, blue)",
+    "-webkit-linear-gradient(30deg bottom, red, blue)",
+    "-webkit-linear-gradient(10px top 50deg, red, blue)",
+    "-webkit-linear-gradient(50deg 10px top, red, blue)",
+    // * initial <legacy-gradient-line> which includes explicit "center":
+    "-webkit-linear-gradient(center, red, blue)",
+    "-webkit-linear-gradient(left center, red, blue)",
+    "-webkit-linear-gradient(top center, red, blue)",
+    "-webkit-linear-gradient(center top, red, blue)",
+
+    // Linear syntax that's invalid for -webkit, but valid for -moz & unprefixed:
+    // * "to" syntax:
+    "-webkit-linear-gradient(to top, red, blue)",
 
     // * <shape> followed by angle:
     "-webkit-radial-gradient(circle 10deg, red, blue)",
 
     // Radial syntax that's invalid for both -webkit & -moz, but valid for
     // unprefixed:
     // * "<shape> at <position>" syntax:
     "-webkit-radial-gradient(circle at left bottom, red, blue)",
@@ -701,19 +718,16 @@ if (IsCSSPropertyPrefEnabled("layout.css
     // Radial syntax that's invalid for both -webkit & unprefixed, but valid
     // for -moz:
     // * initial angle
     "-webkit-radial-gradient(30deg, red, blue)",
     // * initial angle/position combo
     "-webkit-radial-gradient(top 30deg, red, blue)",
     "-webkit-radial-gradient(left top 30deg, red, blue)",
     "-webkit-radial-gradient(10px 20px 30deg, red, blue)"
-
-    // Syntax that's invalid for -webkit, but valid for -moz & unprefixed:
-    // XXXdholbert (populated in a later patch)
   );
 }
 
 var gCSSProperties = {
   "animation": {
     domProp: "animation",
     inherited: false,
     type: CSS_TYPE_TRUE_SHORTHAND,
--- a/media/mtransport/third_party/nICEr/src/stun/addrs.c
+++ b/media/mtransport/third_party/nICEr/src/stun/addrs.c
@@ -194,17 +194,17 @@ stun_get_win32_addrs(nr_local_addr addrs
       c = strchr(munged_ifname, ' ');
       while (c != NULL) {
         *c = '_';
          c = strchr(munged_ifname, ' ');
       }
       c = strchr(munged_ifname, '.');
       while (c != NULL) {
         *c = '+';
-         c = strchr(munged_ifname, '+');
+         c = strchr(munged_ifname, '.');
       }
 
       if ((tmpAddress->IfIndex != 0) || (tmpAddress->Ipv6IfIndex != 0)) {
         IP_ADAPTER_UNICAST_ADDRESS *u = 0;
 
         for (u = tmpAddress->FirstUnicastAddress; u != 0; u = u->Next) {
           SOCKET_ADDRESS *sa_addr = &u->Address;
 
--- a/mobile/android/base/GeckoEditable.java
+++ b/mobile/android/base/GeckoEditable.java
@@ -13,16 +13,17 @@ import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Semaphore;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.gfx.LayerView;
+import org.mozilla.gecko.mozglue.JNIObject;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
 
 import android.os.Handler;
 import android.os.Looper;
 import android.text.Editable;
 import android.text.InputFilter;
@@ -38,17 +39,17 @@ import android.util.Log;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 
 /*
    GeckoEditable implements only some functions of Editable
    The field mText contains the actual underlying
    SpannableStringBuilder/Editable that contains our text.
 */
-final class GeckoEditable
+final class GeckoEditable extends JNIObject
         implements InvocationHandler, Editable,
                    GeckoEditableClient, GeckoEditableListener, GeckoEventListener {
 
     private static final boolean DEBUG = false;
     private static final String LOGTAG = "GeckoEditable";
 
     // Filters to implement Editable's filtering functionality
     private InputFilter[] mFilters;
@@ -70,16 +71,90 @@ final class GeckoEditable
     private int mIcUpdateSeqno;
     private int mLastIcUpdateSeqno;
     private boolean mUpdateGecko;
     private boolean mFocused; // Used by IC thread
     private boolean mGeckoFocused; // Used by Gecko thread
     private volatile boolean mSuppressCompositions;
     private volatile boolean mSuppressKeyUp;
 
+    private static final int IME_RANGE_CARETPOSITION = 1;
+    private static final int IME_RANGE_RAWINPUT = 2;
+    private static final int IME_RANGE_SELECTEDRAWTEXT = 3;
+    private static final int IME_RANGE_CONVERTEDTEXT = 4;
+    private static final int IME_RANGE_SELECTEDCONVERTEDTEXT = 5;
+
+    private static final int IME_RANGE_LINE_NONE = 0;
+    private static final int IME_RANGE_LINE_DOTTED = 1;
+    private static final int IME_RANGE_LINE_DASHED = 2;
+    private static final int IME_RANGE_LINE_SOLID = 3;
+    private static final int IME_RANGE_LINE_DOUBLE = 4;
+    private static final int IME_RANGE_LINE_WAVY = 5;
+
+    private static final int IME_RANGE_UNDERLINE = 1;
+    private static final int IME_RANGE_FORECOLOR = 2;
+    private static final int IME_RANGE_BACKCOLOR = 4;
+    private static final int IME_RANGE_LINECOLOR = 8;
+
+    @WrapForJNI
+    private native void onKeyEvent(int action, int keyCode, int scanCode, int metaState,
+                                   long time, int unicodeChar, int baseUnicodeChar,
+                                   int domPrintableKeyValue, int repeatCount, int flags,
+                                   boolean isSynthesizedImeKey);
+
+    private void onKeyEvent(KeyEvent event, int action, int savedMetaState,
+                            boolean isSynthesizedImeKey) {
+        // Use a separate action argument so we can override the key's original action,
+        // e.g. change ACTION_MULTIPLE to ACTION_DOWN. That way we don't have to allocate
+        // a new key event just to change its action field.
+        //
+        // Normally we expect event.getMetaState() to reflect the current meta-state; however,
+        // some software-generated key events may not have event.getMetaState() set, e.g. key
+        // events from Swype. Therefore, it's necessary to combine the key's meta-states
+        // with the meta-states that we keep separately in KeyListener
+        final int metaState = event.getMetaState() | savedMetaState;
+        final int unmodifiedMetaState = metaState &
+                ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK | KeyEvent.META_META_MASK);
+        final int unicodeChar = event.getUnicodeChar(metaState);
+        final int domPrintableKeyValue =
+                unicodeChar >= ' '               ? unicodeChar :
+                unmodifiedMetaState != metaState ? event.getUnicodeChar(unmodifiedMetaState) :
+                                                   0;
+        onKeyEvent(action, event.getKeyCode(), event.getScanCode(),
+                   metaState, event.getEventTime(), unicodeChar,
+                   // e.g. for Ctrl+A, Android returns 0 for unicodeChar,
+                   // but Gecko expects 'a', so we return that in baseUnicodeChar.
+                   event.getUnicodeChar(0), domPrintableKeyValue, event.getRepeatCount(),
+                   event.getFlags(), isSynthesizedImeKey);
+    }
+
+    @WrapForJNI
+    private native void onImeSynchronize();
+
+    @WrapForJNI
+    private native void onImeAcknowledgeFocus();
+
+    @WrapForJNI
+    private native void onImeReplaceText(int start, int end, String text, boolean composing);
+
+    @WrapForJNI
+    private native void onImeSetSelection(int start, int end);
+
+    @WrapForJNI
+    private native void onImeRemoveComposition();
+
+    @WrapForJNI
+    private native void onImeAddCompositionRange(int start, int end, int rangeType,
+                                                 int rangeStyles, int rangeLineStyle,
+                                                 boolean rangeBoldLine, int rangeForeColor,
+                                                 int rangeBackColor, int rangeLineColor);
+
+    @WrapForJNI
+    private native void onImeUpdateComposition(int start, int end);
+
     /* An action that alters the Editable
 
        Each action corresponds to a Gecko event. While the Gecko event is being sent to the Gecko
        thread, the action stays on top of mActions queue. After the Gecko event is processed and
        replied, the action is removed from the queue
     */
     private static final class Action {
         // For input events (keypress, etc.); use with IME_SYNCHRONIZE
@@ -216,36 +291,32 @@ final class GeckoEditable
             }
 
             switch (action.mType) {
             case Action.TYPE_EVENT:
             case Action.TYPE_SET_SELECTION:
             case Action.TYPE_SET_SPAN:
             case Action.TYPE_REMOVE_SPAN:
             case Action.TYPE_SET_HANDLER:
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEEvent(
-                        GeckoEvent.ImeAction.IME_SYNCHRONIZE));
+                onImeSynchronize();
                 break;
 
-            case Action.TYPE_COMPOSE_TEXT:
-                // Send different event for composing text.
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEComposeEvent(
-                        action.mStart, action.mEnd, action.mSequence.toString()));
-                return;
-
             case Action.TYPE_REPLACE_TEXT:
                 // try key events first
                 sendCharKeyEvents(action);
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEReplaceEvent(
-                        action.mStart, action.mEnd, action.mSequence.toString()));
+
+                // fall-through
+
+            case Action.TYPE_COMPOSE_TEXT:
+                onImeReplaceText(action.mStart, action.mEnd, action.mSequence.toString(),
+                                  action.mType == Action.TYPE_COMPOSE_TEXT);
                 break;
 
             case Action.TYPE_ACKNOWLEDGE_FOCUS:
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEEvent(
-                        GeckoEvent.ImeAction.IME_ACKNOWLEDGE_FOCUS));
+                onImeAcknowledgeFocus();
                 break;
 
             default:
                 throw new IllegalStateException("Action not processed");
             }
 
             ++mIcUpdateSeqno;
         }
@@ -288,17 +359,18 @@ final class GeckoEditable
                     continue;
                 }
                 if (event.getAction() == KeyEvent.ACTION_UP && mSuppressKeyUp) {
                     continue;
                 }
                 if (DEBUG) {
                     Log.d(LOGTAG, "sending: " + event);
                 }
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEKeyEvent(event));
+                onKeyEvent(event, event.getAction(),
+                           /* metaState */ 0, /* isSynthesizedImeKey */ true);
             }
         }
 
         /**
          * Remove the head of the queue. Throw if queue is empty.
          */
         void poll() {
             if (DEBUG) {
@@ -364,16 +436,37 @@ final class GeckoEditable
         final Class<?>[] PROXY_INTERFACES = { Editable.class };
         mProxy = (Editable)Proxy.newProxyInstance(
                 Editable.class.getClassLoader(),
                 PROXY_INTERFACES, this);
 
         mIcRunHandler = mIcPostHandler = ThreadUtils.getUiHandler();
     }
 
+    @WrapForJNI @Override
+    protected native void disposeNative();
+
+    @WrapForJNI
+    private void onDestroy() {
+        if (DEBUG) {
+            // Called by nsWindow.
+            ThreadUtils.assertOnGeckoThread();
+            Log.d(LOGTAG, "onDestroy()");
+        }
+
+        // Make sure we clear all pending Runnables on the IC thread first,
+        // by calling disposeNative from the IC thread.
+        geckoPostToIc(new Runnable() {
+            @Override
+            public void run() {
+                GeckoEditable.this.disposeNative();
+            }
+        });
+    }
+
     @WrapForJNI
     /* package */ void onViewChange(final GeckoView v) {
         if (DEBUG) {
             // Called by nsWindow.
             ThreadUtils.assertOnGeckoThread();
             Log.d(LOGTAG, "onViewChange(" + v + ")");
         }
 
@@ -469,38 +562,36 @@ final class GeckoEditable
             }
         }
         if (DEBUG) {
             Log.d(LOGTAG, " range = " + composingStart + "-" + composingEnd);
             Log.d(LOGTAG, " selection = " + selStart + "-" + selEnd);
         }
         if (composingStart >= composingEnd) {
             if (selStart >= 0 && selEnd >= 0) {
-                GeckoAppShell.sendEventToGecko(
-                        GeckoEvent.createIMESelectEvent(selStart, selEnd));
+                onImeSetSelection(selStart, selEnd);
             } else {
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEEvent(
-                        GeckoEvent.ImeAction.IME_REMOVE_COMPOSITION));
+                onImeRemoveComposition();
             }
             return;
         }
 
         if (selEnd >= composingStart && selEnd <= composingEnd) {
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createIMERangeEvent(
+            onImeAddCompositionRange(
                     selEnd - composingStart, selEnd - composingStart,
-                    GeckoEvent.IME_RANGE_CARETPOSITION, 0, 0, false, 0, 0, 0));
+                    IME_RANGE_CARETPOSITION, 0, 0, false, 0, 0, 0);
         }
         int rangeStart = composingStart;
         TextPaint tp = new TextPaint();
         TextPaint emptyTp = new TextPaint();
         // set initial foreground color to 0, because we check for tp.getColor() == 0
         // below to decide whether to pass a foreground color to Gecko
         emptyTp.setColor(0);
         do {
-            int rangeType, rangeStyles = 0, rangeLineStyle = GeckoEvent.IME_RANGE_LINE_NONE;
+            int rangeType, rangeStyles = 0, rangeLineStyle = IME_RANGE_LINE_NONE;
             boolean rangeBoldLine = false;
             int rangeForeColor = 0, rangeBackColor = 0, rangeLineColor = 0;
             int rangeEnd = mText.nextSpanTransition(rangeStart, composingEnd, Object.class);
 
             if (selStart > rangeStart && selStart < rangeEnd) {
                 rangeEnd = selStart;
             } else if (selEnd > rangeStart && selEnd < rangeEnd) {
                 rangeEnd = selEnd;
@@ -510,94 +601,93 @@ final class GeckoEditable
 
             if (DEBUG) {
                 Log.d(LOGTAG, " found " + styleSpans.length + " spans @ " +
                               rangeStart + "-" + rangeEnd);
             }
 
             if (styleSpans.length == 0) {
                 rangeType = (selStart == rangeStart && selEnd == rangeEnd)
-                            ? GeckoEvent.IME_RANGE_SELECTEDRAWTEXT
-                            : GeckoEvent.IME_RANGE_RAWINPUT;
+                            ? IME_RANGE_SELECTEDRAWTEXT
+                            : IME_RANGE_RAWINPUT;
             } else {
                 rangeType = (selStart == rangeStart && selEnd == rangeEnd)
-                            ? GeckoEvent.IME_RANGE_SELECTEDCONVERTEDTEXT
-                            : GeckoEvent.IME_RANGE_CONVERTEDTEXT;
+                            ? IME_RANGE_SELECTEDCONVERTEDTEXT
+                            : IME_RANGE_CONVERTEDTEXT;
                 tp.set(emptyTp);
                 for (CharacterStyle span : styleSpans) {
                     span.updateDrawState(tp);
                 }
                 int tpUnderlineColor = 0;
                 float tpUnderlineThickness = 0.0f;
 
                 // These TextPaint fields only exist on Android ICS+ and are not in the SDK.
                 if (Versions.feature14Plus) {
                     tpUnderlineColor = (Integer)getField(tp, "underlineColor", 0);
                     tpUnderlineThickness = (Float)getField(tp, "underlineThickness", 0.0f);
                 }
                 if (tpUnderlineColor != 0) {
-                    rangeStyles |= GeckoEvent.IME_RANGE_UNDERLINE | GeckoEvent.IME_RANGE_LINECOLOR;
+                    rangeStyles |= IME_RANGE_UNDERLINE | IME_RANGE_LINECOLOR;
                     rangeLineColor = tpUnderlineColor;
                     // Approximately translate underline thickness to what Gecko understands
                     if (tpUnderlineThickness <= 0.5f) {
-                        rangeLineStyle = GeckoEvent.IME_RANGE_LINE_DOTTED;
+                        rangeLineStyle = IME_RANGE_LINE_DOTTED;
                     } else {
-                        rangeLineStyle = GeckoEvent.IME_RANGE_LINE_SOLID;
+                        rangeLineStyle = IME_RANGE_LINE_SOLID;
                         if (tpUnderlineThickness >= 2.0f) {
                             rangeBoldLine = true;
                         }
                     }
                 } else if (tp.isUnderlineText()) {
-                    rangeStyles |= GeckoEvent.IME_RANGE_UNDERLINE;
-                    rangeLineStyle = GeckoEvent.IME_RANGE_LINE_SOLID;
+                    rangeStyles |= IME_RANGE_UNDERLINE;
+                    rangeLineStyle = IME_RANGE_LINE_SOLID;
                 }
                 if (tp.getColor() != 0) {
-                    rangeStyles |= GeckoEvent.IME_RANGE_FORECOLOR;
+                    rangeStyles |= IME_RANGE_FORECOLOR;
                     rangeForeColor = tp.getColor();
                 }
                 if (tp.bgColor != 0) {
-                    rangeStyles |= GeckoEvent.IME_RANGE_BACKCOLOR;
+                    rangeStyles |= IME_RANGE_BACKCOLOR;
                     rangeBackColor = tp.bgColor;
                 }
             }
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createIMERangeEvent(
+            onImeAddCompositionRange(
                     rangeStart - composingStart, rangeEnd - composingStart,
                     rangeType, rangeStyles, rangeLineStyle, rangeBoldLine,
-                    rangeForeColor, rangeBackColor, rangeLineColor));
+                    rangeForeColor, rangeBackColor, rangeLineColor);
             rangeStart = rangeEnd;
 
             if (DEBUG) {
                 Log.d(LOGTAG, " added " + rangeType +
                               " : " + Integer.toHexString(rangeStyles) +
                               " : " + Integer.toHexString(rangeForeColor) +
                               " : " + Integer.toHexString(rangeBackColor));
             }
         } while (rangeStart < composingEnd);
 
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createIMECompositionEvent(
-                composingStart, composingEnd));
+        onImeUpdateComposition(composingStart, composingEnd);
     }
 
     // GeckoEditableClient interface
 
     @Override
-    public void sendEvent(final GeckoEvent event) {
+    public void sendKeyEvent(final KeyEvent event, int action, int metaState) {
         if (DEBUG) {
             assertOnIcThread();
-            Log.d(LOGTAG, "sendEvent(" + event + ")");
+            Log.d(LOGTAG, "sendKeyEvent(" + event + ", " + action + ", " + metaState + ")");
         }
         /*
            We are actually sending two events to Gecko here,
-           1. Event from the event parameter (key event, etc.)
+           1. Event from the event parameter (key event)
            2. Sync event from the mActionQueue.offer call
-           The first event is a normal GeckoEvent that does not reply back to us,
+           The first event is a normal event that does not reply back to us,
            the second sync event will have a reply, during which we see that there is a pending
            event-type action, and update the selection/composition/etc. accordingly.
         */
-        GeckoAppShell.sendEventToGecko(event);
+        onKeyEvent(event, action, metaState, /* isSynthesizedImeKey */ false);
         mActionQueue.offer(new Action(Action.TYPE_EVENT));
     }
 
     @Override
     public Editable getEditable() {
         if (!onIcThread()) {
             // Android may be holding an old InputConnection; ignore
             if (DEBUG) {
@@ -844,17 +934,17 @@ final class GeckoEditable
             public void run() {
                 if (type == NOTIFY_IME_OF_FOCUS) {
                     mFocused = true;
                     // Unmask events on the Gecko side
                     mActionQueue.offer(new Action(Action.TYPE_ACKNOWLEDGE_FOCUS));
                 }
 
                 // Make sure there are no other things going on. If we sent
-                // GeckoEvent.IME_ACKNOWLEDGE_FOCUS, this line also makes us
+                // Action.TYPE_ACKNOWLEDGE_FOCUS, this line also makes us
                 // wait for Gecko to update us on the newly focused content
                 mActionQueue.syncWithGecko();
                 mListener.notifyIME(type);
 
                 // Unset mFocused after we call syncWithGecko because
                 // syncWithGecko becomes a no-op when mFocused is false.
                 if (type == NOTIFY_IME_OF_BLUR) {
                     mFocused = false;
--- a/mobile/android/base/GeckoEditableClient.java
+++ b/mobile/android/base/GeckoEditableClient.java
@@ -2,20 +2,21 @@
  * 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/. */
 
 package org.mozilla.gecko;
 
 import android.os.Handler;
 import android.text.Editable;
+import android.view.KeyEvent;
 
 /**
  * Interface for the IC thread.
  */
 interface GeckoEditableClient {
-    void sendEvent(GeckoEvent event);
+    void sendKeyEvent(KeyEvent event, int action, int metaState);
     Editable getEditable();
     void setUpdateGecko(boolean update);
     void setSuppressKeyUp(boolean suppress);
     Handler getInputConnectionHandler();
     boolean setInputConnectionHandler(Handler handler);
 }
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -65,37 +65,34 @@ public class GeckoEvent {
         }
     }
 
     // Make sure to keep these values in sync with the enum in
     // AndroidGeckoEvent in widget/android/AndroidJavaWrappers.h
     @JNITarget
     private enum NativeGeckoEvent {
         NATIVE_POKE(0),
-        KEY_EVENT(1),
         MOTION_EVENT(2),
         SENSOR_EVENT(3),
         LOCATION_EVENT(5),
-        IME_EVENT(6),
         SIZE_CHANGED(8),
         APP_BACKGROUNDING(9),
         APP_FOREGROUNDING(10),
         LOAD_URI(12),
         NOOP(15),
         BROADCAST(19),
         VIEWPORT(20),
         VISITED(21),
         NETWORK_CHANGED(22),
         THUMBNAIL(25),
         SCREENORIENTATION_CHANGED(27),
         COMPOSITOR_CREATE(28),
         COMPOSITOR_PAUSE(29),
         COMPOSITOR_RESUME(30),
         NATIVE_GESTURE_EVENT(31),
-        IME_KEY_EVENT(32),
         CALL_OBSERVER(33),
         REMOVE_OBSERVER(34),
         LOW_MEMORY(35),
         NETWORK_LINK_CHANGE(36),
         TELEMETRY_HISTOGRAM_ADD(37),
         TELEMETRY_UI_SESSION_START(42),
         TELEMETRY_UI_SESSION_STOP(43),
         TELEMETRY_UI_EVENT(44),
@@ -106,53 +103,16 @@ public class GeckoEvent {
 
         public final int value;
 
         private NativeGeckoEvent(int value) {
             this.value = value;
         }
     }
 
-    // Encapsulation of common IME actions.
-    @JNITarget
-    public enum ImeAction {
-        IME_SYNCHRONIZE(0),
-        IME_REPLACE_TEXT(1),
-        IME_SET_SELECTION(2),
-        IME_ADD_COMPOSITION_RANGE(3),
-        IME_UPDATE_COMPOSITION(4),
-        IME_REMOVE_COMPOSITION(5),
-        IME_ACKNOWLEDGE_FOCUS(6),
-        IME_COMPOSE_TEXT(7);
-
-        public final int value;
-
-        private ImeAction(int value) {
-            this.value = value;
-        }
-    }
-
-    public static final int IME_RANGE_CARETPOSITION = 1;
-    public static final int IME_RANGE_RAWINPUT = 2;
-    public static final int IME_RANGE_SELECTEDRAWTEXT = 3;
-    public static final int IME_RANGE_CONVERTEDTEXT = 4;
-    public static final int IME_RANGE_SELECTEDCONVERTEDTEXT = 5;
-
-    public static final int IME_RANGE_LINE_NONE = 0;
-    public static final int IME_RANGE_LINE_DOTTED = 1;
-    public static final int IME_RANGE_LINE_DASHED = 2;
-    public static final int IME_RANGE_LINE_SOLID = 3;
-    public static final int IME_RANGE_LINE_DOUBLE = 4;
-    public static final int IME_RANGE_LINE_WAVY = 5;
-
-    public static final int IME_RANGE_UNDERLINE = 1;
-    public static final int IME_RANGE_FORECOLOR = 2;
-    public static final int IME_RANGE_BACKCOLOR = 4;
-    public static final int IME_RANGE_LINECOLOR = 8;
-
     public static final int ACTION_MAGNIFY_START = 11;
     public static final int ACTION_MAGNIFY = 12;
     public static final int ACTION_MAGNIFY_END = 13;
 
     public static final int ACTION_GAMEPAD_ADDED = 1;
     public static final int ACTION_GAMEPAD_REMOVED = 2;
 
     public static final int ACTION_GAMEPAD_BUTTON = 1;
@@ -172,44 +132,26 @@ public class GeckoEvent {
     private Rect mRect;
     private double mX;
     private double mY;
     private double mZ;
     private double mW;
 
     private int mMetaState;
     private int mFlags;
-    private int mKeyCode;
-    private int mScanCode;
-    private int mUnicodeChar;
-    private int mBaseUnicodeChar; // mUnicodeChar without meta states applied
-    private int mDOMPrintableKeyValue;
-    private int mRepeatCount;
     private int mCount;
-    private int mStart;
-    private int mEnd;
     private String mCharacters;
     private String mCharactersExtra;
     private String mData;
-    private int mRangeType;
-    private int mRangeStyles;
-    private int mRangeLineStyle;
-    private boolean mRangeBoldLine;
-    private int mRangeForeColor;
-    private int mRangeBackColor;
-    private int mRangeLineColor;
     private Location mLocation;
-    private Address mAddress;
 
     private int     mConnectionType;
     private boolean mIsWifi;
     private int     mDHCPGateway;
 
-    private int mNativeWindow;
-
     private short mScreenOrientation;
     private short mScreenAngle;
 
     private ByteBuffer mBuffer;
 
     private int mWidth;
     private int mHeight;
 
@@ -230,70 +172,31 @@ public class GeckoEvent {
     public static GeckoEvent createAppForegroundingEvent() {
         return GeckoEvent.get(NativeGeckoEvent.APP_FOREGROUNDING);
     }
 
     public static GeckoEvent createNoOpEvent() {
         return GeckoEvent.get(NativeGeckoEvent.NOOP);
     }
 
-    public static GeckoEvent createKeyEvent(KeyEvent k, int action, int metaState) {
-        GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.KEY_EVENT);
-        event.initKeyEvent(k, action, metaState);
-        return event;
-    }
-
     public static GeckoEvent createCompositorCreateEvent(int width, int height) {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.COMPOSITOR_CREATE);
         event.mWidth = width;
         event.mHeight = height;
         return event;
     }
 
     public static GeckoEvent createCompositorPauseEvent() {
         return GeckoEvent.get(NativeGeckoEvent.COMPOSITOR_PAUSE);
     }
 
     public static GeckoEvent createCompositorResumeEvent() {
         return GeckoEvent.get(NativeGeckoEvent.COMPOSITOR_RESUME);
     }
 
-    private void initKeyEvent(KeyEvent k, int action, int metaState) {
-        // Use a separate action argument so we can override the key's original action,
-        // e.g. change ACTION_MULTIPLE to ACTION_DOWN. That way we don't have to allocate
-        // a new key event just to change its action field.
-        mAction = action;
-        mTime = k.getEventTime();
-        // Normally we expect k.getMetaState() to reflect the current meta-state; however,
-        // some software-generated key events may not have k.getMetaState() set, e.g. key
-        // events from Swype. Therefore, it's necessary to combine the key's meta-states
-        // with the meta-states that we keep separately in KeyListener
-        mMetaState = k.getMetaState() | metaState;
-        mFlags = k.getFlags();
-        mKeyCode = k.getKeyCode();
-        mScanCode = k.getScanCode();
-        mUnicodeChar = k.getUnicodeChar(mMetaState);
-        // e.g. for Ctrl+A, Android returns 0 for mUnicodeChar,
-        // but Gecko expects 'a', so we return that in mBaseUnicodeChar
-        mBaseUnicodeChar = k.getUnicodeChar(0);
-        mRepeatCount = k.getRepeatCount();
-        mCharacters = k.getCharacters();
-        if (mUnicodeChar >= ' ') {
-            mDOMPrintableKeyValue = mUnicodeChar;
-        } else {
-            int unmodifiedMetaState =
-                mMetaState & ~(KeyEvent.META_ALT_MASK |
-                               KeyEvent.META_CTRL_MASK |
-                               KeyEvent.META_META_MASK);
-            if (unmodifiedMetaState != mMetaState) {
-                mDOMPrintableKeyValue = k.getUnicodeChar(unmodifiedMetaState);
-            }
-        }
-    }
-
     /**
      * This method is a replacement for the the KeyEvent.isGamepadButton method to be
      * compatible with Build.VERSION.SDK_INT < 12. This is an implementation of the
      * same method isGamepadButton available after SDK 12.
      * @param keyCode int with the key code (Android key constant from KeyEvent).
      * @return True if the keycode is a gamepad button, such as {@link #KEYCODE_BUTTON_A}.
      */
     private static boolean isGamepadButton(int keyCode) {
@@ -580,83 +483,16 @@ public class GeckoEvent {
     }
 
     public static GeckoEvent createLocationEvent(Location l) {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.LOCATION_EVENT);
         event.mLocation = l;
         return event;
     }
 
-    public static GeckoEvent createIMEEvent(ImeAction action) {
-        GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
-        event.mAction = action.value;
-        return event;
-    }
-
-    public static GeckoEvent createIMEKeyEvent(KeyEvent k) {
-        GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_KEY_EVENT);
-        event.initKeyEvent(k, k.getAction(), 0);
-        return event;
-    }
-
-    public static GeckoEvent createIMEReplaceEvent(int start, int end, String text) {
-        return createIMETextEvent(false, start, end, text);
-    }
-
-    public static GeckoEvent createIMEComposeEvent(int start, int end, String text) {
-        return createIMETextEvent(true, start, end, text);
-    }
-
-    private static GeckoEvent createIMETextEvent(boolean compose, int start, int end, String text) {
-        GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
-        event.mAction = (compose ? ImeAction.IME_COMPOSE_TEXT : ImeAction.IME_REPLACE_TEXT).value;
-        event.mStart = start;
-        event.mEnd = end;
-        event.mCharacters = text;
-        return event;
-    }
-
-    public static GeckoEvent createIMESelectEvent(int start, int end) {
-        GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
-        event.mAction = ImeAction.IME_SET_SELECTION.value;
-        event.mStart = start;
-        event.mEnd = end;
-        return event;
-    }
-
-    public static GeckoEvent createIMECompositionEvent(int start, int end) {
-        GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
-        event.mAction = ImeAction.IME_UPDATE_COMPOSITION.value;
-        event.mStart = start;
-        event.mEnd = end;
-        return event;
-    }
-
-    public static GeckoEvent createIMERangeEvent(int start,
-                                                 int end, int rangeType,
-                                                 int rangeStyles,
-                                                 int rangeLineStyle,
-                                                 boolean rangeBoldLine,
-                                                 int rangeForeColor,
-                                                 int rangeBackColor,
-                                                 int rangeLineColor) {
-        GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
-        event.mAction = ImeAction.IME_ADD_COMPOSITION_RANGE.value;
-        event.mStart = start;
-        event.mEnd = end;
-        event.mRangeType = rangeType;
-        event.mRangeStyles = rangeStyles;
-        event.mRangeLineStyle = rangeLineStyle;
-        event.mRangeBoldLine = rangeBoldLine;
-        event.mRangeForeColor = rangeForeColor;
-        event.mRangeBackColor = rangeBackColor;
-        event.mRangeLineColor = rangeLineColor;
-        return event;
-    }
-
     public static GeckoEvent createSizeChangedEvent(int w, int h, int screenw, int screenh) {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.SIZE_CHANGED);
         event.mPoints = new Point[2];
         event.mPoints[0] = new Point(w, h);
         event.mPoints[1] = new Point(screenw, screenh);
         return event;
     }
 
--- a/mobile/android/base/GeckoInputConnection.java
+++ b/mobile/android/base/GeckoInputConnection.java
@@ -135,22 +135,24 @@ class GeckoInputConnection
             if (icHandler.getLooper() == uiHandler.getLooper()) {
                 // IC thread is UI thread; safe to run directly
                 runnable.run();
                 return;
             }
             runOnIcThread(icHandler, runnable);
         }
 
-        public void sendEventFromUiThread(final Handler uiHandler,
-                                          final GeckoEditableClient client,
-                                          final GeckoEvent event) {
+        public void sendKeyEventFromUiThread(final Handler uiHandler,
+                                             final GeckoEditableClient client,
+                                             final KeyEvent event,
+                                             final int action,
+                                             final int metaState) {
             runOnIcThread(uiHandler, client, new Runnable() {
                 @Override public void run() {
-                    client.sendEvent(event);
+                    client.sendKeyEvent(event, action, metaState);
                 }
             });
         }
 
         public Editable getEditableForUiThread(final Handler uiHandler,
                                                final GeckoEditableClient client) {
             if (DEBUG) {
                 ThreadUtils.assertOnThread(uiHandler.getLooper().getThread(), AssertBehavior.THROW);
@@ -824,36 +826,40 @@ class GeckoInputConnection
             return false;
         }
         final int action = down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
         event = translateKey(keyCode, event);
         keyCode = event.getKeyCode();
 
         View view = getView();
         if (view == null) {
-            InputThreadUtils.sInstance.sendEventFromUiThread(ThreadUtils.getUiHandler(),
-                mEditableClient, GeckoEvent.createKeyEvent(event, action, 0));
+            InputThreadUtils.sInstance.sendKeyEventFromUiThread(
+                    ThreadUtils.getUiHandler(), mEditableClient, event, action, /* metaState */ 0);
             return true;
         }
 
         // KeyListener returns true if it handled the event for us. KeyListener is only
         // safe to use on the UI thread; therefore we need to pass a proxy Editable to it
         KeyListener keyListener = TextKeyListener.getInstance();
         Handler uiHandler = view.getRootView().getHandler();
         Editable uiEditable = InputThreadUtils.sInstance.
             getEditableForUiThread(uiHandler, mEditableClient);
         boolean skip = shouldSkipKeyListener(keyCode, event);
+
         if (down) {
             mEditableClient.setSuppressKeyUp(true);
         }
         if (skip ||
             (down && !keyListener.onKeyDown(view, uiEditable, keyCode, event)) ||
             (!down && !keyListener.onKeyUp(view, uiEditable, keyCode, event))) {
-            InputThreadUtils.sInstance.sendEventFromUiThread(uiHandler, mEditableClient,
-                GeckoEvent.createKeyEvent(event, action, TextKeyListener.getMetaState(uiEditable)));
+
+            InputThreadUtils.sInstance.sendKeyEventFromUiThread(
+                    uiHandler, mEditableClient,
+                    event, action, TextKeyListener.getMetaState(uiEditable));
+
             if (skip && down) {
                 // Usually, the down key listener call above adjusts meta states for us.
                 // However, if we skip that call above, we have to manually adjust meta
                 // states so the meta states remain consistent
                 TextKeyListener.adjustMetaAfterKeypress(uiEditable);
             }
         }
         if (down) {
--- a/mobile/android/base/GeckoView.java
+++ b/mobile/android/base/GeckoView.java
@@ -113,16 +113,17 @@ public class GeckoView extends LayerView
         }
     };
 
     @WrapForJNI
     private static final class Window extends JNIObject {
         static native void open(Window instance, GeckoView view, int width, int height);
         static native void setLayerClient(Object client);
         @Override protected native void disposeNative();
+        native void close();
     }
 
     private final Window window = new Window();
 
     public GeckoView(Context context) {
         super(context);
         init(context, null, true);
     }
@@ -233,19 +234,26 @@ public class GeckoView extends LayerView
         }
     }
 
     @Override
     public void onDetachedFromWindow()
     {
         super.onDetachedFromWindow();
 
+        // FIXME: because we don't support separate nsWindow for each GeckoView
+        // yet, we have to keep this window around in case another GeckoView
+        // wants to attach. So don't call window.close() for now.
+
         if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
+            // window.close();
             window.disposeNative();
         } else {
+            // GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
+            //        window, "close");
             GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
                     window, "disposeNative");
         }
     }
 
     /* package */ void setInputConnectionListener(final InputConnectionListener icl) {
         mInputConnectionListener = icl;
     }
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -26,16 +26,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
                    nsContentPolicyType aContentPolicyType)
   : mLoadingPrincipal(aLoadingContext ?
                         aLoadingContext->NodePrincipal() : aLoadingPrincipal)
   , mTriggeringPrincipal(aTriggeringPrincipal ?
                            aTriggeringPrincipal : mLoadingPrincipal.get())
   , mLoadingContext(do_GetWeakReference(aLoadingContext))
   , mSecurityFlags(aSecurityFlags)
   , mInternalContentPolicyType(aContentPolicyType)
+  , mTainting(LoadTainting::Basic)
   , mUpgradeInsecureRequests(false)
   , mInnerWindowID(0)
   , mOuterWindowID(0)
   , mParentOuterWindowID(0)
   , mEnforceSecurity(false)
   , mInitialSecurityCheckDone(false)
 {
   MOZ_ASSERT(mLoadingPrincipal);
@@ -387,9 +388,28 @@ LoadInfo::GetRedirectChain(JSContext* aC
 }
 
 const nsTArray<nsCOMPtr<nsIPrincipal>>&
 LoadInfo::RedirectChain()
 {
   return mRedirectChain;
 }
 
+NS_IMETHODIMP
+LoadInfo::GetTainting(uint32_t* aTaintingOut)
+{
+  MOZ_ASSERT(aTaintingOut);
+  *aTaintingOut = static_cast<uint32_t>(mTainting);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::MaybeIncreaseTainting(uint32_t aTainting)
+{
+  NS_ENSURE_ARG(aTainting <= TAINTING_OPAQUE);
+  LoadTainting tainting = static_cast<LoadTainting>(aTainting);
+  if (tainting > mTainting) {
+    mTainting = tainting;
+  }
+  return NS_OK;
+}
+
 } // namespace mozilla
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -87,16 +87,17 @@ private:
   void SetWithCredentialsSecFlag();
   friend class ::nsXMLHttpRequest;
 
   nsCOMPtr<nsIPrincipal>           mLoadingPrincipal;
   nsCOMPtr<nsIPrincipal>           mTriggeringPrincipal;
   nsWeakPtr                        mLoadingContext;
   nsSecurityFlags                  mSecurityFlags;
   nsContentPolicyType              mInternalContentPolicyType;
+  LoadTainting                     mTainting;
   bool                             mUpgradeInsecureRequests;
   uint64_t                         mInnerWindowID;
   uint64_t                         mOuterWindowID;
   uint64_t                         mParentOuterWindowID;
   bool                             mEnforceSecurity;
   bool                             mInitialSecurityCheckDone;
   OriginAttributes                 mOriginAttributes;
   nsTArray<nsCOMPtr<nsIPrincipal>> mRedirectChain;
new file mode 100644
--- /dev/null
+++ b/netwerk/base/LoadTainting.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LoadTainting_h
+#define mozilla_LoadTainting_h
+
+namespace mozilla {
+
+// Define an enumeration to reflect the concept of response tainting from the
+// the fetch spec:
+//
+//  https://fetch.spec.whatwg.org/#concept-request-response-tainting
+//
+// Roughly the tainting means:
+//
+//  * Basic: the request resulted in a same-origin or non-http load
+//  * CORS: the request resulted in a cross-origin load with CORS headers
+//  * Opaque: the request resulted in a cross-origin load without CORS headers
+//
+// The enumeration is purposefully designed such that more restrictive tainting
+// corresponds to a higher integral value.
+//
+// NOTE: Checking the tainting is not currently adequate.  You *must* still
+//       check the final URL and CORS mode on the channel.
+//
+// These values are currently only set on the channel LoadInfo when the request
+// was initiated through fetch() or when a service worker interception occurs.
+// In the future we should set the tainting value within necko so that it is
+// consistently applied.  Once that is done consumers can replace checks against
+// the final URL and CORS mode with checks against tainting.
+enum class LoadTainting : uint8_t
+{
+  Basic = 0,
+  CORS = 1,
+  Opaque = 2
+};
+
+} // namespace mozilla
+
+#endif // mozilla_LoadTainting_h
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -163,16 +163,17 @@ EXPORTS += [
     'nsTemporaryFileInputStream.h',
     'nsURIHashKey.h',
     'nsURLHelper.h',
     'nsURLParsers.h',
 ]
 
 EXPORTS.mozilla += [
     'LoadInfo.h',
+    'LoadTainting.h',
 ]
 
 EXPORTS.mozilla.net += [
     'CaptivePortalService.h',
     'ChannelDiverterChild.h',
     'ChannelDiverterParent.h',
     'Dashboard.h',
     'DashboardTypes.h',
--- a/netwerk/base/nsILoadInfo.idl
+++ b/netwerk/base/nsILoadInfo.idl
@@ -9,28 +9,29 @@
 
 interface nsIDOMDocument;
 interface nsINode;
 interface nsIPrincipal;
 
 %{C++
 #include "nsTArray.h"
 #include "mozilla/BasePrincipal.h"
+#include "mozilla/LoadTainting.h"
 %}
 
 [ref] native const_nsIPrincipalArray(const nsTArray<nsCOMPtr<nsIPrincipal>>);
 native OriginAttributes(mozilla::OriginAttributes);
 [ref] native const_OriginAttributesRef(const mozilla::OriginAttributes);
 
 typedef unsigned long nsSecurityFlags;
 
 /**
  * An nsILoadOwner represents per-load information about who started the load.
  */
-[scriptable, builtinclass, uuid(51313616-163f-4be1-aba0-e3d471b7f7a2)]
+[scriptable, builtinclass, uuid(2ab4c77c-78e6-11e5-8bcf-feff819cdc9f)]
 interface nsILoadInfo : nsISupports
 {
   /**
    * No special security flags:
    */
   const unsigned long SEC_NORMAL = 0;
 
   /**
@@ -438,9 +439,59 @@ interface nsILoadInfo : nsISupports
 
   /**
    * A C++-friendly version of redirectChain.
    * Please note that this array has the same lifetime as the
    * loadInfo object - use with caution!
    */
   [noscript, notxpcom, nostdcall, binaryname(RedirectChain)]
   const_nsIPrincipalArray binaryRedirectChain();
+
+  /**
+  * Constants reflecting the channel tainting.  These are mainly defined here
+  * for script.  Internal C++ code should use the enum defined in LoadTainting.h.
+  * See LoadTainting.h for documentation.
+  */
+  const unsigned long TAINTING_BASIC = 0;
+  const unsigned long TAINTING_CORS = 1;
+  const unsigned long TAINTING_OPAQUE = 2;
+
+  /**
+   * Determine the associated channel's current tainting.  Note, this can
+   * change due to a service worker intercept, so it should be checked after
+   * OnStartRequest() fires.
+   */
+  readonly attribute unsigned long tainting;
+
+  /**
+   * Note a new tainting level and possibly increase the current tainting
+   * to match.  If the tainting level is already greater than the given
+   * value, then there is no effect.  It is not possible to reduce the tainting
+   * level on an existing channel/loadinfo.
+   */
+  void maybeIncreaseTainting(in unsigned long aTainting);
+
+  /**
+   * Various helper code to provide more convenient C++ access to the tainting
+   * attribute and maybeIncreaseTainting().
+   */
+%{C++
+  static_assert(TAINTING_BASIC == static_cast<uint32_t>(mozilla::LoadTainting::Basic),
+                "basic tainting enums should match");
+  static_assert(TAINTING_CORS == static_cast<uint32_t>(mozilla::LoadTainting::CORS),
+                "cors tainting enums should match");
+  static_assert(TAINTING_OPAQUE == static_cast<uint32_t>(mozilla::LoadTainting::Opaque),
+                "opaque tainting enums should match");
+
+  mozilla::LoadTainting GetTainting()
+  {
+    uint32_t tainting = TAINTING_BASIC;
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(GetTainting(&tainting)));
+    return static_cast<mozilla::LoadTainting>(tainting);
+  }
+
+  void MaybeIncreaseTainting(mozilla::LoadTainting aTainting)
+  {
+    uint32_t tainting = static_cast<uint32_t>(aTainting);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(MaybeIncreaseTainting(tainting)));
+  }
+%}
 };
--- a/security/manager/ssl/nsCertPicker.cpp
+++ b/security/manager/ssl/nsCertPicker.cpp
@@ -24,28 +24,38 @@ using namespace mozilla;
 NS_IMPL_ISUPPORTS(nsCertPicker, nsIUserCertPicker)
 
 nsCertPicker::nsCertPicker()
 {
 }
 
 nsCertPicker::~nsCertPicker()
 {
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return;
+  }
+
+  shutdown(calledFromObject);
 }
 
 NS_IMETHODIMP nsCertPicker::PickByUsage(nsIInterfaceRequestor *ctx, 
                                         const char16_t *selectedNickname, 
                                         int32_t certUsage, 
                                         bool allowInvalid, 
                                         bool allowDuplicateNicknames, 
                                         const nsAString &emailAddress,
                                         bool *canceled, 
                                         nsIX509Cert **_retval)
 {
   nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   int32_t selectedIndex = -1;
   bool selectionFound = false;
   char16_t **certNicknameList = nullptr;
   char16_t **certDetailsList = nullptr;
   CERTCertListNode* node = nullptr;
   nsresult rv = NS_OK;
 
   {
@@ -153,26 +163,20 @@ NS_IMETHODIMP nsCertPicker::PickByUsage(
 
   if (CertsToUse) {
     nsICertPickDialogs *dialogs = nullptr;
     rv = getNSSDialogs((void**)&dialogs, 
       NS_GET_IID(nsICertPickDialogs), 
       NS_CERTPICKDIALOGS_CONTRACTID);
 
     if (NS_SUCCEEDED(rv)) {
-      nsPSMUITracker tracker;
-      if (tracker.isUIForbidden()) {
-        rv = NS_ERROR_NOT_AVAILABLE;
-      }
-      else {
-        /* Throw up the cert picker dialog and get back the index of the selected cert */
-        rv = dialogs->PickCertificate(ctx,
-          (const char16_t**)certNicknameList, (const char16_t**)certDetailsList,
-          CertsToUse, &selectedIndex, canceled);
-      }
+      // Show the cert picker dialog and get the index of the selected cert.
+      rv = dialogs->PickCertificate(ctx, (const char16_t**)certNicknameList,
+                                    (const char16_t**)certDetailsList,
+                                    CertsToUse, &selectedIndex, canceled);
 
       NS_RELEASE(dialogs);
     }
   }
 
   int32_t i;
   for (i = 0; i < CertsToUse; ++i) {
     free(certNicknameList[i]);
--- a/security/manager/ssl/nsCertPicker.h
+++ b/security/manager/ssl/nsCertPicker.h
@@ -2,25 +2,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _NSCERTPICKER_H_
 #define _NSCERTPICKER_H_
 
 #include "nsIUserCertPicker.h"
+#include "nsNSSShutDown.h"
 
 #define NS_CERT_PICKER_CID \
   { 0x735959a1, 0xaf01, 0x447e, { 0xb0, 0x2d, 0x56, 0xe9, 0x68, 0xfa, 0x52, 0xb4 } }
 
 class nsCertPicker : public nsIUserCertPicker
+                   , public nsNSSShutDownObject
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIUSERCERTPICKER
 
   nsCertPicker();
 
+  // Nothing to actually release.
+  virtual void virtualDestroyNSSReference() override {}
+
 protected:
   virtual ~nsCertPicker();
 };
 
 #endif //_NSCERTPICKER_H_
--- a/security/manager/ssl/nsKeygenHandler.cpp
+++ b/security/manager/ssl/nsKeygenHandler.cpp
@@ -189,21 +189,26 @@ decode_ec_params(const char *curve)
     return ecparams;
 }
 
 NS_IMPL_ISUPPORTS(nsKeygenFormProcessor, nsIFormProcessor)
 
 nsKeygenFormProcessor::nsKeygenFormProcessor()
 { 
    m_ctx = new PipUIContext();
-
 } 
 
 nsKeygenFormProcessor::~nsKeygenFormProcessor()
 {
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return;
+  }
+
+  shutdown(calledFromObject);
 }
 
 nsresult
 nsKeygenFormProcessor::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
 {
   if (GeckoProcessType_Content == XRE_GetProcessType()) {
     nsCOMPtr<nsISupports> contentProcessor = new nsKeygenFormProcessorContent();
     return contentProcessor->QueryInterface(aIID, aResult);
@@ -241,20 +246,24 @@ nsKeygenFormProcessor::Init()
   mSECKeySizeChoiceList[1].size = 1024;
 
   return NS_OK;
 }
 
 nsresult
 nsKeygenFormProcessor::GetSlot(uint32_t aMechanism, PK11SlotInfo** aSlot)
 {
-  return GetSlotWithMechanism(aMechanism,m_ctx,aSlot);
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return GetSlotWithMechanism(aMechanism, m_ctx, aSlot, locker);
 }
 
-
 uint32_t MapGenMechToAlgoMech(uint32_t mechanism)
 {
     uint32_t searchMech;
 
     /* We are interested in slots based on the ability to perform
        a given algorithm, not on their ability to generate keys usable
        by that algorithm. Therefore, map keygen-specific mechanism tags
        to tags for the corresponding crypto algorithm. */
@@ -279,21 +288,19 @@ uint32_t MapGenMechToAlgoMech(uint32_t m
         searchMech = mechanism;
         break;
     }
     return searchMech;
 }
 
 
 nsresult
-GetSlotWithMechanism(uint32_t aMechanism, 
-                     nsIInterfaceRequestor *m_ctx,
-                     PK11SlotInfo** aSlot)
+GetSlotWithMechanism(uint32_t aMechanism, nsIInterfaceRequestor* m_ctx,
+                     PK11SlotInfo** aSlot, nsNSSShutDownPreventionLock& /*proofOfLock*/)
 {
-    nsNSSShutDownPreventionLock locker;
     PK11SlotList * slotList = nullptr;
     char16_t** tokenNameList = nullptr;
     nsITokenDialogs * dialogs;
     char16_t *unicodeTokenChosen;
     PK11SlotListElement *slotElement, *tmpSlot;
     uint32_t numSlots = 0, i = 0;
     bool canceled;
     nsresult rv = NS_OK;
@@ -344,27 +351,21 @@ GetSlotWithMechanism(uint32_t aMechanism
 
 		/* Throw up the token list dialog and get back the token */
 		rv = getNSSDialogs((void**)&dialogs,
 			               NS_GET_IID(nsITokenDialogs),
                      NS_TOKENDIALOGS_CONTRACTID);
 
 		if (NS_FAILED(rv)) goto loser;
 
-    {
-      nsPSMUITracker tracker;
-      if (!tokenNameList || !*tokenNameList) {
-          rv = NS_ERROR_OUT_OF_MEMORY;
-      }
-      else if (tracker.isUIForbidden()) {
-        rv = NS_ERROR_NOT_AVAILABLE;
-      }
-      else {
-        rv = dialogs->ChooseToken(m_ctx, (const char16_t**)tokenNameList, numSlots, &unicodeTokenChosen, &canceled);
-      }
+    if (!tokenNameList || !*tokenNameList) {
+        rv = NS_ERROR_OUT_OF_MEMORY;
+    } else {
+        rv = dialogs->ChooseToken(m_ctx, (const char16_t**)tokenNameList,
+                                  numSlots, &unicodeTokenChosen, &canceled);
     }
 		NS_RELEASE(dialogs);
 		if (NS_FAILED(rv)) goto loser;
 
 		if (canceled) { rv = NS_ERROR_NOT_AVAILABLE; goto loser; }
 
         // Get the slot //
         slotElement = PK11_GetFirstSafe(slotList);
@@ -447,16 +448,20 @@ GatherKeygenTelemetry(uint32_t keyGenMec
 nsresult
 nsKeygenFormProcessor::GetPublicKey(const nsAString& aValue,
                                     const nsAString& aChallenge,
                                     const nsAFlatString& aKeyType,
                                     nsAString& aOutPublicKey,
                                     const nsAString& aKeyParams)
 {
     nsNSSShutDownPreventionLock locker;
+    if (isAlreadyShutDown()) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+
     nsresult rv = NS_ERROR_FAILURE;
     char *keystring = nullptr;
     char *keyparamsString = nullptr;
     uint32_t keyGenMechanism;
     PK11SlotInfo *slot = nullptr;
     PK11RSAGenParams rsaParams;
     SECOidTag algTag;
     int keysize = 0;
@@ -564,17 +569,17 @@ nsKeygenFormProcessor::GetPublicKey(cons
              */
             algTag = SEC_OID_ANSIX962_ECDSA_SIGNATURE_WITH_SHA1_DIGEST;
             break;
       default:
           goto loser;
       }
 
     /* Make sure token is initialized. */
-    rv = setPassword(slot, m_ctx);
+    rv = setPassword(slot, m_ctx, locker);
     if (NS_FAILED(rv))
         goto loser;
 
     sec_rv = PK11_Authenticate(slot, true, m_ctx);
     if (sec_rv != SECSuccess) {
         goto loser;
     }
 
@@ -593,28 +598,20 @@ nsKeygenFormProcessor::GetPublicKey(cons
                                                    &publicKey, attrFlags, m_ctx);
     } else {
         KeygenRunnable->SetParams( slot, attrFlags, nullptr, 0,
                                    keyGenMechanism, params, m_ctx );
 
         runnable = do_QueryInterface(KeygenRunnable);
         
         if (runnable) {
-            {
-              nsPSMUITracker tracker;
-              if (tracker.isUIForbidden()) {
-                rv = NS_ERROR_NOT_AVAILABLE;
-              }
-              else {
-                rv = dialogs->DisplayGeneratingKeypairInfo(m_ctx, runnable);
-                // We call join on the thread, 
-                // so we can be sure that no simultaneous access to the passed parameters will happen.
-                KeygenRunnable->Join();
-              }
-            }
+            rv = dialogs->DisplayGeneratingKeypairInfo(m_ctx, runnable);
+            // We call join on the thread so we can be sure that no
+            // simultaneous access to the passed parameters will happen.
+            KeygenRunnable->Join();
 
             NS_RELEASE(dialogs);
             if (NS_SUCCEEDED(rv)) {
                 PK11SlotInfo *used_slot = nullptr;
                 rv = KeygenRunnable->ConsumeResult(&used_slot, &privateKey, &publicKey);
                 if (NS_SUCCEEDED(rv) && used_slot) {
                   PK11_FreeSlot(used_slot);
                 }
--- a/security/manager/ssl/nsKeygenHandler.h
+++ b/security/manager/ssl/nsKeygenHandler.h
@@ -4,27 +4,31 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _NSKEYGENHANDLER_H_
 #define _NSKEYGENHANDLER_H_
 // Form Processor
 #include "nsIFormProcessor.h"
 #include "nsTArray.h"
+#include "nsNSSShutDown.h"
 
 nsresult GetSlotWithMechanism(uint32_t mechanism,
-                              nsIInterfaceRequestor *ctx,
-                              PK11SlotInfo **retSlot);
+                              nsIInterfaceRequestor* ctx,
+                              PK11SlotInfo** retSlot,
+                              nsNSSShutDownPreventionLock& /*proofOfLock*/);
 
 #define DEFAULT_RSA_KEYGEN_PE 65537L
 #define DEFAULT_RSA_KEYGEN_ALG SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION
 
 SECKEYECParams *decode_ec_params(const char *curve);
 
-class nsKeygenFormProcessor : public nsIFormProcessor {
+class nsKeygenFormProcessor : public nsIFormProcessor
+                            , public nsNSSShutDownObject
+{
 public:
   nsKeygenFormProcessor();
   nsresult Init();
 
   virtual nsresult ProcessValue(nsIDOMHTMLElement* aElement,
                                 const nsAString& aName,
                                 nsAString& aValue) override;
 
@@ -41,16 +45,19 @@ public:
 
   static nsresult Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult);
 
   static void ExtractParams(nsIDOMHTMLElement* aElement,
                             nsAString& challengeValue,
                             nsAString& keyTypeValue,
                             nsAString& keyParamsValue);
 
+  // Nothing to release.
+  virtual void virtualDestroyNSSReference() override {}
+
 protected:
   virtual ~nsKeygenFormProcessor();
 
   nsresult GetPublicKey(const nsAString& aValue, const nsAString& aChallenge,
                         const nsAFlatString& akeyType, nsAString& aOutPublicKey,
                         const nsAString& aPqg);
   nsresult GetSlot(uint32_t aMechanism, PK11SlotInfo** aSlot);
 private:
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -763,48 +763,62 @@ ShowProtectedAuthPrompt(PK11SlotInfo* sl
 
     NS_RELEASE(dialogs);
   }
 
   return protAuthRetVal;
 }
 
 class PK11PasswordPromptRunnable : public SyncRunnableBase
+                                 , public nsNSSShutDownObject
 {
 public:
   PK11PasswordPromptRunnable(PK11SlotInfo* slot, 
                              nsIInterfaceRequestor* ir)
     : mResult(nullptr),
       mSlot(slot),
       mIR(ir)
   {
   }
+  virtual ~PK11PasswordPromptRunnable();
+
+  // This doesn't own the PK11SlotInfo or any other NSS objects, so there's
+  // nothing to release.
+  virtual void virtualDestroyNSSReference() override {}
   char * mResult; // out
-  virtual void RunOnTargetThread();
+  virtual void RunOnTargetThread() override;
 private:
   PK11SlotInfo* const mSlot; // in
   nsIInterfaceRequestor* const mIR; // in
 };
 
+PK11PasswordPromptRunnable::~PK11PasswordPromptRunnable()
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return;
+  }
+
+  shutdown(calledFromObject);
+}
+
 void PK11PasswordPromptRunnable::RunOnTargetThread()
 {
   static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);
 
   nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return;
+  }
+
   nsresult rv = NS_OK;
   char16_t *password = nullptr;
   bool value = false;
   nsCOMPtr<nsIPrompt> prompt;
 
-  /* TODO: Retry should generate a different dialog message */
-/*
-  if (retry)
-    return nullptr;
-*/
-
   if (!mIR)
   {
     nsNSSComponent::GetNewPrompter(getter_AddRefs(prompt));
   }
   else
   {
     prompt = do_GetInterface(mIR);
     NS_ASSERTION(prompt, "callbacks does not implement nsIPrompt");
@@ -830,29 +844,21 @@ void PK11PasswordPromptRunnable::RunOnTa
   rv = nssComponent->PIPBundleFormatStringFromName("CertPassPrompt",
                                       formatStrings, 1,
                                       promptString);
   free(const_cast<char16_t*>(formatStrings[0]));
 
   if (NS_FAILED(rv))
     return;
 
-  {
-    nsPSMUITracker tracker;
-    if (tracker.isUIForbidden()) {
-      rv = NS_ERROR_NOT_AVAILABLE;
-    }
-    else {
-      // Although the exact value is ignored, we must not pass invalid
-      // bool values through XPConnect.
-      bool checkState = false;
-      rv = prompt->PromptPassword(nullptr, promptString.get(),
-                                  &password, nullptr, &checkState, &value);
-    }
-  }
+  // Although the exact value is ignored, we must not pass invalid bool values
+  // through XPConnect.
+  bool checkState = false;
+  rv = prompt->PromptPassword(nullptr, promptString.get(), &password, nullptr,
+                              &checkState, &value);
   
   if (NS_SUCCEEDED(rv) && value) {
     mResult = ToNewUTF8String(nsDependentString(password));
     free(password);
   }
 }
 
 char*
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -815,38 +815,36 @@ void nsNSSCertificateDB::DisplayCertific
 {
   static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);
 
   if (!NS_IsMainThread()) {
     NS_ERROR("nsNSSCertificateDB::DisplayCertificateAlert called off the main thread");
     return;
   }
 
-  nsPSMUITracker tracker;
-  if (!tracker.isUIForbidden()) {
+  nsCOMPtr<nsIInterfaceRequestor> my_ctx = ctx;
+  if (!my_ctx) {
+    my_ctx = new PipUIContext();
+  }
 
-    nsCOMPtr<nsIInterfaceRequestor> my_ctx = ctx;
-    if (!my_ctx)
-      my_ctx = new PipUIContext();
-
-    // This shall be replaced by embedding ovverridable prompts
-    // as discussed in bug 310446, and should make use of certToShow.
+  // This shall be replaced by embedding ovverridable prompts
+  // as discussed in bug 310446, and should make use of certToShow.
 
-    nsresult rv;
-    nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID, &rv));
-    if (NS_SUCCEEDED(rv)) {
-      nsAutoString tmpMessage;
-      nssComponent->GetPIPNSSBundleString(stringID, tmpMessage);
+  nsresult rv;
+  nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID, &rv));
+  if (NS_SUCCEEDED(rv)) {
+    nsAutoString tmpMessage;
+    nssComponent->GetPIPNSSBundleString(stringID, tmpMessage);
 
-      nsCOMPtr<nsIPrompt> prompt (do_GetInterface(my_ctx));
-      if (!prompt)
-        return;
-    
-      prompt->Alert(nullptr, tmpMessage.get());
+    nsCOMPtr<nsIPrompt> prompt (do_GetInterface(my_ctx));
+    if (!prompt) {
+      return;
     }
+
+    prompt->Alert(nullptr, tmpMessage.get());
   }
 }
 
 
 NS_IMETHODIMP 
 nsNSSCertificateDB::ImportUserCertificate(uint8_t *data, uint32_t length, nsIInterfaceRequestor *ctx)
 {
   if (!NS_IsMainThread()) {
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1266,44 +1266,37 @@ nsNSSComponent::RandomUpdate(void* entro
   PK11_RandomUpdate(entropy, bufLen);
   return NS_OK;
 }
 
 static const char* const PROFILE_CHANGE_NET_TEARDOWN_TOPIC
   = "profile-change-net-teardown";
 static const char* const PROFILE_CHANGE_NET_RESTORE_TOPIC
   = "profile-change-net-restore";
-static const char* const PROFILE_CHANGE_TEARDOWN_TOPIC
-  = "profile-change-teardown";
 static const char* const PROFILE_BEFORE_CHANGE_TOPIC = "profile-before-change";
 static const char* const PROFILE_DO_CHANGE_TOPIC = "profile-do-change";
 
 NS_IMETHODIMP
 nsNSSComponent::Observe(nsISupports* aSubject, const char* aTopic,
                         const char16_t* someData)
 {
-  if (nsCRT::strcmp(aTopic, PROFILE_CHANGE_TEARDOWN_TOPIC) == 0) {
-    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("in PSM code, receiving change-teardown\n"));
-    DoProfileChangeTeardown(aSubject);
-  }
-  else if (nsCRT::strcmp(aTopic, PROFILE_BEFORE_CHANGE_TOPIC) == 0) {
+  if (nsCRT::strcmp(aTopic, PROFILE_BEFORE_CHANGE_TOPIC) == 0) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("receiving profile change topic\n"));
     DoProfileBeforeChange(aSubject);
   }
   else if (nsCRT::strcmp(aTopic, PROFILE_DO_CHANGE_TOPIC) == 0) {
     if (someData && NS_LITERAL_STRING("startup").Equals(someData)) {
       // The application is initializing against a known profile directory for
       // the first time during process execution.
       // However, earlier code execution might have already triggered NSS init.
       // We must ensure that NSS gets shut down prior to any attempt to init
       // it again. We use the same cleanup functionality used when switching
       // profiles. The order of function calls must correspond to the order
       // of notifications sent by Profile Manager (nsProfile).
       DoProfileChangeNetTeardown();
-      DoProfileChangeTeardown(aSubject);
       DoProfileBeforeChange(aSubject);
       DoProfileChangeNetRestore();
     }
 
     bool needsInit = true;
 
     {
       MutexAutoLock lock(mutex);
@@ -1416,23 +1409,17 @@ nsNSSComponent::GetNewPrompter(nsIPrompt
 }
 
 /*static*/ nsresult
 nsNSSComponent::ShowAlertWithConstructedString(const nsString& message)
 {
   nsCOMPtr<nsIPrompt> prompter;
   nsresult rv = GetNewPrompter(getter_AddRefs(prompter));
   if (prompter) {
-    nsPSMUITracker tracker;
-    if (tracker.isUIForbidden()) {
-      NS_WARNING("Suppressing alert because PSM UI is forbidden");
-      rv = NS_ERROR_UNEXPECTED;
-    } else {
-      rv = prompter->Alert(nullptr, message.get());
-    }
+    rv = prompter->Alert(nullptr, message.get());
   }
   return rv;
 }
 
 NS_IMETHODIMP
 nsNSSComponent::ShowAlertFromStringBundle(const char* messageID)
 {
   nsString message;
@@ -1477,17 +1464,16 @@ nsNSSComponent::RegisterObservers()
     // Once we are loaded, don't allow being removed from memory.
     // This makes sense, as initializing NSS is expensive.
 
     // By using false for parameter ownsWeak in AddObserver,
     // we make sure that we won't get unloaded until the application shuts down.
 
     observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
 
-    observerService->AddObserver(this, PROFILE_CHANGE_TEARDOWN_TOPIC, false);
     observerService->AddObserver(this, PROFILE_BEFORE_CHANGE_TOPIC, false);
     observerService->AddObserver(this, PROFILE_DO_CHANGE_TOPIC, false);
     observerService->AddObserver(this, PROFILE_CHANGE_NET_TEARDOWN_TOPIC, false);
     observerService->AddObserver(this, PROFILE_CHANGE_NET_RESTORE_TOPIC, false);
   }
   return NS_OK;
 }
 
@@ -1500,17 +1486,16 @@ nsNSSComponent::DeregisterObservers()
   nsCOMPtr<nsIObserverService> observerService(do_GetService("@mozilla.org/observer-service;1"));
   NS_ASSERTION(observerService, "could not get observer service");
   if (observerService) {
     mObserversRegistered = false;
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent: removing observers\n"));
 
     observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
 
-    observerService->RemoveObserver(this, PROFILE_CHANGE_TEARDOWN_TOPIC);
     observerService->RemoveObserver(this, PROFILE_BEFORE_CHANGE_TOPIC);
     observerService->RemoveObserver(this, PROFILE_DO_CHANGE_TOPIC);
     observerService->RemoveObserver(this, PROFILE_CHANGE_NET_TEARDOWN_TOPIC);
     observerService->RemoveObserver(this, PROFILE_CHANGE_NET_RESTORE_TOPIC);
   }
   return NS_OK;
 }
 
@@ -1518,22 +1503,16 @@ void
 nsNSSComponent::DoProfileChangeNetTeardown()
 {
   if (mCertVerificationThread)
     mCertVerificationThread->requestExit();
   mIsNetworkDown = true;
 }
 
 void
-nsNSSComponent::DoProfileChangeTeardown(nsISupports* aSubject)
-{
-  mShutdownObjectList->ifPossibleDisallowUI();
-}
-
-void
 nsNSSComponent::DoProfileBeforeChange(nsISupports* aSubject)
 {
   NS_ASSERTION(mIsNetworkDown, "nsNSSComponent relies on profile manager to wait for synchronous shutdown of all network activity");
 
   bool needsCleanup = true;
 
   {
     MutexAutoLock lock(mutex);
@@ -1544,17 +1523,16 @@ nsNSSComponent::DoProfileBeforeChange(ns
       // multiple times.
       needsCleanup = false;
     }
   }
 
   if (needsCleanup) {
     ShutdownNSS();
   }
-  mShutdownObjectList->allowUI();
 }
 
 void
 nsNSSComponent::DoProfileChangeNetRestore()
 {
   // XXX this doesn't work well, since nothing expects null pointers
   deleteBackgroundThreads();
   createBackgroundThreads();
@@ -1644,43 +1622,34 @@ getNSSDialogs(void** _result, REFNSIID a
   }
 
   rv = svc->QueryInterface(aIID, _result);
 
   return rv;
 }
 
 nsresult
-setPassword(PK11SlotInfo* slot, nsIInterfaceRequestor* ctx)
+setPassword(PK11SlotInfo* slot, nsIInterfaceRequestor* ctx,
+            nsNSSShutDownPreventionLock& /*proofOfLock*/)
 {
-  nsNSSShutDownPreventionLock locker;
   nsresult rv = NS_OK;
 
   if (PK11_NeedUserInit(slot)) {
     nsITokenPasswordDialogs* dialogs;
     bool canceled;
     NS_ConvertUTF8toUTF16 tokenName(PK11_GetTokenName(slot));
 
     rv = getNSSDialogs((void**)&dialogs,
                        NS_GET_IID(nsITokenPasswordDialogs),
                        NS_TOKENPASSWORDSDIALOG_CONTRACTID);
 
     if (NS_FAILED(rv)) goto loser;
 
-    {
-      nsPSMUITracker tracker;
-      if (tracker.isUIForbidden()) {
-        rv = NS_ERROR_NOT_AVAILABLE;
-      }
-      else {
-        rv = dialogs->SetPassword(ctx,
-                                  tokenName.get(),
-                                  &canceled);
-      }
-    }
+    rv = dialogs->SetPassword(ctx, tokenName.get(), &canceled);
+
     NS_RELEASE(dialogs);
     if (NS_FAILED(rv)) goto loser;
 
     if (canceled) { rv = NS_ERROR_NOT_AVAILABLE; goto loser; }
   }
  loser:
   return rv;
 }
--- a/security/manager/ssl/nsNSSComponent.h
+++ b/security/manager/ssl/nsNSSComponent.h
@@ -179,17 +179,16 @@ private:
   nsresult InitializePIPNSSBundle();
   nsresult ConfigureInternalPKCS11Token();
   nsresult RegisterObservers();
   nsresult DeregisterObservers();
 
   // Methods that we use to handle the profile change notifications (and to
   // synthesize a full profile change when we're just doing a profile startup):
   void DoProfileChangeNetTeardown();
-  void DoProfileChangeTeardown(nsISupports* aSubject);
   void DoProfileBeforeChange(nsISupports* aSubject);
   void DoProfileChangeNetRestore();
 
   Mutex mutex;
 
   nsCOMPtr<nsIStringBundle> mPIPNSSBundle;
   nsCOMPtr<nsIStringBundle> mNSSErrorsBundle;
   bool mNSSInitialized;
--- a/security/manager/ssl/nsNSSHelper.h
+++ b/security/manager/ssl/nsNSSHelper.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef NSS_HELPER_
 #define NSS_HELPER_
 
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
+#include "nsNSSShutDown.h"
 #include "pk11func.h"
 
 //
 // Implementation of an nsIInterfaceRequestor for use
 // as context for NSS calls
 //
 class PipUIContext : public nsIInterfaceRequestor
 {
@@ -46,12 +47,12 @@ pip_ucs2_ascii_conversion_fn(PRBool toUn
                              unsigned int *outBufLen,
                              PRBool swapBytes);
 }
 
 //
 // A function that sets the password on an unitialized slot.
 //
 nsresult
-setPassword(PK11SlotInfo *slot, nsIInterfaceRequestor *ctx);
+setPassword(PK11SlotInfo* slot, nsIInterfaceRequestor* ctx,
+            nsNSSShutDownPreventionLock& /*proofOfLock*/);
 
 #endif
-
--- a/security/manager/ssl/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -2379,28 +2379,21 @@ ClientAuthDataRunnable::RunOnTargetThrea
         NS_CLIENTAUTHDIALOGS_CONTRACTID);
 
       if (NS_FAILED(rv)) {
         NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(CertsToUse, certNicknameList);
         NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(CertsToUse, certDetailsList);
         goto loser;
       }
 
-      {
-        nsPSMUITracker tracker;
-        if (tracker.isUIForbidden()) {
-          rv = NS_ERROR_NOT_AVAILABLE;
-        } else {
-          rv = dialogs->ChooseCertificate(mSocketInfo, cn_host_port.get(),
-            org.get(), issuer.get(),
-            (const char16_t**)certNicknameList,
-            (const char16_t**)certDetailsList,
-            CertsToUse, &selectedIndex, &canceled);
-        }
-      }
+      rv = dialogs->ChooseCertificate(mSocketInfo, cn_host_port.get(),
+                                      org.get(), issuer.get(),
+                                      (const char16_t**)certNicknameList,
+                                      (const char16_t**)certDetailsList,
+                                      CertsToUse, &selectedIndex, &canceled);
 
       NS_RELEASE(dialogs);
       NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(CertsToUse, certNicknameList);
       NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(CertsToUse, certDetailsList);
 
       if (NS_FAILED(rv)) goto loser;
 
       // even if the user has canceled, we want to remember that, to avoid repeating prompts
--- a/security/manager/ssl/nsNSSShutDown.cpp
+++ b/security/manager/ssl/nsNSSShutDown.cpp
@@ -107,27 +107,16 @@ nsresult nsNSSShutDownList::doPK11Logout
     if (pklco) {
       pklco->logout();
     }
   }
 
   return NS_OK;
 }
 
-bool nsNSSShutDownList::ifPossibleDisallowUI()
-{
-  bool isNowDisallowed = mActivityState.ifPossibleDisallowUI();
-  return isNowDisallowed;
-}
-
-void nsNSSShutDownList::allowUI()
-{
-  mActivityState.allowUI();
-}
-
 nsresult nsNSSShutDownList::evaporateAllNSSResources()
 {
   if (PR_SUCCESS != mActivityState.restrictActivityToCurrentThread()) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to restrict activity to current thread\n"));
     return NS_ERROR_FAILURE;
   }
 
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("now evaporating NSS resources\n"));
@@ -164,18 +153,16 @@ nsNSSShutDownList *nsNSSShutDownList::co
   return singleton;
 }
 
 nsNSSActivityState::nsNSSActivityState()
 :mNSSActivityStateLock("nsNSSActivityState.mNSSActivityStateLock"), 
  mNSSActivityChanged(mNSSActivityStateLock,
                      "nsNSSActivityState.mNSSActivityStateLock"),
  mNSSActivityCounter(0),
- mBlockingUICounter(0),
- mIsUIForbidden(false),
  mNSSRestrictedThread(nullptr)
 {
 }
 
 nsNSSActivityState::~nsNSSActivityState()
 {
 }
 
@@ -194,97 +181,34 @@ void nsNSSActivityState::leave()
 {
   MutexAutoLock lock(mNSSActivityStateLock);
 
   --mNSSActivityCounter;
 
   mNSSActivityChanged.NotifyAll();
 }
 
-void nsNSSActivityState::enterBlockingUIState()
-{
-  MutexAutoLock lock(mNSSActivityStateLock);
-
-  ++mBlockingUICounter;
-}
-
-void nsNSSActivityState::leaveBlockingUIState()
+PRStatus nsNSSActivityState::restrictActivityToCurrentThread()
 {
   MutexAutoLock lock(mNSSActivityStateLock);
 
-  --mBlockingUICounter;
-}
-
-bool nsNSSActivityState::isBlockingUIActive()
-{
-  MutexAutoLock lock(mNSSActivityStateLock);
-  return (mBlockingUICounter > 0);
-}
-
-bool nsNSSActivityState::isUIForbidden()
-{
-  MutexAutoLock lock(mNSSActivityStateLock);
-  return mIsUIForbidden;
-}
-
-bool nsNSSActivityState::ifPossibleDisallowUI()
-{
-  bool retval = false;
-  MutexAutoLock lock(mNSSActivityStateLock);
-
-  // Checking and disallowing the UI must be done atomically.
-
-  if (!mBlockingUICounter) {
-    // No UI is currently shown, we are able to evaporate.
-    retval = true;
-    // Remember to disallow UI.
-    // To clear the "forbidden" state, call restrictActivityToCurrentThread()
-    // and releaseCurrentThreadActivityRestriction().
-    mIsUIForbidden = true;
-  }
-  return retval;
-}
-
-void nsNSSActivityState::allowUI()
-{
-  MutexAutoLock lock(mNSSActivityStateLock);
-
-  mIsUIForbidden = false;
-}
-
-PRStatus nsNSSActivityState::restrictActivityToCurrentThread()
-{
-  PRStatus retval = PR_FAILURE;
-  MutexAutoLock lock(mNSSActivityStateLock);
-  
-  if (!mBlockingUICounter) {
-    while (0 < mNSSActivityCounter && !mBlockingUICounter) {
-      mNSSActivityChanged.Wait(PR_TicksPerSecond());
-    }
-      
-    if (mBlockingUICounter) {
-      // This should never happen.
-      // If we arrive here, our logic is broken.
-      PR_ASSERT(0);
-    }
-    else {
-      mNSSRestrictedThread = PR_GetCurrentThread();
-      retval = PR_SUCCESS;
-    }
+  while (mNSSActivityCounter > 0) {
+    mNSSActivityChanged.Wait(PR_TicksPerSecond());
   }
 
-  return retval;
+  mNSSRestrictedThread = PR_GetCurrentThread();
+
+  return PR_SUCCESS;
 }
 
 void nsNSSActivityState::releaseCurrentThreadActivityRestriction()
 {
   MutexAutoLock lock(mNSSActivityStateLock);
 
   mNSSRestrictedThread = nullptr;
-  mIsUIForbidden = false;
 
   mNSSActivityChanged.NotifyAll();
 }
 
 nsNSSShutDownPreventionLock::nsNSSShutDownPreventionLock()
 {
   nsNSSActivityState *state = nsNSSShutDownList::getActivityState();
   if (!state)
@@ -296,35 +220,8 @@ nsNSSShutDownPreventionLock::nsNSSShutDo
 nsNSSShutDownPreventionLock::~nsNSSShutDownPreventionLock()
 {
   nsNSSActivityState *state = nsNSSShutDownList::getActivityState();
   if (!state)
     return;
   
   state->leave();
 }
-
-nsPSMUITracker::nsPSMUITracker()
-{
-  nsNSSActivityState *state = nsNSSShutDownList::getActivityState();
-  if (!state)
-    return;
-  
-  state->enterBlockingUIState();
-}
-
-nsPSMUITracker::~nsPSMUITracker()
-{
-  nsNSSActivityState *state = nsNSSShutDownList::getActivityState();
-  if (!state)
-    return;
-  
-  state->leaveBlockingUIState();
-}
-
-bool nsPSMUITracker::isUIForbidden()
-{
-  nsNSSActivityState *state = nsNSSShutDownList::getActivityState();
-  if (!state)
-    return false;
-
-  return state->isUIForbidden();
-}
--- a/security/manager/ssl/nsNSSShutDown.h
+++ b/security/manager/ssl/nsNSSShutDown.h
@@ -20,40 +20,18 @@ class nsNSSActivityState
 public:
   nsNSSActivityState();
   ~nsNSSActivityState();
 
   // Call enter/leave when PSM enters a scope during which
   // shutting down NSS is prohibited.
   void enter();
   void leave();
-  
-  // Call enter/leave when PSM is about to show a UI
-  // while still holding resources.
-  void enterBlockingUIState();
-  void leaveBlockingUIState();
-  
-  // Is the activity aware of any blocking PSM UI currently shown?
-  bool isBlockingUIActive();
-
-  // Is it forbidden to bring up an UI while holding resources?
-  bool isUIForbidden();
-  
-  // Check whether setting the current thread restriction is possible. If it
-  // is possible, the state tracking will have ensured that we will stay in
-  // this state. As of writing, this includes forbidding PSM UI.
-  bool ifPossibleDisallowUI();
-
-  // Notify the state tracking that going to the restricted state is
-  // no longer planned.
-  // As of writing, this includes clearing the "PSM UI forbidden" flag.
-  void allowUI();
-
-  // If currently no UI is shown, wait for all activity to stop,
-  // and block any other thread on entering relevant PSM code.
+  // Wait for all activity to stop, and block any other thread on entering
+  // relevant PSM code.
   PRStatus restrictActivityToCurrentThread();
   
   // Go back to normal state.
   void releaseCurrentThreadActivityRestriction();
 
 private:
   // The lock protecting all our member variables.
   mozilla::Mutex mNSSActivityStateLock;
@@ -61,46 +39,29 @@ private:
   // The activity variable, bound to our lock,
   // used either to signal the activity counter reaches zero,
   // or a thread restriction has been released.
   mozilla::CondVar mNSSActivityChanged;
 
   // The number of active scopes holding resources.
   int mNSSActivityCounter;
 
-  // The number of scopes holding resources while blocked
-  // showing an UI.
-  int mBlockingUICounter;
-
-  // Whether bringing up UI is currently forbidden
-  bool mIsUIForbidden;
-
   // nullptr means "no restriction"
   // if not null, activity is only allowed on that thread
   PRThread* mNSSRestrictedThread;
 };
 
 // Helper class that automatically enters/leaves the global activity state
 class nsNSSShutDownPreventionLock
 {
 public:
   nsNSSShutDownPreventionLock();
   ~nsNSSShutDownPreventionLock();
 };
 
-// Helper class that automatically enters/leaves the global UI tracking
-class nsPSMUITracker
-{
-public:
-  nsPSMUITracker();
-  ~nsPSMUITracker();
-  
-  bool isUIForbidden();
-};
-
 // Singleton, used by nsNSSComponent to track the list of PSM objects,
 // which hold NSS resources and support the "early cleanup mechanism".
 class nsNSSShutDownList
 {
 public:
   ~nsNSSShutDownList();
 
   static nsNSSShutDownList *construct();
@@ -109,23 +70,16 @@ public:
   static void remember(nsNSSShutDownObject *o);
   static void forget(nsNSSShutDownObject *o);
 
   // track instances that would like notification when
   // a PK11 logout operation is performed.
   static void remember(nsOnPK11LogoutCancelObject *o);
   static void forget(nsOnPK11LogoutCancelObject *o);
 
-  // If possible to do "early cleanup" at the current time, remember that we want to
-  // do it, and disallow actions that would change the possibility.
-  bool ifPossibleDisallowUI();
-
-  // Notify that it is no longer planned to do the "early cleanup".
-  void allowUI();
-  
   // Do the "early cleanup", if possible.
   nsresult evaporateAllNSSResources();
 
   // PSM has been asked to log out of a token.
   // Notify all registered instances that want to react to that event.
   nsresult doPK11Logout();
   
   static nsNSSActivityState *getActivityState()
--- a/security/manager/ssl/nsPK11TokenDB.cpp
+++ b/security/manager/ssl/nsPK11TokenDB.cpp
@@ -192,17 +192,17 @@ nsPK11Token::Login(bool force)
   SECStatus srv;
   bool test;
   rv = this->NeedsLogin(&test);
   if (NS_FAILED(rv)) return rv;
   if (test && force) {
     rv = this->LogoutSimple();
     if (NS_FAILED(rv)) return rv;
   }
-  rv = setPassword(mSlot, mUIContext);
+  rv = setPassword(mSlot, mUIContext, locker);
   if (NS_FAILED(rv)) return rv;
   srv = PK11_Authenticate(mSlot, true, mUIContext);
   return (srv == SECSuccess) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP nsPK11Token::LogoutSimple()
 {
   nsNSSShutDownPreventionLock locker;
--- a/security/manager/ssl/nsPKCS12Blob.cpp
+++ b/security/manager/ssl/nsPKCS12Blob.cpp
@@ -56,31 +56,41 @@ nsPKCS12Blob::nsPKCS12Blob():mCertArray(
   mUIContext = new PipUIContext();
 }
 
 // destructor
 nsPKCS12Blob::~nsPKCS12Blob()
 {
   delete mDigestIterator;
   delete mDigest;
+
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return;
+  }
+
+  shutdown(calledFromObject);
 }
 
 // nsPKCS12Blob::SetToken
 //
 // Set the token to use for import/export
 nsresult
 nsPKCS12Blob::SetToken(nsIPK11Token *token)
 {
  nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+  return NS_ERROR_NOT_AVAILABLE;
+ }
  nsresult rv = NS_OK;
  if (token) {
    mToken = token;
  } else {
    PK11SlotInfo *slot;
-   rv = GetSlotWithMechanism(CKM_RSA_PKCS, mUIContext,&slot);
+   rv = GetSlotWithMechanism(CKM_RSA_PKCS, mUIContext, &slot, locker);
    if (NS_FAILED(rv)) {
       mToken = 0;  
    } else {
      mToken = new nsPK11Token(slot);
      PK11_FreeSlot(slot);
    }
  }
  mTokenSet = true;
@@ -437,25 +447,17 @@ nsPKCS12Blob::newPKCS12FilePassword(SECI
   nsresult rv = NS_OK;
   nsAutoString password;
   nsCOMPtr<nsICertificateDialogs> certDialogs;
   rv = ::getNSSDialogs(getter_AddRefs(certDialogs), 
                        NS_GET_IID(nsICertificateDialogs),
                        NS_CERTIFICATEDIALOGS_CONTRACTID);
   if (NS_FAILED(rv)) return rv;
   bool pressedOK;
-  {
-    nsPSMUITracker tracker;
-    if (tracker.isUIForbidden()) {
-      rv = NS_ERROR_NOT_AVAILABLE;
-    }
-    else {
-      rv = certDialogs->SetPKCS12FilePassword(mUIContext, password, &pressedOK);
-    }
-  }
+  rv = certDialogs->SetPKCS12FilePassword(mUIContext, password, &pressedOK);
   if (NS_FAILED(rv) || !pressedOK) return rv;
   unicodeToItem(password.get(), unicodePw);
   return NS_OK;
 }
 
 // getPKCS12FilePassword
 //
 // Launch a dialog requesting the user for the password to a PKCS#12 file.
@@ -466,25 +468,17 @@ nsPKCS12Blob::getPKCS12FilePassword(SECI
   nsresult rv = NS_OK;
   nsAutoString password;
   nsCOMPtr<nsICertificateDialogs> certDialogs;
   rv = ::getNSSDialogs(getter_AddRefs(certDialogs), 
                        NS_GET_IID(nsICertificateDialogs),
                        NS_CERTIFICATEDIALOGS_CONTRACTID);
   if (NS_FAILED(rv)) return rv;
   bool pressedOK;
-  {
-    nsPSMUITracker tracker;
-    if (tracker.isUIForbidden()) {
-      rv = NS_ERROR_NOT_AVAILABLE;
-    }
-    else {
-      rv = certDialogs->GetPKCS12FilePassword(mUIContext, password, &pressedOK);
-    }
-  }
+  rv = certDialogs->GetPKCS12FilePassword(mUIContext, password, &pressedOK);
   if (NS_FAILED(rv) || !pressedOK) return rv;
   unicodeToItem(password.get(), unicodePw);
   return NS_OK;
 }
 
 // inputToDecoder
 //
 // Given a decoder, read bytes from file and input them to the decoder.
--- a/security/manager/ssl/nsPKCS12Blob.h
+++ b/security/manager/ssl/nsPKCS12Blob.h
@@ -21,22 +21,25 @@
 
 class nsIX509Cert;
 
 //
 // nsPKCS12Blob
 //
 // Class for importing/exporting PKCS#12 blobs
 //
-class nsPKCS12Blob
+class nsPKCS12Blob : public nsNSSShutDownObject
 {
 public:
   nsPKCS12Blob();
   virtual ~nsPKCS12Blob();
 
+  // Nothing to release.
+  virtual void virtualDestroyNSSReference() override {}
+
   // Set the token to use (default is internal)
   nsresult SetToken(nsIPK11Token *token);
 
   // PKCS#12 Import
   nsresult ImportFromFile(nsIFile *file);
 
   // PKCS#12 Export
   nsresult ExportToFile(nsIFile *file, nsIX509Cert **certs, int numCerts);
--- a/security/manager/ssl/nsSDR.cpp
+++ b/security/manager/ssl/nsSDR.cpp
@@ -40,35 +40,46 @@ NS_IMPL_ISUPPORTS(nsSecretDecoderRing, n
 nsSecretDecoderRing::nsSecretDecoderRing()
 {
   // initialize superclass
 }
 
 // nsSecretDecoderRing destructor
 nsSecretDecoderRing::~nsSecretDecoderRing()
 {
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return;
+  }
+
+  shutdown(calledFromObject);
 }
 
-NS_IMETHODIMP nsSecretDecoderRing::
-Encrypt(unsigned char * data, int32_t dataLen, unsigned char * *result, int32_t *_retval)
+NS_IMETHODIMP
+nsSecretDecoderRing::Encrypt(unsigned char* data, int32_t dataLen,
+                             unsigned char** result, int32_t* _retval)
 {
   nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   nsresult rv = NS_OK;
   ScopedPK11SlotInfo slot;
   SECItem keyid;
   SECItem request;
   SECItem reply;
   SECStatus s;
   nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
 
   slot = PK11_GetInternalKeySlot();
   if (!slot) { rv = NS_ERROR_NOT_AVAILABLE; goto loser; }
 
   /* Make sure token is initialized. */
-  rv = setPassword(slot, ctx);
+  rv = setPassword(slot, ctx, locker);
   if (NS_FAILED(rv))
     goto loser;
 
   /* Force authentication */
   s = PK11_Authenticate(slot, true, ctx);
   if (s != SECSuccess) { rv = NS_ERROR_FAILURE; goto loser; }
 
   /* Use default key id */
@@ -185,20 +196,24 @@ DecryptString(const char *crypt, char **
 
 loser:
   if (decrypted) PORT_Free(decrypted);
   if (decoded) PR_DELETE(decoded);
 
   return rv;
 }
 
-NS_IMETHODIMP nsSecretDecoderRing::
-ChangePassword()
+NS_IMETHODIMP
+nsSecretDecoderRing::ChangePassword()
 {
   nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   nsresult rv;
   ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
   if (!slot) return NS_ERROR_NOT_AVAILABLE;
 
   /* Convert UTF8 token name to UCS2 */
   NS_ConvertUTF8toUTF16 tokenName(PK11_GetTokenName(slot));
 
   /* Get the set password dialog handler imlementation */
@@ -206,27 +221,17 @@ ChangePassword()
 
   rv = getNSSDialogs(getter_AddRefs(dialogs),
                      NS_GET_IID(nsITokenPasswordDialogs),
                      NS_TOKENPASSWORDSDIALOG_CONTRACTID);
   if (NS_FAILED(rv)) return rv;
 
   nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
   bool canceled;
-
-  {
-    nsPSMUITracker tracker;
-    if (tracker.isUIForbidden()) {
-      rv = NS_ERROR_NOT_AVAILABLE;
-    }
-    else {
-      rv = dialogs->SetPassword(ctx, tokenName.get(), &canceled);
-    }
-  }
-
+  rv = dialogs->SetPassword(ctx, tokenName.get(), &canceled);
   /* canceled is ignored */
 
   return rv;
 }
 
 NS_IMETHODIMP nsSecretDecoderRing::
 Logout()
 {
--- a/security/manager/ssl/nsSDR.h
+++ b/security/manager/ssl/nsSDR.h
@@ -25,34 +25,36 @@
 
 // ===============================================
 // nsSecretDecoderRing - implementation of nsISecretDecoderRing
 // ===============================================
 
 #define NS_SDR_CID \
   { 0x0c4f1ddc, 0x1dd2, 0x11b2, { 0x9d, 0x95, 0xf2, 0xfd, 0xf1, 0x13, 0x04, 0x4b } }
 
-class nsSecretDecoderRing
-: public nsISecretDecoderRing,
-  public nsISecretDecoderRingConfig
+class nsSecretDecoderRing : public nsISecretDecoderRing
+                          , public nsISecretDecoderRingConfig
+                          , public nsNSSShutDownObject
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISECRETDECODERRING
   NS_DECL_NSISECRETDECODERRINGCONFIG
 
   nsSecretDecoderRing();
 
+  // Nothing to release.
+  virtual void virtualDestroyNSSReference() override {}
+
 protected:
   virtual ~nsSecretDecoderRing();
 
 private:
 
   /**
    * encode - encodes binary into BASE64 string.
    * decode - decode BASE64 string into binary.
    */
   nsresult encode(const unsigned char *data, int32_t dataLen, char **_retval);
   nsresult decode(const char *data, unsigned char **result, int32_t * _retval);
-
 };
 
 #endif /* _NSSDR_H_ */
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -27,16 +27,25 @@
 
 #include "sandbox/linux/bpf_dsl/bpf_dsl.h"
 #include "sandbox/linux/seccomp-bpf/linux_seccomp.h"
 #include "sandbox/linux/services/linux_syscalls.h"
 
 using namespace sandbox::bpf_dsl;
 #define CASES SANDBOX_BPF_DSL_CASES
 
+// Fill in defines in case of old headers.
+// (Warning: these are wrong on PA-RISC.)
+#ifndef MADV_NOHUGEPAGE
+#define MADV_NOHUGEPAGE 15
+#endif
+#ifndef MADV_DONTDUMP
+#define MADV_DONTDUMP 16
+#endif
+
 // To avoid visual confusion between "ifdef ANDROID" and "ifndef ANDROID":
 #ifndef ANDROID
 #define DESKTOP
 #endif
 
 // This file defines the seccomp-bpf system call filter policies.
 // See also SandboxFilterUtil.h, for the CASES_FOR_* macros and
 // SandboxFilterBase::Evaluate{Socket,Ipc}Call.
@@ -690,16 +699,20 @@ public:
       return Trap(OpenTrap, mPlugin);
 
       // ipc::Shmem
     case __NR_mprotect:
       return Allow();
     case __NR_madvise: {
       Arg<int> advice(2);
       return If(advice == MADV_DONTNEED, Allow())
+#ifdef MOZ_ASAN
+        .ElseIf(advice == MADV_NOHUGEPAGE, Allow())
+        .ElseIf(advice == MADV_DONTDUMP, Allow())
+#endif
         .Else(InvalidSyscall());
     }
 
     default:
       return SandboxPolicyCommon::EvaluateSyscall(sysno);
     }
   }
 };
--- a/testing/docker/desktop-test/Dockerfile
+++ b/testing/docker/desktop-test/Dockerfile
@@ -12,16 +12,19 @@ COPY           tc-vcs-config.yml /etc/ta
 # TODO: remove
 ADD            https://raw.githubusercontent.com/taskcluster/buildbot-step/master/buildbot_step /home/worker/bin/buildbot_step
 RUN chmod u+x /home/worker/bin/buildbot_step
 
 # TODO: remove
 ADD            https://s3-us-west-2.amazonaws.com/test-caching/packages/linux64-stackwalk /usr/local/bin/linux64-minidump_stackwalk
 RUN chmod u+x /usr/local/bin/linux64-minidump_stackwalk
 
+# allow the worker user to access video devices
+RUN usermod -a -G video worker
+
 RUN mkdir Documents; mkdir Pictures; mkdir Music; mkdir Videos; mkdir artifacts
 
 # install a new enough npm, plus tc-vcs and tc-npm-cache
 RUN npm install -g npm@^2.0.0
 RUN npm install -g taskcluster-vcs@2.3.12
 RUN npm install -g taskcluster-npm-cache@1.1.14
 ENV PATH $PATH:/home/worker/bin
 
--- a/testing/docker/desktop-test/VERSION
+++ b/testing/docker/desktop-test/VERSION
@@ -1,1 +1,1 @@
-0.4.2
+0.4.3
--- a/testing/mach_commands.py
+++ b/testing/mach_commands.py
@@ -598,18 +598,22 @@ class PushToTry(MachCommandBase):
         """
 
         from mozbuild.testing import TestResolver
         from mozbuild.controller.building import BuildDriver
         from autotry import AutoTry
 
         print("mach try is under development, please file bugs blocking 1149670.")
 
-        resolver = self._spawn(TestResolver)
-        at = AutoTry(self.topsrcdir, resolver, self._mach_context)
+        resolver_func = lambda: self._spawn(TestResolver)
+        at = AutoTry(self.topsrcdir, resolver_func, self._mach_context)
+
+        if kwargs["list"]:
+            at.list_presets()
+            sys.exit()
 
         if kwargs["load"] is not None:
             defaults = at.load_config(kwargs["load"])
 
             if defaults is None:
                 print("No saved configuration called %s found in autotry.ini" % kwargs["load"],
                       file=sys.stderr)
 
--- a/testing/tools/autotry/autotry.py
+++ b/testing/tools/autotry/autotry.py
@@ -33,16 +33,18 @@ def arg_parser():
     parser.add_argument('--no-push', dest='push', action='store_false',
                         help='Do not push to try as a result of running this command (if '
                         'specified this command will only print calculated try '
                         'syntax and selection info).')
     parser.add_argument('--save', dest="save", action='store',
                         help="Save the command line arguments for future use with --preset.")
     parser.add_argument('--preset', dest="load", action='store',
                         help="Load a saved set of arguments. Additional arguments will override saved ones.")
+    parser.add_argument('--list', action='store_true',
+                        help="List all saved try strings")
     parser.add_argument('extra_args', nargs=argparse.REMAINDER,
                         help='Extra arguments to put in the try push.')
     parser.add_argument('-v', "--verbose", dest='verbose', action='store_true', default=False,
                         help='Print detailed information about the resulting test selection '
                         'and commands performed.')
     return parser
 
 class TryArgumentTokenizer(object):
@@ -164,27 +166,34 @@ class AutoTry(object):
         "chrome": "mochitest-o",
         "browser-chrome": "mochitest-bc",
         "devtools-chrome": "mochitest-dt",
         "crashtest": "crashtest",
         "reftest": "reftest",
         "web-platform-tests": "web-platform-tests",
     }
 
-    def __init__(self, topsrcdir, resolver, mach_context):
+    def __init__(self, topsrcdir, resolver_func, mach_context):
         self.topsrcdir = topsrcdir
-        self.resolver = resolver
+        self._resolver_func = resolver_func
+        self._resolver = None
         self.mach_context = mach_context
 
         if os.path.exists(os.path.join(self.topsrcdir, '.hg')):
             self._use_git = False
         else:
             self._use_git = True
 
     @property
+    def resolver(self):
+        if self._resolver is None:
+            self._resolver = self.resolver_func
+        return self._resolver
+
+    @property
     def config_path(self):
         return os.path.join(self.mach_context.state_dir, "autotry.ini")
 
     def load_config(self, name):
         config = ConfigParser.RawConfigParser()
         success = config.read([self.config_path])
         if not success:
             return None
@@ -193,16 +202,33 @@ class AutoTry(object):
             data = config.get("try", name)
         except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
             return None
 
         kwargs = vars(arg_parser().parse_args(self.split_try_string(data)))
 
         return kwargs
 
+    def list_presets(self):
+        config = ConfigParser.RawConfigParser()
+        success = config.read([self.config_path])
+
+        data = []
+        if success:
+            try:
+                data = config.items("try")
+            except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+                pass
+
+        if not data:
+            print("No presets found")
+
+        for name, try_string in data:
+            print("%s: %s" % (name, try_string))
+
     def split_try_string(self, data):
         return re.findall(r'(?:\[.*?\]|\S)+', data)
 
     def save_config(self, name, data):
         assert data.startswith("try: ")
         data = data[len("try: "):]
 
         parser = ConfigParser.RawConfigParser()
--- a/testing/web-platform/mozilla/meta/MANIFEST.json
+++ b/testing/web-platform/mozilla/meta/MANIFEST.json
@@ -200,22 +200,16 @@
           }
         ],
         "service-workers/service-worker/fetch-request-fallback.https.html": [
           {
             "path": "service-workers/service-worker/fetch-request-fallback.https.html",
             "url": "/_mozilla/service-workers/service-worker/fetch-request-fallback.https.html"
           }
         ],
-        "service-workers/service-worker/fetch-request-html-imports.https.html": [
-          {
-            "path": "service-workers/service-worker/fetch-request-html-imports.https.html",
-            "url": "/_mozilla/service-workers/service-worker/fetch-request-html-imports.https.html"
-          }
-        ],
         "service-workers/service-worker/fetch-request-no-freshness-headers.https.html": [
           {
             "path": "service-workers/service-worker/fetch-request-no-freshness-headers.https.html",
             "url": "/_mozilla/service-workers/service-worker/fetch-request-no-freshness-headers.https.html"
           }
         ],
         "service-workers/service-worker/fetch-request-resources.https.html": [
           {
deleted file mode 100644
--- a/testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-html-imports.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[fetch-request-html-imports.https.html]
-  type: testharness
-  [Verify the FetchEvent for HTMLImports]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/mozilla/meta/service-workers/service-worker/referer.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[referer.https.html]
-  type: testharness
-  [Verify the referer]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-request-html-imports.https.html
+++ /dev/null
@@ -1,61 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: FetchEvent for HTMLImports</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/get-host-info.sub.js"></script>
-<script src="resources/test-helpers.sub.js?pipe=sub"></script>
-<script>
-async_test(function(t) {
-    var SCOPE = 'resources/fetch-request-html-imports-iframe.sub.html';
-    var SCRIPT = 'resources/fetch-request-html-imports-worker.sub.js';
-    var host_info = get_host_info();
-    service_worker_unregister_and_register(t, SCRIPT, SCOPE)
-      .then(function(registration) {
-          return wait_for_state(t, registration.installing, 'activated');
-        })
-      .then(function() { return with_iframe(SCOPE); })
-      .then(function(frame) {
-          var same = frame.contentWindow.document.getElementById("same").import;
-          var same_same = same.getElementById("same-same").import;
-          var same_other = same.getElementById("same-other").import;
-          var other =
-              frame.contentWindow.document.getElementById("other").import;
-          var other_same = other.getElementById("other-same").import;
-          var other_other = other.getElementById("other-other").import;
-
-          assert_equals(
-              same.body.innerText,
-              'mode=cors credentials=include',
-              'The request mode and credentials for same origin HTMLImport ' +
-              'must be set correctly.');
-          assert_equals(
-              same_same.body.innerText,
-              'mode=cors credentials=include',
-              'The request mode and credentials for same origin HTMLImport ' +
-              'from same origin HTMLImports must be set correctly.');
-          assert_equals(
-              same_other.body.innerText,
-              'mode=cors credentials=omit',
-              'The request mode and credentials for other origin HTMLImport ' +
-              'from same origin HTMLImports must be set correctly.');
-          assert_equals(
-              other.body.innerText,
-              'mode=cors credentials=omit',
-              'The request mode and credentials for other origin HTMLImport ' +
-              'must be set correctly.');
-          assert_equals(
-              other_same.body.innerText,
-              'mode=cors credentials=include',
-              'The request mode and credentials for same origin HTMLImport ' +
-              'from other origin HTMLImports must be set correctly.');
-          assert_equals(
-              other_other.body.innerText,
-              'mode=cors credentials=omit',
-              'The request mode and credentials for other origin HTMLImport ' +
-              'from other origin HTMLImport must be set correctly.');
-
-          service_worker_unregister_and_done(t, SCOPE);
-        })
-      .catch(unreached_rejection(t));
-  }, 'Verify the FetchEvent for HTMLImports');
-</script>
deleted file mode 100644
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-html-imports-iframe.sub.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link id="same" rel="import" href="http://{{domains[www]}}:{{ports[http][0]}}/dummy-dir/same.html">
-<link id="other" rel="import" href="http://{{host}}:{{ports[http][0]}}/dummy-dir/other.html">
deleted file mode 100644
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-html-imports-worker.sub.js
+++ /dev/null
@@ -1,25 +0,0 @@
-self.addEventListener('fetch', function(event) {
-    var url = event.request.url;
-    if (url.indexOf('dummy-dir') == -1) {
-      return;
-    }
-    var result = 'mode=' + event.request.mode +
-      ' credentials=' + event.request.credentials;
-    if (url == 'http://{{domains[www]}}:{{ports[http][0]}}/dummy-dir/same.html') {
-      event.respondWith(new Response(
-        result +
-        '<link id="same-same" rel="import" ' +
-        'href="http://{{domains[www]}}:{{ports[http][0]}}/dummy-dir/same-same.html">' +
-        '<link id="same-other" rel="import" ' +
-        ' href="http://{{host}}:{{ports[http][0]}}/dummy-dir/same-other.html">'));
-    } else if (url == 'http://{{host}}:{{ports[http][0]}}/dummy-dir/other.html') {
-      event.respondWith(new Response(
-        result +
-        '<link id="other-same" rel="import" ' +
-        ' href="http://{{domains[www]}}:{{ports[http][0]}}/dummy-dir/other-same.html">' +
-        '<link id="other-other" rel="import" ' +
-        ' href="http://{{host}}:{{ports[http][0]}}/dummy-dir/other-other.html">'));
-    } else {
-      event.respondWith(new Response(result));
-    }
-  });
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/referer-iframe.html
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/referer-iframe.html
@@ -1,38 +1,39 @@
 <script src="../resources/get-host-info.sub.js"></script>
+<script src="test-helpers.sub.js"></script>
 <script>
 function check_referer(url, expected_referer) {
   return fetch(url)
     .then(function(res) { return res.json(); })
     .then(function(headers) {
-        if (headers['Referer'] === expected_referer) {
+        if (headers['referer'] === expected_referer) {
           return Promise.resolve();
         } else {
           return Promise.reject('Referer for ' + url + ' must be ' +
                                 expected_referer + ' but got ' +
-                                headers['Referer']);
+                                headers['referer']);
         }
       });
 }
 
 window.addEventListener('message', function(evt) {
     var host_info = get_host_info();
     var port = evt.ports[0];
     check_referer('request-headers.py?ignore=true',
                   host_info['HTTPS_ORIGIN'] +
-                  '/service-worker/resources/referer-iframe.html')
+                  base_path() + 'referer-iframe.html')
       .then(function() {
           return check_referer(
               'request-headers.py',
               host_info['HTTPS_ORIGIN'] +
-              '/service-worker/resources/fetch-rewrite-worker.js');
+              base_path() + 'fetch-rewrite-worker.js');
         })
       .then(function() {
           return check_referer(
               'request-headers.py?url=request-headers.py',
               host_info['HTTPS_ORIGIN'] +
-              '/service-worker/resources/fetch-rewrite-worker.js');
+              base_path() + 'fetch-rewrite-worker.js');
         })
       .then(function() { port.postMessage({results: 'finish'}); })
       .catch(function(e) { port.postMessage({results: 'failure:' + e}); });
   });
 </script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/request-headers.py
@@ -0,0 +1,6 @@
+import json
+
+def main(request, response):
+    data = {key:request.headers[key] for key,value in request.headers.iteritems()}
+
+    return [("Content-Type", "application/json")], json.dumps(data)
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -27,40 +27,24 @@ jfieldID AndroidGeckoEvent::jToolTypes =
 jfieldID AndroidGeckoEvent::jPointRadii = 0;
 jfieldID AndroidGeckoEvent::jOrientations = 0;
 jfieldID AndroidGeckoEvent::jXField = 0;
 jfieldID AndroidGeckoEvent::jYField = 0;
 jfieldID AndroidGeckoEvent::jZField = 0;
 jfieldID AndroidGeckoEvent::jWField = 0;
 jfieldID AndroidGeckoEvent::jDistanceField = 0;
 jfieldID AndroidGeckoEvent::jRectField = 0;
-jfieldID AndroidGeckoEvent::jNativeWindowField = 0;
 
 jfieldID AndroidGeckoEvent::jCharactersField = 0;
 jfieldID AndroidGeckoEvent::jCharactersExtraField = 0;
 jfieldID AndroidGeckoEvent::jDataField = 0;
-jfieldID AndroidGeckoEvent::jDOMPrintableKeyValueField = 0;
-jfieldID AndroidGeckoEvent::jKeyCodeField = 0;
-jfieldID AndroidGeckoEvent::jScanCodeField = 0;
 jfieldID AndroidGeckoEvent::jMetaStateField = 0;
 jfieldID AndroidGeckoEvent::jFlagsField = 0;
-jfieldID AndroidGeckoEvent::jUnicodeCharField = 0;
-jfieldID AndroidGeckoEvent::jBaseUnicodeCharField = 0;
-jfieldID AndroidGeckoEvent::jRepeatCountField = 0;
 jfieldID AndroidGeckoEvent::jCountField = 0;
-jfieldID AndroidGeckoEvent::jStartField = 0;
-jfieldID AndroidGeckoEvent::jEndField = 0;
 jfieldID AndroidGeckoEvent::jPointerIndexField = 0;
-jfieldID AndroidGeckoEvent::jRangeTypeField = 0;
-jfieldID AndroidGeckoEvent::jRangeStylesField = 0;
-jfieldID AndroidGeckoEvent::jRangeLineStyleField = 0;
-jfieldID AndroidGeckoEvent::jRangeBoldLineField = 0;
-jfieldID AndroidGeckoEvent::jRangeForeColorField = 0;
-jfieldID AndroidGeckoEvent::jRangeBackColorField = 0;
-jfieldID AndroidGeckoEvent::jRangeLineColorField = 0;
 jfieldID AndroidGeckoEvent::jLocationField = 0;
 jfieldID AndroidGeckoEvent::jConnectionTypeField = 0;
 jfieldID AndroidGeckoEvent::jIsWifiField = 0;
 jfieldID AndroidGeckoEvent::jDHCPGatewayField = 0;
 jfieldID AndroidGeckoEvent::jScreenOrientationField = 0;
 jfieldID AndroidGeckoEvent::jScreenAngleField = 0;
 jfieldID AndroidGeckoEvent::jByteBufferField = 0;
 jfieldID AndroidGeckoEvent::jWidthField = 0;
@@ -139,35 +123,20 @@ AndroidGeckoEvent::InitGeckoEventClass(J
     jYField = geckoEvent.getField("mY", "D");
     jZField = geckoEvent.getField("mZ", "D");
     jWField = geckoEvent.getField("mW", "D");
     jRectField = geckoEvent.getField("mRect", "Landroid/graphics/Rect;");
 
     jCharactersField = geckoEvent.getField("mCharacters", "Ljava/lang/String;");
     jCharactersExtraField = geckoEvent.getField("mCharactersExtra", "Ljava/lang/String;");
     jDataField = geckoEvent.getField("mData", "Ljava/lang/String;");
-    jKeyCodeField = geckoEvent.getField("mKeyCode", "I");
-    jScanCodeField = geckoEvent.getField("mScanCode", "I");
     jMetaStateField = geckoEvent.getField("mMetaState", "I");
     jFlagsField = geckoEvent.getField("mFlags", "I");
-    jUnicodeCharField = geckoEvent.getField("mUnicodeChar", "I");
-    jBaseUnicodeCharField = geckoEvent.getField("mBaseUnicodeChar", "I");
-    jDOMPrintableKeyValueField = geckoEvent.getField("mDOMPrintableKeyValue", "I");
-    jRepeatCountField = geckoEvent.getField("mRepeatCount", "I");
     jCountField = geckoEvent.getField("mCount", "I");
-    jStartField = geckoEvent.getField("mStart", "I");
-    jEndField = geckoEvent.getField("mEnd", "I");
     jPointerIndexField = geckoEvent.getField("mPointerIndex", "I");
-    jRangeTypeField = geckoEvent.getField("mRangeType", "I");
-    jRangeStylesField = geckoEvent.getField("mRangeStyles", "I");
-    jRangeLineStyleField = geckoEvent.getField("mRangeLineStyle", "I");
-    jRangeBoldLineField = geckoEvent.getField("mRangeBoldLine", "Z");
-    jRangeForeColorField = geckoEvent.getField("mRangeForeColor", "I");
-    jRangeBackColorField = geckoEvent.getField("mRangeBackColor", "I");
-    jRangeLineColorField = geckoEvent.getField("mRangeLineColor", "I");
     jLocationField = geckoEvent.getField("mLocation", "Landroid/location/Location;");
     jConnectionTypeField = geckoEvent.getField("mConnectionType", "I");
     jIsWifiField = geckoEvent.getField("mIsWifi", "Z");
     jDHCPGatewayField = geckoEvent.getField("mDHCPGateway", "I");
     jScreenOrientationField = geckoEvent.getField("mScreenOrientation", "S");
     jScreenAngleField = geckoEvent.getField("mScreenAngle", "S");
     jByteBufferField = geckoEvent.getField("mBuffer", "Ljava/nio/ByteBuffer;");
     jWidthField = geckoEvent.getField("mWidth", "I");
@@ -393,31 +362,16 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
     mType = jenv->GetIntField(jobj, jTypeField);
     mAckNeeded = jenv->GetBooleanField(jobj, jAckNeededField);
 
     switch (mType) {
         case SIZE_CHANGED:
             ReadPointArray(mPoints, jenv, jPoints, 2);
             break;
 
-        case KEY_EVENT:
-        case IME_KEY_EVENT:
-            mTime = jenv->GetLongField(jobj, jTimeField);
-            mMetaState = jenv->GetIntField(jobj, jMetaStateField);
-            mFlags = jenv->GetIntField(jobj, jFlagsField);
-            mKeyCode = jenv->GetIntField(jobj, jKeyCodeField);
-            mScanCode = jenv->GetIntField(jobj, jScanCodeField);
-            mUnicodeChar = jenv->GetIntField(jobj, jUnicodeCharField);
-            mBaseUnicodeChar = jenv->GetIntField(jobj, jBaseUnicodeCharField);
-            mDOMPrintableKeyValue =
-                jenv->GetIntField(jobj, jDOMPrintableKeyValueField);
-            mRepeatCount = jenv->GetIntField(jobj, jRepeatCountField);
-            ReadCharactersField(jenv);
-            break;
-
         case NATIVE_GESTURE_EVENT:
             mTime = jenv->GetLongField(jobj, jTimeField);
             mMetaState = jenv->GetIntField(jobj, jMetaStateField);
             mCount = jenv->GetIntField(jobj, jCountField);
             ReadPointArray(mPoints, jenv, jPoints, mCount);
             mX = jenv->GetDoubleField(jobj, jXField);
 
             break;
@@ -433,40 +387,16 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
             ReadFloatArray(mOrientations, jenv, jOrientations, mCount);
             ReadFloatArray(mPressures, jenv, jPressures, mCount);
             ReadIntArray(mToolTypes, jenv, jToolTypes, mCount);
             ReadPointArray(mPoints, jenv, jPoints, mCount);
             ReadIntArray(mPointIndicies, jenv, jPointIndicies, mCount);
 
             break;
 
-        case IME_EVENT:
-            mStart = jenv->GetIntField(jobj, jStartField);
-            mEnd = jenv->GetIntField(jobj, jEndField);
-
-            if (mAction == IME_REPLACE_TEXT ||
-                    mAction == IME_COMPOSE_TEXT) {
-                ReadCharactersField(jenv);
-            } else if (mAction == IME_UPDATE_COMPOSITION ||
-                    mAction == IME_ADD_COMPOSITION_RANGE) {
-                mRangeType = jenv->GetIntField(jobj, jRangeTypeField);
-                mRangeStyles = jenv->GetIntField(jobj, jRangeStylesField);
-                mRangeLineStyle =
-                    jenv->GetIntField(jobj, jRangeLineStyleField);
-                mRangeBoldLine =
-                    jenv->GetBooleanField(jobj, jRangeBoldLineField);
-                mRangeForeColor =
-                    jenv->GetIntField(jobj, jRangeForeColorField);
-                mRangeBackColor =
-                    jenv->GetIntField(jobj, jRangeBackColorField);
-                mRangeLineColor =
-                    jenv->GetIntField(jobj, jRangeLineColorField);
-            }
-            break;
-
         case SENSOR_EVENT:
              mX = jenv->GetDoubleField(jobj, jXField);
              mY = jenv->GetDoubleField(jobj, jYField);
              mZ = jenv->GetDoubleField(jobj, jZField);
              mW = jenv->GetDoubleField(jobj, jWField);
              mFlags = jenv->GetIntField(jobj, jFlagsField);
              mMetaState = jenv->GetIntField(jobj, jMetaStateField);
              break;
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -454,23 +454,16 @@ public:
     static void InitGeckoEventClass(JNIEnv *jEnv);
 
     static AndroidGeckoEvent* MakeNativePoke() {
         AndroidGeckoEvent *event = new AndroidGeckoEvent();
         event->Init(NATIVE_POKE);
         return event;
     }
 
-    static AndroidGeckoEvent* MakeIMEEvent(int aAction) {
-        AndroidGeckoEvent *event = new AndroidGeckoEvent();
-        event->Init(IME_EVENT);
-        event->mAction = aAction;
-        return event;
-    }
-
     static AndroidGeckoEvent* MakeFromJavaObject(JNIEnv *jenv, jobject jobj) {
         AndroidGeckoEvent *event = new AndroidGeckoEvent();
         event->Init(jenv, jobj);
         return event;
     }
 
     static AndroidGeckoEvent* CopyResizeEvent(AndroidGeckoEvent *aResizeEvent) {
         AndroidGeckoEvent *event = new AndroidGeckoEvent();
@@ -503,19 +496,16 @@ public:
         event->mApzEventStatus = aEventStatus;
         return event;
     }
 
     bool IsInputEvent() const {
         return mType == AndroidGeckoEvent::MOTION_EVENT ||
             mType == AndroidGeckoEvent::NATIVE_GESTURE_EVENT ||
             mType == AndroidGeckoEvent::LONG_PRESS ||
-            mType == AndroidGeckoEvent::KEY_EVENT ||
-            mType == AndroidGeckoEvent::IME_EVENT ||
-            mType == AndroidGeckoEvent::IME_KEY_EVENT ||
             mType == AndroidGeckoEvent::APZ_INPUT_EVENT;
     }
 
     int Action() { return mAction; }
     int Type() { return mType; }
     bool AckNeeded() { return mAckNeeded; }
     int64_t Time() { return mTime; }
     const nsTArray<nsIntPoint>& Points() { return mPoints; }
@@ -527,40 +517,25 @@ public:
     double X() { return mX; }
     double Y() { return mY; }
     double Z() { return mZ; }
     double W() { return mW; }
     const nsIntRect& Rect() { return mRect; }
     nsAString& Characters() { return mCharacters; }
     nsAString& CharactersExtra() { return mCharactersExtra; }
     nsAString& Data() { return mData; }
-    int KeyCode() { return mKeyCode; }
-    int ScanCode() { return mScanCode; }
     int MetaState() { return mMetaState; }
     Modifiers DOMModifiers() const;
     bool IsAltPressed() const { return (mMetaState & AMETA_ALT_MASK) != 0; }
     bool IsShiftPressed() const { return (mMetaState & AMETA_SHIFT_MASK) != 0; }
     bool IsCtrlPressed() const { return (mMetaState & AMETA_CTRL_MASK) != 0; }
     bool IsMetaPressed() const { return (mMetaState & AMETA_META_MASK) != 0; }
     int Flags() { return mFlags; }
-    int UnicodeChar() { return mUnicodeChar; }
-    int BaseUnicodeChar() { return mBaseUnicodeChar; }
-    int DOMPrintableKeyValue() { return mDOMPrintableKeyValue; }
-    int RepeatCount() const { return mRepeatCount; }
     int Count() { return mCount; }
-    int Start() { return mStart; }
-    int End() { return mEnd; }
     int PointerIndex() { return mPointerIndex; }
-    int RangeType() { return mRangeType; }
-    int RangeStyles() { return mRangeStyles; }
-    int RangeLineStyle() { return mRangeLineStyle; }
-    bool RangeBoldLine() { return mRangeBoldLine; }
-    int RangeForeColor() { return mRangeForeColor; }
-    int RangeBackColor() { return mRangeBackColor; }
-    int RangeLineColor() { return mRangeLineColor; }
     nsGeoPosition* GeoPosition() { return mGeoPosition; }
     int ConnectionType() { return mConnectionType; }
     bool IsWifi() { return mIsWifi; }
     int DHCPGateway() { return mDHCPGateway; }
     short ScreenOrientation() { return mScreenOrientation; }
     short ScreenAngle() { return mScreenAngle; }
     RefCountedJavaObject* ByteBuffer() { return mByteBuffer; }
     int Width() { return mWidth; }
@@ -589,24 +564,17 @@ protected:
     nsTArray<nsIntPoint> mPoints;
     nsTArray<nsIntPoint> mPointRadii;
     nsTArray<int> mPointIndicies;
     nsTArray<float> mOrientations;
     nsTArray<float> mPressures;
     nsTArray<int> mToolTypes;
     nsIntRect mRect;
     int mFlags, mMetaState;
-    int mKeyCode, mScanCode;
-    int mUnicodeChar, mBaseUnicodeChar, mDOMPrintableKeyValue;
-    int mRepeatCount;
     int mCount;
-    int mStart, mEnd;
-    int mRangeType, mRangeStyles, mRangeLineStyle;
-    bool mRangeBoldLine;
-    int mRangeForeColor, mRangeBackColor, mRangeLineColor;
     double mX, mY, mZ, mW;
     int mPointerIndex;
     nsString mCharacters, mCharactersExtra, mData;
     RefPtr<nsGeoPosition> mGeoPosition;
     int mConnectionType;
     bool mIsWifi;
     int mDHCPGateway;
     short mScreenOrientation;
@@ -657,40 +625,24 @@ protected:
     static jfieldID jToolTypes;
     static jfieldID jPointRadii;
     static jfieldID jXField;
     static jfieldID jYField;
     static jfieldID jZField;
     static jfieldID jWField;
     static jfieldID jDistanceField;
     static jfieldID jRectField;
-    static jfieldID jNativeWindowField;
 
     static jfieldID jCharactersField;
     static jfieldID jCharactersExtraField;
     static jfieldID jDataField;
-    static jfieldID jDOMPrintableKeyValueField;
-    static jfieldID jKeyCodeField;
-    static jfieldID jScanCodeField;
     static jfieldID jMetaStateField;
     static jfieldID jFlagsField;
     static jfieldID jCountField;
-    static jfieldID jStartField;
-    static jfieldID jEndField;
     static jfieldID jPointerIndexField;
-    static jfieldID jUnicodeCharField;
-    static jfieldID jBaseUnicodeCharField;
-    static jfieldID jRepeatCountField;
-    static jfieldID jRangeTypeField;
-    static jfieldID jRangeStylesField;
-    static jfieldID jRangeLineStyleField;
-    static jfieldID jRangeBoldLineField;
-    static jfieldID jRangeForeColorField;
-    static jfieldID jRangeBackColorField;
-    static jfieldID jRangeLineColorField;
     static jfieldID jLocationField;
 
     static jfieldID jConnectionTypeField;
     static jfieldID jIsWifiField;
     static jfieldID jDHCPGatewayField;
 
     static jfieldID jScreenOrientationField;
     static jfieldID jScreenAngleField;
@@ -703,21 +655,19 @@ protected:
     static jfieldID jGamepadButtonField;
     static jfieldID jGamepadButtonPressedField;
     static jfieldID jGamepadButtonValueField;
     static jfieldID jGamepadValuesField;
 
 public:
     enum {
         NATIVE_POKE = 0,
-        KEY_EVENT = 1,
         MOTION_EVENT = 2,
         SENSOR_EVENT = 3,
         LOCATION_EVENT = 5,
-        IME_EVENT = 6,
         SIZE_CHANGED = 8,
         APP_BACKGROUNDING = 9,
         APP_FOREGROUNDING = 10,
         LOAD_URI = 12,
         NOOP = 15,
         FORCED_RESIZE = 16, // used internally in nsAppShell/nsWindow
         APZ_INPUT_EVENT = 17, // used internally in AndroidJNI/nsAppShell/nsWindow
         BROADCAST = 19,
@@ -725,17 +675,16 @@ public:
         VISITED = 21,
         NETWORK_CHANGED = 22,
         THUMBNAIL = 25,
         SCREENORIENTATION_CHANGED = 27,
         COMPOSITOR_CREATE = 28,
         COMPOSITOR_PAUSE = 29,
         COMPOSITOR_RESUME = 30,
         NATIVE_GESTURE_EVENT = 31,
-        IME_KEY_EVENT = 32,
         CALL_OBSERVER = 33,
         REMOVE_OBSERVER = 34,
         LOW_MEMORY = 35,
         NETWORK_LINK_CHANGE = 36,
         TELEMETRY_HISTOGRAM_ADD = 37,
         ADD_OBSERVER = 38,
         TELEMETRY_UI_SESSION_START = 42,
         TELEMETRY_UI_SESSION_STOP = 43,
@@ -752,32 +701,16 @@ public:
         MEMORY_PRESSURE_NONE = 0,
         MEMORY_PRESSURE_CLEANUP = 1,
         MEMORY_PRESSURE_LOW = 2,
         MEMORY_PRESSURE_MEDIUM = 3,
         MEMORY_PRESSURE_HIGH = 4
     };
 
     enum {
-        // Internal Gecko events
-        IME_FLUSH_CHANGES = -2,
-        IME_UPDATE_CONTEXT = -1,
-        // Events from Java to Gecko
-        IME_SYNCHRONIZE = 0,
-        IME_REPLACE_TEXT = 1,
-        IME_SET_SELECTION = 2,
-        IME_ADD_COMPOSITION_RANGE = 3,
-        IME_UPDATE_COMPOSITION = 4,
-        IME_REMOVE_COMPOSITION = 5,
-        IME_ACKNOWLEDGE_FOCUS = 6,
-        IME_COMPOSE_TEXT = 7,
-        dummy_ime_enum_list_end
-    };
-
-    enum {
         ACTION_GAMEPAD_ADDED = 1,
         ACTION_GAMEPAD_REMOVED = 2
     };
 
     enum {
         ACTION_GAMEPAD_BUTTON = 1,
         ACTION_GAMEPAD_AXES = 2
     };
--- a/widget/android/GeneratedJNINatives.h
+++ b/widget/android/GeneratedJNINatives.h
@@ -47,16 +47,63 @@ public:
                 ::template Wrap<&Impl::NotifyAlarmFired>)
     };
 };
 
 template<class Impl>
 constexpr JNINativeMethod AlarmReceiver::Natives<Impl>::methods[];
 
 template<class Impl>
+class GeckoEditable::Natives : public mozilla::jni::NativeImpl<GeckoEditable, Impl>
+{
+public:
+    static constexpr JNINativeMethod methods[] = {
+
+        mozilla::jni::MakeNativeMethod<GeckoEditable::DisposeNative_t>(
+                mozilla::jni::NativeStub<GeckoEditable::DisposeNative_t, Impl>
+                ::template Wrap<&Impl::DisposeNative>),
+
+        mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeAcknowledgeFocus_t>(
+                mozilla::jni::NativeStub<GeckoEditable::OnImeAcknowledgeFocus_t, Impl>
+                ::template Wrap<&Impl::OnImeAcknowledgeFocus>),
+
+        mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeAddCompositionRange_t>(
+                mozilla::jni::NativeStub<GeckoEditable::OnImeAddCompositionRange_t, Impl>
+                ::template Wrap<&Impl::OnImeAddCompositionRange>),
+
+        mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeRemoveComposition_t>(
+                mozilla::jni::NativeStub<GeckoEditable::OnImeRemoveComposition_t, Impl>
+                ::template Wrap<&Impl::OnImeRemoveComposition>),
+
+        mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeReplaceText_t>(
+                mozilla::jni::NativeStub<GeckoEditable::OnImeReplaceText_t, Impl>
+                ::template Wrap<&Impl::OnImeReplaceText>),
+
+        mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeSetSelection_t>(
+                mozilla::jni::NativeStub<GeckoEditable::OnImeSetSelection_t, Impl>
+                ::template Wrap<&Impl::OnImeSetSelection>),
+
+        mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeSynchronize_t>(
+                mozilla::jni::NativeStub<GeckoEditable::OnImeSynchronize_t, Impl>
+                ::template Wrap<&Impl::OnImeSynchronize>),
+
+        mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeUpdateComposition_t>(
+                mozilla::jni::NativeStub<GeckoEditable::OnImeUpdateComposition_t, Impl>
+                ::template Wrap<&Impl::OnImeUpdateComposition>),
+
+        mozilla::jni::MakeNativeMethod<GeckoEditable::OnKeyEvent_t>(
+                mozilla::jni::NativeStub<GeckoEditable::OnKeyEvent_t, Impl>
+                ::template Wrap<&Impl::OnKeyEvent>)
+    };
+};
+
+template<class Impl>
+constexpr JNINativeMethod GeckoEditable::Natives<Impl>::methods[];
+
+template<class Impl>
 class GeckoJavaSampler::Natives : public mozilla::jni::NativeImpl<GeckoJavaSampler, Impl>
 {
 public:
     static constexpr JNINativeMethod methods[] = {
 
         mozilla::jni::MakeNativeMethod<GeckoJavaSampler::GetProfilerTime_t>(
                 mozilla::jni::NativeStub<GeckoJavaSampler::GetProfilerTime_t, Impl>
                 ::template Wrap<&Impl::GetProfilerTime>)
@@ -149,16 +196,20 @@ template<class Impl>
 constexpr JNINativeMethod GeckoThread::Natives<Impl>::methods[];
 
 template<class Impl>
 class GeckoView::Window::Natives : public mozilla::jni::NativeImpl<Window, Impl>
 {
 public:
     static constexpr JNINativeMethod methods[] = {
 
+        mozilla::jni::MakeNativeMethod<GeckoView::Window::Close_t>(
+                mozilla::jni::NativeStub<GeckoView::Window::Close_t, Impl>
+                ::template Wrap<&Impl::Close>),
+
         mozilla::jni::MakeNativeMethod<GeckoView::Window::DisposeNative_t>(
                 mozilla::jni::NativeStub<GeckoView::Window::DisposeNative_t, Impl>
                 ::template Wrap<&Impl::DisposeNative>),
 
         mozilla::jni::MakeNativeMethod<GeckoView::Window::Open_t>(
                 mozilla::jni::NativeStub<GeckoView::Window::Open_t, Impl>
                 ::template Wrap<&Impl::Open>),
 
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -731,32 +731,67 @@ constexpr char GeckoEditable::name[];
 constexpr char GeckoEditable::New_t::name[];
 constexpr char GeckoEditable::New_t::signature[];
 
 auto GeckoEditable::New() -> GeckoEditable::LocalRef
 {
     return mozilla::jni::Constructor<New_t>::Call(nullptr, nullptr);
 }
 
+constexpr char GeckoEditable::DisposeNative_t::name[];
+constexpr char GeckoEditable::DisposeNative_t::signature[];
+
 constexpr char GeckoEditable::NotifyIME_t::name[];
 constexpr char GeckoEditable::NotifyIME_t::signature[];
 
 auto GeckoEditable::NotifyIME(int32_t a0) const -> void
 {
     return mozilla::jni::Method<NotifyIME_t>::Call(this, nullptr, a0);
 }
 
 constexpr char GeckoEditable::NotifyIMEContext_t::name[];
 constexpr char GeckoEditable::NotifyIMEContext_t::signature[];
 
 auto GeckoEditable::NotifyIMEContext(int32_t a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2, mozilla::jni::String::Param a3) const -> void
 {
     return mozilla::jni::Method<NotifyIMEContext_t>::Call(this, nullptr, a0, a1, a2, a3);
 }
 
+constexpr char GeckoEditable::OnDestroy_t::name[];
+constexpr char GeckoEditable::OnDestroy_t::signature[];
+
+auto GeckoEditable::OnDestroy() const -> void
+{
+    return mozilla::jni::Method<OnDestroy_t>::Call(this, nullptr);
+}
+
+constexpr char GeckoEditable::OnImeAcknowledgeFocus_t::name[];
+constexpr char GeckoEditable::OnImeAcknowledgeFocus_t::signature[];
+
+constexpr char GeckoEditable::OnImeAddCompositionRange_t::name[];
+constexpr char GeckoEditable::OnImeAddCompositionRange_t::signature[];
+
+constexpr char GeckoEditable::OnImeRemoveComposition_t::name[];
+constexpr char GeckoEditable::OnImeRemoveComposition_t::signature[];
+
+constexpr char GeckoEditable::OnImeReplaceText_t::name[];
+constexpr char GeckoEditable::OnImeReplaceText_t::signature[];
+
+constexpr char GeckoEditable::OnImeSetSelection_t::name[];
+constexpr char GeckoEditable::OnImeSetSelection_t::signature[];
+
+constexpr char GeckoEditable::OnImeSynchronize_t::name[];
+constexpr char GeckoEditable::OnImeSynchronize_t::signature[];
+
+constexpr char GeckoEditable::OnImeUpdateComposition_t::name[];
+constexpr char GeckoEditable::OnImeUpdateComposition_t::signature[];
+
+constexpr char GeckoEditable::OnKeyEvent_t::name[];
+constexpr char GeckoEditable::OnKeyEvent_t::signature[];
+
 constexpr char GeckoEditable::OnSelectionChange_t::name[];
 constexpr char GeckoEditable::OnSelectionChange_t::signature[];
 
 auto GeckoEditable::OnSelectionChange(int32_t a0, int32_t a1) const -> void
 {
     return mozilla::jni::Method<OnSelectionChange_t>::Call(this, nullptr, a0, a1);
 }
 
@@ -1062,16 +1097,19 @@ constexpr char GeckoView::Window::name[]
 constexpr char GeckoView::Window::New_t::name[];
 constexpr char GeckoView::Window::New_t::signature[];
 
 auto GeckoView::Window::New() -> Window::LocalRef
 {
     return mozilla::jni::Constructor<New_t>::Call(nullptr, nullptr);
 }
 
+constexpr char GeckoView::Window::Close_t::name[];
+constexpr char GeckoView::Window::Close_t::signature[];
+
 constexpr char GeckoView::Window::DisposeNative_t::name[];
 constexpr char GeckoView::Window::DisposeNative_t::signature[];
 
 constexpr char GeckoView::Window::Open_t::name[];
 constexpr char GeckoView::Window::Open_t::signature[];
 
 constexpr char GeckoView::Window::SetLayerClient_t::name[];
 constexpr char GeckoView::Window::SetLayerClient_t::signature[];
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -1752,16 +1752,31 @@ public:
         static const bool isMultithreaded = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     static auto New() -> GeckoEditable::LocalRef;
 
 public:
+    struct DisposeNative_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "disposeNative";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+public:
     struct NotifyIME_t {
         typedef GeckoEditable Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t> Args;
         static constexpr char name[] = "notifyIME";
         static constexpr char signature[] =
@@ -1791,16 +1806,181 @@ public:
         static const bool isMultithreaded = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     auto NotifyIMEContext(int32_t, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param) const -> void;
 
 public:
+    struct OnDestroy_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "onDestroy";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+    auto OnDestroy() const -> void;
+
+public:
+    struct OnImeAcknowledgeFocus_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "onImeAcknowledgeFocus";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+public:
+    struct OnImeAddCompositionRange_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                bool,
+                int32_t,
+                int32_t,
+                int32_t> Args;
+        static constexpr char name[] = "onImeAddCompositionRange";
+        static constexpr char signature[] =
+                "(IIIIIZIII)V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+public:
+    struct OnImeRemoveComposition_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "onImeRemoveComposition";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+public:
+    struct OnImeReplaceText_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t,
+                int32_t,
+                mozilla::jni::String::Param,
+                bool> Args;
+        static constexpr char name[] = "onImeReplaceText";
+        static constexpr char signature[] =
+                "(IILjava/lang/String;Z)V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+public:
+    struct OnImeSetSelection_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t,
+                int32_t> Args;
+        static constexpr char name[] = "onImeSetSelection";
+        static constexpr char signature[] =
+                "(II)V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+public:
+    struct OnImeSynchronize_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "onImeSynchronize";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+public:
+    struct OnImeUpdateComposition_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t,
+                int32_t> Args;
+        static constexpr char name[] = "onImeUpdateComposition";
+        static constexpr char signature[] =
+                "(II)V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+public:
+    struct OnKeyEvent_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int64_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                bool> Args;
+        static constexpr char name[] = "onKeyEvent";
+        static constexpr char signature[] =
+                "(IIIIJIIIIIZ)V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+public:
     struct OnSelectionChange_t {
         typedef GeckoEditable Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 int32_t> Args;
         static constexpr char name[] = "onSelectionChange";
@@ -1848,16 +2028,18 @@ public:
         static const bool isStatic = false;
         static const bool isMultithreaded = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     auto OnViewChange(mozilla::jni::Object::Param) const -> void;
 
+public:
+    template<class Impl> class Natives;
 };
 
 class GeckoEditableListener : public mozilla::jni::Class<GeckoEditableListener>
 {
 public:
     typedef mozilla::jni::Ref<GeckoEditableListener> Ref;
     typedef mozilla::jni::LocalRef<GeckoEditableListener> LocalRef;
     typedef mozilla::jni::GlobalRef<GeckoEditableListener> GlobalRef;
@@ -2770,16 +2952,31 @@ public:
         static const bool isMultithreaded = true;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     static auto New() -> Window::LocalRef;
 
 public:
+    struct Close_t {
+        typedef Window Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "close";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+public:
     struct DisposeNative_t {
         typedef Window Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "disposeNative";
         static constexpr char signature[] =
                 "()V";
new file mode 100644
--- /dev/null
+++ b/widget/android/bindings/KeyEvent-classes.txt
@@ -0,0 +1,1 @@
+android.view.KeyEvent
--- a/widget/android/bindings/Makefile.in
+++ b/widget/android/bindings/Makefile.in
@@ -21,14 +21,15 @@ sdk_processor := \
 
 %.cpp %.h: $(ANDROID_SDK)/android.jar %-classes.txt
 	$(sdk_processor) $(ANDROID_SDK)/android.jar $(srcdir)/$*-classes.txt $(CURDIR) $* 16
 
 # We'd like these to be defined in a future GENERATED_EXPORTS list.
 bindings_exports_FILES := \
 	AndroidRect.h \
   Bundle.h \
+  KeyEvent.h \
   MediaCodec.h \
   SurfaceTexture.h \
   $(NULL)
 bindings_exports_DEST = $(DIST)/include
 bindings_exports_TARGET := export
 INSTALL_TARGETS += bindings_exports
--- a/widget/android/bindings/moz.build
+++ b/widget/android/bindings/moz.build
@@ -4,16 +4,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # List of stems to generate .cpp and .h files for.  To add a stem, add it to
 # this list and ensure that $(stem)-classes.txt exists in this directory.
 generated = [
     'AndroidRect',
     'Bundle',
+    'KeyEvent',
     'MediaCodec',
     'SurfaceTexture'
 ]
 
 SOURCES += ['!%s.cpp' % stem for stem in generated]
 
 # We'd like to add these to a future GENERATED_EXPORTS list, but for now we mark
 # them as generated here and manually install them in Makefile.in.
--- a/widget/android/jni/Natives.h
+++ b/widget/android/jni/Natives.h
@@ -251,16 +251,17 @@ struct ProxyArg<Ref<T>>
     static Type From(JNIEnv* env, JNIType val)
     {
         return Type(env, T::Ref::From(val));
     }
 };
 
 template<typename T> struct ProxyArg<const T&> : ProxyArg<T> {};
 template<> struct ProxyArg<Param<String>> : ProxyArg<Ref<String>> {};
+template<class T> struct ProxyArg<LocalRef<T>> : ProxyArg<Ref<T>> {};
 
 // ProxyNativeCall implements the functor object that is passed to
 // UsesNativeCallProxy::OnNativeCall
 template<class Impl, class Owner, bool IsStatic,
          bool HasThisArg /* has instance/class local ref in the call */,
          typename... Args>
 class ProxyNativeCall
 {
@@ -350,24 +351,24 @@ class ProxyNativeCall
 
 public:
     static const bool isStatic = IsStatic;
 
     ProxyNativeCall(ProxyNativeCall&&) = default;
     ProxyNativeCall(const ProxyNativeCall&) = default;
 
     // Get class ref for static calls or object ref for instance calls.
-    typename ThisArgClass::Param GetThisArg() { return mThisArg; }
+    typename ThisArgClass::Param GetThisArg() const { return mThisArg; }
 
     // Return if target is the given function pointer / pointer-to-member.
     // Because we can only compare pointers of the same type, we use a
     // templated overload that is chosen only if given a different type of
     // pointer than our target pointer type.
-    bool IsTarget(NativeCallType call) { return call == mNativeCall; }
-    template<typename T> bool IsTarget(T&&) { return false; }
+    bool IsTarget(NativeCallType call) const { return call == mNativeCall; }
+    template<typename T> bool IsTarget(T&&) const { return false; }
 
     void operator()()
     {
         JNIEnv* const env = GetEnvForThread();
         typename ThisArgClass::LocalRef thisArg(env, mThisArg);
         Call<IsStatic, HasThisArg>(
                 thisArg, typename IndexSequenceFor<Args...>::Type());
 
@@ -510,30 +511,29 @@ public:
             return;
         }
         auto self = Owner::LocalRef::Adopt(env, instance);
         (impl->*Method)(self, TypeAdapter<Args>::ToNative(env, args)...);
         self.Forget();
     }
 
     // Overload for DisposeNative
-    template<void (NativeImpl<Owner, Impl>::*Method) (const typename Owner::LocalRef&)>
+    template<void (*DisposeNative) (const typename Owner::LocalRef&)>
     static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance)
     {
         if (mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value) {
-            Dispatch(ProxyNativeCall<Impl, Owner, /* IsStatic */ false,
-                    /* HasThisArg */ true>(Method, env, instance));
-            return;
-        }
-        Impl* const impl = NativePtr<Impl>::Get(env, instance);
-        if (!impl) {
+            auto cls = ClassObject::LocalRef::Adopt(
+                    env, env->GetObjectClass(instance));
+            Dispatch(ProxyNativeCall<Impl, Owner, /* IsStatic */ true,
+                    /* HasThisArg */ false, const typename Owner::LocalRef&>(
+                    DisposeNative, env, cls.Get(), instance));
             return;
         }
         auto self = Owner::LocalRef::Adopt(env, instance);
-        (impl->*Method)(self);
+        (Impl::DisposeNative)(self);
         self.Forget();
     }
 };
 
 // Specialization for static methods with non-void return type
 template<class Traits, class Impl, typename... Args>
 class NativeStubImpl<Traits, Impl, jni::Args<Args...>,
                      /* IsStatic = */ true, /* IsVoid = */ false>
@@ -673,24 +673,24 @@ protected:
     }
 
     // Get the C++ instance associated with a Java instance.
     // There is always a pending exception if the return value is nullptr.
     static Impl* GetNative(const typename Cls::LocalRef& instance) {
         return NativePtr<Impl>::Get(instance);
     }
 
+    static void DisposeNative(const typename Cls::LocalRef& instance) {
+        NativePtr<Impl>::Clear(instance);
+    }
+
     NativeImpl() {
         // Initialize on creation if not already initialized.
         Init();
     }
-
-    void DisposeNative(const typename Cls::LocalRef& instance) {
-        NativePtr<Impl>::Clear(instance);
-    }
 };
 
 // Define static member.
 template<class C, class I>
 bool NativeImpl<C, I>::sInited;
 
 } // namespace jni
 } // namespace mozilla
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -378,17 +378,17 @@ class nsAppShell::LegacyGeckoEvent : pub
     mozilla::UniquePtr<AndroidGeckoEvent> ae;
 
 public:
     LegacyGeckoEvent(AndroidGeckoEvent* e) : ae(e) {}
 
     void Run() override;
     void PostTo(mozilla::LinkedList<Event>& queue) override;
 
-    mozilla::HangMonitor::ActivityType ActivityType() const
+    Event::Type ActivityType() const override
     {
         return ae->IsInputEvent() ? mozilla::HangMonitor::kUIActivity
                                   : mozilla::HangMonitor::kGeneralActivity;
     }
 };
 
 void
 nsAppShell::PostEvent(AndroidGeckoEvent* event)
--- a/widget/android/nsAppShell.h
+++ b/widget/android/nsAppShell.h
@@ -28,34 +28,36 @@ void NotifyEvent();
 class nsWindow;
 
 class nsAppShell :
     public nsBaseAppShell
 {
 public:
     struct Event : mozilla::LinkedListElement<Event>
     {
+        typedef mozilla::HangMonitor::ActivityType Type;
+
         bool HasSameTypeAs(const Event* other) const
         {
             // Compare vtable addresses to determine same type.
             return *reinterpret_cast<const uintptr_t*>(this)
                     == *reinterpret_cast<const uintptr_t*>(other);
         }
 
         virtual ~Event() {}
         virtual void Run() = 0;
 
         virtual void PostTo(mozilla::LinkedList<Event>& queue)
         {
             queue.insertBack(this);
         }
 
-        virtual mozilla::HangMonitor::ActivityType ActivityType() const
+        virtual Type ActivityType() const
         {
-            return mozilla::HangMonitor::kGeneralActivity;
+            return Type::kGeneralActivity;
         }
     };
 
     class LegacyGeckoEvent;
 
     template<typename T>
     class LambdaEvent : public Event
     {
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -8,37 +8,40 @@
 #include <unistd.h>
 
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextComposition.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
+#include "mozilla/TypeTraits.h"
 #include "mozilla/WeakPtr.h"
 
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/unused.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/layers/RenderTrace.h"
 #include <algorithm>
 
 using mozilla::dom::ContentParent;
 using mozilla::dom::ContentChild;
 using mozilla::unused;
 
 #include "nsWindow.h"
 
+#include "nsIBaseWindow.h"
 #include "nsIObserverService.h"
 #include "nsISelection.h"
 #include "nsISupportsArray.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIWidgetListener.h"
 #include "nsIWindowWatcher.h"
+#include "nsIXULWindow.h"
 
 #include "nsAppShell.h"
 #include "nsFocusManager.h"
 #include "nsIdleService.h"
 #include "nsLayoutUtils.h"
 #include "nsViewManager.h"
 
 #include "WidgetUtils.h"
@@ -63,16 +66,17 @@ using mozilla::unused;
 #include "AndroidContentController.h"
 
 #include "nsTArray.h"
 
 #include "AndroidBridge.h"
 #include "AndroidBridgeUtilities.h"
 #include "android_npapi.h"
 #include "GeneratedJNINatives.h"
+#include "KeyEvent.h"
 
 #include "imgIEncoder.h"
 
 #include "nsString.h"
 #include "GeckoProfiler.h" // For PROFILER_LABEL
 #include "nsIXULRuntime.h"
 
 using namespace mozilla;
@@ -128,19 +132,16 @@ public:
 
         return NS_OK;
     }
 };
 
 NS_IMPL_ISUPPORTS(ContentCreationNotifier,
                   nsIObserver)
 
-static bool gMenu;
-static bool gMenuConsumed;
-
 // All the toplevel windows that have been created; these are in
 // stacking order, so the window at gAndroidBounds[0] is the topmost
 // one.
 static nsTArray<nsWindow*> gTopLevelWindows;
 
 // FIXME: because we don't support separate nsWindow for each GeckoView
 // yet, we have to attach a new GeckoView to an existing nsWindow if it
 // exists. Eventually, an nsWindow will be opened/closed as each GeckoView
@@ -149,80 +150,253 @@ static nsWindow* gGeckoViewWindow;
 
 static bool sFailedToCreateGLContext = false;
 
 // Multitouch swipe thresholds in inches
 static const double SWIPE_MAX_PINCH_DELTA_INCHES = 0.4;
 static const double SWIPE_MIN_DISTANCE_INCHES = 0.6;
 
 
-class nsWindow::Natives
+class nsWindow::Natives final
     : public GeckoView::Window::Natives<Natives>
+    , public GeckoEditable::Natives<Natives>
     , public SupportsWeakPtr<Natives>
     , public UsesGeckoThreadProxy
 {
-    typedef GeckoView::Window::Natives<Natives> Base;
-
     nsWindow& window;
 
+    template<typename T>
+    class WindowEvent : public nsAppShell::LambdaEvent<T>
+    {
+        typedef nsAppShell::LambdaEvent<T> Base;
+
+        // Static calls are never stale since they don't need native instances.
+        template<bool Static>
+        typename mozilla::EnableIf<Static, bool>::Type IsStaleCall()
+        { return false; }
+
+        template<bool Static>
+        typename mozilla::EnableIf<!Static, bool>::Type IsStaleCall()
+        {
+            JNIEnv* const env = mozilla::jni::GetEnvForThread();
+            const auto& thisArg = Base::lambda.GetThisArg();
+
+            const auto natives = reinterpret_cast<mozilla::WeakPtr<Natives>*>(
+                    jni::GetNativeHandle(env, thisArg.Get()));
+            jni::HandleUncaughtException(env);
+
+            // The call is stale if the nsWindow has been destroyed on the
+            // Gecko side, but the Java object is still attached to it through
+            // a weak pointer. Stale calls should be discarded. Note that it's
+            // an error if natives is nullptr here; we return false but the
+            // native call will throw an error.
+            return natives && !natives->get();
+        }
+
+    public:
+        WindowEvent(T&& l) : Base(mozilla::Move(l)) {}
+
+        void Run() override
+        {
+            if (!IsStaleCall<T::isStatic>()) {
+                return Base::Run();
+            }
+        }
+
+        nsAppShell::Event::Type ActivityType() const override
+        {
+            // Events that result in user-visible changes count as UI events.
+            if (Base::lambda.IsTarget(&Natives::OnKeyEvent) ||
+                Base::lambda.IsTarget(&Natives::OnImeReplaceText) ||
+                Base::lambda.IsTarget(&Natives::OnImeSetSelection) ||
+                Base::lambda.IsTarget(&Natives::OnImeRemoveComposition) ||
+                Base::lambda.IsTarget(&Natives::OnImeUpdateComposition))
+            {
+                return nsAppShell::Event::Type::kUIActivity;
+            }
+            return Base::ActivityType();
+        }
+    };
+
 public:
+    typedef GeckoView::Window::Natives<Natives> Base;
+    typedef GeckoEditable::Natives<Natives> EditableBase;
+
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(Natives);
 
     template<typename Functor>
-    static void OnNativeCall(Functor&& call)
+    static void OnNativeCall(Functor&& aCall)
     {
-        if (call.IsTarget(&Open) && NS_IsMainThread()) {
+        if (aCall.IsTarget(&Open) && NS_IsMainThread()) {
             // Gecko state probably just switched to PROFILE_READY, and the
             // event loop is not running yet. Skip the event loop here so we
             // can get a head start on opening our window.
-            return call();
+            return aCall();
         }
-        return UsesGeckoThreadProxy::OnNativeCall(mozilla::Move(call));
+        return nsAppShell::gAppShell->PostEvent(mozilla::MakeUnique<
+                WindowEvent<Functor>>(mozilla::Move(aCall)));
     }
 
-    Natives(nsWindow* w) : window(*w) {}
-
-    // Detach and destroy the window that we created in Open().
-    void DisposeNative(const GeckoView::Window::LocalRef& instance);
-
+    Natives(nsWindow* aWindow)
+        : window(*aWindow)
+        , mIMERanges(new TextRangeArray())
+        , mIMEMaskEventsCount(1) // Mask IME events since there's no focus yet
+        , mIMEUpdatingContext(false)
+        , mIMESelectionChanged(false)
+        , mIMEMaskSelectionUpdate(false)
+    {}
+
+    ~Natives();
+
+    using Base::DisposeNative;
+    using EditableBase::DisposeNative;
+
+    /**
+     * GeckoView methods
+     */
+public:
     // Create and attach a window.
-    static void Open(const jni::ClassObject::LocalRef& cls,
-                     GeckoView::Window::Param gvWindow,
-                     GeckoView::Param view,
-                     int32_t width, int32_t height);
+    static void Open(const jni::ClassObject::LocalRef& aCls,
+                     GeckoView::Window::Param aWindow,
+                     GeckoView::Param aView,
+                     int32_t aWidth, int32_t aHeight);
 
     // Set the active layer client object
-    static void SetLayerClient(jni::Object::Param client)
+    static void SetLayerClient(jni::Object::Param aClient)
     {
         MOZ_ASSERT(NS_IsMainThread());
         AndroidBridge::Bridge()->SetLayerClient(
-                widget::GeckoLayerClient::Ref::From(client.Get()));
+                widget::GeckoLayerClient::Ref::From(aClient.Get()));
     }
+
+    // Close and destroy the nsWindow.
+    void Close();
+
+    /**
+     * GeckoEditable methods
+     */
+private:
+    /*
+        Rules for managing IME between Gecko and Java:
+
+        * Gecko controls the text content, and Java shadows the Gecko text
+           through text updates
+        * Java controls the selection, and Gecko shadows the Java selection
+           through set selection events
+        * Java controls the composition, and Gecko shadows the Java
+           composition through update composition events
+    */
+
+    struct IMETextChange final {
+        int32_t mStart, mOldEnd, mNewEnd;
+
+        IMETextChange() :
+            mStart(-1), mOldEnd(-1), mNewEnd(-1) {}
+
+        IMETextChange(const IMENotification& aIMENotification)
+            : mStart(aIMENotification.mTextChangeData.mStartOffset)
+            , mOldEnd(aIMENotification.mTextChangeData.mRemovedEndOffset)
+            , mNewEnd(aIMENotification.mTextChangeData.mAddedEndOffset)
+        {
+            MOZ_ASSERT(aIMENotification.mMessage ==
+                           mozilla::widget::NOTIFY_IME_OF_TEXT_CHANGE,
+                       "IMETextChange initialized with wrong notification");
+            MOZ_ASSERT(aIMENotification.mTextChangeData.IsValid(),
+                       "The text change notification isn't initialized");
+            MOZ_ASSERT(aIMENotification.mTextChangeData.IsInInt32Range(),
+                       "The text change notification is out of range");
+        }
+
+        bool IsEmpty() const { return mStart < 0; }
+    };
+
+    // GeckoEditable instance used by this nsWindow;
+    mozilla::widget::GeckoEditable::GlobalRef mEditable;
+    nsAutoTArray<mozilla::UniquePtr<mozilla::WidgetEvent>, 8> mIMEKeyEvents;
+    nsAutoTArray<IMETextChange, 4> mIMETextChanges;
+    InputContext mInputContext;
+    RefPtr<mozilla::TextRangeArray> mIMERanges;
+    int32_t mIMEMaskEventsCount; // Mask events when > 0
+    bool mIMEUpdatingContext;
+    bool mIMESelectionChanged;
+    bool mIMEMaskSelectionUpdate;
+
+    void SendIMEDummyKeyEvents();
+    void AddIMETextChange(const IMETextChange& aChange);
+    void PostFlushIMEChanges();
+    void FlushIMEChanges();
+
+public:
+    bool NotifyIME(const IMENotification& aIMENotification);
+    void SetInputContext(const InputContext& aContext,
+                         const InputContextAction& aAction);
+    InputContext GetInputContext();
+
+    // Handle an Android KeyEvent.
+    void OnKeyEvent(int32_t aAction, int32_t aKeyCode, int32_t aScanCode,
+                    int32_t aMetaState, int64_t aTime, int32_t aUnicodeChar,
+                    int32_t aBaseUnicodeChar, int32_t aDomPrintableKeyValue,
+                    int32_t aRepeatCount, int32_t aFlags,
+                    bool aIsSynthesizedImeKey);
+
+    // Synchronize Gecko thread with the InputConnection thread.
+    void OnImeSynchronize();
+
+    // Acknowledge focus change and send new text and selection.
+    void OnImeAcknowledgeFocus();
+
+    // Replace a range of text with new text.
+    void OnImeReplaceText(int32_t aStart, int32_t aEnd,
+                          jni::String::Param aText, bool aComposing);
+
+    // Set selection to a certain range.
+    void OnImeSetSelection(int32_t aStart, int32_t aEnd);
+
+    // Remove any active composition.
+    void OnImeRemoveComposition();
+
+    // Add styling for a range within the active composition.
+    void OnImeAddCompositionRange(int32_t aStart, int32_t aEnd,
+            int32_t aRangeType, int32_t aRangeStyle, int32_t aRangeLineStyle,
+            bool aRangeBoldLine, int32_t aRangeForeColor,
+            int32_t aRangeBackColor, int32_t aRangeLineColor);
+
+    // Update styling for the active composition using previous-added ranges.
+    void OnImeUpdateComposition(int32_t aStart, int32_t aEnd);
 };
 
+nsWindow::Natives::~Natives()
+{
+    // Disassociate our GeckoEditable instance with our native object.
+    // OnDestroy will call disposeNative after any pending native calls have
+    // been made.
+    MOZ_ASSERT(mEditable);
+    mEditable->OnDestroy();
+}
+
 void
-nsWindow::Natives::Open(const jni::ClassObject::LocalRef& cls,
-                        GeckoView::Window::Param gvWindow,
-                        GeckoView::Param view,
-                        int32_t width, int32_t height)
+nsWindow::Natives::Open(const jni::ClassObject::LocalRef& aCls,
+                        GeckoView::Window::Param aWindow,
+                        GeckoView::Param aView,
+                        int32_t aWidth, int32_t aHeight)
 {
     MOZ_ASSERT(NS_IsMainThread());
 
     PROFILER_LABEL("nsWindow", "Natives::Open",
                    js::ProfileEntry::Category::OTHER);
 
     if (gGeckoViewWindow) {
         // Should have been created the first time.
         MOZ_ASSERT(gGeckoViewWindow->mNatives);
 
         // Associate our previous GeckoEditable with the new GeckoView.
-        gGeckoViewWindow->mEditable->OnViewChange(view);
-
-        AttachNative(GeckoView::Window::LocalRef(cls.Env(), gvWindow),
-                     gGeckoViewWindow->mNatives.get());
+        gGeckoViewWindow->mNatives->mEditable->OnViewChange(aView);
+
+        Base::AttachNative(GeckoView::Window::LocalRef(aCls.Env(), aWindow),
+                           gGeckoViewWindow->mNatives.get());
         return;
     }
 
     nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
     MOZ_ASSERT(ww);
 
     nsAdoptingCString url = Preferences::GetCString("toolkit.defaultChromeURI");
     if (!url) {
@@ -233,65 +407,69 @@ nsWindow::Natives::Open(const jni::Class
             = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID);
     nsCOMPtr<nsISupportsPRInt32> widthArg
             = do_CreateInstance(NS_SUPPORTS_PRINT32_CONTRACTID);
     nsCOMPtr<nsISupportsPRInt32> heightArg
             = do_CreateInstance(NS_SUPPORTS_PRINT32_CONTRACTID);
 
     // Arguments are optional so it's okay if this fails.
     if (args && widthArg && heightArg &&
-            NS_SUCCEEDED(widthArg->SetData(width)) &&
-            NS_SUCCEEDED(heightArg->SetData(height)))
+            NS_SUCCEEDED(widthArg->SetData(aWidth)) &&
+            NS_SUCCEEDED(heightArg->SetData(aHeight)))
     {
         args->AppendElement(widthArg);
         args->AppendElement(heightArg);
     }
 
     nsCOMPtr<nsIDOMWindow> window;
     ww->OpenWindow(nullptr, url, "_blank", "chrome,dialog=no,all",
                    args, getter_AddRefs(window));
     MOZ_ASSERT(window);
 
     nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(window);
     MOZ_ASSERT(widget);
 
     gGeckoViewWindow = static_cast<nsWindow*>(widget.get());
-    gGeckoViewWindow->mNatives = mozilla::MakeUnique<Natives>(gGeckoViewWindow);
+    UniquePtr<Natives> natives = mozilla::MakeUnique<Natives>(gGeckoViewWindow);
 
     // Create GeckoEditable for the new nsWindow/GeckoView pair.
-    gGeckoViewWindow->mEditable = GeckoEditable::New();
-    gGeckoViewWindow->mEditable->OnViewChange(view);
-
-    AttachNative(GeckoView::Window::LocalRef(cls.Env(), gvWindow),
-                 gGeckoViewWindow->mNatives.get());
+    GeckoEditable::LocalRef editable = GeckoEditable::New();
+    EditableBase::AttachNative(editable, natives.get());
+    natives->mEditable = editable;
+    editable->OnViewChange(aView);
+
+    Base::AttachNative(GeckoView::Window::LocalRef(aCls.Env(), aWindow),
+                       natives.get());
+    gGeckoViewWindow->mNatives = mozilla::Move(natives);
 }
 
 void
-nsWindow::Natives::DisposeNative(const GeckoView::Window::LocalRef& instance)
+nsWindow::Natives::Close()
 {
-    // FIXME: because we don't support separate nsWindow for each GeckoView
-    // yet, we have to keep this window around in case another GeckoView
-    // wants to attach.
-    /*
-    if (mWidgetListener) {
-        nsCOMPtr<nsIXULWindow> xulWindow(mWidgetListener->GetXULWindow());
-        // GeckoView-created top-level windows should be a XUL window.
-        MOZ_ASSERT(xulWindow);
-        nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(xulWindow));
-        MOZ_ASSERT(baseWindow);
-        baseWindow->Destroy();
+    nsIWidgetListener* const widgetListener = window.mWidgetListener;
+
+    if (!widgetListener) {
+        return;
     }
-    */
-    Base::DisposeNative(instance);
+
+    nsCOMPtr<nsIXULWindow> xulWindow(widgetListener->GetXULWindow());
+    // GeckoView-created top-level windows should be a XUL window.
+    MOZ_ASSERT(xulWindow);
+
+    nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(xulWindow));
+    MOZ_ASSERT(baseWindow);
+
+    baseWindow->Destroy();
 }
 
 void
 nsWindow::InitNatives()
 {
-    nsWindow::Natives::Init();
+    nsWindow::Natives::Base::Init();
+    nsWindow::Natives::EditableBase::Init();
 }
 
 nsWindow*
 nsWindow::TopWindow()
 {
     if (!gTopLevelWindows.IsEmpty())
         return gTopLevelWindows[0];
     return nullptr;
@@ -325,21 +503,16 @@ nsWindow::DumpWindows(const nsTArray<nsW
         LogWindow(w, i, indent);
         DumpWindows(w->mChildren, indent+1);
     }
 }
 
 nsWindow::nsWindow() :
     mIsVisible(false),
     mParent(nullptr),
-    mIMEMaskSelectionUpdate(false),
-    mIMEMaskEventsCount(1), // Mask IME events since there's no focus yet
-    mIMERanges(new TextRangeArray()),
-    mIMEUpdatingContext(false),
-    mIMESelectionChanged(false),
     mAwaitingFullScreen(false),
     mIsFullScreen(false)
 {
 }
 
 nsWindow::~nsWindow()
 {
     gTopLevelWindows.RemoveElement(this);
@@ -405,16 +578,21 @@ nsWindow::Create(nsIWidget *aParent,
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::Destroy(void)
 {
     nsBaseWidget::mOnDestroyCalled = true;
 
+    if (mNatives) {
+        // Disassociate our native object with GeckoView.
+        mNatives = nullptr;
+    }
+
     while (mChildren.Length()) {
         // why do we still have children?
         ALOG("### Warning: Destroying window %p and reparenting child %p to null!", (void*)this, (void*)mChildren[0]);
         mChildren[0]->SetParent(nullptr);
     }
 
     if (IsTopLevel())
         gTopLevelWindows.RemoveElement(this);
@@ -1011,33 +1189,16 @@ nsWindow::OnGlobalAndroidEvent(AndroidGe
             break;
         }
 
         case AndroidGeckoEvent::NATIVE_GESTURE_EVENT: {
             win->OnNativeGestureEvent(ae);
             break;
         }
 
-        case AndroidGeckoEvent::KEY_EVENT:
-            win->UserActivity();
-            win->OnKeyEvent(ae);
-            break;
-
-        case AndroidGeckoEvent::IME_EVENT:
-            gGeckoViewWindow->UserActivity();
-            gGeckoViewWindow->OnIMEEvent(ae);
-            break;
-
-        case AndroidGeckoEvent::IME_KEY_EVENT:
-            // Keys synthesized by Java IME code are saved in the mIMEKeyEvents
-            // array until the next IME_REPLACE_TEXT event, at which point
-            // these keys are dispatched in sequence.
-            win->mIMEKeyEvents.AppendElement(*ae);
-            break;
-
         case AndroidGeckoEvent::COMPOSITOR_PAUSE:
             // The compositor gets paused when the app is about to go into the
             // background. While the compositor is paused, we need to ensure that
             // no layer tree updates (from draw events) occur, since the compositor
             // cannot make a GL context current in order to process updates.
             if (sCompositorChild) {
                 sCompositorChild->SendPause();
             }
@@ -1424,19 +1585,19 @@ static unsigned int ConvertAndroidKeyCod
         default:
             ALOG("ConvertAndroidKeyCodeToDOMKeyCode: "
                  "No DOM keycode for Android keycode %d", androidKeyCode);
         return 0;
     }
 }
 
 static KeyNameIndex
-ConvertAndroidKeyCodeToKeyNameIndex(AndroidGeckoEvent& aAndroidGeckoEvent)
+ConvertAndroidKeyCodeToKeyNameIndex(int keyCode, int action,
+                                    int domPrintableKeyValue)
 {
-    int keyCode = aAndroidGeckoEvent.KeyCode();
     // Special-case alphanumeric keycodes because they are most common.
     if (keyCode >= AKEYCODE_A && keyCode <= AKEYCODE_Z) {
         return KEY_NAME_INDEX_USE_STRING;
     }
 
     if (keyCode >= AKEYCODE_0 && keyCode <= AKEYCODE_9) {
         return KEY_NAME_INDEX_USE_STRING;
     }
@@ -1541,274 +1702,229 @@ ConvertAndroidKeyCodeToKeyNameIndex(Andr
 
         case AKEYCODE_MANNER_MODE:
         case AKEYCODE_3D_MODE:
         case AKEYCODE_CONTACTS:
             return KEY_NAME_INDEX_Unidentified;
 
         case AKEYCODE_UNKNOWN:
             MOZ_ASSERT(
-                aAndroidGeckoEvent.Action() != AKEY_EVENT_ACTION_MULTIPLE,
+                action != AKEY_EVENT_ACTION_MULTIPLE,
                 "Don't call this when action is AKEY_EVENT_ACTION_MULTIPLE!");
             // It's actually an unknown key if the action isn't ACTION_MULTIPLE.
             // However, it might cause text input.  So, let's check the value.
-            return aAndroidGeckoEvent.DOMPrintableKeyValue() ?
+            return domPrintableKeyValue ?
                 KEY_NAME_INDEX_USE_STRING : KEY_NAME_INDEX_Unidentified;
 
         default:
             ALOG("ConvertAndroidKeyCodeToKeyNameIndex: "
                  "No DOM key name index for Android keycode %d", keyCode);
             return KEY_NAME_INDEX_Unidentified;
     }
 }
 
 static CodeNameIndex
-ConvertAndroidScanCodeToCodeNameIndex(AndroidGeckoEvent& aAndroidGeckoEvent)
+ConvertAndroidScanCodeToCodeNameIndex(int scanCode)
 {
-    switch (aAndroidGeckoEvent.ScanCode()) {
+    switch (scanCode) {
 
 #define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
         case aNativeKey: return aCodeNameIndex;
 
 #include "NativeKeyToDOMCodeName.h"
 
 #undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
 
         default:
           return CODE_NAME_INDEX_UNKNOWN;
     }
 }
 
-static void InitPluginEvent(ANPEvent* pluginEvent, ANPKeyActions keyAction,
-                            AndroidGeckoEvent& key)
+static bool
+IsModifierKey(int32_t keyCode)
+{
+    using mozilla::widget::sdk::KeyEvent;
+    return keyCode == KeyEvent::KEYCODE_ALT_LEFT ||
+           keyCode == KeyEvent::KEYCODE_ALT_RIGHT ||
+           keyCode == KeyEvent::KEYCODE_SHIFT_LEFT ||
+           keyCode == KeyEvent::KEYCODE_SHIFT_RIGHT ||
+           keyCode == KeyEvent::KEYCODE_CTRL_LEFT ||
+           keyCode == KeyEvent::KEYCODE_CTRL_RIGHT ||
+           keyCode == KeyEvent::KEYCODE_META_LEFT ||
+           keyCode == KeyEvent::KEYCODE_META_RIGHT;
+}
+
+static Modifiers
+GetModifiers(int32_t metaState)
 {
-    int androidKeyCode = key.KeyCode();
-    uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(androidKeyCode);
-
-    int modifiers = 0;
-    if (key.IsAltPressed())
-      modifiers |= kAlt_ANPKeyModifier;
-    if (key.IsShiftPressed())
-      modifiers |= kShift_ANPKeyModifier;
-
-    pluginEvent->inSize = sizeof(ANPEvent);
-    pluginEvent->eventType = kKey_ANPEventType;
-    pluginEvent->data.key.action = keyAction;
-    pluginEvent->data.key.nativeCode = androidKeyCode;
-    pluginEvent->data.key.virtualCode = domKeyCode;
-    pluginEvent->data.key.unichar = key.UnicodeChar();
-    pluginEvent->data.key.modifiers = modifiers;
-    pluginEvent->data.key.repeatCount = key.RepeatCount();
+    using mozilla::widget::sdk::KeyEvent;
+    return (metaState & KeyEvent::META_ALT_MASK ? MODIFIER_ALT : 0)
+        | (metaState & KeyEvent::META_SHIFT_MASK ? MODIFIER_SHIFT : 0)
+        | (metaState & KeyEvent::META_CTRL_MASK ? MODIFIER_CONTROL : 0)
+        | (metaState & KeyEvent::META_META_MASK ? MODIFIER_META : 0)
+        | (metaState & KeyEvent::META_FUNCTION_ON ? MODIFIER_FN : 0)
+        | (metaState & KeyEvent::META_CAPS_LOCK_ON ? MODIFIER_CAPSLOCK : 0)
+        | (metaState & KeyEvent::META_NUM_LOCK_ON ? MODIFIER_NUMLOCK : 0)
+        | (metaState & KeyEvent::META_SCROLL_LOCK_ON ? MODIFIER_SCROLLLOCK : 0);
 }
 
-void
-nsWindow::InitKeyEvent(WidgetKeyboardEvent& event, AndroidGeckoEvent& key,
-                       ANPEvent* pluginEvent)
+static void
+InitKeyEvent(WidgetKeyboardEvent& event,
+             int32_t action, int32_t keyCode, int32_t scanCode,
+             int32_t metaState, int64_t time, int32_t unicodeChar,
+             int32_t baseUnicodeChar, int32_t domPrintableKeyValue,
+             int32_t repeatCount, int32_t flags)
 {
-    event.mKeyNameIndex = ConvertAndroidKeyCodeToKeyNameIndex(key);
-    if (event.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
-        int keyValue = key.DOMPrintableKeyValue();
-        if (keyValue) {
-            event.mKeyValue = static_cast<char16_t>(keyValue);
-        }
-    }
-    event.mCodeNameIndex = ConvertAndroidScanCodeToCodeNameIndex(key);
-    uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(key.KeyCode());
+    const uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(keyCode);
+    const int32_t charCode = unicodeChar ? unicodeChar : baseUnicodeChar;
+
+    event.modifiers = GetModifiers(metaState);
 
     if (event.mMessage == eKeyPress) {
         // Android gives us \n, so filter out some control characters.
-        int charCode = key.UnicodeChar();
-        if (!charCode) {
-            charCode = key.BaseUnicodeChar();
-        }
         event.isChar = (charCode >= ' ');
         event.charCode = event.isChar ? charCode : 0;
-        event.keyCode = (event.charCode > 0) ? 0 : domKeyCode;
+        event.keyCode = event.isChar ? 0 : domKeyCode;
         event.mPluginEvent.Clear();
-    } else {
-#ifdef DEBUG
-        if (event.mMessage != eKeyDown && event.mMessage != eKeyUp) {
-            ALOG("InitKeyEvent: unexpected event.mMessage %d", event.mMessage);
+
+        // For keypress, if the unicode char already has modifiers applied, we
+        // don't specify extra modifiers. If UnicodeChar() != BaseUnicodeChar()
+        // it means UnicodeChar() already has modifiers applied.
+        // Note that on Android 4.x, Alt modifier isn't set when the key input
+        // causes text input even while right Alt key is pressed.  However,
+        // this is necessary for Android 2.3 compatibility.
+        if (unicodeChar && unicodeChar != baseUnicodeChar) {
+            event.modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL
+                                              | MODIFIER_META);
         }
-#endif // DEBUG
-
-        // Flash will want a pluginEvent for keydown and keyup events.
-        ANPKeyActions action = event.mMessage == eKeyDown
-                             ? kDown_ANPKeyAction
-                             : kUp_ANPKeyAction;
-        InitPluginEvent(pluginEvent, action, key);
-
+
+    } else {
         event.isChar = false;
         event.charCode = 0;
         event.keyCode = domKeyCode;
-        event.mPluginEvent.Copy(*pluginEvent);
-    }
-
-    event.modifiers = key.DOMModifiers();
-    if (gMenu) {
-        event.modifiers |= MODIFIER_CONTROL;
-    }
-    // For keypress, if the unicode char already has modifiers applied, we
-    // don't specify extra modifiers. If UnicodeChar() != BaseUnicodeChar()
-    // it means UnicodeChar() already has modifiers applied.
-    // Note that on Android 4.x, Alt modifier isn't set when the key input
-    // causes text input even while right Alt key is pressed.  However, this
-    // is necessary for Android 2.3 compatibility.
-    if (event.mMessage == eKeyPress &&
-        key.UnicodeChar() && key.UnicodeChar() != key.BaseUnicodeChar()) {
-        event.modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL | MODIFIER_META);
+
+        ANPEvent pluginEvent;
+        pluginEvent.inSize = sizeof(pluginEvent);
+        pluginEvent.eventType = kKey_ANPEventType;
+        pluginEvent.data.key.action = event.mMessage == eKeyDown
+                ? kDown_ANPKeyAction : kUp_ANPKeyAction;
+        pluginEvent.data.key.nativeCode = keyCode;
+        pluginEvent.data.key.virtualCode = domKeyCode;
+        pluginEvent.data.key.unichar = charCode;
+        pluginEvent.data.key.modifiers =
+                (metaState & sdk::KeyEvent::META_SHIFT_MASK
+                        ? kShift_ANPKeyModifier : 0) |
+                (metaState & sdk::KeyEvent::META_ALT_MASK
+                        ? kAlt_ANPKeyModifier : 0);
+        pluginEvent.data.key.repeatCount = repeatCount;
+        event.mPluginEvent.Copy(pluginEvent);
     }
 
     event.mIsRepeat =
         (event.mMessage == eKeyDown || event.mMessage == eKeyPress) &&
-        (!!(key.Flags() & AKEY_EVENT_FLAG_LONG_PRESS) || !!key.RepeatCount());
+        ((flags & sdk::KeyEvent::FLAG_LONG_PRESS) || repeatCount);
+
+    event.mKeyNameIndex = ConvertAndroidKeyCodeToKeyNameIndex(
+            keyCode, action, domPrintableKeyValue);
+    event.mCodeNameIndex = ConvertAndroidScanCodeToCodeNameIndex(scanCode);
+
+    if (event.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
+            domPrintableKeyValue) {
+        event.mKeyValue = char16_t(domPrintableKeyValue);
+    }
+
     event.location =
         WidgetKeyboardEvent::ComputeLocationFromCodeValue(event.mCodeNameIndex);
-    event.time = key.Time();
-
-    if (gMenu)
-        gMenuConsumed = true;
+    event.time = time;
 }
 
 void
-nsWindow::HandleSpecialKey(AndroidGeckoEvent *ae)
+nsWindow::Natives::OnKeyEvent(int32_t aAction, int32_t aKeyCode,
+        int32_t aScanCode, int32_t aMetaState, int64_t aTime,
+        int32_t aUnicodeChar, int32_t aBaseUnicodeChar,
+        int32_t aDomPrintableKeyValue, int32_t aRepeatCount, int32_t aFlags,
+        bool aIsSynthesizedImeKey)
 {
-    RefPtr<nsWindow> kungFuDeathGrip(this);
-    nsCOMPtr<nsIAtom> command;
-    bool isDown = ae->Action() == AKEY_EVENT_ACTION_DOWN;
-    bool isLongPress = !!(ae->Flags() & AKEY_EVENT_FLAG_LONG_PRESS);
-    bool doCommand = false;
-    uint32_t keyCode = ae->KeyCode();
-
-    if (isDown) {
-        switch (keyCode) {
-            case AKEYCODE_BACK:
-                if (isLongPress) {
-                    command = nsGkAtoms::Clear;
-                    doCommand = true;
-                }
-                break;
-            case AKEYCODE_MENU:
-                gMenu = true;
-                gMenuConsumed = isLongPress;
-                break;
-        }
-    } else {
-        switch (keyCode) {
-            case AKEYCODE_BACK: {
-                // XXX Where is the keydown event for this??
-                WidgetKeyboardEvent pressEvent(true, eKeyPress, this);
-                ANPEvent pluginEvent;
-                InitKeyEvent(pressEvent, *ae, &pluginEvent);
-                DispatchEvent(&pressEvent);
-                return;
-            }
-            case AKEYCODE_MENU:
-                gMenu = false;
-                if (!gMenuConsumed) {
-                    command = nsGkAtoms::Menu;
-                    doCommand = true;
-                }
-                break;
-            case AKEYCODE_SEARCH:
-                command = nsGkAtoms::Search;
-                doCommand = true;
-                break;
-            default:
-                ALOG("Unknown special key code!");
-                return;
-        }
-    }
-    if (doCommand) {
-        WidgetCommandEvent event(true, nsGkAtoms::onAppCommand, command, this);
-        InitEvent(event);
-        DispatchEvent(&event);
-    }
-}
-
-void
-nsWindow::OnKeyEvent(AndroidGeckoEvent *ae)
-{
-    RefPtr<nsWindow> kungFuDeathGrip(this);
-    RemoveIMEComposition();
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    window.UserActivity();
+    window.RemoveIMEComposition();
+
     EventMessage msg;
-    switch (ae->Action()) {
-    case AKEY_EVENT_ACTION_DOWN:
+    if (aAction == sdk::KeyEvent::ACTION_DOWN) {
         msg = eKeyDown;
-        break;
-    case AKEY_EVENT_ACTION_UP:
+    } else if (aAction == sdk::KeyEvent::ACTION_UP) {
         msg = eKeyUp;
-        break;
-    case AKEY_EVENT_ACTION_MULTIPLE:
+    } else if (aAction == sdk::KeyEvent::ACTION_MULTIPLE) {
         // Keys with multiple action are handled in Java,
         // and we should never see one here
         MOZ_CRASH("Cannot handle key with multiple action");
-    default:
+    } else {
         ALOG("Unknown key action event!");
         return;
     }
 
-    bool firePress = ae->Action() == AKEY_EVENT_ACTION_DOWN;
-    switch (ae->KeyCode()) {
-    case AKEYCODE_SHIFT_LEFT:
-    case AKEYCODE_SHIFT_RIGHT:
-    case AKEYCODE_ALT_LEFT:
-    case AKEYCODE_ALT_RIGHT:
-    case AKEYCODE_CTRL_LEFT:
-    case AKEYCODE_CTRL_RIGHT:
-        firePress = false;
-        break;
-    case AKEYCODE_BACK:
-    case AKEYCODE_MENU:
-    case AKEYCODE_SEARCH:
-        HandleSpecialKey(ae);
+    nsEventStatus status = nsEventStatus_eIgnore;
+    WidgetKeyboardEvent event(true, msg, &window);
+    window.InitEvent(event, nullptr);
+    InitKeyEvent(event, aAction, aKeyCode, aScanCode, aMetaState, aTime,
+                 aUnicodeChar, aBaseUnicodeChar, aDomPrintableKeyValue,
+                 aRepeatCount, aFlags);
+
+    if (aIsSynthesizedImeKey) {
+        // Keys synthesized by Java IME code are saved in the mIMEKeyEvents
+        // array until the next IME_REPLACE_TEXT event, at which point
+        // these keys are dispatched in sequence.
+        mIMEKeyEvents.AppendElement(
+                mozilla::UniquePtr<WidgetEvent>(event.Duplicate()));
+    } else {
+        window.DispatchEvent(&event, status);
+    }
+
+    if (window.Destroyed() ||
+            status == nsEventStatus_eConsumeNoDefault ||
+            msg != eKeyDown || IsModifierKey(aKeyCode)) {
+        // Skip sending key press event.
         return;
     }
 
-    nsEventStatus status;
-    WidgetKeyboardEvent event(true, msg, this);
-    ANPEvent pluginEvent;
-    InitKeyEvent(event, *ae, &pluginEvent);
-    DispatchEvent(&event, status);
-
-    if (Destroyed())
-        return;
-    if (!firePress || status == nsEventStatus_eConsumeNoDefault) {
-        return;
+    WidgetKeyboardEvent pressEvent(true, eKeyPress, &window);
+    window.InitEvent(pressEvent, nullptr);
+    InitKeyEvent(pressEvent, aAction, aKeyCode, aScanCode, aMetaState, aTime,
+                 aUnicodeChar, aBaseUnicodeChar, aDomPrintableKeyValue,
+                 aRepeatCount, aFlags);
+
+    if (aIsSynthesizedImeKey) {
+        mIMEKeyEvents.AppendElement(
+                mozilla::UniquePtr<WidgetEvent>(pressEvent.Duplicate()));
+    } else {
+        window.DispatchEvent(&pressEvent, status);
     }
-
-    WidgetKeyboardEvent pressEvent(true, eKeyPress, this);
-    InitKeyEvent(pressEvent, *ae, &pluginEvent);
-#ifdef DEBUG_ANDROID_WIDGET
-    __android_log_print(ANDROID_LOG_INFO, "Gecko", "Dispatching key pressEvent with keyCode %d charCode %d shift %d alt %d sym/ctrl %d metamask %d", pressEvent.keyCode, pressEvent.charCode, pressEvent.IsShift(), pressEvent.IsAlt(), pressEvent.IsControl(), ae->MetaState());
-#endif
-    DispatchEvent(&pressEvent);
 }
 
 #ifdef DEBUG_ANDROID_IME
 #define ALOGIME(args...) ALOG(args)
 #else
 #define ALOGIME(args...) ((void)0)
 #endif
 
 static nscolor
-ConvertAndroidColor(uint32_t argb)
+ConvertAndroidColor(uint32_t aArgb)
 {
-    return NS_RGBA((argb & 0x00ff0000) >> 16,
-                   (argb & 0x0000ff00) >> 8,
-                   (argb & 0x000000ff),
-                   (argb & 0xff000000) >> 24);
+    return NS_RGBA((aArgb & 0x00ff0000) >> 16,
+                   (aArgb & 0x0000ff00) >> 8,
+                   (aArgb & 0x000000ff),
+                   (aArgb & 0xff000000) >> 24);
 }
 
 class AutoIMEMask {
 private:
     bool mOldMask, *mMask;
 public:
-    AutoIMEMask(bool &mask) : mOldMask(mask), mMask(&mask) {
-        mask = true;
+    AutoIMEMask(bool &aMask) : mOldMask(aMask), mMask(&aMask) {
+        aMask = true;
     }
     ~AutoIMEMask() {
         *mMask = mOldMask;
     }
 };
 
 /*
  * Get the current composition object, if any.
@@ -1827,456 +1943,259 @@ void
 nsWindow::RemoveIMEComposition()
 {
     // Remove composition on Gecko side
     if (!GetIMEComposition()) {
         return;
     }
 
     RefPtr<nsWindow> kungFuDeathGrip(this);
-    AutoIMEMask selMask(mIMEMaskSelectionUpdate);
-
     WidgetCompositionEvent compositionCommitEvent(true, eCompositionCommitAsIs,
                                                   this);
     InitEvent(compositionCommitEvent, nullptr);
     DispatchEvent(&compositionCommitEvent);
 }
 
 /*
  * Send dummy key events for pages that are unaware of input events,
  * to provide web compatibility for pages that depend on key events.
  * Our dummy key events have 0 as the keycode.
  */
 void
-nsWindow::SendIMEDummyKeyEvents()
+nsWindow::Natives::SendIMEDummyKeyEvents()
 {
-    WidgetKeyboardEvent downEvent(true, eKeyDown, this);
+    WidgetKeyboardEvent downEvent(true, eKeyDown, &window);
+    window.InitEvent(downEvent, nullptr);
     MOZ_ASSERT(downEvent.keyCode == 0);
-    DispatchEvent(&downEvent);
-
-    WidgetKeyboardEvent upEvent(true, eKeyUp, this);
+    window.DispatchEvent(&downEvent);
+
+    WidgetKeyboardEvent upEvent(true, eKeyUp, &window);
+    window.InitEvent(upEvent, nullptr);
     MOZ_ASSERT(upEvent.keyCode == 0);
-    DispatchEvent(&upEvent);
+    window.DispatchEvent(&upEvent);
 }
 
 void
-nsWindow::OnIMEEvent(AndroidGeckoEvent *ae)
+nsWindow::Natives::AddIMETextChange(const IMETextChange& aChange)
 {
-    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
-    /*
-        Rules for managing IME between Gecko and Java:
-
-        * Gecko controls the text content, and Java shadows the Gecko text
-           through text updates
-        * Java controls the selection, and Gecko shadows the Java selection
-           through set selection events
-        * Java controls the composition, and Gecko shadows the Java
-           composition through update composition events
-    */
-    RefPtr<nsWindow> kungFuDeathGrip(this);
-
-    if (ae->Action() == AndroidGeckoEvent::IME_ACKNOWLEDGE_FOCUS) {
-        MOZ_ASSERT(mIMEMaskEventsCount > 0);
-        mIMEMaskEventsCount--;
-        if (!mIMEMaskEventsCount) {
-            // The focusing handshake sequence is complete, and Java is waiting
-            // on Gecko. Now we can notify Java of the newly focused content
-            mIMETextChanges.Clear();
-            mIMESelectionChanged = false;
-            // NotifyIMEOfTextChange also notifies selection
-            // Use 'INT32_MAX / 2' here because subsequent text changes might
-            // combine with this text change, and overflow might occur if
-            // we just use INT32_MAX
-            IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
-            notification.mTextChangeData.mStartOffset = 0;
-            notification.mTextChangeData.mRemovedEndOffset =
-                notification.mTextChangeData.mAddedEndOffset = INT32_MAX / 2;
-            NotifyIMEOfTextChange(notification);
-            FlushIMEChanges();
-        }
-        mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
-        return;
-
-    } else if (ae->Action() == AndroidGeckoEvent::IME_UPDATE_CONTEXT) {
-        mEditable->NotifyIMEContext(mInputContext.mIMEState.mEnabled,
-                                    mInputContext.mHTMLInputType,
-                                    mInputContext.mHTMLInputInputmode,
-                                    mInputContext.mActionHint);
-        mIMEUpdatingContext = false;
-        return;
-    }
-
-    if (mIMEMaskEventsCount > 0) {
-        // Still reply to events, but don't do anything else
-        if (ae->Action() == AndroidGeckoEvent::IME_SYNCHRONIZE ||
-            ae->Action() == AndroidGeckoEvent::IME_COMPOSE_TEXT ||
-            ae->Action() == AndroidGeckoEvent::IME_REPLACE_TEXT) {
-            mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
-        }
-        return;
-    }
-
-    switch (ae->Action()) {
-    case AndroidGeckoEvent::IME_FLUSH_CHANGES:
-        {
-            FlushIMEChanges();
-        }
-        break;
-
-    case AndroidGeckoEvent::IME_SYNCHRONIZE:
-        {
-            FlushIMEChanges();
-            mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
+    mIMETextChanges.AppendElement(aChange);
+
+    // Now that we added a new range we need to go back and
+    // update all the ranges before that.
+    // Ranges that have offsets which follow this new range
+    // need to be updated to reflect new offsets
+    const int32_t delta = aChange.mNewEnd - aChange.mOldEnd;
+    for (int32_t i = mIMETextChanges.Length() - 2; i >= 0; i--) {
+        IMETextChange& previousChange = mIMETextChanges[i];
+        if (previousChange.mStart > aChange.mOldEnd) {
+            previousChange.mStart += delta;
+            previousChange.mOldEnd += delta;
+            previousChange.mNewEnd += delta;
         }
-        break;
-
-    case AndroidGeckoEvent::IME_REPLACE_TEXT:
-    case AndroidGeckoEvent::IME_COMPOSE_TEXT:
-        {
-            /*
-                Replace text in Gecko thread from ae->Start() to ae->End()
-                  with the string ae->Characters()
-
-                Selection updates are masked so the result of our temporary
-                  selection event is not passed on to Java
-            */
-            AutoIMEMask selMask(mIMEMaskSelectionUpdate);
-            const auto composition(GetIMEComposition());
-            MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
-
-            if (!mIMEKeyEvents.IsEmpty() || !composition ||
-                uint32_t(ae->Start()) !=
-                    composition->NativeOffsetOfStartComposition() ||
-                uint32_t(ae->End()) !=
-                    composition->NativeOffsetOfStartComposition() +
-                    composition->String().Length()) {
-
-                // Only start a new composition if we have key events,
-                // if we don't have an existing composition, or
-                // the replaced text does not match our composition.
-                RemoveIMEComposition();
-
-                {
-                    WidgetSelectionEvent event(true, eSetSelection, this);
-                    InitEvent(event, nullptr);
-                    event.mOffset = uint32_t(ae->Start());
-                    event.mLength = uint32_t(ae->End() - ae->Start());
-                    event.mExpandToClusterBoundary = false;
-                    DispatchEvent(&event);
-                }
-
-                if (!mIMEKeyEvents.IsEmpty()) {
-                    for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) {
-                        OnKeyEvent(&mIMEKeyEvents[i]);
-                    }
-                    mIMEKeyEvents.Clear();
-                    FlushIMEChanges();
-                    mEditable->NotifyIME(
-                            GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
-                    // Break out of the switch block
-                    break;
-                }
-
-                {
-                    WidgetCompositionEvent event(true, eCompositionStart, this);
-                    InitEvent(event, nullptr);
-                    DispatchEvent(&event);
-                }
-
-            } else if (composition->String().Equals(ae->Characters())) {
-                /* If the new text is the same as the existing composition text,
-                 * the NS_COMPOSITION_CHANGE event does not generate a text
-                 * change notification. However, the Java side still expects
-                 * one, so we manually generate a notification. */
-                IMEChange dummyChange;
-                dummyChange.mStart = ae->Start();
-                dummyChange.mOldEnd = dummyChange.mNewEnd = ae->End();
-                AddIMETextChange(dummyChange);
-            }
-
-            {
-                WidgetCompositionEvent event(true, eCompositionChange, this);
-                InitEvent(event, nullptr);
-                event.mData = ae->Characters();
-
-                // Include proper text ranges to make the editor happy.
-                TextRange range;
-                range.mStartOffset = 0;
-                range.mEndOffset = event.mData.Length();
-                range.mRangeType = NS_TEXTRANGE_RAWINPUT;
-                event.mRanges = new TextRangeArray();
-                event.mRanges->AppendElement(range);
-
-                DispatchEvent(&event);
-            }
-
-            // Don't end composition when composing text.
-            if (ae->Action() != AndroidGeckoEvent::IME_COMPOSE_TEXT) {
-                WidgetCompositionEvent compositionCommitEvent(
-                        true, eCompositionCommitAsIs, this);
-                InitEvent(compositionCommitEvent, nullptr);
-                DispatchEvent(&compositionCommitEvent);
-            }
-
-            if (mInputContext.mMayBeIMEUnaware) {
-                SendIMEDummyKeyEvents();
-            }
-
-            FlushIMEChanges();
-            mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
+    }
+
+    // Now go through all ranges to merge any ranges that are connected
+    // srcIndex is the index of the range to merge from
+    // dstIndex is the index of the range to potentially merge into
+    int32_t srcIndex = mIMETextChanges.Length() - 1;
+    int32_t dstIndex = srcIndex;
+
+    while (--dstIndex >= 0) {
+        IMETextChange& src = mIMETextChanges[srcIndex];
+        IMETextChange& dst = mIMETextChanges[dstIndex];
+        // When merging a more recent change into an older
+        // change, we need to compare recent change's (start, oldEnd)
+        // range to the older change's (start, newEnd)
+        if (src.mOldEnd < dst.mStart || dst.mNewEnd < src.mStart) {
+            // No overlap between ranges
+            continue;
         }
-        break;
-
-    case AndroidGeckoEvent::IME_SET_SELECTION:
-        {
-            /*
-                Set Gecko selection to ae->Start() to ae->End()
-
-                Selection updates are masked to prevent Java from being
-                  notified of the new selection
-            */
-            AutoIMEMask selMask(mIMEMaskSelectionUpdate);
-            RemoveIMEComposition();
-            WidgetSelectionEvent selEvent(true, eSetSelection, this);
-            InitEvent(selEvent, nullptr);
-
-            int32_t start = ae->Start(), end = ae->End();
-
-            if (start < 0 || end < 0) {
-                WidgetQueryContentEvent event(true, eQuerySelectedText, this);
-                InitEvent(event, nullptr);
-                DispatchEvent(&event);
-                MOZ_ASSERT(event.mSucceeded);
-
-                if (start < 0)
-                    start = int32_t(event.GetSelectionStart());
-                if (end < 0)
-                    end = int32_t(event.GetSelectionEnd());
-            }
-
-            selEvent.mOffset = std::min(start, end);
-            selEvent.mLength = std::max(start, end) - selEvent.mOffset;
-            selEvent.mReversed = start > end;
-            selEvent.mExpandToClusterBoundary = false;
-
-            DispatchEvent(&selEvent);
-        }
-        break;
-    case AndroidGeckoEvent::IME_ADD_COMPOSITION_RANGE:
-        {
-            TextRange range;
-            range.mStartOffset = ae->Start();
-            range.mEndOffset = ae->End();
-            range.mRangeType = ae->RangeType();
-            range.mRangeStyle.mDefinedStyles = ae->RangeStyles();
-            range.mRangeStyle.mLineStyle = ae->RangeLineStyle();
-            range.mRangeStyle.mIsBoldLine = ae->RangeBoldLine();
-            range.mRangeStyle.mForegroundColor =
-                    ConvertAndroidColor(uint32_t(ae->RangeForeColor()));
-            range.mRangeStyle.mBackgroundColor =
-                    ConvertAndroidColor(uint32_t(ae->RangeBackColor()));
-            range.mRangeStyle.mUnderlineColor =
-                    ConvertAndroidColor(uint32_t(ae->RangeLineColor()));
-            mIMERanges->AppendElement(range);
+        // When merging two ranges, there are generally four posibilities:
+        // [----(----]----), (----[----]----),
+        // [----(----)----], (----[----)----]
+        // where [----] is the first range and (----) is the second range
+        // As seen above, the start of the merged range is always the lesser
+        // of the two start offsets. OldEnd and NewEnd then need to be
+        // adjusted separately depending on the case. In any case, the change
+        // in text length of the merged range should be the sum of text length
+        // changes of the two original ranges, i.e.,
+        // newNewEnd - newOldEnd == newEnd1 - oldEnd1 + newEnd2 - oldEnd2
+        dst.mStart = std::min(dst.mStart, src.mStart);
+        if (src.mOldEnd < dst.mNewEnd) {
+            // New range overlaps or is within previous range; merge
+            dst.mNewEnd += src.mNewEnd - src.mOldEnd;
+        } else { // src.mOldEnd >= dst.mNewEnd
+            // New range overlaps previous range; merge
+            dst.mOldEnd += src.mOldEnd - dst.mNewEnd;
+            dst.mNewEnd = src.mNewEnd;
         }
-        break;
-    case AndroidGeckoEvent::IME_UPDATE_COMPOSITION:
-        {
-            /*
-                Update the composition from ae->Start() to ae->End() using
-                  information from added ranges. This is only used for
-                  visual indication and does not affect the text content.
-                  Only the offsets are specified and not the text content
-                  to eliminate the possibility of this event altering the
-                  text content unintentionally.
-
-                Selection updates are masked so the result of
-                  temporary events are not passed on to Java
-            */
-            AutoIMEMask selMask(mIMEMaskSelectionUpdate);
-            const auto composition(GetIMEComposition());
-            MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
-
-            WidgetCompositionEvent event(true, eCompositionChange, this);
-            InitEvent(event, nullptr);
-
-            event.mRanges = new TextRangeArray();
-            mIMERanges.swap(event.mRanges);
-
-            if (!composition ||
-                uint32_t(ae->Start()) !=
-                    composition->NativeOffsetOfStartComposition() ||
-                uint32_t(ae->End()) !=
-                    composition->NativeOffsetOfStartComposition() +
-                    composition->String().Length()) {
-
-                // Only start new composition if we don't have an existing one,
-                // or if the existing composition doesn't match the new one.
-                RemoveIMEComposition();
-
-                {
-                    WidgetSelectionEvent event(true, eSetSelection, this);
-                    InitEvent(event, nullptr);
-                    event.mOffset = uint32_t(ae->Start());
-                    event.mLength = uint32_t(ae->End() - ae->Start());
-                    event.mExpandToClusterBoundary = false;
-                    DispatchEvent(&event);
-                }
-
-                {
-                    WidgetQueryContentEvent queryEvent(true, eQuerySelectedText,
-                                                       this);
-                    InitEvent(queryEvent, nullptr);
-                    DispatchEvent(&queryEvent);
-                    MOZ_ASSERT(queryEvent.mSucceeded);
-                    event.mData = queryEvent.mReply.mString;
-                }
-
-                {
-                    WidgetCompositionEvent event(true, eCompositionStart, this);
-                    InitEvent(event, nullptr);
-                    DispatchEvent(&event);
-                }
-
-            } else {
-                // If the new composition matches the existing composition,
-                // reuse the old composition.
-                event.mData = composition->String();
-            }
-
-#ifdef DEBUG_ANDROID_IME
-            const NS_ConvertUTF16toUTF8 data(event.mData);
-            const char* text = data.get();
-            ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%u, range=%u",
-                    text, event.mData.Length(), event.mRanges->Length());
-#endif // DEBUG_ANDROID_IME
-
-            DispatchEvent(&event);
-        }
-        break;
-
-    case AndroidGeckoEvent::IME_REMOVE_COMPOSITION:
-        {
-            /*
-             *  Remove any previous composition.  This is only used for
-             *    visual indication and does not affect the text content.
-             *
-             *  Selection updates are masked so the result of
-             *    temporary events are not passed on to Java
-             */
-            AutoIMEMask selMask(mIMEMaskSelectionUpdate);
-            RemoveIMEComposition();
-            mIMERanges->Clear();
-        }
-        break;
+        // src merged to dst; delete src.
+        mIMETextChanges.RemoveElementAt(srcIndex);
+        // Any ranges that we skip over between src and dst are not mergeable
+        // so we can safely continue the merge starting at dst
+        srcIndex = dstIndex;
     }
 }
 
 void
-nsWindow::UserActivity()
+nsWindow::Natives::PostFlushIMEChanges()
 {
-  if (!mIdleService) {
-    mIdleService = do_GetService("@mozilla.org/widget/idleservice;1");
-  }
-
-  if (mIdleService) {
-    mIdleService->ResetIdleTimeOut(0);
-  }
+    if (!mIMETextChanges.IsEmpty() || mIMESelectionChanged) {
+        // Already posted
+        return;
+    }
+
+    // Keep a strong reference to the window to keep 'this' alive.
+    RefPtr<nsWindow> window(&this->window);
+
+    nsAppShell::gAppShell->PostEvent([this, window] {
+        if (!window->Destroyed()) {
+            FlushIMEChanges();
+        }
+    });
 }
 
-nsresult
-nsWindow::NotifyIMEInternal(const IMENotification& aIMENotification)
+void
+nsWindow::Natives::FlushIMEChanges()
 {
-    MOZ_ASSERT(this == FindTopLevel());
-
-    if (!mEditable) {
-        return NS_ERROR_NOT_AVAILABLE;
+    // Only send change notifications if we are *not* masking events,
+    // i.e. if we have a focused editor,
+    NS_ENSURE_TRUE_VOID(!mIMEMaskEventsCount);
+
+    nsCOMPtr<nsISelection> imeSelection;
+    nsCOMPtr<nsIContent> imeRoot;
+
+    // If we are receiving notifications, we must have selection/root content.
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(IMEStateManager::GetFocusSelectionAndRoot(
+            getter_AddRefs(imeSelection), getter_AddRefs(imeRoot))));
+
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    window.UserActivity();
+
+    for (uint32_t i = 0; i < mIMETextChanges.Length(); i++) {
+        IMETextChange &change = mIMETextChanges[i];
+
+        if (change.mStart == change.mOldEnd &&
+                change.mStart == change.mNewEnd) {
+            continue;
+        }
+
+        WidgetQueryContentEvent event(true, eQueryTextContent, &window);
+
+        if (change.mNewEnd != change.mStart) {
+            window.InitEvent(event, nullptr);
+            event.InitForQueryTextContent(change.mStart,
+                                          change.mNewEnd - change.mStart);
+            window.DispatchEvent(&event);
+            NS_ENSURE_TRUE_VOID(event.mSucceeded);
+            NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
+        }
+
+        mEditable->OnTextChange(event.mReply.mString, change.mStart,
+                                change.mOldEnd, change.mNewEnd);
     }
+    mIMETextChanges.Clear();
+
+    if (mIMESelectionChanged) {
+        WidgetQueryContentEvent event(true, eQuerySelectedText, &window);
+        window.InitEvent(event, nullptr);
+        window.DispatchEvent(&event);
+
+        NS_ENSURE_TRUE_VOID(event.mSucceeded);
+        NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
+
+        mEditable->OnSelectionChange(int32_t(event.GetSelectionStart()),
+                                     int32_t(event.GetSelectionEnd()));
+        mIMESelectionChanged = false;
+    }
+}
+
+bool
+nsWindow::Natives::NotifyIME(const IMENotification& aIMENotification)
+{
+    MOZ_ASSERT(mEditable);
 
     switch (aIMENotification.mMessage) {
-        case REQUEST_TO_COMMIT_COMPOSITION:
-            //ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION: s=%d", aState);
-            RemoveIMEComposition();
+        case REQUEST_TO_COMMIT_COMPOSITION: {
+            ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION");
+            window.RemoveIMEComposition();
             mEditable->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
-            return NS_OK;
-
-        case REQUEST_TO_CANCEL_COMPOSITION:
+            return true;
+        }
+
+        case REQUEST_TO_CANCEL_COMPOSITION: {
             ALOGIME("IME: REQUEST_TO_CANCEL_COMPOSITION");
 
             // Cancel composition on Gecko side
-            if (!!GetIMEComposition()) {
-                RefPtr<nsWindow> kungFuDeathGrip(this);
-
+            if (window.GetIMEComposition()) {
+                RefPtr<nsWindow> kungFuDeathGrip(&window);
                 WidgetCompositionEvent compositionCommitEvent(
-                                         true, eCompositionCommit, this);
-                InitEvent(compositionCommitEvent, nullptr);
-                // Dispatch it with empty mData value for canceling the
-                // composition
-                DispatchEvent(&compositionCommitEvent);
+                        true, eCompositionCommit, &window);
+                window.InitEvent(compositionCommitEvent, nullptr);
+                // Dispatch it with empty mData value for canceling composition.
+                window.DispatchEvent(&compositionCommitEvent);
             }
 
             mEditable->NotifyIME(REQUEST_TO_CANCEL_COMPOSITION);
-            return NS_OK;
-
-        case NOTIFY_IME_OF_FOCUS:
+            return true;
+        }
+
+        case NOTIFY_IME_OF_FOCUS: {
             ALOGIME("IME: NOTIFY_IME_OF_FOCUS");
             mEditable->NotifyIME(NOTIFY_IME_OF_FOCUS);
-            return NS_OK;
-
-        case NOTIFY_IME_OF_BLUR:
+            return true;
+        }
+
+        case NOTIFY_IME_OF_BLUR: {
             ALOGIME("IME: NOTIFY_IME_OF_BLUR");
 
             // Mask events because we lost focus. On the next focus event,
             // Gecko will notify Java, and Java will send an acknowledge focus
             // event back to Gecko. That is where we unmask event handling
             mIMEMaskEventsCount++;
 
             mEditable->NotifyIME(NOTIFY_IME_OF_BLUR);
-            return NS_OK;
-
-        case NOTIFY_IME_OF_SELECTION_CHANGE:
+            return true;
+        }
+
+        case NOTIFY_IME_OF_SELECTION_CHANGE: {
             if (mIMEMaskSelectionUpdate) {
-                return NS_OK;
+                return true;
             }
 
             ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE");
 
             PostFlushIMEChanges();
             mIMESelectionChanged = true;
-            return NS_OK;
-
-        case NOTIFY_IME_OF_TEXT_CHANGE:
-            return NotifyIMEOfTextChange(aIMENotification);
+            return true;
+        }
+
+        case NOTIFY_IME_OF_TEXT_CHANGE: {
+            ALOGIME("IME: NotifyIMEOfTextChange: s=%d, oe=%d, ne=%d",
+                    aIMENotification.mTextChangeData.mStartOffset,
+                    aIMENotification.mTextChangeData.mRemovedEndOffset,
+                    aIMENotification.mTextChangeData.mAddedEndOffset);
+
+            /* Make sure Java's selection is up-to-date */
+            PostFlushIMEChanges();
+            mIMESelectionChanged = true;
+            AddIMETextChange(IMETextChange(aIMENotification));
+            return true;
+        }
 
         default:
-            return NS_ERROR_NOT_IMPLEMENTED;
+            return false;
     }
 }
 
-NS_IMETHODIMP_(void)
-nsWindow::SetInputContext(const InputContext& aContext,
-                          const InputContextAction& aAction)
+void
+nsWindow::Natives::SetInputContext(const InputContext& aContext,
+                                   const InputContextAction& aAction)
 {
-#ifdef MOZ_B2GDROID
-    // Disable the Android keyboard on b2gdroid.
-    return;
-#endif
-    nsWindow *top = FindTopLevel();
-    if (top && this != top) {
-        // We are using an IME event later to notify Java, and the IME event
-        // will be processed by the top window. Therefore, to ensure the
-        // IME event uses the correct mInputContext, we need to let the top
-        // window process SetInputContext
-        top->SetInputContext(aContext, aAction);
-        return;
-    }
-
-    if (!mEditable) {
-        return;
-    }
+    MOZ_ASSERT(mEditable);
 
     ALOGIME("IME: SetInputContext: s=0x%X, 0x%X, action=0x%X, 0x%X",
             aContext.mIMEState.mEnabled, aContext.mIMEState.mOpen,
             aAction.mCause, aAction.mFocusChange);
 
     // Ensure that opening the virtual keyboard is allowed for this specific
     // InputContext depending on the content.ime.strict.policy pref
     if (aContext.mIMEState.mEnabled != IMEState::DISABLED &&
@@ -2303,194 +2222,444 @@ nsWindow::SetInputContext(const InputCon
         // Don't reset keyboard when we should simply open the vkb
         mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OPEN_VKB);
         return;
     }
 
     if (mIMEUpdatingContext) {
         return;
     }
-    AndroidGeckoEvent *event = AndroidGeckoEvent::MakeIMEEvent(
-            AndroidGeckoEvent::IME_UPDATE_CONTEXT);
-    nsAppShell::gAppShell->PostEvent(event);
+
+    // Keep a strong reference to the window to keep 'this' alive.
+    RefPtr<nsWindow> window(&this->window);
     mIMEUpdatingContext = true;
+
+    nsAppShell::gAppShell->PostEvent([this, window] {
+        mIMEUpdatingContext = false;
+        if (window->Destroyed()) {
+            return;
+        }
+        MOZ_ASSERT(mEditable);
+        mEditable->NotifyIMEContext(mInputContext.mIMEState.mEnabled,
+                                    mInputContext.mHTMLInputType,
+                                    mInputContext.mHTMLInputInputmode,
+                                    mInputContext.mActionHint);
+    });
 }
 
-NS_IMETHODIMP_(InputContext)
-nsWindow::GetInputContext()
+InputContext
+nsWindow::Natives::GetInputContext()
 {
-    nsWindow *top = FindTopLevel();
-    if (top && this != top) {
-        // We let the top window process SetInputContext,
-        // so we should let it process GetInputContext as well.
-        return top->GetInputContext();
-    }
     InputContext context = mInputContext;
     context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
     // We assume that there is only one context per process on Android
     context.mNativeIMEContext = nullptr;
     return context;
 }
 
 void
-nsWindow::PostFlushIMEChanges()
+nsWindow::Natives::OnImeSynchronize()
+{
+    if (!mIMEMaskEventsCount) {
+        FlushIMEChanges();
+    }
+    mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
+}
+
+void
+nsWindow::Natives::OnImeAcknowledgeFocus()
+{
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+    MOZ_ASSERT(mIMEMaskEventsCount > 0);
+
+    if (--mIMEMaskEventsCount > 0) {
+        // Still not focused; reply to events, but don't do anything else.
+        return OnImeSynchronize();
+    }
+
+    // The focusing handshake sequence is complete, and Java is waiting
+    // on Gecko. Now we can notify Java of the newly focused content
+    mIMETextChanges.Clear();
+    mIMESelectionChanged = false;
+    // NotifyIMEOfTextChange also notifies selection
+    // Use 'INT32_MAX / 2' here because subsequent text changes might
+    // combine with this text change, and overflow might occur if
+    // we just use INT32_MAX
+    IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
+    notification.mTextChangeData.mStartOffset = 0;
+    notification.mTextChangeData.mRemovedEndOffset =
+        notification.mTextChangeData.mAddedEndOffset = INT32_MAX / 2;
+    NotifyIME(notification);
+    OnImeSynchronize();
+}
+
+void
+nsWindow::Natives::OnImeReplaceText(int32_t aStart, int32_t aEnd,
+                                    jni::String::Param aText, bool aComposing)
 {
-    if (!mIMETextChanges.IsEmpty() || mIMESelectionChanged) {
-        // Already posted
-        return;
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused; still reply to events, but don't do anything else.
+        return OnImeSynchronize();
     }
-    AndroidGeckoEvent *event = AndroidGeckoEvent::MakeIMEEvent(
-            AndroidGeckoEvent::IME_FLUSH_CHANGES);
-    nsAppShell::gAppShell->PostEvent(event);
+
+    /*
+        Replace text in Gecko thread from aStart to aEnd with the string text.
+
+        Selection updates are masked so the result of our temporary
+          selection event is not passed on to Java
+    */
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    AutoIMEMask selMask(mIMEMaskSelectionUpdate);
+    nsString string(aText);
+
+    const auto composition(window.GetIMEComposition());
+    MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
+
+    if (!mIMEKeyEvents.IsEmpty() || !composition ||
+        uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
+        uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
+                          composition->String().Length())
+    {
+        // Only start a new composition if we have key events,
+        // if we don't have an existing composition, or
+        // the replaced text does not match our composition.
+        window.RemoveIMEComposition();
+
+        {
+            WidgetSelectionEvent event(true, eSetSelection, &window);
+            window.InitEvent(event, nullptr);
+            event.mOffset = uint32_t(aStart);
+            event.mLength = uint32_t(aEnd - aStart);
+            event.mExpandToClusterBoundary = false;
+            window.DispatchEvent(&event);
+        }
+
+        if (!mIMEKeyEvents.IsEmpty()) {
+            nsEventStatus status;
+            for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) {
+                const auto event = static_cast<WidgetGUIEvent*>(
+                        mIMEKeyEvents[i].get());
+                if (event->mMessage == eKeyPress &&
+                        status == nsEventStatus_eConsumeNoDefault) {
+                    MOZ_ASSERT(i > 0 &&
+                            mIMEKeyEvents[i - 1]->mMessage == eKeyDown);
+                    // The previous key down event resulted in eConsumeNoDefault
+                    // so we should not dispatch the current key press event.
+                    continue;
+                }
+                // widget for duplicated events is initially nullptr.
+                event->widget = &window;
+                window.DispatchEvent(event, status);
+            }
+            mIMEKeyEvents.Clear();
+            return OnImeSynchronize();
+        }
+
+        {
+            WidgetCompositionEvent event(true, eCompositionStart, &window);
+            window.InitEvent(event, nullptr);
+            window.DispatchEvent(&event);
+        }
+
+    } else if (composition->String().Equals(string)) {
+        /* If the new text is the same as the existing composition text,
+         * the NS_COMPOSITION_CHANGE event does not generate a text
+         * change notification. However, the Java side still expects
+         * one, so we manually generate a notification. */
+        IMETextChange dummyChange;
+        dummyChange.mStart = aStart;
+        dummyChange.mOldEnd = dummyChange.mNewEnd = aEnd;
+        AddIMETextChange(dummyChange);
+    }
+
+    // Previous events may have destroyed our composition; bail in that case.
+    if (window.GetIMEComposition()) {
+        WidgetCompositionEvent event(true, eCompositionChange, &window);
+        window.InitEvent(event, nullptr);
+        event.mData = string;
+
+        // Include proper text ranges to make the editor happy.
+        TextRange range;
+        range.mStartOffset = 0;
+        range.mEndOffset = event.mData.Length();
+        range.mRangeType = NS_TEXTRANGE_RAWINPUT;
+        event.mRanges = new TextRangeArray();
+        event.mRanges->AppendElement(range);
+
+        window.DispatchEvent(&event);
+    }
+
+    // Don't end composition when composing text or composition was destroyed.
+    if (!aComposing && window.GetIMEComposition()) {
+        WidgetCompositionEvent compositionCommitEvent(
+                true, eCompositionCommitAsIs, &window);
+        window.InitEvent(compositionCommitEvent, nullptr);
+        window.DispatchEvent(&compositionCommitEvent);
+    }
+
+    if (mInputContext.mMayBeIMEUnaware) {
+        SendIMEDummyKeyEvents();
+    }
+    OnImeSynchronize();
 }
 
 void
-nsWindow::FlushIMEChanges()
+nsWindow::Natives::OnImeSetSelection(int32_t aStart, int32_t aEnd)
+{
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused.
+        return;
+    }
+
+    /*
+        Set Gecko selection to aStart to aEnd.
+
+        Selection updates are masked to prevent Java from being
+          notified of the new selection
+    */
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    AutoIMEMask selMask(mIMEMaskSelectionUpdate);
+    WidgetSelectionEvent selEvent(true, eSetSelection, &window);
+
+    window.InitEvent(selEvent, nullptr);
+    window.RemoveIMEComposition();
+
+    if (aStart < 0 || aEnd < 0) {
+        WidgetQueryContentEvent event(true, eQuerySelectedText, &window);
+        window.InitEvent(event, nullptr);
+        window.DispatchEvent(&event);
+        MOZ_ASSERT(event.mSucceeded);
+
+        if (aStart < 0)
+            aStart = int32_t(event.GetSelectionStart());
+        if (aEnd < 0)
+            aEnd = int32_t(event.GetSelectionEnd());
+    }
+
+    selEvent.mOffset = std::min(aStart, aEnd);
+    selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset;
+    selEvent.mReversed = aStart > aEnd;
+    selEvent.mExpandToClusterBoundary = false;
+
+    window.DispatchEvent(&selEvent);
+}
+
+void
+nsWindow::Natives::OnImeRemoveComposition()
+{
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused.
+        return;
+    }
+
+    /*
+     *  Remove any previous composition.  This is only used for
+     *    visual indication and does not affect the text content.
+     *
+     *  Selection updates are masked so the result of
+     *    temporary events are not passed on to Java
+     */
+    AutoIMEMask selMask(mIMEMaskSelectionUpdate);
+    window.RemoveIMEComposition();
+    mIMERanges->Clear();
+}
+
+void
+nsWindow::Natives::OnImeAddCompositionRange(
+        int32_t aStart, int32_t aEnd, int32_t aRangeType, int32_t aRangeStyle,
+        int32_t aRangeLineStyle, bool aRangeBoldLine, int32_t aRangeForeColor,
+        int32_t aRangeBackColor, int32_t aRangeLineColor)
 {
-    // Only send change notifications if we are *not* masking events,
-    // i.e. if we have a focused editor,
-    NS_ENSURE_TRUE_VOID(!mIMEMaskEventsCount);
-
-    nsCOMPtr<nsISelection> imeSelection;
-    nsCOMPtr<nsIContent> imeRoot;
-
-    // If we are receiving notifications, we must have selection/root content.
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(IMEStateManager::GetFocusSelectionAndRoot(
-            getter_AddRefs(imeSelection), getter_AddRefs(imeRoot))));
-
-    RefPtr<nsWindow> kungFuDeathGrip(this);
-
-    for (uint32_t i = 0; i < mIMETextChanges.Length(); i++) {
-        IMEChange &change = mIMETextChanges[i];
-
-        if (change.mStart == change.mOldEnd &&
-                change.mStart == change.mNewEnd) {
-            continue;
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused.
+        return;
+    }
+
+    TextRange range;
+    range.mStartOffset = aStart;
+    range.mEndOffset = aEnd;
+    range.mRangeType = aRangeType;
+    range.mRangeStyle.mDefinedStyles = aRangeStyle;
+    range.mRangeStyle.mLineStyle = aRangeLineStyle;
+    range.mRangeStyle.mIsBoldLine = aRangeBoldLine;
+    range.mRangeStyle.mForegroundColor =
+            ConvertAndroidColor(uint32_t(aRangeForeColor));
+    range.mRangeStyle.mBackgroundColor =
+            ConvertAndroidColor(uint32_t(aRangeBackColor));
+    range.mRangeStyle.mUnderlineColor =
+            ConvertAndroidColor(uint32_t(aRangeLineColor));
+    mIMERanges->AppendElement(range);
+}
+
+void
+nsWindow::Natives::OnImeUpdateComposition(int32_t aStart, int32_t aEnd)
+{
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused.
+        return;
+    }
+
+    /*
+        Update the composition from aStart to aEnd using
+          information from added ranges. This is only used for
+          visual indication and does not affect the text content.
+          Only the offsets are specified and not the text content
+          to eliminate the possibility of this event altering the
+          text content unintentionally.
+
+        Selection updates are masked so the result of
+          temporary events are not passed on to Java
+    */
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    AutoIMEMask selMask(mIMEMaskSelectionUpdate);
+    const auto composition(window.GetIMEComposition());
+    MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
+
+    WidgetCompositionEvent event(true, eCompositionChange, &window);
+    window.InitEvent(event, nullptr);
+
+    event.mRanges = new TextRangeArray();
+    mIMERanges.swap(event.mRanges);
+
+    if (!composition ||
+        uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
+        uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
+                          composition->String().Length())
+    {
+        // Only start new composition if we don't have an existing one,
+        // or if the existing composition doesn't match the new one.
+        window.RemoveIMEComposition();
+
+        {
+            WidgetSelectionEvent event(true, eSetSelection, &window);
+            window.InitEvent(event, nullptr);
+            event.mOffset = uint32_t(aStart);
+            event.mLength = uint32_t(aEnd - aStart);
+            event.mExpandToClusterBoundary = false;
+            window.DispatchEvent(&event);
         }
 
-        WidgetQueryContentEvent event(true, eQueryTextContent, this);
-
-        if (change.mNewEnd != change.mStart) {
-            InitEvent(event, nullptr);
-            event.InitForQueryTextContent(change.mStart,
-                                          change.mNewEnd - change.mStart);
-            DispatchEvent(&event);
-            NS_ENSURE_TRUE_VOID(event.mSucceeded);
-            NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
+        {
+            WidgetQueryContentEvent queryEvent(true, eQuerySelectedText,
+                                               &window);
+            window.InitEvent(queryEvent, nullptr);
+            window.DispatchEvent(&queryEvent);
+            MOZ_ASSERT(queryEvent.mSucceeded);
+            event.mData = queryEvent.mReply.mString;
         }
 
-        mEditable->OnTextChange(event.mReply.mString, change.mStart,
-                                change.mOldEnd, change.mNewEnd);
+        {
+            WidgetCompositionEvent event(true, eCompositionStart, &window);
+            window.InitEvent(event, nullptr);
+            window.DispatchEvent(&event);
+        }
+
+    } else {
+        // If the new composition matches the existing composition,
+        // reuse the old composition.
+        event.mData = composition->String();
     }
-    mIMETextChanges.Clear();
-
-    if (mIMESelectionChanged) {
-        WidgetQueryContentEvent event(true, eQuerySelectedText, this);
-        InitEvent(event, nullptr);
-
-        DispatchEvent(&event);
-        NS_ENSURE_TRUE_VOID(event.mSucceeded);
-        NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
-
-        mEditable->OnSelectionChange(int32_t(event.GetSelectionStart()),
-                                     int32_t(event.GetSelectionEnd()));
-        mIMESelectionChanged = false;
+
+#ifdef DEBUG_ANDROID_IME
+    const NS_ConvertUTF16toUTF8 data(event.mData);
+    const char* text = data.get();
+    ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%u, range=%u",
+            text, event.mData.Length(), event.mRanges->Length());
+#endif // DEBUG_ANDROID_IME
+
+    // Previous events may have destroyed our composition; bail in that case.
+    if (window.GetIMEComposition()) {
+        window.DispatchEvent(&event);
     }
 }
 
+void
+nsWindow::UserActivity()
+{
+  if (!mIdleService) {
+    mIdleService = do_GetService("@mozilla.org/widget/idleservice;1");
+  }
+
+  if (mIdleService) {
+    mIdleService->ResetIdleTimeOut(0);
+  }
+}
+
 nsresult
-nsWindow::NotifyIMEOfTextChange(const IMENotification& aIMENotification)
+nsWindow::NotifyIMEInternal(const IMENotification& aIMENotification)
 {
     MOZ_ASSERT(this == FindTopLevel());
 
-    MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE,
-               "NotifyIMEOfTextChange() is called with invaild notification");
-
-    ALOGIME("IME: NotifyIMEOfTextChange: s=%d, oe=%d, ne=%d",
-            aIMENotification.mTextChangeData.mStartOffset,
-            aIMENotification.mTextChangeData.mRemovedEndOffset,
-            aIMENotification.mTextChangeData.mAddedEndOffset);
-
-    /* Make sure Java's selection is up-to-date */
-    mIMESelectionChanged = false;
-    NotifyIME(NOTIFY_IME_OF_SELECTION_CHANGE);
-
-    PostFlushIMEChanges();
-    AddIMETextChange(IMEChange(aIMENotification));
-    return NS_OK;
-}
-
-void
-nsWindow::AddIMETextChange(const IMEChange& aChange) {
-
-    mIMETextChanges.AppendElement(aChange);
-
-    // Now that we added a new range we need to go back and
-    // update all the ranges before that.
-    // Ranges that have offsets which follow this new range
-    // need to be updated to reflect new offsets
-    const int32_t delta = aChange.mNewEnd - aChange.mOldEnd;
-    for (int32_t i = mIMETextChanges.Length() - 2; i >= 0; i--) {
-        IMEChange &previousChange = mIMETextChanges[i];
-        if (previousChange.mStart > aChange.mOldEnd) {
-            previousChange.mStart += delta;
-            previousChange.mOldEnd += delta;
-            previousChange.mNewEnd += delta;
-        }
+    if (!mNatives) {
+        // Non-GeckoView windows don't support IME operations.
+        return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    if (mNatives->NotifyIME(aIMENotification)) {
+        return NS_OK;
     }
-
-    // Now go through all ranges to merge any ranges that are connected
-    // srcIndex is the index of the range to merge from
-    // dstIndex is the index of the range to potentially merge into
-    int32_t srcIndex = mIMETextChanges.Length() - 1;
-    int32_t dstIndex = srcIndex;
-
-    while (--dstIndex >= 0) {
-        IMEChange &src = mIMETextChanges[srcIndex];
-        IMEChange &dst = mIMETextChanges[dstIndex];
-        // When merging a more recent change into an older
-        // change, we need to compare recent change's (start, oldEnd)
-        // range to the older change's (start, newEnd)
-        if (src.mOldEnd < dst.mStart || dst.mNewEnd < src.mStart) {
-            // No overlap between ranges
-            continue;
-        }
-        // When merging two ranges, there are generally four posibilities:
-        // [----(----]----), (----[----]----),
-        // [----(----)----], (----[----)----]
-        // where [----] is the first range and (----) is the second range
-        // As seen above, the start of the merged range is always the lesser
-        // of the two start offsets. OldEnd and NewEnd then need to be
-        // adjusted separately depending on the case. In any case, the change
-        // in text length of the merged range should be the sum of text length
-        // changes of the two original ranges, i.e.,
-        // newNewEnd - newOldEnd == newEnd1 - oldEnd1 + newEnd2 - oldEnd2
-        dst.mStart = std::min(dst.mStart, src.mStart);
-        if (src.mOldEnd < dst.mNewEnd) {
-            // New range overlaps or is within previous range; merge
-            dst.mNewEnd += src.mNewEnd - src.mOldEnd;
-        } else { // src.mOldEnd >= dst.mNewEnd
-            // New range overlaps previous range; merge
-            dst.mOldEnd += src.mOldEnd - dst.mNewEnd;
-            dst.mNewEnd = src.mNewEnd;
-        }
-        // src merged to dst; delete src.
-        mIMETextChanges.RemoveElementAt(srcIndex);
-        // Any ranges that we skip over between src and dst are not mergeable
-        // so we can safely continue the merge starting at dst
-        srcIndex = dstIndex;
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(void)
+nsWindow::SetInputContext(const InputContext& aContext,
+                          const InputContextAction& aAction)
+{
+#ifdef MOZ_B2GDROID
+    // Disable the Android keyboard on b2gdroid.
+    return;
+#endif
+
+    nsWindow* top = FindTopLevel();
+    MOZ_ASSERT(top);
+
+    if (!top->mNatives) {
+        // Non-GeckoView windows don't support IME operations.
+        return;
     }
+
+    // We are using an IME event later to notify Java, and the IME event
+    // will be processed by the top window. Therefore, to ensure the
+    // IME event uses the correct mInputContext, we need to let the top
+    // window process SetInputContext
+    top->mNatives->SetInputContext(aContext, aAction);
+}
+
+NS_IMETHODIMP_(InputContext)
+nsWindow::GetInputContext()
+{
+    nsWindow* top = FindTopLevel();
+    MOZ_ASSERT(top);
+
+    if (!top->mNatives) {
+        // Non-GeckoView windows don't support IME operations.
+        return InputContext();
+    }
+
+    // We let the top window process SetInputContext,
+    // so we should let it process GetInputContext as well.
+    return top->mNatives->GetInputContext();
 }
 
 nsIMEUpdatePreference
 nsWindow::GetIMEUpdatePreference()
 {
     // While a plugin has focus, nsWindow for Android doesn't need any
     // notifications.
-    if (mInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
+    if (GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) {
       return nsIMEUpdatePreference();
     }
     return nsIMEUpdatePreference(
         nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE |
         nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE);
 }
 
 void
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -44,31 +44,26 @@ public:
 
     NS_DECL_ISUPPORTS_INHERITED
 
     static void InitNatives();
     class Natives;
     // Object that implements native GeckoView calls;
     // nullptr for nsWindows that were not opened from GeckoView.
     mozilla::UniquePtr<Natives> mNatives;
-    // GeckoEditable instance used by this nsWindow;
-    // nullptr for nsWindows that are not GeckoViews.
-    mozilla::widget::GeckoEditable::GlobalRef mEditable;
 
     static void OnGlobalAndroidEvent(mozilla::AndroidGeckoEvent *ae);
     static mozilla::gfx::IntSize GetAndroidScreenBounds();
     static nsWindow* TopWindow();
 
     bool OnContextmenuEvent(mozilla::AndroidGeckoEvent *ae);
     void OnLongTapEvent(mozilla::AndroidGeckoEvent *ae);
     bool OnMultitouchEvent(mozilla::AndroidGeckoEvent *ae);
     void OnNativeGestureEvent(mozilla::AndroidGeckoEvent *ae);
     void OnMouseEvent(mozilla::AndroidGeckoEvent *ae);
-    void OnKeyEvent(mozilla::AndroidGeckoEvent *ae);
-    void OnIMEEvent(mozilla::AndroidGeckoEvent *ae);
 
     void OnSizeChanged(const mozilla::gfx::IntSize& aSize);
 
     void InitEvent(mozilla::WidgetGUIEvent& event, nsIntPoint* aPoint = 0);
 
     //
     // nsIWidget
     //
@@ -139,18 +134,16 @@ public:
                                int32_t aVertical) override
     {
         return NS_ERROR_NOT_IMPLEMENTED;
     }
 
     NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
                                       const InputContextAction& aAction) override;
     NS_IMETHOD_(InputContext) GetInputContext() override;
-
-    nsresult NotifyIMEOfTextChange(const IMENotification& aIMENotification);
     virtual nsIMEUpdatePreference GetIMEUpdatePreference() override;
 
     LayerManager* GetLayerManager (PLayerTransactionChild* aShadowManager = nullptr,
                                    LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
                                    LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT,
                                    bool* aAllowRetaining = nullptr) override;
 
     NS_IMETHOD ReparentNativeWidget(nsIWidget* aNewParent) override;
@@ -183,48 +176,18 @@ public:
                                const FrameMetrics::ViewID& aViewId,
                                const mozilla::Maybe<ZoomConstraints>& aConstraints) override;
 
 protected:
     void BringToFront();
     nsWindow *FindTopLevel();
     bool IsTopLevel();
 
-    struct IMEChange {
-        int32_t mStart, mOldEnd, mNewEnd;
-
-        IMEChange() :
-            mStart(-1), mOldEnd(-1), mNewEnd(-1)
-        {
-        }
-        IMEChange(const IMENotification& aIMENotification)
-            : mStart(aIMENotification.mTextChangeData.mStartOffset)
-            , mOldEnd(aIMENotification.mTextChangeData.mRemovedEndOffset)
-            , mNewEnd(aIMENotification.mTextChangeData.mAddedEndOffset)
-        {
-            MOZ_ASSERT(aIMENotification.mMessage ==
-                           mozilla::widget::NOTIFY_IME_OF_TEXT_CHANGE,
-                       "IMEChange initialized with wrong notification");
-            MOZ_ASSERT(aIMENotification.mTextChangeData.IsValid(),
-                       "The text change notification isn't initialized");
-            MOZ_ASSERT(aIMENotification.mTextChangeData.IsInInt32Range(),
-                       "The text change notification is out of range");
-        }
-        bool IsEmpty() const
-        {
-            return mStart < 0;
-        }
-    };
-
     RefPtr<mozilla::TextComposition> GetIMEComposition();
     void RemoveIMEComposition();
-    void SendIMEDummyKeyEvents();
-    void AddIMETextChange(const IMEChange& aChange);
-    void PostFlushIMEChanges();
-    void FlushIMEChanges();
 
     void ConfigureAPZCTreeManager() override;
     void ConfigureAPZControllerThread() override;
 
     already_AddRefed<GeckoContentController> CreateRootContentController() override;
 
     // Call this function when the users activity is the direct cause of an
     // event (like a keypress or mouse click).
@@ -234,41 +197,27 @@ protected:
     nsTArray<nsWindow*> mChildren;
     nsWindow* mParent;
 
     double mStartDist;
     double mLastDist;
 
     nsCOMPtr<nsIIdleServiceInternal> mIdleService;
 
-    bool mIMEMaskSelectionUpdate;
-    int32_t mIMEMaskEventsCount; // Mask events when > 0
-    RefPtr<mozilla::TextRangeArray> mIMERanges;
-    bool mIMEUpdatingContext;
-    nsAutoTArray<mozilla::AndroidGeckoEvent, 8> mIMEKeyEvents;
-    nsAutoTArray<IMEChange, 4> mIMETextChanges;
-    bool mIMESelectionChanged;
-
     bool mAwaitingFullScreen;
     bool mIsFullScreen;
 
-    InputContext mInputContext;
-
     virtual nsresult NotifyIMEInternal(
                          const IMENotification& aIMENotification) override;
 
     static void DumpWindows();
     static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
     static void LogWindow(nsWindow *win, int index, int indent);
 
 private:
-    void InitKeyEvent(mozilla::WidgetKeyboardEvent& event,
-                      mozilla::AndroidGeckoEvent& key,
-                      ANPEvent* pluginEvent);
-    void HandleSpecialKey(mozilla::AndroidGeckoEvent *ae);
     void CreateLayerManager(int aCompositorWidth, int aCompositorHeight);
     void RedrawAll();
 
     mozilla::AndroidLayerRendererFrame mLayerRendererFrame;
 
     static mozilla::StaticRefPtr<mozilla::layers::APZCTreeManager> sApzcTreeManager;
     static mozilla::StaticRefPtr<mozilla::layers::LayerManager> sLayerManager;
     static mozilla::StaticRefPtr<mozilla::layers::CompositorParent> sCompositorParent;
--- a/widget/windows/IMMHandler.cpp
+++ b/widget/windows/IMMHandler.cpp
@@ -1740,17 +1740,17 @@ IMMHandler::HandleDocumentFeed(nsWindow*
   if (targetOffset > int32_t(str.Length())) {
     MOZ_LOG(gIMMLog, LogLevel::Error,
       ("IMM: HandleDocumentFeed, FAILED, due to the caret offset is invalid"));
     return false;
   }
 
   // Get the focused paragraph, we decide that it starts from the previous CRLF
   // (or start of the editor) to the next one (or the end of the editor).
-  int32_t paragraphStart = str.RFind("", false, targetOffset, -1) + 1;
+  int32_t paragraphStart = str.RFind("\n", false, targetOffset, -1) + 1;
   int32_t paragraphEnd =
     str.Find("\r", false, targetOffset + targetLength, -1);
   if (paragraphEnd < 0) {
     paragraphEnd = str.Length();
   }
   nsDependentSubstring paragraph(str, paragraphStart,
                                  paragraphEnd - paragraphStart);