Merge m-c to m-i, a=merge
authorSeth Fowler <seth@mozilla.com>
Sat, 19 Sep 2015 13:28:29 -0700
changeset 296032 3d9c627fc1c5f623e63e0ff7a3758f6975b9db9b
parent 295982 b547f85da6c3e56923271c4a96ed2031869ba860 (current diff)
parent 296031 a4e440c29da95fc250b4a47ac8b6d7552d9bfe4f (diff)
child 296033 4ce4da0007104e7e45c30bb7712ff55725ca0faf
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone43.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 m-i, a=merge
dom/push/PushServiceHttp2Crypto.jsm
dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys.js
--- a/caps/nsNullPrincipal.cpp
+++ b/caps/nsNullPrincipal.cpp
@@ -168,16 +168,19 @@ nsNullPrincipal::Read(nsIObjectInputStre
   NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNullPrincipal::Write(nsIObjectOutputStream* aStream)
 {
+  NS_ENSURE_TRUE(mOriginAttributes.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
+                 NS_ERROR_INVALID_ARG);
+
   nsAutoCString suffix;
   OriginAttributesRef().CreateSuffix(suffix);
 
   nsresult rv = aStream->WriteStringZ(suffix.get());
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
--- a/caps/nsPrincipal.cpp
+++ b/caps/nsPrincipal.cpp
@@ -422,16 +422,18 @@ nsPrincipal::Read(nsIObjectInputStream* 
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPrincipal::Write(nsIObjectOutputStream* aStream)
 {
   NS_ENSURE_STATE(mCodebase);
+  NS_ENSURE_TRUE(mOriginAttributes.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
+                 NS_ERROR_INVALID_ARG);
 
   nsresult rv = NS_WriteOptionalCompoundObject(aStream, mCodebase, NS_GET_IID(nsIURI),
                                                true);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   rv = NS_WriteOptionalCompoundObject(aStream, mDomain, NS_GET_IID(nsIURI),
--- a/caps/tests/unit/test_origin.js
+++ b/caps/tests/unit/test_origin.js
@@ -98,16 +98,30 @@ function run_test() {
   var exampleOrg_addon = ssm.createCodebasePrincipal(makeURI('http://example.org'), {addonId: 'dummy'});
   checkOriginAttributes(exampleOrg_addon, { addonId: "dummy" }, '^addonId=dummy');
   do_check_eq(exampleOrg_addon.origin, 'http://example.org^addonId=dummy');
 
   // Make sure that we refuse to create .origin for principals with UNKNOWN_APP_ID.
   var simplePrin = ssm.getSimpleCodebasePrincipal(makeURI('http://example.com'));
   try { simplePrin.origin; do_check_true(false); } catch (e) { do_check_true(true); }
 
+  // Make sure we don't crash when serializing them either.
+  try {
+    let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
+                       createInstance(Ci.nsIObjectOutputStream);
+    let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+    pipe.init(false, false, 0, 0xffffffff, null);
+    binaryStream.setOutputStream(pipe.outputStream);
+    binaryStream.writeCompoundObject(simplePrin, Ci.nsISupports, true);
+    binaryStream.close();
+  } catch (e) {
+    do_check_true(true);
+  }
+
+
   // Just userContext.
   var exampleOrg_userContext = ssm.createCodebasePrincipal(makeURI('http://example.org'), {userContextId: 42});
   checkOriginAttributes(exampleOrg_userContext, { userContextId: 42 }, '^userContextId=42');
   do_check_eq(exampleOrg_userContext.origin, 'http://example.org^userContextId=42');
 
   // UserContext and Addon.
   var exampleOrg_userContextAddon = ssm.createCodebasePrincipal(makeURI('http://example.org'), {addonId: 'dummy', userContextId: 42});
   var nullPrin_userContextAddon = ssm.createNullPrincipal({addonId: 'dummy', userContextId: 42});
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -163,17 +163,19 @@ const mozilla::Module::ContractIDEntry k
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "credits", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "license", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "logo", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "memory", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "mozilla", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "neterror", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "networking", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newaddon", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
+#ifdef NIGHTLY_BUILD
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "performance", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
+#endif
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "plugins", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "serviceworkers", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "srcdoc", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "support", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "telemetry", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "webrtc", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_URI_LOADER_CONTRACTID, &kNS_URI_LOADER_CID },
   { NS_DOCUMENTLOADER_SERVICE_CONTRACTID, &kNS_DOCUMENTLOADER_SERVICE_CID },
--- a/dom/devicestorage/DeviceStorage.h
+++ b/dom/devicestorage/DeviceStorage.h
@@ -244,16 +244,17 @@ public:
   already_AddRefed<DOMRequest> CreateFileDescriptor(const nsAString& aPath,
                                                     DeviceStorageFileDescriptor* aDSFD,
                                                     ErrorResult& aRv);
 
   bool CanBeMounted();
   bool CanBeFormatted();
   bool CanBeShared();
   bool IsRemovable();
+  bool LowDiskSpace();
   bool Default();
   void GetStorageName(nsAString& aStorageName);
 
   already_AddRefed<Promise>
   GetRoot(ErrorResult& aRv);
 
   static void
   CreateDeviceStorageFor(nsPIDOMWindow* aWin,
--- a/dom/devicestorage/DeviceStorageStatics.cpp
+++ b/dom/devicestorage/DeviceStorageStatics.cpp
@@ -67,16 +67,17 @@ DeviceStorageStatics::InitializeDirs()
   }
 
   MOZ_ASSERT(sInstance->mInitialized);
 }
 
 DeviceStorageStatics::DeviceStorageStatics()
   : mInitialized(false)
   , mPromptTesting(false)
+  , mLowDiskSpace(false)
 {
   DS_LOG_INFO("");
 }
 
 DeviceStorageStatics::~DeviceStorageStatics()
 {
   DS_LOG_INFO("");
 }
@@ -353,16 +354,26 @@ DeviceStorageStatics::IsPromptTesting()
 {
   StaticMutexAutoLock lock(sMutex);
   if (NS_WARN_IF(!sInstance)) {
     return false;
   }
   return sInstance->mPromptTesting;
 }
 
+/* static */ bool
+DeviceStorageStatics::LowDiskSpace()
+{
+  StaticMutexAutoLock lock(sMutex);
+  if (NS_WARN_IF(!sInstance)) {
+    return false;
+  }
+  return sInstance->mLowDiskSpace;
+}
+
 /* static */ void
 DeviceStorageStatics::GetWritableName(nsString& aName)
 {
   StaticMutexAutoLock lock(sMutex);
   if (NS_WARN_IF(!sInstance)) {
     aName.Truncate();
     return;
   }
@@ -600,37 +611,39 @@ DeviceStorageStatics::Observe(nsISupport
     while (i > 0) {
       --i;
       mListeners[i]->OnFileWatcherUpdate(data, file);
     }
     return NS_OK;
   }
 
   if (!strcmp(aTopic, kDiskSpaceWatcher)) {
-    // 'disk-space-watcher' notifications are sent when there is a modification
-    // of a file in a specific location while a low device storage situation
-    // exists or after recovery of a low storage situation. For Firefox OS,
-    // these notifications are specific for apps storage.
-    bool lowDiskSpace = false;
-    if (!NS_strcmp(aData, MOZ_UTF16("full"))) {
-      lowDiskSpace = true;
-    } else if (NS_strcmp(aData, MOZ_UTF16("free"))) {
-      return NS_OK;
-    }
-
     StaticMutexAutoLock lock(sMutex);
     if (NS_WARN_IF(!sInstance)) {
       return NS_OK;
     }
 
+    // 'disk-space-watcher' notifications are sent when there is a modification
+    // of a file in a specific location while a low device storage situation
+    // exists or after recovery of a low storage situation. For Firefox OS,
+    // these notifications are specific for apps storage.
+    if (!NS_strcmp(aData, MOZ_UTF16("full"))) {
+      sInstance->mLowDiskSpace = true;
+    } else if (!NS_strcmp(aData, MOZ_UTF16("free"))) {
+      sInstance->mLowDiskSpace = false;
+    } else {
+      return NS_OK;
+    }
+
+
     uint32_t i = mListeners.Length();
-    DS_LOG_INFO("disk space %d (%u)", lowDiskSpace, i);
+    DS_LOG_INFO("disk space %d (%u)", sInstance->mLowDiskSpace, i);
     while (i > 0) {
       --i;
-      mListeners[i]->OnDiskSpaceWatcher(lowDiskSpace);
+      mListeners[i]->OnDiskSpaceWatcher(sInstance->mLowDiskSpace);
     }
     return NS_OK;
   }
 
   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     StaticMutexAutoLock lock(sMutex);
     if (NS_WARN_IF(!sInstance)) {
       return NS_OK;
--- a/dom/devicestorage/DeviceStorageStatics.h
+++ b/dom/devicestorage/DeviceStorageStatics.h
@@ -25,16 +25,17 @@ public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   static void Initialize();
   static void InitializeDirs();
   static void AddListener(nsDOMDeviceStorage* aListener);
   static void RemoveListener(nsDOMDeviceStorage* aListener);
 
+  static bool LowDiskSpace();
   static bool IsPromptTesting();
   static void GetWritableName(nsString& aName);
   static void SetWritableName(const nsAString& aName);
 
   static bool HasOverrideRootDir();
   static already_AddRefed<nsIFile> GetAppsDir();
   static already_AddRefed<nsIFile> GetCrashesDir();
   static already_AddRefed<nsIFile> GetPicturesDir();
@@ -87,16 +88,17 @@ private:
     nsCOMPtr<nsIThread> mOwningThread;
   };
 
   nsTArray<nsRefPtr<ListenerWrapper> > mListeners;
   nsCOMPtr<nsIFile> mDirs[TYPE_COUNT];
 
   bool mInitialized;
   bool mPromptTesting;
+  bool mLowDiskSpace;
   nsString mWritableName;
 
   static StaticRefPtr<DeviceStorageStatics> sInstance;
   static StaticMutex sMutex;
 };
 
 } // namespace devicestorage
 } // namespace dom
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -3369,16 +3369,22 @@ nsDOMDeviceStorage::CanBeShared()
 }
 
 bool
 nsDOMDeviceStorage::IsRemovable()
 {
   return mIsRemovable;
 }
 
+bool
+nsDOMDeviceStorage::LowDiskSpace()
+{
+  return DeviceStorageStatics::LowDiskSpace();
+}
+
 already_AddRefed<Promise>
 nsDOMDeviceStorage::GetRoot(ErrorResult& aRv)
 {
   if (!mFileSystem) {
     mFileSystem = new DeviceStorageFileSystem(mStorageType, mStorageName);
     mFileSystem->Init(this);
   }
   return mozilla::dom::Directory::GetRoot(mFileSystem, aRv);
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -37,16 +37,17 @@
 #include "InternalRequest.h"
 #include "InternalResponse.h"
 
 #include "nsFormData.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 #include "Workers.h"
+#include "FetchUtil.h"
 
 namespace mozilla {
 namespace dom {
 
 using namespace workers;
 
 class WorkerFetchResolver final : public FetchDriverObserver
 {
@@ -904,63 +905,16 @@ ExtractByteStreamFromBody(const ArrayBuf
     return ExtractFromURLSearchParams(params, aStream, aContentType);
   }
 
   NS_NOTREACHED("Should never reach here");
   return NS_ERROR_FAILURE;
 }
 
 namespace {
-class StreamDecoder final
-{
-  nsCOMPtr<nsIUnicodeDecoder> mDecoder;
-  nsString mDecoded;
-
-public:
-  StreamDecoder()
-    : mDecoder(EncodingUtils::DecoderForEncoding("UTF-8"))
-  {
-    MOZ_ASSERT(mDecoder);
-  }
-
-  nsresult
-  AppendText(const char* aSrcBuffer, uint32_t aSrcBufferLen)
-  {
-    int32_t destBufferLen;
-    nsresult rv =
-      mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, &destBufferLen);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (!mDecoded.SetCapacity(mDecoded.Length() + destBufferLen, fallible)) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    char16_t* destBuffer = mDecoded.BeginWriting() + mDecoded.Length();
-    int32_t totalChars = mDecoded.Length();
-
-    int32_t srcLen = (int32_t) aSrcBufferLen;
-    int32_t outLen = destBufferLen;
-    rv = mDecoder->Convert(aSrcBuffer, &srcLen, destBuffer, &outLen);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-    totalChars += outLen;
-    mDecoded.SetLength(totalChars);
-
-    return NS_OK;
-  }
-
-  nsString&
-  GetText()
-  {
-    return mDecoded;
-  }
-};
-
 /*
  * Called on successfully reading the complete stream.
  */
 template <class Derived>
 class ContinueConsumeBodyRunnable final : public WorkerRunnable
 {
   // This has been addrefed before this runnable is dispatched,
   // released in WorkerRun().
@@ -1431,138 +1385,91 @@ FetchBody<Derived>::ContinueConsumeBody(
 
   // Finish successfully consuming body according to type.
   MOZ_ASSERT(aResult);
 
   AutoJSAPI jsapi;
   jsapi.Init(DerivedClass()->GetParentObject());
   JSContext* cx = jsapi.cx();
 
+  ErrorResult error;
+
   switch (mConsumeType) {
     case CONSUME_ARRAYBUFFER: {
       JS::Rooted<JSObject*> arrayBuffer(cx);
-      arrayBuffer = JS_NewArrayBufferWithContents(cx, aResultLength, reinterpret_cast<void *>(aResult));
-      if (!arrayBuffer) {
-        JS_ClearPendingException(cx);
-        localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
-        NS_WARNING("OUT OF MEMORY");
-        return;
+      FetchUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult,
+                                    error);
+
+      error.WouldReportJSException();
+      if (!error.Failed()) {
+        JS::Rooted<JS::Value> val(cx);
+        val.setObjectOrNull(arrayBuffer);
+
+        localPromise->MaybeResolve(cx, val);
+        // ArrayBuffer takes over ownership.
+        autoFree.Reset();
       }
-
-      JS::Rooted<JS::Value> val(cx);
-      val.setObjectOrNull(arrayBuffer);
-      localPromise->MaybeResolve(cx, val);
-      // ArrayBuffer takes over ownership.
-      autoFree.Reset();
-      return;
+      break;
     }
     case CONSUME_BLOB: {
-      nsRefPtr<dom::Blob> blob =
-        Blob::CreateMemoryBlob(DerivedClass()->GetParentObject(),
-                               reinterpret_cast<void *>(aResult), aResultLength,
-                               NS_ConvertUTF8toUTF16(mMimeType));
-
-      if (!blob) {
-        localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
-        return;
+      nsRefPtr<dom::Blob> blob = FetchUtil::ConsumeBlob(
+        DerivedClass()->GetParentObject(), NS_ConvertUTF8toUTF16(mMimeType),
+        aResultLength, aResult, error);
+      error.WouldReportJSException();
+      if (!error.Failed()) {
+        localPromise->MaybeResolve(blob);
+        // File takes over ownership.
+        autoFree.Reset();
       }
-
-      localPromise->MaybeResolve(blob);
-      // File takes over ownership.
-      autoFree.Reset();
-      return;
+      break;
     }
     case CONSUME_FORMDATA: {
       nsCString data;
       data.Adopt(reinterpret_cast<char*>(aResult), aResultLength);
       autoFree.Reset();
 
-      NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data");
-
-      // Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary=
-      // but disallow multipart/form-datafoobar.
-      bool isValidFormDataMimeType = StringBeginsWith(mMimeType, formDataMimeType);
-
-      if (isValidFormDataMimeType && mMimeType.Length() > formDataMimeType.Length()) {
-        isValidFormDataMimeType = mMimeType[formDataMimeType.Length()] == ';';
-      }
-
-      if (isValidFormDataMimeType) {
-        FormDataParser parser(mMimeType, data, DerivedClass()->GetParentObject());
-        if (!parser.Parse()) {
-          ErrorResult result;
-          result.ThrowTypeError(MSG_BAD_FORMDATA);
-          localPromise->MaybeReject(result);
-          return;
-        }
-
-        nsRefPtr<nsFormData> fd = parser.FormData();
-        MOZ_ASSERT(fd);
+      nsRefPtr<nsFormData> fd = FetchUtil::ConsumeFormData(
+        DerivedClass()->GetParentObject(),
+        mMimeType, data, error);
+      if (!error.Failed()) {
         localPromise->MaybeResolve(fd);
-      } else {
-        NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
-        bool isValidUrlEncodedMimeType = StringBeginsWith(mMimeType, urlDataMimeType);
-
-        if (isValidUrlEncodedMimeType && mMimeType.Length() > urlDataMimeType.Length()) {
-          isValidUrlEncodedMimeType = mMimeType[urlDataMimeType.Length()] == ';';
-        }
-
-        if (isValidUrlEncodedMimeType) {
-          URLParams params;
-          params.ParseInput(data);
-
-          nsRefPtr<nsFormData> fd = new nsFormData(DerivedClass()->GetParentObject());
-          FillFormIterator iterator(fd);
-          DebugOnly<bool> status = params.ForEach(iterator);
-          MOZ_ASSERT(status);
-
-          localPromise->MaybeResolve(fd);
-        } else {
-          ErrorResult result;
-          result.ThrowTypeError(MSG_BAD_FORMDATA);
-          localPromise->MaybeReject(result);
-        }
       }
-      return;
+      break;
     }
     case CONSUME_TEXT:
       // fall through handles early exit.
     case CONSUME_JSON: {
-      StreamDecoder decoder;
-      decoder.AppendText(reinterpret_cast<char*>(aResult), aResultLength);
-
-      nsString& decoded = decoder.GetText();
-      if (mConsumeType == CONSUME_TEXT) {
-        localPromise->MaybeResolve(decoded);
-        return;
-      }
+      nsString decoded;
+      if (NS_SUCCEEDED(FetchUtil::ConsumeText(aResultLength, aResult, decoded))) {
+        if (mConsumeType == CONSUME_TEXT) {
+          localPromise->MaybeResolve(decoded);
+        } else {
+          JS::Rooted<JS::Value> json(cx);
+          FetchUtil::ConsumeJson(cx, &json, decoded, error);
+          if (!error.Failed()) {
+            localPromise->MaybeResolve(cx, json);
+          }
+        }
+      };
+      break;
+    }
+    default:
+      NS_NOTREACHED("Unexpected consume body type");
+  }
 
-      AutoForceSetExceptionOnContext forceExn(cx);
-      JS::Rooted<JS::Value> json(cx);
-      if (!JS_ParseJSON(cx, decoded.get(), decoded.Length(), &json)) {
-        if (!JS_IsExceptionPending(cx)) {
-          localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
-          return;
-        }
-
-        JS::Rooted<JS::Value> exn(cx);
-        DebugOnly<bool> gotException = JS_GetPendingException(cx, &exn);
-        MOZ_ASSERT(gotException);
-
-        JS_ClearPendingException(cx);
-        localPromise->MaybeReject(cx, exn);
-        return;
-      }
-
-      localPromise->MaybeResolve(cx, json);
-      return;
+  error.WouldReportJSException();
+  if (error.Failed()) {
+    if (error.IsJSException()) {
+      JS::Rooted<JS::Value> exn(cx);
+      error.StealJSException(cx, &exn);
+      localPromise->MaybeReject(cx, exn);
+    } else {
+      localPromise->MaybeReject(error);
     }
   }
-
-  NS_NOTREACHED("Unexpected consume body type");
 }
 
 template <class Derived>
 already_AddRefed<Promise>
 FetchBody<Derived>::ConsumeBody(ConsumeType aType, ErrorResult& aRv)
 {
   mConsumeType = aType;
   if (BodyUsed()) {
@@ -1617,10 +1524,11 @@ FetchBody<Derived>::SetMimeType()
 
 template
 void
 FetchBody<Request>::SetMimeType();
 
 template
 void
 FetchBody<Response>::SetMimeType();
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/FetchUtil.cpp
+++ b/dom/fetch/FetchUtil.cpp
@@ -1,15 +1,68 @@
 #include "FetchUtil.h"
+
 #include "nsError.h"
+#include "nsIUnicodeDecoder.h"
 #include "nsString.h"
 
+#include "mozilla/dom/EncodingUtils.h"
+
 namespace mozilla {
 namespace dom {
 
+namespace {
+class StreamDecoder final
+{
+  nsCOMPtr<nsIUnicodeDecoder> mDecoder;
+  nsString mDecoded;
+
+public:
+  StreamDecoder()
+    : mDecoder(EncodingUtils::DecoderForEncoding("UTF-8"))
+  {
+    MOZ_ASSERT(mDecoder);
+  }
+
+  nsresult
+  AppendText(const char* aSrcBuffer, uint32_t aSrcBufferLen)
+  {
+    int32_t destBufferLen;
+    nsresult rv =
+      mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, &destBufferLen);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (!mDecoded.SetCapacity(mDecoded.Length() + destBufferLen, fallible)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    char16_t* destBuffer = mDecoded.BeginWriting() + mDecoded.Length();
+    int32_t totalChars = mDecoded.Length();
+
+    int32_t srcLen = (int32_t) aSrcBufferLen;
+    int32_t outLen = destBufferLen;
+    rv = mDecoder->Convert(aSrcBuffer, &srcLen, destBuffer, &outLen);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    totalChars += outLen;
+    mDecoded.SetLength(totalChars);
+
+    return NS_OK;
+  }
+
+  nsString&
+  GetText()
+  {
+    return mDecoded;
+  }
+};
+}
+
 // static
 nsresult
 FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod)
 {
   nsAutoCString upperCaseMethod(aMethod);
   ToUpperCase(upperCaseMethod);
   if (upperCaseMethod.EqualsLiteral("CONNECT") ||
       upperCaseMethod.EqualsLiteral("TRACE") ||
@@ -28,10 +81,138 @@ FetchUtil::GetValidRequestMethod(const n
     outMethod = upperCaseMethod;
   }
   else {
     outMethod = aMethod; // Case unchanged for non-standard methods
   }
   return NS_OK;
 }
 
+// static
+void
+FetchUtil::ConsumeArrayBuffer(JSContext* aCx,
+                              JS::MutableHandle<JSObject*> aValue,
+                              uint32_t aInputLength, uint8_t* aInput,
+                              ErrorResult& aRv)
+{
+  JS::Rooted<JSObject*> arrayBuffer(aCx);
+  arrayBuffer = JS_NewArrayBufferWithContents(aCx, aInputLength,
+    reinterpret_cast<void *>(aInput));
+  if (!arrayBuffer) {
+    JS_ClearPendingException(aCx);
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+  aValue.set(arrayBuffer);
+}
+
+// static
+already_AddRefed<Blob>
+FetchUtil::ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
+                       uint32_t aInputLength, uint8_t* aInput,
+                       ErrorResult& aRv)
+{
+  nsRefPtr<Blob> blob =
+    Blob::CreateMemoryBlob(aParent,
+                           reinterpret_cast<void *>(aInput), aInputLength,
+                           aMimeType);
+
+  if (!blob) {
+    aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
+    return nullptr;
+  }
+  return blob.forget();
+}
+
+// static
+already_AddRefed<nsFormData>
+FetchUtil::ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
+                           const nsCString& aStr, ErrorResult& aRv)
+{
+  NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data");
+
+  // Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary=
+  // but disallow multipart/form-datafoobar.
+  bool isValidFormDataMimeType = StringBeginsWith(aMimeType, formDataMimeType);
+
+  if (isValidFormDataMimeType && aMimeType.Length() > formDataMimeType.Length()) {
+    isValidFormDataMimeType = aMimeType[formDataMimeType.Length()] == ';';
+  }
+
+  if (isValidFormDataMimeType) {
+    FormDataParser parser(aMimeType, aStr, aParent);
+    if (!parser.Parse()) {
+      aRv.ThrowTypeError(MSG_BAD_FORMDATA);
+      return nullptr;
+    }
+
+    nsRefPtr<nsFormData> fd = parser.FormData();
+    MOZ_ASSERT(fd);
+    return fd.forget();
+  }
+
+  NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
+  bool isValidUrlEncodedMimeType = StringBeginsWith(aMimeType, urlDataMimeType);
+
+  if (isValidUrlEncodedMimeType && aMimeType.Length() > urlDataMimeType.Length()) {
+    isValidUrlEncodedMimeType = aMimeType[urlDataMimeType.Length()] == ';';
+  }
+
+  if (isValidUrlEncodedMimeType) {
+    URLParams params;
+    params.ParseInput(aStr);
+
+    nsRefPtr<nsFormData> fd = new nsFormData(aParent);
+    FillFormIterator iterator(fd);
+    DebugOnly<bool> status = params.ForEach(iterator);
+    MOZ_ASSERT(status);
+
+    return fd.forget();
+  }
+
+  aRv.ThrowTypeError(MSG_BAD_FORMDATA);
+  return nullptr;
+}
+
+// static
+nsresult
+FetchUtil::ConsumeText(uint32_t aInputLength, uint8_t* aInput,
+                       nsString& aText)
+{
+  StreamDecoder decoder;
+  nsresult rv = decoder.AppendText(reinterpret_cast<char*>(aInput),
+                                   aInputLength);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  aText = decoder.GetText();
+  return NS_OK;
+}
+
+// static
+void
+FetchUtil::ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+                       const nsString& aStr, ErrorResult& aRv)
+{
+  aRv.MightThrowJSException();
+
+  AutoForceSetExceptionOnContext forceExn(aCx);
+  JS::Rooted<JS::Value> json(aCx);
+  if (!JS_ParseJSON(aCx, aStr.get(), aStr.Length(), &json)) {
+    if (!JS_IsExceptionPending(aCx)) {
+      aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
+      return;
+    }
+
+    JS::Rooted<JS::Value> exn(aCx);
+    DebugOnly<bool> gotException = JS_GetPendingException(aCx, &exn);
+    MOZ_ASSERT(gotException);
+
+    JS_ClearPendingException(aCx);
+    aRv.ThrowJSException(aCx, exn);
+    return;
+  }
+
+  aValue.set(json);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/FetchUtil.h
+++ b/dom/fetch/FetchUtil.h
@@ -1,13 +1,17 @@
 #ifndef mozilla_dom_FetchUtil_h
 #define mozilla_dom_FetchUtil_h
 
 #include "nsString.h"
 #include "nsError.h"
+#include "nsFormData.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/File.h"
 
 namespace mozilla {
 namespace dom {
 
 class FetchUtil final
 {
 private:
   FetchUtil() = delete;
@@ -16,13 +20,53 @@ public:
   /**
   * Sets outMethod to a valid HTTP request method string based on an input method.
   * Implements checks and normalization as specified by the Fetch specification.
   * Returns NS_ERROR_DOM_SECURITY_ERR if the method is invalid.
   * Otherwise returns NS_OK and the normalized method via outMethod.
   */
   static nsresult
   GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod);
+
+  /**
+   * Creates an array buffer from an array, assigning the result to |aValue|.
+   * The array buffer takes ownership of |aInput|, which must be allocated
+   * by |malloc|.
+   */
+  static void
+  ConsumeArrayBuffer(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
+                     uint32_t aInputLength, uint8_t* aInput, ErrorResult& aRv);
+
+  /**
+   * Creates an in-memory blob from an array. The blob takes ownership of
+   * |aInput|, which must be allocated by |malloc|.
+   */
+  static already_AddRefed<Blob>
+  ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
+              uint32_t aInputLength, uint8_t* aInput, ErrorResult& aRv);
+
+  /**
+   * Creates a form data object from a UTF-8 encoded |aStr|. Returns |nullptr|
+   * and sets |aRv| to MSG_BAD_FORMDATA if |aStr| contains invalid data.
+   */
+  static already_AddRefed<nsFormData>
+  ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
+                  const nsCString& aStr, ErrorResult& aRv);
+
+  /**
+   * UTF-8 decodes |aInput| into |aText|. The caller may free |aInput|
+   * once this method returns.
+   */
+  static nsresult
+  ConsumeText(uint32_t aInputLength, uint8_t* aInput, nsString& aText);
+
+  /**
+   * Parses a UTF-8 encoded |aStr| as JSON, assigning the result to |aValue|.
+   * Sets |aRv| to a syntax error if |aStr| contains invalid data.
+   */
+  static void
+  ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+              const nsString& aStr, ErrorResult& aRv);
 };
 
 } // namespace dom
 } // namespace mozilla
 #endif
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -29,17 +29,17 @@ interface nsIServiceWorkerInfo : nsISupp
   readonly attribute DOMString scope;
   readonly attribute DOMString scriptSpec;
   readonly attribute DOMString currentWorkerURL;
 
   readonly attribute DOMString activeCacheName;
   readonly attribute DOMString waitingCacheName;
 };
 
-[scriptable, builtinclass, uuid(8d80dd18-597b-4378-b41e-768bfe48dd4f)]
+[scriptable, builtinclass, uuid(471b2d5d-64c3-4dea-bde1-219853dcaac8)]
 interface nsIServiceWorkerManager : nsISupports
 {
   /**
    * Registers a ServiceWorker with script loaded from `aScriptURI` to act as
    * the ServiceWorker for aScope.  Requires a valid entry settings object on
    * the stack. This means you must call this from content code 'within'
    * a window.
    *
@@ -136,19 +136,20 @@ interface nsIServiceWorkerManager : nsIS
                                   in AString aTitle,
                                   in AString aDir,
                                   in AString aLang,
                                   in AString aBody,
                                   in AString aTag,
                                   in AString aIcon,
                                   in AString aData,
                                   in AString aBehavior);
-  void sendPushEvent(in ACString aOriginAttributes,
-                     in ACString aScope,
-                     in DOMString aData);
+  [optional_argc] void sendPushEvent(in ACString aOriginAttributes,
+                                     in ACString aScope,
+                                     [optional] in uint32_t aDataLength,
+                                     [optional, array, size_is(aDataLength)] in uint8_t aDataBytes);
   void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes,
                                        in ACString scope);
 
   void updateAllRegistrations();
 };
 
 %{ C++
 #define SERVICEWORKERMANAGER_CONTRACTID "@mozilla.org/serviceworkers/manager;1"
--- a/dom/locales/en-US/chrome/xslt/xslt.properties
+++ b/dom/locales/en-US/chrome/xslt/xslt.properties
@@ -28,11 +28,12 @@ 23 = XPath parse failure: ':' unexpected
 24 = XPath parse failure: '!' unexpected, negation is not():
 25 = XPath parse failure: illegal character found:
 26 = XPath parse failure: binary operator expected:
 27 = An XSLT stylesheet load was blocked for security reasons.
 28 = Evaluating an invalid expression.
 29 = Unbalanced curly brace.
 30 = Creating an element with an invalid QName.
 31 = Variable binding shadows variable binding within the same template.
+32 = Call to the key function not allowed.
 
 LoadingError = Error loading stylesheet: %S
 TransformError = Error during XSLT transformation: %S
new file mode 100644
--- /dev/null
+++ b/dom/push/PushCrypto.jsm
@@ -0,0 +1,221 @@
+/* jshint moz: true, esnext: true */
+/* 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/. */
+
+'use strict';
+
+const Cu = Components.utils;
+
+Cu.importGlobalProperties(['crypto']);
+
+this.EXPORTED_SYMBOLS = ['PushCrypto', 'concatArray',
+                         'getEncryptionKeyParams', 'getEncryptionParams',
+                         'base64UrlDecode'];
+
+var ENCRYPT_INFO = new TextEncoder('utf-8').encode('Content-Encoding: aesgcm128');
+var NONCE_INFO = new TextEncoder('utf-8').encode('Content-Encoding: nonce');
+
+this.getEncryptionKeyParams = function(encryptKeyField) {
+  var params = encryptKeyField.split(',');
+  return params.reduce((m, p) => {
+    var pmap = p.split(';').reduce(parseHeaderFieldParams, {});
+    if (pmap.keyid && pmap.dh) {
+      m[pmap.keyid] = pmap.dh;
+    }
+    return m;
+  }, {});
+};
+
+this.getEncryptionParams = function(encryptField) {
+  var p = encryptField.split(',', 1)[0];
+  if (!p) {
+    return null;
+  }
+  return p.split(';').reduce(parseHeaderFieldParams, {});
+};
+
+var parseHeaderFieldParams = (m, v) => {
+  var i = v.indexOf('=');
+  if (i >= 0) {
+    // A quoted string with internal quotes is invalid for all the possible
+    // values of this header field.
+    m[v.substring(0, i).trim()] = v.substring(i + 1).trim()
+                                    .replace(/^"(.*)"$/, '$1');
+  }
+  return m;
+};
+
+function chunkArray(array, size) {
+  var start = array.byteOffset || 0;
+  array = array.buffer || array;
+  var index = 0;
+  var result = [];
+  while(index + size <= array.byteLength) {
+    result.push(new Uint8Array(array, start + index, size));
+    index += size;
+  }
+  if (index < array.byteLength) {
+    result.push(new Uint8Array(array, start + index));
+  }
+  return result;
+}
+
+this.base64UrlDecode = function(s) {
+  s = s.replace(/-/g, '+').replace(/_/g, '/');
+
+  // Replace padding if it was stripped by the sender.
+  // See http://tools.ietf.org/html/rfc4648#section-4
+  switch (s.length % 4) {
+    case 0:
+      break; // No pad chars in this case
+    case 2:
+      s += '==';
+      break; // Two pad chars
+    case 3:
+      s += '=';
+      break; // One pad char
+    default:
+      throw new Error('Illegal base64url string!');
+  }
+
+  // With correct padding restored, apply the standard base64 decoder
+  var decoded = atob(s);
+
+  var array = new Uint8Array(new ArrayBuffer(decoded.length));
+  for (var i = 0; i < decoded.length; i++) {
+    array[i] = decoded.charCodeAt(i);
+  }
+  return array;
+};
+
+this.concatArray = function(arrays) {
+  var size = arrays.reduce((total, a) => total + a.byteLength, 0);
+  var index = 0;
+  return arrays.reduce((result, a) => {
+    result.set(new Uint8Array(a), index);
+    index += a.byteLength;
+    return result;
+  }, new Uint8Array(size));
+};
+
+var HMAC_SHA256 = { name: 'HMAC', hash: 'SHA-256' };
+
+function hmac(key) {
+  this.keyPromise = crypto.subtle.importKey('raw', key, HMAC_SHA256,
+                                            false, ['sign']);
+}
+
+hmac.prototype.hash = function(input) {
+  return this.keyPromise.then(k => crypto.subtle.sign('HMAC', k, input));
+};
+
+function hkdf(salt, ikm) {
+  this.prkhPromise = new hmac(salt).hash(ikm)
+    .then(prk => new hmac(prk));
+}
+
+hkdf.prototype.generate = function(info, len) {
+  var input = concatArray([info, new Uint8Array([1])]);
+  return this.prkhPromise
+    .then(prkh => prkh.hash(input))
+    .then(h => {
+      if (h.byteLength < len) {
+        throw new Error('Length is too long');
+      }
+      return h.slice(0, len);
+    });
+};
+
+/* generate a 96-bit IV for use in GCM, 48-bits of which are populated */
+function generateNonce(base, index) {
+  if (index >= Math.pow(2, 48)) {
+    throw new Error('Error generating IV - index is too large.');
+  }
+  var nonce = base.slice(0, 12);
+  nonce = new Uint8Array(nonce);
+  for (var i = 0; i < 6; ++i) {
+    nonce[nonce.byteLength - 1 - i] ^= (index / Math.pow(256, i)) & 0xff;
+  }
+  return nonce;
+}
+
+this.PushCrypto = {
+
+  generateKeys: function() {
+    return crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256'},
+                                     true,
+                                     ['deriveBits'])
+      .then(cryptoKey =>
+         Promise.all([
+           crypto.subtle.exportKey('raw', cryptoKey.publicKey),
+           // TODO: change this when bug 1048931 lands.
+           crypto.subtle.exportKey('jwk', cryptoKey.privateKey)
+         ]));
+  },
+
+  decodeMsg: function(aData, aPrivateKey, aRemotePublicKey, aSalt, aRs) {
+
+    if (aData.byteLength === 0) {
+      // Zero length messages will be passed as null.
+      return Promise.resolve(null);
+    }
+
+    // The last chunk of data must be less than aRs, if it is not return an
+    // error.
+    if (aData.byteLength % (aRs + 16) === 0) {
+      return Promise.reject(new Error('Data truncated'));
+    }
+
+    return Promise.all([
+      crypto.subtle.importKey('raw', base64UrlDecode(aRemotePublicKey),
+                              { name: 'ECDH', namedCurve: 'P-256' },
+                              false,
+                              ['deriveBits']),
+      crypto.subtle.importKey('jwk', aPrivateKey,
+                              { name: 'ECDH', namedCurve: 'P-256' },
+                              false,
+                              ['deriveBits'])
+    ])
+    .then(keys =>
+      crypto.subtle.deriveBits({ name: 'ECDH', public: keys[0] }, keys[1], 256))
+    .then(rawKey => {
+      var kdf = new hkdf(base64UrlDecode(aSalt), new Uint8Array(rawKey));
+      return Promise.all([
+        kdf.generate(ENCRYPT_INFO, 16)
+          .then(gcmBits =>
+                crypto.subtle.importKey('raw', gcmBits, 'AES-GCM', false,
+                                        ['decrypt'])),
+        kdf.generate(NONCE_INFO, 12)
+      ])
+    })
+    .then(r =>
+      // AEAD_AES_128_GCM expands ciphertext to be 16 octets longer.
+      Promise.all(chunkArray(aData, aRs + 16).map((slice, index) =>
+        this._decodeChunk(slice, index, r[1], r[0]))))
+    .then(r => concatArray(r));
+  },
+
+  _decodeChunk: function(aSlice, aIndex, aNonce, aKey) {
+    return crypto.subtle.decrypt({name: 'AES-GCM',
+                                  iv: generateNonce(aNonce, aIndex)
+                                 },
+                                 aKey, aSlice)
+      .then(decoded => {
+        decoded = new Uint8Array(decoded);
+        if (decoded.length == 0) {
+          return Promise.reject(new Error('Decoded array is too short!'));
+        } else if (decoded[0] > decoded.length) {
+          return Promise.reject(new Error ('Padding is wrong!'));
+        } else {
+          // All padded bytes must be zero except the first one.
+          for (var i = 1; i <= decoded[0]; i++) {
+            if (decoded[i] != 0) {
+              return Promise.reject(new Error('Padding is wrong!'));
+            }
+          }
+          return decoded.slice(decoded[0] + 1);
+        }
+      });
+  }
+};
--- a/dom/push/PushRecord.jsm
+++ b/dom/push/PushRecord.jsm
@@ -39,16 +39,18 @@ const QUOTA_REFRESH_TRANSITIONS_SQL = [
 ].join(",");
 
 function PushRecord(props) {
   this.pushEndpoint = props.pushEndpoint;
   this.scope = props.scope;
   this.originAttributes = props.originAttributes;
   this.pushCount = props.pushCount || 0;
   this.lastPush = props.lastPush || 0;
+  this.p256dhPublicKey = props.p256dhPublicKey;
+  this.p256dhPrivateKey = props.p256dhPrivateKey;
   this.setQuota(props.quota);
   this.ctime = (typeof props.ctime === "number") ? props.ctime : 0;
 }
 
 PushRecord.prototype = {
   setQuota(suggestedQuota) {
     this.quota = (!isNaN(suggestedQuota) && suggestedQuota >= 0) ?
                  suggestedQuota : prefs.get("maxQuotaPerSubscription");
@@ -186,22 +188,24 @@ PushRecord.prototype = {
     return this.quota === 0;
   },
 
   toRegistration() {
     return {
       pushEndpoint: this.pushEndpoint,
       lastPush: this.lastPush,
       pushCount: this.pushCount,
+      p256dhKey: this.p256dhPublicKey,
     };
   },
 
   toRegister() {
     return {
       pushEndpoint: this.pushEndpoint,
+      p256dhKey: this.p256dhPublicKey,
     };
   },
 };
 
 // Define lazy getters for the principal and scope URI. IndexedDB can't store
 // `nsIPrincipal` objects, so we keep them in a private weak map.
 var principals = new WeakMap();
 Object.defineProperties(PushRecord.prototype, {
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -23,16 +23,17 @@ const {PushDB} = Cu.import("resource://g
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 const {PushServiceWebSocket} = Cu.import("resource://gre/modules/PushServiceWebSocket.jsm");
 const {PushServiceHttp2} = Cu.import("resource://gre/modules/PushServiceHttp2.jsm");
+const {PushCrypto} = Cu.import("resource://gre/modules/PushCrypto.jsm");
 
 // Currently supported protocols: WebSocket.
 const CONNECTION_PROTOCOLS = [PushServiceWebSocket, PushServiceHttp2];
 
 XPCOMUtils.defineLazyModuleGetter(this, "AlarmService",
                                   "resource://gre/modules/AlarmService.jsm");
 
 this.EXPORTED_SYMBOLS = ["PushService"];
@@ -705,62 +706,85 @@ this.PushService = {
     }
   },
 
   // Fires a push-register system message to all applications that have
   // registration.
   _notifyAllAppsRegister: function() {
     debug("notifyAllAppsRegister()");
     // records are objects describing the registration as stored in IndexedDB.
-    return this._db.getAllUnexpired().then(records =>
-      records.forEach(record =>
-        this._notifySubscriptionChangeObservers(record)
-      )
-    );
+    return this._db.getAllUnexpired().then(records => {
+      records.forEach(record => {
+        this._notifySubscriptionChangeObservers(record);
+      });
+    });
   },
 
   dropRegistrationAndNotifyApp: function(aKeyId) {
     return this._db.getByKeyID(aKeyId).then(record => {
       this._notifySubscriptionChangeObservers(record);
       return this._db.delete(aKeyId);
     });
   },
 
   updateRegistrationAndNotifyApp: function(aOldKey, aRecord) {
     return this._db.delete(aOldKey)
       .then(_ => this._db.put(aRecord))
       .then(record => this._notifySubscriptionChangeObservers(record));
   },
 
+  ensureP256dhKey: function(record) {
+    if (record.p256dhPublicKey && record.p256dhPrivateKey) {
+      return Promise.resolve(record);
+    }
+    // We do not have a encryption key. so we need to generate it. This
+    // is only going to happen on db upgrade from version 4 to higher.
+    return PushCrypto.generateKeys()
+      .then(exportedKeys => {
+        return this.updateRecordAndNotifyApp(record.keyID, record => {
+          record.p256dhPublicKey = exportedKeys[0];
+          record.p256dhPrivateKey = exportedKeys[1];
+          return record;
+        });
+      }, error => {
+        return this.dropRegistrationAndNotifyApp(record.keyID).then(
+          () => Promise.reject(error));
+      });
+  },
+
   updateRecordAndNotifyApp: function(aKeyID, aUpdateFunc) {
     return this._db.update(aKeyID, aUpdateFunc)
-      .then(record => this._notifySubscriptionChangeObservers(record));
+      .then(record => {
+        this._notifySubscriptionChangeObservers(record);
+        return record;
+      });
   },
 
   _recordDidNotNotify: function(reason) {
     Services.telemetry.
       getHistogramById("PUSH_API_NOTIFICATION_RECEIVED_BUT_DID_NOT_NOTIFY").
       add(reason);
   },
 
   /**
    * Dispatches an incoming message to a service worker, recalculating the
    * quota for the associated push registration. If the quota is exceeded,
    * the registration and message will be dropped, and the worker will not
    * be notified.
    *
    * @param {String} keyID The push registration ID.
    * @param {String} message The message contents.
+   * @param {Object} cryptoParams The message encryption settings.
    * @param {Function} updateFunc A function that receives the existing
    *  registration record as its argument, and returns a new record. If the
    *  function returns `null` or `undefined`, the record will not be updated.
    *  `PushServiceWebSocket` uses this to drop incoming updates with older
    *  versions.
    */
-  receivedPushMessage: function(keyID, message, updateFunc) {
+  receivedPushMessage: function(keyID, message, cryptoParams, updateFunc) {
     debug("receivedPushMessage()");
     Services.telemetry.getHistogramById("PUSH_API_NOTIFICATION_RECEIVED").add();
 
     let shouldNotify = false;
     return this.getByKeyID(keyID).then(record => {
       if (!record) {
         this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_KEY_NOT_FOUND);
         throw new Error("No record for key ID " + keyID);
@@ -774,45 +798,59 @@ this.PushService = {
           this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_NO_HISTORY);
       }
       return this._db.update(keyID, record => {
         let newRecord = updateFunc(record);
         if (!newRecord) {
           this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_NO_VERSION_INCREMENT);
           return null;
         }
-        // FIXME(nsm): WHY IS expired checked here but then also checked in the next case?
+        // Because `unregister` is advisory only, we can still receive messages
+        // for stale Simple Push registrations from the server. To work around
+        // this, we check if the record has expired before *and* after updating
+        // the quota.
         if (newRecord.isExpired()) {
-          // Because `unregister` is advisory only, we can still receive messages
-          // for stale registrations from the server.
           debug("receivedPushMessage: Ignoring update for expired key ID " + keyID);
           return null;
         }
         newRecord.receivedPush(lastVisit);
         return newRecord;
       });
     }).then(record => {
       var notified = false;
       if (!record) {
         return notified;
       }
-
-      if (shouldNotify) {
-        notified = this._notifyApp(record, message);
+      let decodedPromise;
+      if (cryptoParams) {
+        decodedPromise = PushCrypto.decodeMsg(
+          message,
+          record.p256dhPrivateKey,
+          cryptoParams.dh,
+          cryptoParams.salt,
+          cryptoParams.rs
+        );
+      } else {
+        decodedPromise = Promise.resolve(null);
       }
-      if (record.isExpired()) {
-        this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_EXPIRED);
-        // Drop the registration in the background. If the user returns to the
-        // site, the service worker will be notified on the next `idle-daily`
-        // event.
-        this._sendUnregister(record).catch(error => {
-          debug("receivedPushMessage: Unregister error: " + error);
-        });
-      }
-      return notified;
+      return decodedPromise.then(message => {
+        if (shouldNotify) {
+          notified = this._notifyApp(record, message);
+        }
+        if (record.isExpired()) {
+          this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_EXPIRED);
+          // Drop the registration in the background. If the user returns to the
+          // site, the service worker will be notified on the next `idle-daily`
+          // event.
+          this._sendUnregister(record).catch(error => {
+            debug("receivedPushMessage: Unregister error: " + error);
+          });
+        }
+        return notified;
+      });
     }).catch(error => {
       debug("receivedPushMessage: Error notifying app: " + error);
     });
   },
 
   _notifyApp: function(aPushRecord, message) {
     if (!aPushRecord || !aPushRecord.scope ||
         aPushRecord.originAttributes === undefined) {
@@ -822,35 +860,43 @@ this.PushService = {
     }
 
     debug("notifyApp() " + aPushRecord.scope);
     // Notify XPCOM observers.
     let notification = Cc["@mozilla.org/push/ObserverNotification;1"]
                          .createInstance(Ci.nsIPushObserverNotification);
     notification.pushEndpoint = aPushRecord.pushEndpoint;
     notification.version = aPushRecord.version;
-    notification.data = message;
+
+    let payload = ArrayBuffer.isView(message) ?
+                  new Uint8Array(message.buffer) : message;
+    if (payload) {
+      notification.data = "";
+      for (let i = 0; i < payload.length; i++) {
+        notification.data += String.fromCharCode(payload[i]);
+      }
+    }
+
     notification.lastPush = aPushRecord.lastPush;
     notification.pushCount = aPushRecord.pushCount;
 
     Services.obs.notifyObservers(
       notification,
       "push-notification",
       aPushRecord.scope
     );
 
     // If permission has been revoked, trash the message.
     if (!aPushRecord.hasPermission()) {
       debug("Does not have permission for push.");
       return false;
     }
 
-    // TODO data.
     let data = {
-      payload: message,
+      payload: payload,
       originAttributes: aPushRecord.originAttributes,
       scope: aPushRecord.scope
     };
 
     Services.telemetry.getHistogramById("PUSH_API_NOTIFY").add();
     this._notifyListeners('push', data);
     return true;
   },
--- a/dom/push/PushServiceChildPreload.jsm
+++ b/dom/push/PushServiceChildPreload.jsm
@@ -18,18 +18,22 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "@mozilla.org/serviceworkers/manager;1",
                                    "nsIServiceWorkerManager");
 
 var processType = Cc["@mozilla.org/xre/app-info;1"]
                     .getService(Ci.nsIXULRuntime).processType;
 var isParent = processType === Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
 
 Services.cpmm.addMessageListener("push", function (aMessage) {
-  swm.sendPushEvent(aMessage.data.originAttributes,
-                    aMessage.data.scope, aMessage.data.payload);
+  let {originAttributes, scope, payload} = aMessage.data;
+  if (payload) {
+    swm.sendPushEvent(originAttributes, scope, payload.length, payload);
+  } else {
+    swm.sendPushEvent(originAttributes, scope);
+  }
 });
 
 Services.cpmm.addMessageListener("pushsubscriptionchange", function (aMessage) {
   swm.sendPushSubscriptionChangeEvent(aMessage.data.originAttributes,
                                       aMessage.data.scope);
 });
 
 if (!isParent) {
--- a/dom/push/PushServiceHttp2.jsm
+++ b/dom/push/PushServiceHttp2.jsm
@@ -14,18 +14,22 @@ const {PushDB} = Cu.import("resource://g
 const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
-const {PushServiceHttp2Crypto, concatArray} =
-  Cu.import("resource://gre/modules/PushServiceHttp2Crypto.jsm");
+const {
+  PushCrypto,
+  concatArray,
+  getEncryptionKeyParams,
+  getEncryptionParams,
+} = Cu.import("resource://gre/modules/PushCrypto.jsm");
 
 this.EXPORTED_SYMBOLS = ["PushServiceHttp2"];
 
 const prefs = new Preferences("dom.push.");
 
 // Don't modify this, instead set dom.push.debug.
 // Set debug first so that all debugging actually works.
 var gDebuggingEnabled = prefs.get("debug");
@@ -171,52 +175,30 @@ PushChannelListener.prototype = {
                                                          msg,
                                                          dh,
                                                          salt,
                                                          rs);
     }
   }
 };
 
-var parseHeaderFieldParams = (m, v) => {
-  var i = v.indexOf('=');
-  if (i >= 0) {
-    // A quoted string with internal quotes is invalid for all the possible
-    // values of this header field.
-    m[v.substring(0, i).trim()] = v.substring(i + 1).trim()
-                                    .replace(/^"(.*)"$/, '$1');
-  }
-  return m;
-};
-
 function encryptKeyFieldParser(aRequest) {
   try {
     var encryptKeyField = aRequest.getRequestHeader("Encryption-Key");
-
-    var params = encryptKeyField.split(',');
-    return params.reduce((m, p) => {
-      var pmap = p.split(';').reduce(parseHeaderFieldParams, {});
-      if (pmap.keyid && pmap.dh) {
-        m[pmap.keyid] = pmap.dh;
-      }
-      return m;
-    }, {});
-
+    return getEncryptionKeyParams(encryptKeyField);
   } catch(e) {
     // getRequestHeader can throw.
     return null;
   }
 }
 
 function encryptFieldParser(aRequest) {
   try {
-    return aRequest.getRequestHeader("Encryption")
-             .split(',', 1)[0]
-             .split(';')
-             .reduce(parseHeaderFieldParams, {});
+    var encryptField = aRequest.getRequestHeader("Encryption");
+    return getEncryptionParams(encryptField);
   } catch(e) {
     // getRequestHeader can throw.
     return null;
   }
 }
 
 var PushServiceDelete = function(resolve, reject) {
   this._resolve = resolve;
@@ -528,17 +510,17 @@ this.PushServiceHttp2 = {
   _subscribeResource: function(aRecord) {
     debug("subscribeResource()");
 
     return this._subscribeResourceInternal({
       record: aRecord,
       retries: 0
     })
     .then(result =>
-      PushServiceHttp2Crypto.generateKeys()
+      PushCrypto.generateKeys()
       .then(exportedKeys => {
         result.p256dhPublicKey = exportedKeys[0];
         result.p256dhPrivateKey = exportedKeys[1];
         this._conns[result.subscriptionUri] = {
           channel: null,
           listener: null,
           countUnableToConnect: 0,
           lastStartListening: 0,
@@ -719,44 +701,21 @@ this.PushServiceHttp2 = {
   },
 
   // Start listening if subscriptions present.
   startConnections: function(aSubscriptions) {
     debug("startConnections() " + aSubscriptions.length);
 
     for (let i = 0; i < aSubscriptions.length; i++) {
       let record = aSubscriptions[i];
-      if (record.p256dhPublicKey && record.p256dhPrivateKey) {
+      this._mainPushService.ensureP256dhKey(record).then(record => {
         this._startSingleConnection(record);
-      } else {
-        // We do not have a encryption key. so we need to generate it. This
-        // is only going to happen on db upgrade from version 4 to higher.
-        PushServiceHttp2Crypto.generateKeys()
-          .then(exportedKeys => {
-            if (this._mainPushService) {
-              return this._mainPushService
-                .updateRecordAndNotifyApp(record.subscriptionUri, record => {
-                  record.p256dhPublicKey = exportedKeys[0];
-                  record.p256dhPrivateKey = exportedKeys[1];
-                  return record;
-                });
-            }
-          }, error => {
-            record = null;
-            if (this._mainPushService) {
-              this._mainPushService
-                .dropRegistrationAndNotifyApp(record.subscriptionUri);
-            }
-          })
-          .then(_ => {
-            if (record) {
-              this._startSingleConnection(record);
-            }
-          });
-      }
+      }, error => {
+        debug("startConnections: Error updating record " + record.keyID);
+      });
     }
   },
 
   _startSingleConnection: function(record) {
     debug("_startSingleConnection()");
     if (typeof this._conns[record.subscriptionUri] != "object") {
       this._conns[record.subscriptionUri] = {channel: null,
                                              listener: null,
@@ -870,65 +829,55 @@ this.PushServiceHttp2 = {
     } else {
       this._retryAfterBackoff(aSubscriptionUri, -1);
     }
   },
 
   _pushChannelOnStop: function(aUri, aAckUri, aMessage, dh, salt, rs) {
     debug("pushChannelOnStop() ");
 
-    this._mainPushService.getByKeyID(aUri)
-    .then(aPushRecord =>
-      PushServiceHttp2Crypto.decodeMsg(aMessage, aPushRecord.p256dhPrivateKey,
-                                       dh, salt, rs)
-      .then(msg => {
-        var msgString = '';
-        for (var i=0; i<msg.length; i++) {
-          msgString += String.fromCharCode(msg[i]);
-        }
-        return this._mainPushService.receivedPushMessage(aUri,
-                                                         msgString,
-                                                         record => {
-          // Always update the stored record.
-          return record;
-        });
-      })
+    let cryptoParams = {
+      dh: dh,
+      salt: salt,
+      rs: rs,
+    };
+    this._mainPushService.receivedPushMessage(
+      aUri, aMessage, cryptoParams, record => {
+        // Always update the stored record.
+        return record;
+      }
     )
     .then(_ => this._ackMsgRecv(aAckUri))
     .catch(err => {
       debug("Error receiving message: " + err);
     });
   },
 
   onAlarmFired: function() {
     this._startConnectionsWaitingForAlarm();
   },
 };
 
 function PushRecordHttp2(record) {
   PushRecord.call(this, record);
   this.subscriptionUri = record.subscriptionUri;
   this.pushReceiptEndpoint = record.pushReceiptEndpoint;
-  this.p256dhPublicKey = record.p256dhPublicKey;
-  this.p256dhPrivateKey = record.p256dhPrivateKey;
 }
 
 PushRecordHttp2.prototype = Object.create(PushRecord.prototype, {
   keyID: {
     get() {
       return this.subscriptionUri;
     },
   },
 });
 
 PushRecordHttp2.prototype.toRegistration = function() {
   let registration = PushRecord.prototype.toRegistration.call(this);
   registration.pushReceiptEndpoint = this.pushReceiptEndpoint;
-  registration.p256dhKey = this.p256dhPublicKey;
   return registration;
 };
 
 PushRecordHttp2.prototype.toRegister = function() {
   let register = PushRecord.prototype.toRegister.call(this);
   register.pushReceiptEndpoint = this.pushReceiptEndpoint;
-  register.p256dhKey = this.p256dhPublicKey;
   return register;
 };
deleted file mode 100644
--- a/dom/push/PushServiceHttp2Crypto.jsm
+++ /dev/null
@@ -1,189 +0,0 @@
-/* jshint moz: true, esnext: true */
-/* 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/. */
-
-'use strict';
-
-const Cu = Components.utils;
-
-Cu.importGlobalProperties(['crypto']);
-
-this.EXPORTED_SYMBOLS = ['PushServiceHttp2Crypto', 'concatArray'];
-
-var ENCRYPT_INFO = new TextEncoder('utf-8').encode('Content-Encoding: aesgcm128');
-var NONCE_INFO = new TextEncoder('utf-8').encode('Content-Encoding: nonce');
-
-function chunkArray(array, size) {
-  var start = array.byteOffset || 0;
-  array = array.buffer || array;
-  var index = 0;
-  var result = [];
-  while(index + size <= array.byteLength) {
-    result.push(new Uint8Array(array, start + index, size));
-    index += size;
-  }
-  if (index < array.byteLength) {
-    result.push(new Uint8Array(array, start + index));
-  }
-  return result;
-}
-
-function base64UrlDecode(s) {
-  s = s.replace(/-/g, '+').replace(/_/g, '/');
-
-  // Replace padding if it was stripped by the sender.
-  // See http://tools.ietf.org/html/rfc4648#section-4
-  switch (s.length % 4) {
-    case 0:
-      break; // No pad chars in this case
-    case 2:
-      s += '==';
-      break; // Two pad chars
-    case 3:
-      s += '=';
-      break; // One pad char
-    default:
-      throw new Error('Illegal base64url string!');
-  }
-
-  // With correct padding restored, apply the standard base64 decoder
-  var decoded = atob(s);
-
-  var array = new Uint8Array(new ArrayBuffer(decoded.length));
-  for (var i = 0; i < decoded.length; i++) {
-    array[i] = decoded.charCodeAt(i);
-  }
-  return array;
-}
-
-this.concatArray = function(arrays) {
-  var size = arrays.reduce((total, a) => total + a.byteLength, 0);
-  var index = 0;
-  return arrays.reduce((result, a) => {
-    result.set(new Uint8Array(a), index);
-    index += a.byteLength;
-    return result;
-  }, new Uint8Array(size));
-};
-
-var HMAC_SHA256 = { name: 'HMAC', hash: 'SHA-256' };
-
-function hmac(key) {
-  this.keyPromise = crypto.subtle.importKey('raw', key, HMAC_SHA256,
-                                            false, ['sign']);
-}
-
-hmac.prototype.hash = function(input) {
-  return this.keyPromise.then(k => crypto.subtle.sign('HMAC', k, input));
-};
-
-function hkdf(salt, ikm) {
-  this.prkhPromise = new hmac(salt).hash(ikm)
-    .then(prk => new hmac(prk));
-}
-
-hkdf.prototype.generate = function(info, len) {
-  var input = concatArray([info, new Uint8Array([1])]);
-  return this.prkhPromise
-    .then(prkh => prkh.hash(input))
-    .then(h => {
-      if (h.byteLength < len) {
-        throw new Error('Length is too long');
-      }
-      return h.slice(0, len);
-    });
-};
-
-/* generate a 96-bit IV for use in GCM, 48-bits of which are populated */
-function generateNonce(base, index) {
-  if (index >= Math.pow(2, 48)) {
-    throw new Error('Error generating IV - index is too large.');
-  }
-  var nonce = base.slice(0, 12);
-  nonce = new Uint8Array(nonce);
-  for (var i = 0; i < 6; ++i) {
-    nonce[nonce.byteLength - 1 - i] ^= (index / Math.pow(256, i)) & 0xff;
-  }
-  return nonce;
-}
-
-this.PushServiceHttp2Crypto = {
-
-  generateKeys: function() {
-    return crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256'},
-                                     true,
-                                     ['deriveBits'])
-      .then(cryptoKey =>
-         Promise.all([
-           crypto.subtle.exportKey('raw', cryptoKey.publicKey),
-           // TODO: change this when bug 1048931 lands.
-           crypto.subtle.exportKey('jwk', cryptoKey.privateKey)
-         ]));
-  },
-
-  decodeMsg: function(aData, aPrivateKey, aRemotePublicKey, aSalt, aRs) {
-
-    if (aData.byteLength === 0) {
-      // Zero length messages will be passed as null.
-      return Promise.resolve(null);
-    }
-
-    // The last chunk of data must be less than aRs, if it is not return an
-    // error.
-    if (aData.byteLength % (aRs + 16) === 0) {
-      return Promise.reject(new Error('Data truncated'));
-    }
-
-    return Promise.all([
-      crypto.subtle.importKey('raw', base64UrlDecode(aRemotePublicKey),
-                              { name: 'ECDH', namedCurve: 'P-256' },
-                              false,
-                              ['deriveBits']),
-      crypto.subtle.importKey('jwk', aPrivateKey,
-                              { name: 'ECDH', namedCurve: 'P-256' },
-                              false,
-                              ['deriveBits'])
-    ])
-    .then(keys =>
-      crypto.subtle.deriveBits({ name: 'ECDH', public: keys[0] }, keys[1], 256))
-    .then(rawKey => {
-      var kdf = new hkdf(base64UrlDecode(aSalt), new Uint8Array(rawKey));
-      return Promise.all([
-        kdf.generate(ENCRYPT_INFO, 16)
-          .then(gcmBits =>
-                crypto.subtle.importKey('raw', gcmBits, 'AES-GCM', false,
-                                        ['decrypt'])),
-        kdf.generate(NONCE_INFO, 12)
-      ])
-    })
-    .then(r =>
-      // AEAD_AES_128_GCM expands ciphertext to be 16 octets longer.
-      Promise.all(chunkArray(aData, aRs + 16).map((slice, index) =>
-        this._decodeChunk(slice, index, r[1], r[0]))))
-    .then(r => concatArray(r));
-  },
-
-  _decodeChunk: function(aSlice, aIndex, aNonce, aKey) {
-    return crypto.subtle.decrypt({name: 'AES-GCM',
-                                  iv: generateNonce(aNonce, aIndex)
-                                 },
-                                 aKey, aSlice)
-      .then(decoded => {
-        decoded = new Uint8Array(decoded);
-        if (decoded.length == 0) {
-          return Promise.reject(new Error('Decoded array is too short!'));
-        } else if (decoded[0] > decoded.length) {
-          return Promise.reject(new Error ('Padding is wrong!'));
-        } else {
-          // All padded bytes must be zero except the first one.
-          for (var i = 1; i <= decoded[0]; i++) {
-            if (decoded[i] != 0) {
-              return Promise.reject(new Error('Padding is wrong!'));
-            }
-          }
-          return decoded.slice(decoded[0] + 1);
-        }
-      });
-  }
-};
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -7,16 +7,22 @@
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm");
 const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm");
+const {
+  PushCrypto,
+  base64UrlDecode,
+  getEncryptionKeyParams,
+  getEncryptionParams,
+} = Cu.import("resource://gre/modules/PushCrypto.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
@@ -28,30 +34,51 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "@mozilla.org/power/powermanagerservice;1",
                                    "nsIPowerManagerService");
 #endif
 
 var threadManager = Cc["@mozilla.org/thread-manager;1"]
                       .getService(Ci.nsIThreadManager);
 
 const kPUSHWSDB_DB_NAME = "pushapi";
-const kPUSHWSDB_DB_VERSION = 4; // Change this if the IndexedDB format changes
+const kPUSHWSDB_DB_VERSION = 5; // Change this if the IndexedDB format changes
 const kPUSHWSDB_STORE_NAME = "pushapi";
 
 const kUDP_WAKEUP_WS_STATUS_CODE = 4774;  // WebSocket Close status code sent
                                           // by server to signal that it can
                                           // wake client up using UDP.
 
 const prefs = new Preferences("dom.push.");
 
 this.EXPORTED_SYMBOLS = ["PushServiceWebSocket"];
 
 // Don't modify this, instead set dom.push.debug.
 var gDebuggingEnabled = true;
 
+function getCryptoParams(headers) {
+  if (!headers) {
+    return null;
+  }
+  var keymap = getEncryptionKeyParams(headers.encryption_key);
+  if (!keymap) {
+    return null;
+  }
+  var enc = getEncryptionParams(headers.encryption);
+  if (!enc || !enc.keyid) {
+    return null;
+  }
+  var dh = keymap[enc.keyid];
+  var salt = enc.salt;
+  var rs = (enc.rs)? parseInt(enc.rs, 10) : 4096;
+  if (!dh || !salt || isNaN(rs) || (rs <= 1)) {
+    return null;
+  }
+  return {dh, salt, rs};
+}
+
 function debug(s) {
   if (gDebuggingEnabled) {
     dump("-*- PushServiceWebSocket.jsm: " + s + "\n");
   }
 }
 
 // Set debug first so that all debugging actually works.
 gDebuggingEnabled = prefs.get("debug");
@@ -131,21 +158,21 @@ this.PushServiceWebSocket = {
 
   disconnect: function() {
     this._shutdownWS();
   },
 
   observe: function(aSubject, aTopic, aData) {
 
     switch (aTopic) {
-      case "nsPref:changed":
-        if (aData == "dom.push.debug") {
-          gDebuggingEnabled = prefs.get("debug");
-        }
-        break;
+    case "nsPref:changed":
+      if (aData == "dom.push.debug") {
+        gDebuggingEnabled = prefs.get("debug");
+      }
+      break;
     case "timer-callback":
       if (aSubject == this._requestTimeoutTimer) {
         if (Object.keys(this._pendingRequests).length === 0) {
           this._requestTimeoutTimer.cancel();
         }
 
         // Set to true if at least one request timed out.
         let requestTimedOut = false;
@@ -260,16 +287,19 @@ this.PushServiceWebSocket = {
    */
   _lastGoodPingInterval: 0,
 
   /**
    * Maximum ping interval that we can reach.
    */
   _upperLimit: 0,
 
+  /** Indicates whether the server supports Web Push-style message delivery. */
+  _dataEnabled: false,
+
   /**
    * Sends a message to the Push Server through an open websocket.
    * typeof(msg) shall be an object
    */
   _wsSendMessage: function(msg) {
     if (!this._ws) {
       debug("No WebSocket initialized. Cannot send a message.");
       return;
@@ -351,16 +381,18 @@ this.PushServiceWebSocket = {
     // or receiving notifications.
     this._shutdownWS();
 
     if (this._requestTimeoutTimer) {
       this._requestTimeoutTimer.cancel();
     }
 
     this._mainPushService = null;
+
+    this._dataEnabled = false;
   },
 
   /**
    * How retries work:  The goal is to ensure websocket is always up on
    * networks not supporting UDP. So the websocket should only be shutdown if
    * onServerClose indicates UDP wakeup.  If WS is closed due to socket error,
    * _reconnectAfterBackoff() is called.  The retry alarm is started and when
    * it times out, beginWSSetup() is called again.
@@ -744,22 +776,37 @@ this.PushServiceWebSocket = {
     // To avoid sticking extra large values sent by an evil server into prefs.
     if (reply.uaid.length > 128) {
       debug("UAID received from server was too long: " +
             reply.uaid);
       this._shutdownWS();
       return;
     }
 
+    let notifyRequestQueue = () => {
+      if (this._notifyRequestQueue) {
+        this._notifyRequestQueue();
+        this._notifyRequestQueue = null;
+      }
+    };
+
     function finishHandshake() {
       this._UAID = reply.uaid;
       this._currentState = STATE_READY;
-      if (this._notifyRequestQueue) {
-        this._notifyRequestQueue();
-        this._notifyRequestQueue = null;
+      this._dataEnabled = !!reply.use_webpush;
+      if (this._dataEnabled) {
+        this._mainPushService.getAllUnexpired().then(records =>
+          Promise.all(records.map(record =>
+            this._mainPushService.ensureP256dhKey(record).catch(error => {
+              debug("finishHandshake: Error updating record " + record.keyID);
+            })
+          ))
+        ).then(notifyRequestQueue);
+      } else {
+        notifyRequestQueue();
       }
     }
 
     // By this point we've got a UAID from the server that we are ready to
     // accept.
     //
     // If we already had a valid UAID before, we have to ask apps to
     // re-register.
@@ -816,21 +863,58 @@ this.PushServiceWebSocket = {
       dump("PushWebSocket " +  JSON.stringify(record));
       Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_WS_TIME").add(Date.now() - tmp.ctime);
       tmp.resolve(record);
     } else {
       tmp.reject(reply);
     }
   },
 
+  _handleDataUpdate: function(update) {
+    let promise;
+    if (typeof update.channelID != "string") {
+      debug("handleDataUpdate: Discarding message without channel ID");
+      return;
+    }
+    if (typeof update.data != "string") {
+      promise = this._mainPushService.receivedPushMessage(
+        update.channelID,
+        null,
+        null,
+        record => record
+      );
+    } else {
+      let params = getCryptoParams(update.headers);
+      if (!params) {
+        debug("handleDataUpdate: Discarding invalid encrypted message");
+        return;
+      }
+      let message = base64UrlDecode(update.data);
+      promise = this._mainPushService.receivedPushMessage(
+        update.channelID,
+        message,
+        params,
+        record => record
+      );
+    }
+    promise.then(() => this._sendAck(update.channelID)).catch(err => {
+      debug("handleDataUpdate: Error delivering message: " + err);
+    });
+  },
+
   /**
    * Protocol handler invoked by server message.
    */
   _handleNotificationReply: function(reply) {
     debug("handleNotificationReply()");
+    if (this._dataEnabled) {
+      this._handleDataUpdate(reply);
+      return;
+    }
+
     if (typeof reply.updates !== 'object') {
       debug("No 'updates' field in response. Type = " + typeof reply.updates);
       return;
     }
 
     debug("Reply updates: " + reply.updates.length);
     for (let i = 0; i < reply.updates.length; i++) {
       let update = reply.updates[i];
@@ -897,16 +981,26 @@ this.PushServiceWebSocket = {
 
       return new Promise((resolve, reject) => {
         this._pendingRequests[data.channelID] = {record: record,
                                                  resolve: resolve,
                                                  reject: reject,
                                                  ctime: Date.now()
                                                 };
         this._queueRequest(data);
+      }).then(record => {
+        if (!this._dataEnabled) {
+          return record;
+        }
+        return PushCrypto.generateKeys()
+          .then(([publicKey, privateKey]) => {
+            record.p256dhPublicKey = publicKey;
+            record.p256dhPrivateKey = privateKey;
+            return record;
+          });
       });
     }
 
     this._queueRequest({channelID: record.channelID,
                         messageType: action});
     return Promise.resolve();
   },
 
@@ -956,17 +1050,17 @@ this.PushServiceWebSocket = {
         this._notifyRequestQueue = null;
       }
     }
   },
 
   _receivedUpdate: function(aChannelID, aLatestVersion) {
     debug("Updating: " + aChannelID + " -> " + aLatestVersion);
 
-    this._mainPushService.receivedPushMessage(aChannelID, "", record => {
+    this._mainPushService.receivedPushMessage(aChannelID, null, null, record => {
       if (record.version === null ||
           record.version < aLatestVersion) {
         debug("Version changed for " + aChannelID + ": " + aLatestVersion);
         record.version = aLatestVersion;
         return record;
       }
       debug("No significant version change for " + aChannelID + ": " +
             aLatestVersion);
@@ -985,16 +1079,17 @@ this.PushServiceWebSocket = {
       return;
     }
 
     // Since we've had a successful connection reset the retry fail count.
     this._retryFailCount = 0;
 
     let data = {
       messageType: "hello",
+      use_webpush: true,
     };
 
     if (this._UAID) {
       data.uaid = this._UAID;
     }
 
     function sendHelloMessage(ids) {
       // On success, ids is an array, on error its not.
--- a/dom/push/moz.build
+++ b/dom/push/moz.build
@@ -10,22 +10,22 @@ EXTRA_COMPONENTS += [
     'PushNotificationService.js',
 ]
 
 EXTRA_PP_JS_MODULES += [
     'PushServiceWebSocket.jsm',
 ]
 
 EXTRA_JS_MODULES += [
+    'PushCrypto.jsm',
     'PushDB.jsm',
     'PushRecord.jsm',
     'PushService.jsm',
     'PushServiceChildPreload.jsm',
     'PushServiceHttp2.jsm',
-    'PushServiceHttp2Crypto.jsm',
 ]
 
 MOCHITEST_MANIFESTS += [
     'test/mochitest.ini',
 ]
 
 XPCSHELL_TESTS_MANIFESTS += [
     'test/xpcshell/xpcshell.ini',
--- a/dom/push/test/frame.html
+++ b/dom/push/test/frame.html
@@ -5,18 +5,17 @@
   <script>
 
 
     function waitOnPushMessage(pushSubscription)
     {
       var p = new Promise(function(res, rej) {
         navigator.serviceWorker.onmessage = function(e) {
           if (e.data.type == "finished") {
-            parent.ok(e.data.okay == "yes", "Got a push message.");
-            res(pushSubscription);
+            (e.data.okay == "yes" ? res : rej)(e.data);
           }
         };
       });
       return p;
     }
 
 
   </script>
--- a/dom/push/test/mochitest.ini
+++ b/dom/push/test/mochitest.ini
@@ -1,24 +1,27 @@
 [DEFAULT]
 subsuite = push
 support-files =
   worker.js
   push-server.sjs
   frame.html
+  webpush.js
 
 [test_has_permissions.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_permissions.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_register.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_multiple_register.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_multiple_register_during_service_activation.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_unregister.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_multiple_register_different_scope.html]
 skip-if = os == "android" || toolkit == "gonk"
+[test_data.html]
+skip-if = os == "android" || toolkit == "gonk"
 # Disabled for too many intermittent failures (bug 1164432)
 #  [test_try_registering_offline_disabled.html]
 #  skip-if = os == "android" || toolkit == "gonk"
--- a/dom/push/test/push-server.sjs
+++ b/dom/push/test/push-server.sjs
@@ -1,25 +1,51 @@
 function debug(str) {
 //  dump("@@@ push-server " + str + "\n");
 }
 
+function concatUint8Arrays(arrays, size) {
+  let index = 0;
+  return arrays.reduce((result, a) => {
+    result.set(new Uint8Array(a), index);
+    index += a.byteLength;
+    return result;
+  }, new Uint8Array(size));
+}
+
 function handleRequest(request, response)
 {
   debug("handling request!");
 
   const Cc = Components.classes;
   const Ci = Components.interfaces;
 
   let params = request.getHeader("X-Push-Server");
   debug("params = " + params);
 
   let xhr =  Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
-  xhr.open("PUT", params);
-  xhr.send();
+  xhr.open(request.getHeader("X-Push-Method"), params);
+
+  for (let headers = request.headers; headers.hasMoreElements();) {
+    let header = headers.getNext().QueryInterface(Ci.nsISupportsString).data;
+    if (header.toLowerCase() != "x-push-server") {
+      xhr.setRequestHeader(header, request.getHeader(header));
+    }
+  }
+
+  let bodyStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
+  bodyStream.setInputStream(request.bodyInputStream);
+  let size = 0;
+  let data = [];
+  for (let available; available = bodyStream.available();) {
+    data.push(bodyStream.readByteArray(available));
+    size += available;
+  }
+  xhr.send(concatUint8Arrays(data, size));
+
   xhr.onload = function(e) {
     debug("xhr : " + this.status);
   }
   xhr.onerror = function(e) {
     debug("xhr error: " + e);
   }
 
   response.setStatusLine(request.httpVersion, "200", "OK");
new file mode 100644
--- /dev/null
+++ b/dom/push/test/test_data.html
@@ -0,0 +1,186 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1185544: Add data delivery to the WebSocket backend.
+
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/licenses/publicdomain/
+
+-->
+<head>
+  <title>Test for Bug 1185544</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/dom/push/test/webpush.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+</head>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1185544">Mozilla Bug 1185544</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="text/javascript">
+
+  var registration;
+
+  function start() {
+    return navigator.serviceWorker.register("worker.js" + "?" + (Math.random()), {scope: "."})
+    .then(swr => { registration = swr; return swr; });
+  }
+
+  var controlledFrame;
+  function createControlledIFrame(swr) {
+    var p = new Promise(function(res, rej) {
+      var iframe = document.createElement('iframe');
+      iframe.id = "controlledFrame";
+      iframe.src = "http://mochi.test:8888/tests/dom/push/test/frame.html";
+
+      iframe.onload = function() {
+        res(swr)
+      }
+      controlledFrame = iframe;
+      document.body.appendChild(iframe);
+    });
+    return p;
+  }
+
+  function subscribe(swr) {
+    return swr.pushManager.subscribe();
+  }
+
+  function sendRequestToWorker(request) {
+    return new Promise((resolve, reject) => {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = e => {
+        (e.data.error ? reject : resolve)(e.data);
+      };
+      registration.active.postMessage(request, [channel.port2]);
+    });
+  }
+
+  function comparePublicKey(pushSubscription) {
+    // FIXME(kitcambridge): Enable when `ServiceWorkerMessageEvent` is
+    // implemented (bug 1143717).
+    return Promise.resolve(pushSubscription);
+    /*
+    return sendRequestToWorker({ type: "publicKey" }).then(data => {
+      return registration.pushManager.getSubscription().then(
+        pushSubscription => {
+          isDeeply(pushSubscription.getKey("p256dh"), data,
+            "Mismatched key share");
+          return pushSubscription;
+      });
+    });
+    */
+  }
+
+  function waitForMessage(pushSubscription, message) {
+    return Promise.all([
+      controlledFrame.contentWindow.waitOnPushMessage(pushSubscription),
+      webpush(pushSubscription, message),
+    ]).then(([message]) => message);
+  }
+
+  function sendPushMessageFromPage(pushSubscription) {
+    var typedArray = new Uint8Array([226, 130, 40, 240, 40, 140, 188]);
+    var json = { hello: "world" };
+    return waitForMessage(pushSubscription, "Text message from page")
+      .then(message => {
+        is(message.data.text, "Text message from page", "Wrong text message data");
+        return waitForMessage(
+          pushSubscription,
+          typedArray
+        );
+      }).then(message => {
+        isDeeply(new Uint8Array(message.data.arrayBuffer), typedArray,
+          "Wrong array buffer message data");
+        return waitForMessage(
+          pushSubscription,
+          JSON.stringify(json)
+        );
+      }).then(message => {
+        ok(message.data.json.ok, "Unexpected error parsing JSON");
+        isDeeply(message.data.json.value, json, "Wrong JSON message data");
+        return waitForMessage(
+          pushSubscription,
+          ""
+        );
+      }).then(message => {
+        ok(message, "Should include data for empty messages");
+        is(message.data.text, "", "Wrong text for empty message");
+        is(message.data.arrayBuffer.byteLength, 0, "Wrong buffer length for empty message");
+        ok(!message.data.json.ok, "Expected JSON parse error for empty message");
+        return waitForMessage(
+          pushSubscription,
+          new Uint8Array([0x48, 0x69, 0x21, 0x20, 0xf0, 0x9f, 0x91, 0x80])
+        );
+      }).then(message => {
+        is(message.data.text, "Hi! \ud83d\udc40", "Wrong text for message with emoji");
+        return new Promise((resolve, reject) => {
+          var reader = new FileReader();
+          reader.onloadend = event => {
+            if (reader.error) {
+              reject(reader.error);
+            } else {
+              resolve(reader.result);
+            }
+          };
+          reader.readAsText(message.data.blob);
+        });
+      }).then(text => {
+        is(text, "Hi! \ud83d\udc40", "Wrong blob data for message with emoji");
+        is(text, "Hi! \ud83d\udc40", "Wrong blob data for message with emoji");
+        // Send a blank message.
+        return Promise.all([
+          controlledFrame.contentWindow.waitOnPushMessage(pushSubscription),
+          fetch("http://mochi.test:8888/tests/dom/push/test/push-server.sjs", {
+            method: "PUT",
+            headers: {
+              "X-Push-Method": "POST",
+              "X-Push-Server": pushSubscription.endpoint,
+            },
+          }),
+        ]).then(([message]) => message);
+      }).then(message => {
+        ok(!message.data, "Should exclude data for blank messages");
+        return pushSubscription;
+      });
+  }
+
+  function unsubscribe(pushSubscription) {
+    controlledFrame.parentNode.removeChild(controlledFrame);
+    controlledFrame = null;
+    return pushSubscription.unsubscribe();
+  }
+
+  function unregister() {
+    return registration.unregister();
+  }
+
+  function runTest() {
+    start()
+    .then(createControlledIFrame)
+    .then(subscribe)
+    .then(comparePublicKey)
+    .then(sendPushMessageFromPage)
+    .then(unsubscribe)
+    .then(unregister)
+    .catch(function(e) {
+      ok(false, "Some test failed with error " + e);
+    }).then(SimpleTest.finish);
+  }
+
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.push.enabled", true],
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+    ]}, runTest);
+  SpecialPowers.addPermission('push', true, document);
+  SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
--- a/dom/push/test/test_register.html
+++ b/dom/push/test/test_register.html
@@ -52,16 +52,17 @@ http://creativecommons.org/licenses/publ
       return swr;
     });
   }
 
   function sendPushToPushServer(pushEndpoint) {
     // Work around CORS for now.
     var xhr = new XMLHttpRequest();
     xhr.open('GET', "http://mochi.test:8888/tests/dom/push/test/push-server.sjs", true);
+    xhr.setRequestHeader("X-Push-Method", "PUT");
     xhr.setRequestHeader("X-Push-Server", pushEndpoint);
     xhr.onload = function(e) {
       debug("xhr : " + this.status);
     }
     xhr.onerror = function(e) {
       debug("xhr error: " + e);
     }
     xhr.send("version=24601");
new file mode 100644
--- /dev/null
+++ b/dom/push/test/webpush.js
@@ -0,0 +1,206 @@
+/*
+ * Browser-based Web Push client for the application server piece.
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * Uses the WebCrypto API.
+ * Uses the fetch API.  Polyfill: https://github.com/github/fetch
+ */
+
+(function (g) {
+  'use strict';
+
+  var P256DH = {
+    name: 'ECDH',
+    namedCurve: 'P-256'
+  };
+  var webCrypto = g.crypto.subtle;
+  var ENCRYPT_INFO = new TextEncoder('utf-8').encode("Content-Encoding: aesgcm128");
+  var NONCE_INFO = new TextEncoder('utf-8').encode("Content-Encoding: nonce");
+
+  function chunkArray(array, size) {
+    var start = array.byteOffset || 0;
+    array = array.buffer || array;
+    var index = 0;
+    var result = [];
+    while(index + size <= array.byteLength) {
+      result.push(new Uint8Array(array, start + index, size));
+      index += size;
+    }
+    if (index < array.byteLength) {
+      result.push(new Uint8Array(array, start + index));
+    }
+    return result;
+  }
+
+  /* I can't believe that this is needed here, in this day and age ...
+   * Note: these are not efficient, merely expedient.
+   */
+  var base64url = {
+    _strmap: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
+    encode: function(data) {
+      data = new Uint8Array(data);
+      var len = Math.ceil(data.length * 4 / 3);
+      return chunkArray(data, 3).map(chunk => [
+        chunk[0] >>> 2,
+        ((chunk[0] & 0x3) << 4) | (chunk[1] >>> 4),
+        ((chunk[1] & 0xf) << 2) | (chunk[2] >>> 6),
+        chunk[2] & 0x3f
+      ].map(v => base64url._strmap[v]).join('')).join('').slice(0, len);
+    },
+    _lookup: function(s, i) {
+      return base64url._strmap.indexOf(s.charAt(i));
+    },
+    decode: function(str) {
+      var v = new Uint8Array(Math.floor(str.length * 3 / 4));
+      var vi = 0;
+      for (var si = 0; si < str.length;) {
+        var w = base64url._lookup(str, si++);
+        var x = base64url._lookup(str, si++);
+        var y = base64url._lookup(str, si++);
+        var z = base64url._lookup(str, si++);
+        v[vi++] = w << 2 | x >>> 4;
+        v[vi++] = x << 4 | y >>> 2;
+        v[vi++] = y << 6 | z;
+      }
+      return v;
+    }
+  };
+
+  g.base64url = base64url;
+
+  /* Coerces data into a Uint8Array */
+  function ensureView(data) {
+    if (typeof data === 'string') {
+      return new TextEncoder('utf-8').encode(data);
+    }
+    if (data instanceof ArrayBuffer) {
+      return new Uint8Array(data);
+    }
+    if (ArrayBuffer.isView(data)) {
+      return new Uint8Array(data.buffer);
+    }
+    throw new Error('webpush() needs a string or BufferSource');
+  }
+
+  function bsConcat(arrays) {
+    var size = arrays.reduce((total, a) => total + a.byteLength, 0);
+    var index = 0;
+    return arrays.reduce((result, a) => {
+      result.set(new Uint8Array(a), index);
+      index += a.byteLength;
+      return result;
+    }, new Uint8Array(size));
+  }
+
+  function hmac(key) {
+    this.keyPromise = webCrypto.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' },
+                                          false, ['sign']);
+  }
+  hmac.prototype.hash = function(input) {
+    return this.keyPromise.then(k => webCrypto.sign('HMAC', k, input));
+  };
+
+  function hkdf(salt, ikm) {
+    this.prkhPromise = new hmac(salt).hash(ikm)
+      .then(prk => new hmac(prk));
+  }
+
+  hkdf.prototype.generate = function(info, len) {
+    var input = bsConcat([info, new Uint8Array([1])]);
+    return this.prkhPromise
+      .then(prkh => prkh.hash(input))
+      .then(h => {
+        if (h.byteLength < len) {
+          throw new Error('Length is too long');
+        }
+        return h.slice(0, len);
+      });
+  };
+
+  /* generate a 96-bit IV for use in GCM, 48-bits of which are populated */
+  function generateNonce(base, index) {
+    var nonce = base.slice(0, 12);
+    for (var i = 0; i < 6; ++i) {
+      nonce[nonce.length - 1 - i] ^= (index / Math.pow(256, i)) & 0xff;
+    }
+    return nonce;
+  }
+
+  function encrypt(localKey, remoteShare, salt, data) {
+    return webCrypto.importKey('raw', remoteShare, P256DH, false, ['deriveBits'])
+      .then(remoteKey =>
+            webCrypto.deriveBits({ name: P256DH.name, public: remoteKey },
+                                 localKey, 256))
+      .then(rawKey => {
+        var kdf = new hkdf(salt, rawKey);
+        return Promise.all([
+          kdf.generate(ENCRYPT_INFO, 16)
+            .then(gcmBits =>
+                  webCrypto.importKey('raw', gcmBits, 'AES-GCM', false, ['encrypt'])),
+          kdf.generate(NONCE_INFO, 12)
+        ]);
+      })
+      .then(([key, nonce]) => {
+        if (data.byteLength === 0) {
+          // Send an authentication tag for empty messages.
+          return webCrypto.encrypt({
+            name: 'AES-GCM',
+            iv: generateNonce(nonce, 0)
+          }, key, new Uint8Array([0])).then(value => [value]);
+        }
+        // 4096 is the default size, though we burn 1 for padding
+        return Promise.all(chunkArray(data, 4095).map((slice, index) => {
+          var padded = bsConcat([new Uint8Array([0]), slice]);
+          return webCrypto.encrypt({
+            name: 'AES-GCM',
+            iv: generateNonce(nonce, index)
+          }, key, padded);
+        }));
+      }).then(bsConcat);
+  }
+
+  /*
+   * Request push for a message.  This returns a promise that resolves when the
+   * push has been delivered to the push service.
+   *
+   * @param subscription A PushSubscription that contains endpoint and p256dh
+   *                     parameters.
+   * @param data         The message to send.
+   */
+  function webpush(subscription, data) {
+    data = ensureView(data);
+
+    var salt = g.crypto.getRandomValues(new Uint8Array(16));
+    return webCrypto.generateKey(P256DH, false, ['deriveBits'])
+      .then(localKey => {
+        return Promise.all([
+          encrypt(localKey.privateKey, subscription.getKey("p256dh"), salt, data),
+          // 1337 p-256 specific haxx to get the raw value out of the spki value
+          webCrypto.exportKey('raw', localKey.publicKey),
+        ]);
+      }).then(([payload, pubkey]) => {
+        var options = {
+          method: 'PUT',
+          headers: {
+            'X-Push-Server': subscription.endpoint,
+            // Web Push requires POST requests.
+            'X-Push-Method': 'POST',
+            'Encryption-Key': 'keyid=p256dh;dh=' + base64url.encode(pubkey),
+            Encryption: 'keyid=p256dh;salt=' + base64url.encode(salt),
+            'Content-Encoding': 'aesgcm128'
+          },
+          body: payload,
+        };
+        return fetch('http://mochi.test:8888/tests/dom/push/test/push-server.sjs', options);
+      }).then(response => {
+        if (response.status / 100 !== 2) {
+          throw new Error('Unable to deliver message');
+        }
+        return response;
+      });
+  }
+
+  g.webpush = webpush;
+}(this));
--- a/dom/push/test/worker.js
+++ b/dom/push/test/worker.js
@@ -1,16 +1,57 @@
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/licenses/publicdomain/
 
 this.onpush = handlePush;
+this.onmessage = handleMessage;
+
+function getJSON(data) {
+  var result = {
+    ok: false,
+  };
+  try {
+    result.value = data.json();
+    result.ok = true;
+  } catch (e) {
+    // Ignore syntax errors for invalid JSON.
+  }
+  return result;
+}
 
 function handlePush(event) {
 
   self.clients.matchAll().then(function(result) {
-    // FIXME(nsm): Bug 1149195 will fix data exposure.
-    if (event instanceof PushEvent && !('data' in event)) {
-      result[0].postMessage({type: "finished", okay: "yes"});
+    if (event instanceof PushEvent) {
+      if (!('data' in event)) {
+        result[0].postMessage({type: "finished", okay: "yes"});
+        return;
+      }
+      var message = {
+        type: "finished",
+        okay: "yes",
+      };
+      if (event.data) {
+        message.data = {
+          text: event.data.text(),
+          arrayBuffer: event.data.arrayBuffer(),
+          json: getJSON(event.data),
+          blob: event.data.blob(),
+        };
+      }
+      result[0].postMessage(message);
       return;
     }
     result[0].postMessage({type: "finished", okay: "no"});
   });
 }
+
+function handleMessage(event) {
+  // FIXME(kitcambridge): Enable when `ServiceWorkerMessageEvent` is
+  // implemented (bug 1143717).
+  /*
+  if (event.data.type == "publicKey") {
+    self.registration.pushManager.getSubscription().then(subscription => {
+      event.ports[0].postMessage(subscription.getKey("p256dh"));
+    });
+  }
+  */
+}
deleted file mode 100644
--- a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-'use strict';
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://testing-common/httpd.js");
-
-const {PushDB, PushService, PushServiceHttp2} = serviceExports;
-
-var httpServer = null;
-
-XPCOMUtils.defineLazyGetter(this, "serverPort", function() {
-  return httpServer.identity.primaryPort;
-});
-
-function listenHandler(metadata, response) {
-  do_check_true(true, "Start listening");
-  httpServer.stop(do_test_finished);
-  response.setHeader("Retry-After", "10");
-  response.setStatusLine(metadata.httpVersion, 500, "Retry");
-}
-
-httpServer = new HttpServer();
-httpServer.registerPathHandler("/subscriptionNoKey", listenHandler);
-httpServer.start(-1);
-
-function run_test() {
-
-  do_get_profile();
-  setPrefs({
-    'http2.retryInterval': 1000,
-    'http2.maxRetries': 2
-  });
-  disableServiceWorkerEvents(
-    'https://example.com/page'
-  );
-
-  run_next_test();
-}
-
-add_task(function* test1() {
-
-  let db = PushServiceHttp2.newPushDB();
-  do_register_cleanup(_ => {
-    return db.drop().then(_ => db.close());
-  });
-
-  do_test_pending();
-
-  var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
-
-  let record = {
-    subscriptionUri: serverURL + '/subscriptionNoKey',
-    pushEndpoint: serverURL + '/pushEndpoint',
-    pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint',
-    scope: 'https://example.com/page',
-    originAttributes: '',
-    quota: Infinity,
-  };
-
-  yield db.put(record);
-
-  let notifyPromise = promiseObserverNotification('push-subscription-change',
-                                                  _ => true);
-
-  PushService.init({
-    serverURI: serverURL + "/subscribe",
-    service: PushServiceHttp2,
-    db
-  });
-
-  yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
-    'Timed out waiting for notifications');
-
-  let aRecord = yield db.getByKeyID(serverURL + '/subscriptionNoKey');
-  ok(aRecord, 'The record should still be there');
-  ok(aRecord.p256dhPublicKey, 'There should be a public key');
-  ok(aRecord.p256dhPrivateKey, 'There should be a private key');
-});
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://testing-common/httpd.js");
+
+const {PushDB, PushService, PushServiceHttp2} = serviceExports;
+
+var httpServer = null;
+
+XPCOMUtils.defineLazyGetter(this, "serverPort", function() {
+  return httpServer.identity.primaryPort;
+});
+
+function listenHandler(metadata, response) {
+  do_check_true(true, "Start listening");
+  httpServer.stop(do_test_finished);
+  response.setHeader("Retry-After", "10");
+  response.setStatusLine(metadata.httpVersion, 500, "Retry");
+}
+
+httpServer = new HttpServer();
+httpServer.registerPathHandler("/subscriptionNoKey", listenHandler);
+httpServer.start(-1);
+
+function run_test() {
+
+  do_get_profile();
+  setPrefs({
+    'http2.retryInterval': 1000,
+    'http2.maxRetries': 2
+  });
+  disableServiceWorkerEvents(
+    'https://example.com/page'
+  );
+
+  run_next_test();
+}
+
+add_task(function* test1() {
+
+  let db = PushServiceHttp2.newPushDB();
+  do_register_cleanup(_ => {
+    return db.drop().then(_ => db.close());
+  });
+
+  do_test_pending();
+
+  var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
+
+  let record = {
+    subscriptionUri: serverURL + '/subscriptionNoKey',
+    pushEndpoint: serverURL + '/pushEndpoint',
+    pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint',
+    scope: 'https://example.com/page',
+    originAttributes: '',
+    quota: Infinity,
+  };
+
+  yield db.put(record);
+
+  let notifyPromise = promiseObserverNotification('push-subscription-change',
+                                                  _ => true);
+
+  PushService.init({
+    serverURI: serverURL + "/subscribe",
+    service: PushServiceHttp2,
+    db
+  });
+
+  yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
+    'Timed out waiting for notifications');
+
+  let aRecord = yield db.getByKeyID(serverURL + '/subscriptionNoKey');
+  ok(aRecord, 'The record should still be there');
+  ok(aRecord.p256dhPublicKey, 'There should be a public key');
+  ok(aRecord.p256dhPrivateKey, 'There should be a private key');
+});
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+const {PushDB, PushService, PushServiceWebSocket, PushCrypto} = serviceExports;
+
+const userAgentID = '4dffd396-6582-471d-8c0c-84f394e9f7db';
+
+function run_test() {
+  do_get_profile();
+  setPrefs({
+    userAgentID,
+  });
+  disableServiceWorkerEvents(
+    'https://example.com/page/1',
+    'https://example.com/page/2',
+    'https://example.com/page/3'
+  );
+  run_next_test();
+}
+
+add_task(function* test_with_data_enabled() {
+  let db = PushServiceWebSocket.newPushDB();
+  do_register_cleanup(() => {return db.drop().then(_ => db.close());});
+
+  let [publicKey, privateKey] = yield PushCrypto.generateKeys();
+  let records = [{
+    channelID: 'eb18f12a-cc42-4f14-accb-3bfc1227f1aa',
+    pushEndpoint: 'https://example.org/push/no-key/1',
+    scope: 'https://example.com/page/1',
+    originAttributes: '',
+    quota: Infinity,
+  }, {
+    channelID: '0d8886b9-8da1-4778-8f5d-1cf93a877ed6',
+    pushEndpoint: 'https://example.org/push/key',
+    scope: 'https://example.com/page/2',
+    originAttributes: '',
+    p256dhPublicKey: publicKey,
+    p256dhPrivateKey: privateKey,
+    quota: Infinity,
+  }];
+  for (let record of records) {
+    yield db.put(record);
+  }
+
+  PushService.init({
+    serverURI: "wss://push.example.org/",
+    networkInfo: new MockDesktopNetworkInfo(),
+    db,
+    makeWebSocket(uri) {
+      return new MockWebSocket(uri, {
+        onHello(request) {
+          ok(request.use_webpush,
+            'Should use Web Push if data delivery is enabled');
+          this.serverSendMsg(JSON.stringify({
+            messageType: 'hello',
+            status: 200,
+            uaid: request.uaid,
+            use_webpush: true,
+          }));
+        },
+        onRegister(request) {
+          this.serverSendMsg(JSON.stringify({
+            messageType: 'register',
+            status: 200,
+            uaid: userAgentID,
+            channelID: request.channelID,
+            pushEndpoint: 'https://example.org/push/new',
+          }));
+        }
+      });
+    },
+  });
+
+  let newRecord = yield PushNotificationService.register(
+    'https://example.com/page/3',
+    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
+  );
+  ok(newRecord.p256dhPublicKey, 'Should generate public keys for new records');
+  ok(newRecord.p256dhPrivateKey, 'Should generate private keys for new records');
+
+  let record = yield db.getByKeyID('eb18f12a-cc42-4f14-accb-3bfc1227f1aa');
+  ok(record.p256dhPublicKey, 'Should add public key to partial record');
+  ok(record.p256dhPrivateKey, 'Should add private key to partial record');
+
+  record = yield db.getByKeyID('0d8886b9-8da1-4778-8f5d-1cf93a877ed6');
+  deepEqual(record.p256dhPublicKey, publicKey,
+    'Should leave existing public key');
+  deepEqual(record.p256dhPrivateKey, privateKey,
+    'Should leave existing private key');
+});
--- a/dom/push/test/xpcshell/xpcshell.ini
+++ b/dom/push/test/xpcshell/xpcshell.ini
@@ -28,22 +28,23 @@ skip-if = toolkit == 'android'
 [test_registration_none.js]
 [test_registration_success.js]
 [test_unregister_empty_scope.js]
 [test_unregister_error.js]
 [test_unregister_invalid_json.js]
 [test_unregister_not_found.js]
 [test_unregister_success.js]
 [test_webapps_cleardata.js]
+[test_updateRecordNoEncryptionKeys_ws.js]
 #http2 test
 [test_resubscribe_4xxCode_http2.js]
 [test_resubscribe_5xxCode_http2.js]
 [test_resubscribe_listening_for_msg_error_http2.js]
 [test_register_5xxCode_http2.js]
-[test_updateRecordNoEncryptionKeys.js]
+[test_updateRecordNoEncryptionKeys_http2.js]
 [test_register_success_http2.js]
 skip-if = !hasNode
 run-sequentially = node server exceptions dont replay well
 [test_register_error_http2.js]
 skip-if = !hasNode
 run-sequentially = node server exceptions dont replay well
 [test_unregister_success_http2.js]
 skip-if = !hasNode
--- a/dom/webidl/DeviceStorage.webidl
+++ b/dom/webidl/DeviceStorage.webidl
@@ -81,12 +81,15 @@ interface DeviceStorage : EventTarget {
 
   // Determines if this storage area is the one which will be used by default
   // for storing new files.
   readonly attribute boolean default;
 
   // Indicates if the storage area denoted by storageName is removable
   readonly attribute boolean isRemovable;
 
+  // True if the storage area is close to being full
+  readonly attribute boolean lowDiskSpace;
+
   [NewObject]
   // XXXbz what type does this really return?
   Promise<any> getRoot();
 };
--- a/dom/webidl/PushEvent.webidl
+++ b/dom/webidl/PushEvent.webidl
@@ -6,18 +6,16 @@
  * The origin of this IDL file is
  * https://w3c.github.io/push-api/
  */
 
 [Constructor(DOMString type, optional PushEventInit eventInitDict),
  Func="nsContentUtils::PushEnabled",
  Exposed=ServiceWorker]
 interface PushEvent : ExtendableEvent {
-  // FIXME(nsm): Bug 1149195.
-  // readonly attribute PushMessageData data;
+  readonly attribute PushMessageData? data;
 };
 
-typedef USVString PushMessageDataInit;
+typedef (BufferSource or USVString) PushMessageDataInit;
 
 dictionary PushEventInit : ExtendableEventInit {
-  // FIXME(nsm): Bug 1149195.
-  // PushMessageDataInit data;
+  PushMessageDataInit data;
 };
--- a/dom/webidl/PushMessageData.webidl
+++ b/dom/webidl/PushMessageData.webidl
@@ -6,15 +6,16 @@
  * The origin of this IDL file is
  * https://w3c.github.io/push-api/
  */
 
 [Func="nsContentUtils::PushEnabled",
  Exposed=ServiceWorker]
 interface PushMessageData
 {
-    // FIXME(nsm): Bug 1149195.
-    // These methods will be exposed once encryption is supported.
-    // ArrayBuffer arrayBuffer();
-    // Blob        blob();
-    // object      json();
-    // USVString   text();
+    [Throws]
+    ArrayBuffer arrayBuffer();
+    [Throws]
+    Blob        blob();
+    [Throws]
+    any         json();
+    USVString   text();
 };
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -21,16 +21,25 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/FetchEventBinding.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/Request.h"
 #include "mozilla/dom/Response.h"
 #include "mozilla/dom/WorkerScope.h"
 #include "mozilla/dom/workers/bindings/ServiceWorker.h"
 
+#ifndef MOZ_SIMPLEPUSH
+#include "nsIUnicodeDecoder.h"
+#include "nsIUnicodeEncoder.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/FetchUtil.h"
+#include "mozilla/dom/TypedArray.h"
+#endif
+
 #include "WorkerPrivate.h"
 
 using namespace mozilla::dom;
 
 BEGIN_WORKERS_NAMESPACE
 
 CancelChannelRunnable::CancelChannelRunnable(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                                              nsresult aStatus)
@@ -430,63 +439,205 @@ NS_IMPL_RELEASE_INHERITED(ExtendableEven
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ExtendableEvent)
 NS_INTERFACE_MAP_END_INHERITING(Event)
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(ExtendableEvent, Event, mPromises)
 
 #ifndef MOZ_SIMPLEPUSH
 
-PushMessageData::PushMessageData(const nsAString& aData)
-  : mData(aData)
+namespace {
+nsresult
+ExtractBytesFromArrayBufferView(const ArrayBufferView& aView, nsTArray<uint8_t>& aBytes)
+{
+  MOZ_ASSERT(aBytes.IsEmpty());
+  aView.ComputeLengthAndData();
+  aBytes.InsertElementsAt(0, aView.Data(), aView.Length());
+  return NS_OK;
+}
+
+nsresult
+ExtractBytesFromArrayBuffer(const ArrayBuffer& aBuffer, nsTArray<uint8_t>& aBytes)
+{
+  MOZ_ASSERT(aBytes.IsEmpty());
+  aBuffer.ComputeLengthAndData();
+  aBytes.InsertElementsAt(0, aBuffer.Data(), aBuffer.Length());
+  return NS_OK;
+}
+
+nsresult
+ExtractBytesFromUSVString(const nsAString& aStr, nsTArray<uint8_t>& aBytes)
 {
+  MOZ_ASSERT(aBytes.IsEmpty());
+  nsCOMPtr<nsIUnicodeEncoder> encoder = EncodingUtils::EncoderForEncoding("UTF-8");
+  if (!encoder) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  int32_t srcLen = aStr.Length();
+  int32_t destBufferLen;
+  nsresult rv = encoder->GetMaxLength(aStr.BeginReading(), srcLen, &destBufferLen);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  aBytes.SetLength(destBufferLen);
+
+  char* destBuffer = reinterpret_cast<char*>(aBytes.Elements());
+  int32_t outLen = destBufferLen;
+  rv = encoder->Convert(aStr.BeginReading(), &srcLen, destBuffer, &outLen);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(outLen <= destBufferLen);
+  aBytes.SetLength(outLen);
+
+  return NS_OK;
 }
 
+nsresult
+ExtractBytesFromData(const OwningArrayBufferViewOrArrayBufferOrUSVString& aDataInit, nsTArray<uint8_t>& aBytes)
+{
+  if (aDataInit.IsArrayBufferView()) {
+    const ArrayBufferView& view = aDataInit.GetAsArrayBufferView();
+    return ExtractBytesFromArrayBufferView(view, aBytes);
+  } else if (aDataInit.IsArrayBuffer()) {
+    const ArrayBuffer& buffer = aDataInit.GetAsArrayBuffer();
+    return ExtractBytesFromArrayBuffer(buffer, aBytes);
+  } else if (aDataInit.IsUSVString()) {
+    return ExtractBytesFromUSVString(aDataInit.GetAsUSVString(), aBytes);
+  }
+  NS_NOTREACHED("Unexpected push message data");
+  return NS_ERROR_FAILURE;
+}
+}
+
+PushMessageData::PushMessageData(nsISupports* aOwner,
+                                 const nsTArray<uint8_t>& aBytes)
+  : mOwner(aOwner), mBytes(aBytes) {}
+
 PushMessageData::~PushMessageData()
 {
 }
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(PushMessageData);
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushMessageData, mOwner)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessageData)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessageData)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessageData)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 void
-PushMessageData::Json(JSContext* cx, JS::MutableHandle<JSObject*> aRetval)
+PushMessageData::Json(JSContext* cx, JS::MutableHandle<JS::Value> aRetval,
+                      ErrorResult& aRv)
 {
-  //todo bug 1149195.  Don't be lazy.
-   NS_ABORT();
+  if (NS_FAILED(EnsureDecodedText())) {
+    aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
+    return;
+  }
+  FetchUtil::ConsumeJson(cx, aRetval, mDecodedText, aRv);
 }
 
 void
 PushMessageData::Text(nsAString& aData)
 {
-  aData = mData;
+  if (NS_SUCCEEDED(EnsureDecodedText())) {
+    aData = mDecodedText;
+  }
 }
 
 void
-PushMessageData::ArrayBuffer(JSContext* cx, JS::MutableHandle<JSObject*> aRetval)
+PushMessageData::ArrayBuffer(JSContext* cx,
+                             JS::MutableHandle<JSObject*> aRetval,
+                             ErrorResult& aRv)
 {
-  //todo bug 1149195.  Don't be lazy.
-   NS_ABORT();
+  uint8_t* data = GetContentsCopy();
+  if (data) {
+    FetchUtil::ConsumeArrayBuffer(cx, aRetval, mBytes.Length(), data, aRv);
+  }
+}
+
+already_AddRefed<mozilla::dom::Blob>
+PushMessageData::Blob(ErrorResult& aRv)
+{
+  uint8_t* data = GetContentsCopy();
+  if (data) {
+    nsRefPtr<mozilla::dom::Blob> blob = FetchUtil::ConsumeBlob(
+      mOwner, EmptyString(), mBytes.Length(), data, aRv);
+    if (blob) {
+      return blob.forget();
+    }
+  }
+  return nullptr;
 }
 
-mozilla::dom::Blob*
-PushMessageData::Blob()
+NS_METHOD
+PushMessageData::EnsureDecodedText()
 {
-  //todo bug 1149195.  Don't be lazy.
-  NS_ABORT();
-  return nullptr;
+  if (mBytes.IsEmpty() || !mDecodedText.IsEmpty()) {
+    return NS_OK;
+  }
+  nsresult rv = FetchUtil::ConsumeText(
+    mBytes.Length(),
+    reinterpret_cast<uint8_t*>(mBytes.Elements()),
+    mDecodedText
+  );
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mDecodedText.Truncate();
+    return rv;
+  }
+  return NS_OK;
+}
+
+uint8_t*
+PushMessageData::GetContentsCopy()
+{
+  uint32_t length = mBytes.Length();
+  void* data = malloc(length);
+  if (!data) {
+    return nullptr;
+  }
+  memcpy(data, mBytes.Elements(), length);
+  return reinterpret_cast<uint8_t*>(data);
 }
 
 PushEvent::PushEvent(EventTarget* aOwner)
   : ExtendableEvent(aOwner)
 {
 }
 
+already_AddRefed<PushEvent>
+PushEvent::Constructor(mozilla::dom::EventTarget* aOwner,
+                       const nsAString& aType,
+                       const PushEventInit& aOptions,
+                       ErrorResult& aRv)
+{
+  nsRefPtr<PushEvent> e = new PushEvent(aOwner);
+  bool trusted = e->Init(aOwner);
+  e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
+  e->SetTrusted(trusted);
+  if(aOptions.mData.WasPassed()){
+    nsTArray<uint8_t> bytes;
+    nsresult rv = ExtractBytesFromData(aOptions.mData.Value(), bytes);
+    if (NS_FAILED(rv)) {
+      aRv.Throw(rv);
+      return nullptr;
+    }
+    e->mData = new PushMessageData(aOwner, bytes);
+  }
+  return e.forget();
+}
+
+NS_IMPL_ADDREF_INHERITED(PushEvent, ExtendableEvent)
+NS_IMPL_RELEASE_INHERITED(PushEvent, ExtendableEvent)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PushEvent)
+NS_INTERFACE_MAP_END_INHERITING(ExtendableEvent)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PushEvent, ExtendableEvent, mData)
+
 #endif /* ! MOZ_SIMPLEPUSH */
 
 END_WORKERS_NAMESPACE
--- a/dom/workers/ServiceWorkerEvents.h
+++ b/dom/workers/ServiceWorkerEvents.h
@@ -12,16 +12,17 @@
 #include "mozilla/dom/FetchEventBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/Response.h"
 #include "mozilla/dom/workers/bindings/ServiceWorker.h"
 
 #ifndef MOZ_SIMPLEPUSH
 #include "mozilla/dom/PushEventBinding.h"
 #include "mozilla/dom/PushMessageDataBinding.h"
+#include "mozilla/dom/File.h"
 #endif
 
 #include "nsProxyRelease.h"
 #include "nsContentUtils.h"
 
 class nsIInterceptedChannel;
 
 namespace mozilla {
@@ -163,96 +164,88 @@ public:
   }
 };
 
 #ifndef MOZ_SIMPLEPUSH
 
 class PushMessageData final : public nsISupports,
                               public nsWrapperCache
 {
-  nsString mData;
-
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushMessageData)
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
   {
     return mozilla::dom::PushMessageDataBinding_workers::Wrap(aCx, this, aGivenProto);
   }
 
   nsISupports* GetParentObject() const {
-    return nullptr;
+    return mOwner;
   }
 
-  void Json(JSContext* cx, JS::MutableHandle<JSObject*> aRetval);
+  void Json(JSContext* cx, JS::MutableHandle<JS::Value> aRetval,
+            ErrorResult& aRv);
   void Text(nsAString& aData);
-  void ArrayBuffer(JSContext* cx, JS::MutableHandle<JSObject*> aRetval);
-  mozilla::dom::Blob* Blob();
+  void ArrayBuffer(JSContext* cx, JS::MutableHandle<JSObject*> aRetval,
+                   ErrorResult& aRv);
+  already_AddRefed<mozilla::dom::Blob> Blob(ErrorResult& aRv);
 
-  explicit PushMessageData(const nsAString& aData);
+  PushMessageData(nsISupports* aOwner, const nsTArray<uint8_t>& aBytes);
 private:
+  nsCOMPtr<nsISupports> mOwner;
+  nsTArray<uint8_t> mBytes;
+  nsString mDecodedText;
   ~PushMessageData();
 
+  NS_METHOD EnsureDecodedText();
+  uint8_t* GetContentsCopy();
 };
 
 class PushEvent final : public ExtendableEvent
 {
-  // FIXME(nsm): Bug 1149195.
-  // nsRefPtr<PushMessageData> mData;
+  nsRefPtr<PushMessageData> mData;
   nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
 
 protected:
   explicit PushEvent(mozilla::dom::EventTarget* aOwner);
   ~PushEvent() {}
 
 public:
-  // FIXME(nsm): Bug 1149195.
-  // Add cycle collection macros once data is re-exposed.
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PushEvent, ExtendableEvent)
   NS_FORWARD_TO_EVENT
 
   virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
   {
     return mozilla::dom::PushEventBinding_workers::Wrap(aCx, this, aGivenProto);
   }
 
   static already_AddRefed<PushEvent>
   Constructor(mozilla::dom::EventTarget* aOwner,
               const nsAString& aType,
-              const PushEventInit& aOptions)
-  {
-    nsRefPtr<PushEvent> e = new PushEvent(aOwner);
-    bool trusted = e->Init(aOwner);
-    e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
-    e->SetTrusted(trusted);
-    // FIXME(nsm): Bug 1149195.
-    //if(aOptions.mData.WasPassed()){
-    //  e->mData = new PushMessageData(aOptions.mData.Value());
-    //}
-    return e.forget();
-  }
+              const PushEventInit& aOptions,
+              ErrorResult& aRv);
 
   static already_AddRefed<PushEvent>
   Constructor(const GlobalObject& aGlobal,
               const nsAString& aType,
               const PushEventInit& aOptions,
               ErrorResult& aRv)
   {
     nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
-    return Constructor(owner, aType, aOptions);
+    return Constructor(owner, aType, aOptions, aRv);
   }
 
   void PostInit(nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker)
   {
     mServiceWorker = aServiceWorker;
   }
 
-  PushMessageData* Data()
+  PushMessageData* GetData() const
   {
-    // FIXME(nsm): Bug 1149195.
-    MOZ_CRASH("Should not be called!");
-    return nullptr;
+    return mData;
   }
 };
 #endif /* ! MOZ_SIMPLEPUSH */
 
 END_WORKERS_NAMESPACE
 #endif /* mozilla_dom_workers_serviceworkerevents_h__ */
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -67,16 +67,20 @@
 #include "ServiceWorkerScriptCache.h"
 #include "ServiceWorkerEvents.h"
 #include "SharedWorker.h"
 #include "WorkerInlines.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 
+#ifndef MOZ_SIMPLEPUSH
+#include "mozilla/dom/TypedArray.h"
+#endif
+
 #ifdef PostMessage
 #undef PostMessage
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
@@ -2288,42 +2292,49 @@ public:
     return NS_OK;
   }
 };
 
 #ifndef MOZ_SIMPLEPUSH
 
 class SendPushEventRunnable final : public WorkerRunnable
 {
-  nsString mData;
+  Maybe<nsTArray<uint8_t>> mData;
   nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
 
 public:
   SendPushEventRunnable(
     WorkerPrivate* aWorkerPrivate,
-    const nsAString& aData,
     nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker)
-      : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
-      , mData(aData)
-      , mServiceWorker(aServiceWorker)
-  {
-    AssertIsOnMainThread();
-    MOZ_ASSERT(aWorkerPrivate);
-    MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
-  }
+      : SendPushEventRunnable(aWorkerPrivate, aServiceWorker,
+                              Nothing()) {}
+
+  SendPushEventRunnable(
+    WorkerPrivate* aWorkerPrivate,
+    const nsTArray<uint8_t>& aData,
+    nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker)
+      : SendPushEventRunnable(aWorkerPrivate, aServiceWorker,
+                              Some(aData)) {}
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
 
+
     PushEventInit pei;
-    // FIXME(nsm): Bug 1149195.
-    // pei.mData.Construct(mData);
+    if (mData) {
+      const nsTArray<uint8_t>& bytes = mData.ref();
+      JSObject* data = Uint8Array::Create(aCx, bytes.Length(), bytes.Elements());
+      if (!data) {
+        return false;
+      }
+      pei.mData.Construct().SetAsArrayBufferView().Init(data);
+    }
     pei.mBubbles = false;
     pei.mCancelable = false;
 
     ErrorResult result;
     nsRefPtr<PushEvent> event =
       PushEvent::Constructor(globalObj, NS_LITERAL_STRING("push"), pei, result);
     if (NS_WARN_IF(result.Failed())) {
       result.SuppressException();
@@ -2337,16 +2348,30 @@ public:
       DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(), event);
     if (waitUntilPromise) {
       nsRefPtr<KeepAliveHandler> handler = new KeepAliveHandler(mServiceWorker);
       waitUntilPromise->AppendNativeHandler(handler);
     }
 
     return true;
   }
+
+private:
+  SendPushEventRunnable(
+    WorkerPrivate* aWorkerPrivate,
+    nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
+    Maybe<nsTArray<uint8_t>> aData)
+      : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
+      , mData(aData)
+      , mServiceWorker(aServiceWorker)
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(aWorkerPrivate);
+    MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+  }
 };
 
 class SendPushSubscriptionChangeEventRunnable final : public WorkerRunnable
 {
   nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
 
 public:
   SendPushSubscriptionChangeEventRunnable(
@@ -2382,17 +2407,19 @@ public:
   }
 };
 
 #endif /* ! MOZ_SIMPLEPUSH */
 
 NS_IMETHODIMP
 ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
                                     const nsACString& aScope,
-                                    const nsAString& aData)
+                                    uint32_t aDataLength,
+                                    uint8_t* aDataBytes,
+                                    uint8_t optional_argc)
 {
 #ifdef MOZ_SIMPLEPUSH
   return NS_ERROR_NOT_AVAILABLE;
 #else
   OriginAttributes attrs;
   if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
     return NS_ERROR_INVALID_ARG;
   }
@@ -2401,19 +2428,29 @@ ServiceWorkerManager::SendPushEvent(cons
     CreateServiceWorkerForScope(attrs, aScope, nullptr /* failure runnable */);
   if (NS_WARN_IF(!serviceWorker)) {
     return NS_ERROR_FAILURE;
   }
 
   nsMainThreadPtrHandle<ServiceWorker> serviceWorkerHandle(
     new nsMainThreadPtrHolder<ServiceWorker>(serviceWorker));
 
-  nsRefPtr<SendPushEventRunnable> r =
-    new SendPushEventRunnable(serviceWorker->GetWorkerPrivate(), aData,
-                              serviceWorkerHandle);
+  nsRefPtr<SendPushEventRunnable> r;
+  if (optional_argc == 2) {
+    nsTArray<uint8_t> data;
+    if (!data.InsertElementsAt(0, aDataBytes, aDataLength, fallible)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+    r = new SendPushEventRunnable(serviceWorker->GetWorkerPrivate(), data,
+                                  serviceWorkerHandle);
+  } else {
+    MOZ_ASSERT(optional_argc == 0);
+    r = new SendPushEventRunnable(serviceWorker->GetWorkerPrivate(),
+                                  serviceWorkerHandle);
+  }
 
   AutoJSAPI jsapi;
   jsapi.Init();
   if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
new file mode 100644
--- /dev/null
+++ b/dom/xslt/crashtests/527558_1.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xml" href="#bug"?>
+<!DOCTYPE doc [
+<!ATTLIST xsl:transform
+  id	ID	#REQUIRED>
+]>
+<doc>
+<xsl:transform id="bug"
+                version="2.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+  <xsl:key name="k0" match="e1" use="key('k0', 'foobar')" /> 
+  <xsl:template id="t1" name="t1" match="key('k0', '1/2/2003')" />
+</xsl:transform>
+
+<e1 a1="foobar" a2="foobar"/>
+
+</doc>
--- a/dom/xslt/crashtests/crashtests.list
+++ b/dom/xslt/crashtests/crashtests.list
@@ -1,16 +1,17 @@
 load 91332.xml
 load 111994.xml
 load 182460-table.xhtml
 load 226425.xml
 load 406106-1.html
 load 483444.xml
 load 485217.xml
 load 485286.xml
+load 527558_1.xml
 load 528300.xml
 load 528488.xml
 load 528963.xml
 load 545927.html
 load 601543.html
 load 603844.html
 load 602115.html
 load 667315.xml
--- a/dom/xslt/xpath/txIXPathContext.h
+++ b/dom/xslt/xpath/txIXPathContext.h
@@ -46,16 +46,24 @@ public:
      * Should nametests parsed in this context be case-sensitive
      */
     virtual bool caseInsensitiveNameTests() = 0;
 
     /*
      * Callback to be used by the Parser if errors are detected.
      */
     virtual void SetErrorOffset(uint32_t aOffset) = 0;
+
+    enum Allowed {
+        KEY_FUNCTION = 1 << 0
+    };
+    virtual bool allowed(Allowed aAllowed)
+    {
+        return true;
+    }
 };
 
 /*
  * txIMatchContext
  *
  * Interface used for matching XSLT Patters.
  * This is the part of txIEvalContext (see below), that is independent
  * of the context node when evaluating a XPath expression, too.
--- a/dom/xslt/xslt/txPatternParser.cpp
+++ b/dom/xslt/xslt/txPatternParser.cpp
@@ -4,42 +4,45 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "txPatternParser.h"
 #include "txExprLexer.h"
 #include "nsGkAtoms.h"
 #include "nsError.h"
 #include "txStringUtils.h"
 #include "txXSLTPatterns.h"
-#include "txIXPathContext.h"
+#include "txStylesheetCompiler.h"
 #include "txPatternOptimizer.h"
 
 
-txPattern* txPatternParser::createPattern(const nsAFlatString& aPattern,
-                                          txIParseContext* aContext)
+nsresult txPatternParser::createPattern(const nsAFlatString& aPattern,
+                                        txIParseContext* aContext,
+                                        txPattern** aResult)
 {
     txExprLexer lexer;
     nsresult rv = lexer.parse(aPattern);
     if (NS_FAILED(rv)) {
         // XXX error report parsing error
-        return 0;
+        return rv;
     }
     nsAutoPtr<txPattern> pattern;
     rv = createUnionPattern(lexer, aContext, *getter_Transfers(pattern));
     if (NS_FAILED(rv)) {
         // XXX error report parsing error
-        return 0;
+        return rv;
     }
 
     txPatternOptimizer optimizer;
     txPattern* newPattern = nullptr;
     rv = optimizer.optimize(pattern, &newPattern);
-    NS_ENSURE_SUCCESS(rv, nullptr);
+    NS_ENSURE_SUCCESS(rv, rv);
 
-    return newPattern ? newPattern : pattern.forget();
+    *aResult = newPattern ? newPattern : pattern.forget();
+
+    return NS_OK;
 }
 
 nsresult txPatternParser::createUnionPattern(txExprLexer& aLexer,
                                              txIParseContext* aContext,
                                              txPattern*& aPattern)
 {
     nsresult rv = NS_OK;
     txPattern* locPath = 0;
@@ -241,16 +244,19 @@ nsresult txPatternParser::createKeyPatte
     if (aLexer.nextToken()->mType != Token::COMMA && 
         aLexer.peek()->mType != Token::LITERAL)
         return NS_ERROR_XPATH_PARSE_FAILURE;
     const nsDependentSubstring& value =
         aLexer.nextToken()->Value();
     if (aLexer.nextToken()->mType != Token::R_PAREN)
         return NS_ERROR_XPATH_PARSE_FAILURE;
 
+    if (!aContext->allowed(txIParseContext::KEY_FUNCTION))
+        return NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED;
+
     const char16_t* colon;
     if (!XMLUtils::isValidQName(PromiseFlatString(key), &colon))
         return NS_ERROR_XPATH_PARSE_FAILURE;
     nsCOMPtr<nsIAtom> prefix, localName;
     int32_t namespaceID;
     nsresult rv = resolveQName(key, getter_AddRefs(prefix), aContext,
                                getter_AddRefs(localName), namespaceID);
     if (NS_FAILED(rv))
--- a/dom/xslt/xslt/txPatternParser.h
+++ b/dom/xslt/xslt/txPatternParser.h
@@ -4,21 +4,24 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef TX_PATTERNPARSER_H
 #define TX_PATTERNPARSER_H
 
 #include "txXSLTPatterns.h"
 #include "txExprParser.h"
 
+class txStylesheetCompilerState;
+
 class txPatternParser : public txExprParser
 {
 public:
-    static txPattern* createPattern(const nsAFlatString& aPattern,
-                                    txIParseContext* aContext);
+    static nsresult createPattern(const nsAFlatString& aPattern,
+                                  txIParseContext* aContext,
+                                  txPattern** aResult);
 protected:
     static nsresult createUnionPattern(txExprLexer& aLexer,
                                        txIParseContext* aContext,
                                        txPattern*& aPattern);
     static nsresult createLocPathPattern(txExprLexer& aLexer,
                                          txIParseContext* aContext,
                                          txPattern*& aPattern);
     static nsresult createIdPattern(txExprLexer& aLexer,
--- a/dom/xslt/xslt/txStylesheetCompileHandlers.cpp
+++ b/dom/xslt/xslt/txStylesheetCompileHandlers.cpp
@@ -178,17 +178,17 @@ getExprAttr(txStylesheetAttr* aAttribute
     nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
                                aName, aRequired, &attr);
     if (!attr) {
         return rv;
     }
 
     rv = txExprParser::createExpr(attr->mValue, &aState,
                                   getter_Transfers(aExpr));
-    if (NS_FAILED(rv) && aState.fcp()) {
+    if (NS_FAILED(rv) && aState.ignoreError(rv)) {
         // use default value in fcp for not required exprs
         if (aRequired) {
             aExpr = new txErrorExpr(
 #ifdef TX_TO_STRING
                                     attr->mValue
 #endif
                                     );
             NS_ENSURE_TRUE(aExpr, NS_ERROR_OUT_OF_MEMORY);
@@ -250,20 +250,21 @@ getPatternAttr(txStylesheetAttr* aAttrib
     aPattern = nullptr;
     txStylesheetAttr* attr = nullptr;
     nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
                                aName, aRequired, &attr);
     if (!attr) {
         return rv;
     }
 
-    aPattern = txPatternParser::createPattern(attr->mValue, &aState);
-    if (!aPattern && (aRequired || !aState.fcp())) {
+    rv = txPatternParser::createPattern(attr->mValue, &aState,
+                                        getter_Transfers(aPattern));
+    if (NS_FAILED(rv) && (aRequired || !aState.ignoreError(rv))) {
         // XXX ErrorReport: XSLT-Pattern parse failure
-        return NS_ERROR_XPATH_PARSE_FAILURE;
+        return rv;
     }
 
     return NS_OK;
 }
 
 static nsresult
 getNumberAttr(txStylesheetAttr* aAttributes,
               int32_t aAttrCount,
@@ -833,26 +834,30 @@ txFnStartKey(int32_t aNamespaceID,
              txStylesheetCompilerState& aState)
 {
     nsresult rv = NS_OK;
     txExpandedName name;
     rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true,
                       aState, name);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    aState.mDisAllowed = txIParseContext::KEY_FUNCTION;
+
     nsAutoPtr<txPattern> match;
     rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::match, true,
                         aState, match);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsAutoPtr<Expr> use;
     rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::use, true,
                      aState, use);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    aState.mDisAllowed = 0;
+
     rv = aState.mStylesheet->addKey(name, Move(match), Move(use));
     NS_ENSURE_SUCCESS(rv, rv);
     
     return aState.pushHandlerTable(gTxIgnoreHandler);
 }
 
 static nsresult
 txFnEndKey(txStylesheetCompilerState& aState)
--- a/dom/xslt/xslt/txStylesheetCompiler.cpp
+++ b/dom/xslt/xslt/txStylesheetCompiler.cpp
@@ -523,16 +523,17 @@ txStylesheetCompiler::maybeDoneCompiling
  */
 
 
 txStylesheetCompilerState::txStylesheetCompilerState(txACompileObserver* aObserver)
     : mHandlerTable(nullptr),
       mSorter(nullptr),
       mDOE(false),
       mSearchingForFallback(false),
+      mDisAllowed(0),
       mObserver(aObserver),
       mEmbedStatus(eNoEmbed),
       mDoneWithThisStylesheet(false),
       mNextInstrPtr(nullptr),
       mToplevelIterator(nullptr)
 {
     // Embedded stylesheets have another handler, which is set in
     // txStylesheetCompiler::init if the baseURI has a fragment identifier.
@@ -918,16 +919,19 @@ TX_ConstructXSLTFunction(nsIAtom* aName,
                          txStylesheetCompilerState* aState,
                          FunctionCall** aFunction)
 {
     if (aName == nsGkAtoms::document) {
         *aFunction =
             new DocumentFunctionCall(aState->mElementContext->mBaseURI);
     }
     else if (aName == nsGkAtoms::key) {
+        if (!aState->allowed(txIParseContext::KEY_FUNCTION)) {
+            return NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED;
+        }
         *aFunction =
             new txKeyFunctionCall(aState->mElementContext->mMappings);
     }
     else if (aName == nsGkAtoms::formatNumber) {
         *aFunction =
             new txFormatNumberFunctionCall(aState->mStylesheet,
                                            aState->mElementContext->mMappings);
     }
--- a/dom/xslt/xslt/txStylesheetCompiler.h
+++ b/dom/xslt/xslt/txStylesheetCompiler.h
@@ -135,26 +135,40 @@ public:
      */
     bool fcp()
     {
         return mElementContext->mForwardsCompatibleParsing;
     }
 
     void SetErrorOffset(uint32_t aOffset) override;
 
+    bool allowed(Allowed aAllowed) override
+    {
+        return !(mDisAllowed & aAllowed);
+    }
+
+    bool ignoreError(nsresult aResult)
+    {
+        // Some errors shouldn't be ignored even in forwards compatible parsing
+        // mode.
+        return aResult != NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED &&
+               fcp();
+    }
+
     static void shutdown();
 
 
     nsRefPtr<txStylesheet> mStylesheet;
     txHandlerTable* mHandlerTable;
     nsAutoPtr<txElementContext> mElementContext;
     txPushNewContext* mSorter;
     nsAutoPtr<txList> mChooseGotoList;
     bool mDOE;
     bool mSearchingForFallback;
+    uint16_t mDisAllowed;
 
 protected:
     nsRefPtr<txACompileObserver> mObserver;
     nsTArray<txInScopeVariable*> mInScopeVariables;
     nsTArray<txStylesheetCompiler*> mChildCompilerList;
     // embed info, target information is the ID
     nsString mTarget;
     enum 
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -396,26 +396,26 @@ Factory::CreateDrawTargetForData(Backend
       newTarget = new DrawTargetCairo();
       if (newTarget->Init(aData, aSize, aStride, aFormat)) {
         retVal = newTarget.forget();
       }
       break;
     }
 #endif
   default:
-    gfxDebug() << "Invalid draw target type specified.";
+    gfxCriticalNote << "Invalid draw target type specified: " << (int)aBackend;
     return nullptr;
   }
 
   if (mRecorder && retVal) {
     return MakeAndAddRef<DrawTargetRecording>(mRecorder, retVal, true);
   }
 
   if (!retVal) {
-    gfxDebug() << "Failed to create DrawTarget, Type: " << int(aBackend) << " Size: " << aSize;
+    gfxCriticalNote << "Failed to create DrawTarget, Type: " << int(aBackend) << " Size: " << aSize << ", Data: " << hexa(aData) << ", Stride: " << aStride;
   }
 
   return retVal.forget();
 }
 
 already_AddRefed<DrawTarget>
 Factory::CreateTiledDrawTarget(const TileSet& aTileSet)
 {
--- a/gfx/layers/ImageDataSerializer.cpp
+++ b/gfx/layers/ImageDataSerializer.cpp
@@ -146,19 +146,23 @@ ImageDataSerializerBase::GetFormat() con
   MOZ_ASSERT(IsValid());
   return GetBufferInfo(mData, mDataSize)->format;
 }
 
 already_AddRefed<DrawTarget>
 ImageDataSerializerBase::GetAsDrawTarget(gfx::BackendType aBackend)
 {
   MOZ_ASSERT(IsValid());
-  return gfx::Factory::CreateDrawTargetForData(aBackend,
+  RefPtr<DrawTarget> dt = gfx::Factory::CreateDrawTargetForData(aBackend,
                                                GetData(), GetSize(),
                                                GetStride(), GetFormat());
+  if (!dt) {
+    gfxCriticalNote << "Failed GetAsDrawTarget " << IsValid() << ", " << hexa(size_t(mData)) << " + " << SurfaceBufferInfo::GetOffset() << ", " << GetSize() << ", " << GetStride() << ", " << (int)GetFormat();
+  }
+  return dt.forget();
 }
 
 already_AddRefed<gfx::DataSourceSurface>
 ImageDataSerializerBase::GetAsSurface()
 {
   MOZ_ASSERT(IsValid());
   return Factory::CreateWrappingDataSourceSurface(GetData(),
                                                   GetStride(),
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -840,25 +840,29 @@ BufferTextureClient::BorrowDrawTarget()
 
   if (mDrawTarget) {
     mDrawTarget->SetTransform(Matrix());
     return mDrawTarget;
   }
 
   ImageDataSerializer serializer(GetBuffer(), GetBufferSize());
   if (!serializer.IsValid()) {
+    gfxCriticalNote << "Invalid serializer " << IsValid() << ", " << IsLocked() << ", " << GetBufferSize();
     return nullptr;
   }
 
   mDrawTarget = serializer.GetAsDrawTarget(mBackend);
   if (mDrawTarget) {
     return mDrawTarget;
   }
 
   mDrawTarget = serializer.GetAsDrawTarget(BackendType::CAIRO);
+  if (!mDrawTarget) {
+    gfxCriticalNote << "BorrowDrawTarget failure, original backend " << (int)mBackend;
+  }
 
   return mDrawTarget;
 }
 
 void
 BufferTextureClient::UpdateFromSurface(gfx::SourceSurface* aSurface)
 {
   ImageDataSerializer serializer(GetBuffer(), GetBufferSize());
new file mode 100644
--- /dev/null
+++ b/image/StreamingLexer.h
@@ -0,0 +1,355 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * StreamingLexer is a lexing framework designed to make it simple to write
+ * image decoders without worrying about the details of how the data is arriving
+ * from the network.
+ */
+
+#ifndef mozilla_image_StreamingLexer_h
+#define mozilla_image_StreamingLexer_h
+
+#include <algorithm>
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Vector.h"
+
+namespace mozilla {
+namespace image {
+
+/// Buffering behaviors for StreamingLexer transitions.
+enum class BufferingStrategy
+{
+  BUFFERED,   // Data will be buffered and processed in one chunk.
+  UNBUFFERED  // Data will be processed as it arrives, in multiple chunks.
+};
+
+/// @return true if @aState is a terminal state.
+template <typename State>
+bool IsTerminalState(State aState)
+{
+  return aState == State::SUCCESS ||
+         aState == State::FAILURE;
+}
+
+/**
+ * LexerTransition is a type used to give commands to the lexing framework.
+ * Code that uses StreamingLexer can create LexerTransition values using the
+ * static methods on Transition, and then return them to the lexing framework
+ * for execution.
+ */
+template <typename State>
+class LexerTransition
+{
+public:
+  State NextState() const { return mNextState; }
+  State UnbufferedState() const { return *mUnbufferedState; }
+  size_t Size() const { return mSize; }
+  BufferingStrategy Buffering() const { return mBufferingStrategy; }
+
+private:
+  friend struct Transition;
+
+  LexerTransition(const State& aNextState,
+                  const Maybe<State>& aUnbufferedState,
+                  size_t aSize,
+                  BufferingStrategy aBufferingStrategy)
+    : mNextState(aNextState)
+    , mUnbufferedState(aUnbufferedState)
+    , mSize(aSize)
+    , mBufferingStrategy(aBufferingStrategy)
+  {
+    MOZ_ASSERT_IF(mBufferingStrategy == BufferingStrategy::UNBUFFERED,
+                  mUnbufferedState);
+    MOZ_ASSERT_IF(mUnbufferedState,
+                  mBufferingStrategy == BufferingStrategy::UNBUFFERED);
+  }
+
+  State mNextState;
+  Maybe<State> mUnbufferedState;
+  size_t mSize;
+  BufferingStrategy mBufferingStrategy;
+};
+
+struct Transition
+{
+  /// Transition to @aNextState, buffering @aSize bytes of data.
+  template <typename State>
+  static LexerTransition<State>
+  To(const State& aNextState, size_t aSize)
+  {
+    MOZ_ASSERT(!IsTerminalState(aNextState));
+    return LexerTransition<State>(aNextState, Nothing(), aSize,
+                                  BufferingStrategy::BUFFERED);
+  }
+
+  /**
+   * Transition to @aNextState via @aUnbufferedState, reading @aSize bytes of
+   * data unbuffered.
+   *
+   * The unbuffered data will be delivered in state @aUnbufferedState, which may
+   * be invoked repeatedly until all @aSize bytes have been delivered. Then,
+   * @aNextState will be invoked with no data. No state transitions are allowed
+   * from @aUnbufferedState except for transitions to a terminal state, so
+   * @aNextState will always be reached unless lexing terminates early.
+   */
+  template <typename State>
+  static LexerTransition<State>
+  ToUnbuffered(const State& aNextState,
+               const State& aUnbufferedState,
+               size_t aSize)
+  {
+    MOZ_ASSERT(!IsTerminalState(aNextState));
+    MOZ_ASSERT(!IsTerminalState(aUnbufferedState));
+    return LexerTransition<State>(aNextState, Some(aUnbufferedState), aSize,
+                                  BufferingStrategy::UNBUFFERED);
+  }
+
+  /**
+   * Continue receiving unbuffered data. @aUnbufferedState should be the same
+   * state as the @aUnbufferedState specified in the preceding call to
+   * ToUnbuffered().
+   *
+   * This should be used during an unbuffered read initiated by ToUnbuffered().
+   */
+  template <typename State>
+  static LexerTransition<State>
+  ContinueUnbuffered(const State& aUnbufferedState)
+  {
+    MOZ_ASSERT(!IsTerminalState(aUnbufferedState));
+    return LexerTransition<State>(aUnbufferedState, Nothing(), 0,
+                                  BufferingStrategy::BUFFERED);
+  }
+
+  /**
+   * Terminate lexing, ending up in terminal state @aFinalState.
+   *
+   * No more data will be delivered after Terminate() is used.
+   */
+  template <typename State>
+  static LexerTransition<State>
+  Terminate(const State& aFinalState)
+  {
+    MOZ_ASSERT(IsTerminalState(aFinalState));
+    return LexerTransition<State>(aFinalState, Nothing(), 0,
+                                  BufferingStrategy::BUFFERED);
+  }
+
+private:
+  Transition();
+};
+
+/**
+ * StreamingLexer is a lexing framework designed to make it simple to write
+ * image decoders without worrying about the details of how the data is arriving
+ * from the network.
+ *
+ * To use StreamingLexer:
+ *
+ *  - Create a State type. This should be an |enum class| listing all of the
+ *    states that you can be in while lexing the image format you're trying to
+ *    read. It must contain the two terminal states SUCCESS and FAILURE.
+ *
+ *  - Add an instance of StreamingLexer<State> to your decoder class. Initialize
+ *    it with a Transition::To() the state that you want to start lexing in.
+ *
+ *  - In your decoder's WriteInternal method(), call Lex(), passing in the input
+ *    data and length that are passed to WriteInternal(). You also need to pass
+ *    a lambda which dispatches to lexing code for each state based on the State
+ *    value that's passed in. The lambda generally should just continue a
+ *    |switch| statement that calls different methods for each State value. Each
+ *    method should return a LexerTransition<State>, which the lambda should
+ *    return in turn.
+ *
+ *  - Write the methods that actually implement lexing for your image format.
+ *    These methods should return either Transition::To(), to move on to another
+ *    state, or Transition::Terminate(), if lexing has terminated in either
+ *    success or failure. (There are also additional transitions for unbuffered
+ *    reads; see below.)
+ *
+ * That's all there is to it. The StreamingLexer will track your position in the
+ * input and buffer enough data so that your lexing methods can process
+ * everything in one pass. Lex() returns Nothing() if more data is needed, in
+ * which case you should just return from WriteInternal(). If lexing reaches a
+ * terminal state, Lex() returns Some(State::SUCCESS) or Some(State::FAILURE),
+ * and you can check which one to determine if lexing succeeded or failed and do
+ * any necessary cleanup.
+ *
+ * There's one more wrinkle: some lexers may want to *avoid* buffering in some
+ * cases, and just process the data as it comes in. This is useful if, for
+ * example, you just want to skip over a large section of data; there's no point
+ * in buffering data you're just going to ignore.
+ *
+ * You can begin an unbuffered read with Transition::ToUnbuffered(). This works
+ * a little differently than Transition::To() in that you specify *two* states.
+ * The @aUnbufferedState argument specifies a state that will be called
+ * repeatedly with unbuffered data, as soon as it arrives. The implementation
+ * for that state should return either a transition to a terminal state, or
+ * Transition::ContinueUnbuffered(). Once the amount of data requested in the
+ * original call to Transition::ToUnbuffered() has been delivered, Lex() will
+ * transition to the @aNextState state specified via Transition::ToUnbuffered().
+ * That state will be invoked with *no* data; it's just called to signal that
+ * the unbuffered read is over.
+ *
+ * XXX(seth): We should be able to get of the |State| stuff totally once bug
+ * 1198451 lands, since we can then just return a function representing the next
+ * state directly.
+ */
+template <typename State, size_t InlineBufferSize = 16>
+class StreamingLexer
+{
+public:
+  explicit StreamingLexer(LexerTransition<State> aStartState)
+    : mTransition(aStartState)
+    , mToReadUnbuffered(0)
+  { }
+
+  template <typename Func>
+  Maybe<State> Lex(const char* aInput, size_t aLength, Func aFunc)
+  {
+    if (IsTerminalState(mTransition.NextState())) {
+      // We've already reached a terminal state. We never deliver any more data
+      // in this case; just return the terminal state again immediately.
+      return Some(mTransition.NextState());
+    }
+
+    if (mToReadUnbuffered > 0) {
+      // We're continuing an unbuffered read.
+
+      MOZ_ASSERT(mBuffer.empty(),
+                 "Shouldn't be continuing an unbuffered read and a buffered "
+                 "read at the same time");
+
+      size_t toRead = std::min(mToReadUnbuffered, aLength);
+
+      // Call aFunc with the unbuffered state to indicate that we're in the middle
+      // of an unbuffered read. We enforce that any state transition passed back
+      // to us is either a terminal states or takes us back to the unbuffered
+      // state.
+      LexerTransition<State> unbufferedTransition =
+        aFunc(mTransition.UnbufferedState(), aInput, toRead);
+      if (IsTerminalState(unbufferedTransition.NextState())) {
+        mTransition = unbufferedTransition;
+        return Some(mTransition.NextState());  // Done!
+      }
+      MOZ_ASSERT(mTransition.UnbufferedState() ==
+                   unbufferedTransition.NextState());
+
+      aInput += toRead;
+      aLength -= toRead;
+      mToReadUnbuffered -= toRead;
+      if (mToReadUnbuffered != 0) {
+        return Nothing();  // Need more input.
+      }
+
+      // We're done with the unbuffered read, so transition to the next state.
+      mTransition = aFunc(mTransition.NextState(), nullptr, 0);
+      if (IsTerminalState(mTransition.NextState())) {
+        return Some(mTransition.NextState());  // Done!
+      }
+    } else if (0 < mBuffer.length()) {
+      // We're continuing a buffered read.
+
+      MOZ_ASSERT(mToReadUnbuffered == 0,
+                 "Shouldn't be continuing an unbuffered read and a buffered "
+                 "read at the same time");
+      MOZ_ASSERT(mBuffer.length() < mTransition.Size(),
+                 "Buffered more than we needed?");
+
+      size_t toRead = std::min(aLength, mTransition.Size() - mBuffer.length());
+
+      mBuffer.append(aInput, toRead);
+      aInput += toRead;
+      aLength -= toRead;
+      if (mBuffer.length() != mTransition.Size()) {
+        return Nothing();  // Need more input.
+      }
+
+      // We've buffered everything, so transition to the next state.
+      mTransition =
+        aFunc(mTransition.NextState(), mBuffer.begin(), mBuffer.length());
+      mBuffer.clear();
+      if (IsTerminalState(mTransition.NextState())) {
+        return Some(mTransition.NextState());  // Done!
+      }
+    }
+
+    MOZ_ASSERT(mToReadUnbuffered == 0);
+    MOZ_ASSERT(mBuffer.empty());
+
+    // Process states as long as we continue to have enough input to do so.
+    while (mTransition.Size() <= aLength) {
+      size_t toRead = mTransition.Size();
+
+      if (mTransition.Buffering() == BufferingStrategy::BUFFERED) {
+        mTransition = aFunc(mTransition.NextState(), aInput, toRead);
+      } else {
+        MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED);
+
+        // Call aFunc with the unbuffered state to indicate that we're in the
+        // middle of an unbuffered read. We enforce that any state transition
+        // passed back to us is either a terminal states or takes us back to the
+        // unbuffered state.
+        LexerTransition<State> unbufferedTransition =
+          aFunc(mTransition.UnbufferedState(), aInput, toRead);
+        if (IsTerminalState(unbufferedTransition.NextState())) {
+          mTransition = unbufferedTransition;
+          return Some(mTransition.NextState());  // Done!
+        }
+        MOZ_ASSERT(mTransition.UnbufferedState() ==
+                     unbufferedTransition.NextState());
+
+        // We're done with the unbuffered read, so transition to the next state.
+        mTransition = aFunc(mTransition.NextState(), nullptr, 0);
+      }
+
+      aInput += toRead;
+      aLength -= toRead;
+
+      if (IsTerminalState(mTransition.NextState())) {
+        return Some(mTransition.NextState());  // Done!
+      }
+    }
+
+    if (aLength == 0) {
+      // We finished right at a transition point. Just wait for more data.
+      return Nothing();
+    }
+
+    // If the next state is unbuffered, deliver what we can and then wait.
+    if (mTransition.Buffering() == BufferingStrategy::UNBUFFERED) {
+      LexerTransition<State> unbufferedTransition =
+        aFunc(mTransition.UnbufferedState(), aInput, aLength);
+      if (IsTerminalState(unbufferedTransition.NextState())) {
+        mTransition = unbufferedTransition;
+        return Some(mTransition.NextState());  // Done!
+      }
+      MOZ_ASSERT(mTransition.UnbufferedState() ==
+                   unbufferedTransition.NextState());
+
+      mToReadUnbuffered = mTransition.Size() - aLength;
+      return Nothing();  // Need more input.
+    }
+    
+    // If the next state is buffered, buffer what we can and then wait.
+    MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED);
+    if (!mBuffer.reserve(mTransition.Size())) {
+      return Some(State::FAILURE);  // Done due to allocation failure.
+    }
+    mBuffer.append(aInput, aLength);
+    return Nothing();  // Need more input.
+  }
+
+private:
+  Vector<char, InlineBufferSize> mBuffer;
+  LexerTransition<State> mTransition;
+  size_t mToReadUnbuffered;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_StreamingLexer_h
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -12,20 +12,20 @@
 #include "mozilla/Move.h"
 #include "nsICODecoder.h"
 
 #include "RasterImage.h"
 
 namespace mozilla {
 namespace image {
 
-#define ICONCOUNTOFFSET 4
-#define DIRENTRYOFFSET 6
-#define BITMAPINFOSIZE 40
-#define PREFICONSIZE 16
+// Constants.
+static const uint32_t ICOHEADERSIZE = 6;
+static const uint32_t BITMAPINFOSIZE = 40;
+static const uint32_t PREFICONSIZE = 16;
 
 // ----------------------------------------
 // Actual Data Processing
 // ----------------------------------------
 
 uint32_t
 nsICODecoder::CalcAlphaRowSize()
 {
@@ -52,68 +52,63 @@ nsICODecoder::GetNumColors()
       break;
     default:
       numColors = (uint16_t)-1;
     }
   }
   return numColors;
 }
 
-
 nsICODecoder::nsICODecoder(RasterImage* aImage)
- : Decoder(aImage)
-{
-  mPos = mImageOffset = mCurrIcon = mNumIcons = mBPP = mRowBytes = 0;
-  mIsPNG = false;
-  mRow = nullptr;
-  mOldLine = mCurLine = 1; // Otherwise decoder will never start
-}
-
-nsICODecoder::~nsICODecoder()
-{
-  if (mRow) {
-    free(mRow);
-  }
-}
+  : Decoder(aImage)
+  , mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE))
+  , mBestResourceDelta(INT_MIN)
+  , mBestResourceColorDepth(0)
+  , mNumIcons(0)
+  , mCurrIcon(0)
+  , mBPP(0)
+  , mMaskRowSize(0)
+  , mCurrMaskLine(0)
+{ }
 
 void
 nsICODecoder::FinishInternal()
 {
   // We shouldn't be called in error cases
   MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
 
-  // Finish the internally used decoder as well.
-  if (mContainedDecoder && !mContainedDecoder->HasError()) {
-    mContainedDecoder->FinishInternal();
-  }
-
   GetFinalStateFromContainedDecoder();
 }
 
 void
 nsICODecoder::FinishWithErrorInternal()
 {
   GetFinalStateFromContainedDecoder();
 }
 
 void
 nsICODecoder::GetFinalStateFromContainedDecoder()
 {
   if (!mContainedDecoder) {
     return;
   }
 
+  // Finish the internally used decoder.
+  mContainedDecoder->CompleteDecode();
+
   mDecodeDone = mContainedDecoder->GetDecodeDone();
   mDataError = mDataError || mContainedDecoder->HasDataError();
   mFailCode = NS_SUCCEEDED(mFailCode) ? mContainedDecoder->GetDecoderError()
                                       : mFailCode;
   mDecodeAborted = mContainedDecoder->WasAborted();
   mProgress |= mContainedDecoder->TakeProgress();
   mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
   mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
+
+  MOZ_ASSERT(HasError() || !mCurrentFrame || mCurrentFrame->IsImageComplete());
 }
 
 // Returns a buffer filled with the bitmap file header in little endian:
 // Signature 2 bytes 'BM'
 // FileSize      4 bytes File size in bytes
 // reserved      4 bytes unused (=0)
 // DataOffset    4 bytes File offset to Raster Data
 // Returns true if successful
@@ -201,445 +196,451 @@ nsICODecoder::FixBitmapWidth(int8_t* bih
     mDirEntry.mWidth = 0;
   } else {
     mDirEntry.mWidth = (int8_t)width;
   }
   return true;
 }
 
 // The BMP information header's bits per pixel should be trusted
-// more than what we have.  Usually the ICO's BPP is set to 0
+// more than what we have.  Usually the ICO's BPP is set to 0.
 int32_t
-nsICODecoder::ExtractBPPFromBitmap(int8_t* bih)
+nsICODecoder::ReadBPP(const char* aBIH)
 {
+  const int8_t* bih = reinterpret_cast<const int8_t*>(aBIH);
   int32_t bitsPerPixel;
   memcpy(&bitsPerPixel, bih + 14, sizeof(bitsPerPixel));
   NativeEndian::swapFromLittleEndianInPlace(&bitsPerPixel, 1);
   return bitsPerPixel;
 }
 
 int32_t
-nsICODecoder::ExtractBIHSizeFromBitmap(int8_t* bih)
+nsICODecoder::ReadBIHSize(const char* aBIH)
 {
+  const int8_t* bih = reinterpret_cast<const int8_t*>(aBIH);
   int32_t headerSize;
   memcpy(&headerSize, bih, sizeof(headerSize));
   NativeEndian::swapFromLittleEndianInPlace(&headerSize, 1);
   return headerSize;
 }
 
 void
 nsICODecoder::SetHotSpotIfCursor()
 {
   if (!mIsCursor) {
     return;
   }
 
   mImageMetadata.SetHotspot(mDirEntry.mXHotspot, mDirEntry.mYHotspot);
 }
 
-void
-nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
+LexerTransition<ICOState>
+nsICODecoder::ReadHeader(const char* aData)
 {
-  MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
-  MOZ_ASSERT(aBuffer);
-  MOZ_ASSERT(aCount > 0);
+  // If the third byte is 1, this is an icon. If 2, a cursor.
+  if ((aData[2] != 1) && (aData[2] != 2)) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+  mIsCursor = (aData[2] == 2);
 
-  while (aCount && (mPos < ICONCOUNTOFFSET)) { // Skip to the # of icons.
-    if (mPos == 2) { // if the third byte is 1: This is an icon, 2: a cursor
-      if ((*aBuffer != 1) && (*aBuffer != 2)) {
-        PostDataError();
-        return;
-      }
-      mIsCursor = (*aBuffer == 2);
-    }
-    mPos++; aBuffer++; aCount--;
+  // The fifth and sixth bytes specify the number of resources in the file.
+  mNumIcons =
+    LittleEndian::readUint16(reinterpret_cast<const uint16_t*>(aData + 4));
+  if (mNumIcons == 0) {
+    return Transition::Terminate(ICOState::SUCCESS); // Nothing to do.
   }
 
-  if (mPos == ICONCOUNTOFFSET && aCount >= 2) {
-    mNumIcons =
-      LittleEndian::readUint16(reinterpret_cast<const uint16_t*>(aBuffer));
-    aBuffer += 2;
-    mPos += 2;
-    aCount -= 2;
-  }
-
-  if (mNumIcons == 0) {
-    return; // Nothing to do.
-  }
-
-  uint16_t colorDepth = 0;
-
   // If we didn't get a #-moz-resolution, default to PREFICONSIZE.
   if (mResolution.width == 0 && mResolution.height == 0) {
     mResolution.SizeTo(PREFICONSIZE, PREFICONSIZE);
   }
 
-  // A measure of the difference in size between the entry we've found
-  // and the requested size. We will choose the smallest image that is
-  // >= requested size (i.e. we assume it's better to downscale a larger
-  // icon than to upscale a smaller one).
-  int32_t diff = INT_MIN;
+  return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
+}
+
+size_t
+nsICODecoder::FirstResourceOffset() const
+{
+  MOZ_ASSERT(mNumIcons > 0,
+             "Calling FirstResourceOffset before processing header");
 
-  // Loop through each entry's dir entry
-  while (mCurrIcon < mNumIcons) {
-    if (mPos >= DIRENTRYOFFSET + (mCurrIcon * sizeof(mDirEntryArray)) &&
-        mPos < DIRENTRYOFFSET + ((mCurrIcon + 1) * sizeof(mDirEntryArray))) {
-      uint32_t toCopy = sizeof(mDirEntryArray) -
-                        (mPos - DIRENTRYOFFSET - mCurrIcon *
-                         sizeof(mDirEntryArray));
-      if (toCopy > aCount) {
-        toCopy = aCount;
-      }
-      memcpy(mDirEntryArray + sizeof(mDirEntryArray) - toCopy, aBuffer, toCopy);
-      mPos += toCopy;
-      aCount -= toCopy;
-      aBuffer += toCopy;
-    }
-    if (aCount == 0) {
-      return; // Need more data
-    }
+  // The first resource starts right after the directory, which starts right
+  // after the ICO header.
+  return ICOHEADERSIZE + mNumIcons * ICODIRENTRYSIZE;
+}
+
+LexerTransition<ICOState>
+nsICODecoder::ReadDirEntry(const char* aData)
+{
+  mCurrIcon++;
 
-    IconDirEntry e;
-    if (mPos == (DIRENTRYOFFSET + ICODIRENTRYSIZE) +
-                (mCurrIcon * sizeof(mDirEntryArray))) {
-      mCurrIcon++;
-      ProcessDirEntry(e);
-      // We can't use GetRealWidth and GetRealHeight here because those operate
-      // on mDirEntry, here we are going through each item in the directory.
-      // Calculate the delta between this image's size and the desired size,
-      // so we can see if it is better than our current-best option.
-      // In the case of several equally-good images, we use the last one.
-      int32_t delta = (e.mWidth == 0 ? 256 : e.mWidth) - mResolution.width +
-                      (e.mHeight == 0 ? 256 : e.mHeight) - mResolution.height;
-      if (e.mBitCount >= colorDepth &&
-          ((diff < 0 && delta >= diff) || (delta >= 0 && delta <= diff))) {
-        diff = delta;
-        mImageOffset = e.mImageOffset;
-
-        // ensure mImageOffset is >= size of the direntry headers (bug #245631)
-        uint32_t minImageOffset = DIRENTRYOFFSET +
-                                  mNumIcons * sizeof(mDirEntryArray);
-        if (mImageOffset < minImageOffset) {
-          PostDataError();
-          return;
-        }
+  // Read the directory entry.
+  IconDirEntry e;
+  memset(&e, 0, sizeof(e));
+  memcpy(&e.mWidth, aData, sizeof(e.mWidth));
+  memcpy(&e.mHeight, aData + 1, sizeof(e.mHeight));
+  memcpy(&e.mColorCount, aData + 2, sizeof(e.mColorCount));
+  memcpy(&e.mReserved, aData + 3, sizeof(e.mReserved));
+  memcpy(&e.mPlanes, aData + 4, sizeof(e.mPlanes));
+  e.mPlanes = LittleEndian::readUint16(&e.mPlanes);
+  memcpy(&e.mBitCount, aData + 6, sizeof(e.mBitCount));
+  e.mBitCount = LittleEndian::readUint16(&e.mBitCount);
+  memcpy(&e.mBytesInRes, aData + 8, sizeof(e.mBytesInRes));
+  e.mBytesInRes = LittleEndian::readUint32(&e.mBytesInRes);
+  memcpy(&e.mImageOffset, aData + 12, sizeof(e.mImageOffset));
+  e.mImageOffset = LittleEndian::readUint32(&e.mImageOffset);
 
-        colorDepth = e.mBitCount;
-        memcpy(&mDirEntry, &e, sizeof(IconDirEntry));
-      }
-    }
-  }
-
-  if (mPos < mImageOffset) {
-    // Skip to (or at least towards) the desired image offset
-    uint32_t toSkip = mImageOffset - mPos;
-    if (toSkip > aCount) {
-      toSkip = aCount;
-    }
+  // Calculate the delta between this image's size and the desired size, so we
+  // can see if it is better than our current-best option.  In the case of
+  // several equally-good images, we use the last one. "Better" in this case is
+  // determined by |delta|, a measure of the difference in size between the
+  // entry we've found and the requested size. We will choose the smallest image
+  // that is >= requested size (i.e. we assume it's better to downscale a larger
+  // icon than to upscale a smaller one).
+  int32_t delta = GetRealWidth(e) - mResolution.width +
+                  GetRealHeight(e) - mResolution.height;
+  if (e.mBitCount >= mBestResourceColorDepth &&
+      ((mBestResourceDelta < 0 && delta >= mBestResourceDelta) ||
+       (delta >= 0 && delta <= mBestResourceDelta))) {
+    mBestResourceDelta = delta;
 
-    mPos    += toSkip;
-    aBuffer += toSkip;
-    aCount  -= toSkip;
-  }
-
-  // If we are within the first PNGSIGNATURESIZE bytes of the image data,
-  // then we have either a BMP or a PNG.  We use the first PNGSIGNATURESIZE
-  // bytes to determine which one we have.
-  if (mCurrIcon == mNumIcons && mPos >= mImageOffset &&
-      mPos < mImageOffset + PNGSIGNATURESIZE) {
-    uint32_t toCopy = PNGSIGNATURESIZE - (mPos - mImageOffset);
-    if (toCopy > aCount) {
-      toCopy = aCount;
+    // Ensure mImageOffset is >= size of the direntry headers (bug #245631).
+    if (e.mImageOffset < FirstResourceOffset()) {
+      return Transition::Terminate(ICOState::FAILURE);
     }
 
-    memcpy(mSignature + (mPos - mImageOffset), aBuffer, toCopy);
-    mPos += toCopy;
-    aCount -= toCopy;
-    aBuffer += toCopy;
+    mBestResourceColorDepth = e.mBitCount;
+    mDirEntry = e;
+  }
 
-    mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
-                     PNGSIGNATURESIZE);
-    if (mIsPNG) {
-      mContainedDecoder = new nsPNGDecoder(mImage);
-      mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
-      mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
-      mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
-      mContainedDecoder->Init();
-      if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) {
-        return;
-      }
-    }
+  if (mCurrIcon == mNumIcons) {
+    size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset();
+    return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
+                                    ICOState::SKIP_TO_RESOURCE,
+                                    offsetToResource);
   }
 
-  // If we have a PNG, let the PNG decoder do all of the rest of the work
-  if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) {
-    if (!WriteToContainedDecoder(aBuffer, aCount)) {
-      return;
-    }
+  return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
+}
 
-    if (!HasSize() && mContainedDecoder->HasSize()) {
-      nsIntSize size = mContainedDecoder->GetSize();
-      PostSize(size.width, size.height);
+LexerTransition<ICOState>
+nsICODecoder::SniffResource(const char* aData)
+{
+  // We use the first PNGSIGNATURESIZE bytes to determine whether this resource
+  // is a PNG or a BMP.
+  bool isPNG = !memcmp(aData, nsPNGDecoder::pngSignatureBytes,
+                       PNGSIGNATURESIZE);
+  if (isPNG) {
+    // Create a PNG decoder which will do the rest of the work for us.
+    mContainedDecoder = new nsPNGDecoder(mImage);
+    mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
+    mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
+    mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
+    mContainedDecoder->Init();
+
+    if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) {
+      return Transition::Terminate(ICOState::FAILURE);
     }
 
-    mPos += aCount;
-    aBuffer += aCount;
-    aCount = 0;
-
-    // Raymond Chen says that 32bpp only are valid PNG ICOs
-    // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
-    if (!IsMetadataDecode() &&
-        !static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
-      PostDataError();
-    }
-    return;
-  }
-
-  // We've processed all of the icon dir entries and are within the
-  // bitmap info size
-  if (!mIsPNG && mCurrIcon == mNumIcons && mPos >= mImageOffset &&
-      mPos >= mImageOffset + PNGSIGNATURESIZE &&
-      mPos < mImageOffset + BITMAPINFOSIZE) {
-
-    // As we were decoding, we did not know if we had a PNG signature or the
-    // start of a bitmap information header.  At this point we know we had
-    // a bitmap information header and not a PNG signature, so fill the bitmap
-    // information header with the data it should already have.
-    memcpy(mBIHraw, mSignature, PNGSIGNATURESIZE);
-
-    // We've found the icon.
-    uint32_t toCopy = sizeof(mBIHraw) - (mPos - mImageOffset);
-    if (toCopy > aCount) {
-      toCopy = aCount;
+    if (mDirEntry.mBytesInRes <= PNGSIGNATURESIZE) {
+      return Transition::Terminate(ICOState::FAILURE);
     }
 
-    memcpy(mBIHraw + (mPos - mImageOffset), aBuffer, toCopy);
-    mPos += toCopy;
-    aCount -= toCopy;
-    aBuffer += toCopy;
-  }
-
-  // If we have a BMP inside the ICO and we have read the BIH header
-  if (!mIsPNG && mPos == mImageOffset + BITMAPINFOSIZE) {
-
-    // Make sure we have a sane value for the bitmap information header
-    int32_t bihSize = ExtractBIHSizeFromBitmap(reinterpret_cast<int8_t*>
-                                               (mBIHraw));
-    if (bihSize != BITMAPINFOSIZE) {
-      PostDataError();
-      return;
-    }
-    // We are extracting the BPP from the BIH header as it should be trusted
-    // over the one we have from the icon header
-    mBPP = ExtractBPPFromBitmap(reinterpret_cast<int8_t*>(mBIHraw));
-
-    // Init the bitmap decoder which will do most of the work for us
-    // It will do everything except the AND mask which isn't present in bitmaps
-    // bmpDecoder is for local scope ease, it will be freed by mContainedDecoder
+    // Read in the rest of the PNG unbuffered.
+    size_t toRead = mDirEntry.mBytesInRes - PNGSIGNATURESIZE;
+    return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
+                                    ICOState::READ_PNG,
+                                    toRead);
+  } else {
+    // Create a BMP decoder which will do most of the work for us; the exception
+    // is the AND mask, which isn't present in standalone BMPs.
     nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
     mContainedDecoder = bmpDecoder;
     bmpDecoder->SetUseAlphaData(true);
     mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
     mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
     mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
     mContainedDecoder->Init();
 
-    // The ICO format when containing a BMP does not include the 14 byte
-    // bitmap file header. To use the code of the BMP decoder we need to
-    // generate this header ourselves and feed it to the BMP decoder.
-    int8_t bfhBuffer[BMPFILEHEADERSIZE];
-    if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
-      PostDataError();
-      return;
-    }
-    if (!WriteToContainedDecoder((const char*)bfhBuffer, sizeof(bfhBuffer))) {
-      return;
+    // Make sure we have a sane size for the bitmap information header.
+    int32_t bihSize = ReadBIHSize(aData);
+    if (bihSize != static_cast<int32_t>(BITMAPINFOSIZE)) {
+      return Transition::Terminate(ICOState::FAILURE);
     }
 
-    // Setup the cursor hot spot if one is present
-    SetHotSpotIfCursor();
+    // Buffer the first part of the bitmap information header.
+    memcpy(mBIHraw, aData, PNGSIGNATURESIZE);
 
-    // Fix the ICO height from the BIH.
-    // Fix the height on the BIH to be /2 so our BMP decoder will understand.
-    if (!FixBitmapHeight(reinterpret_cast<int8_t*>(mBIHraw))) {
-      PostDataError();
-      return;
-    }
+    // Read in the rest of the bitmap information header.
+    return Transition::To(ICOState::READ_BIH,
+                          BITMAPINFOSIZE - PNGSIGNATURESIZE);
+  }
+}
 
-    // Fix the ICO width from the BIH.
-    if (!FixBitmapWidth(reinterpret_cast<int8_t*>(mBIHraw))) {
-      PostDataError();
-      return;
-    }
+LexerTransition<ICOState>
+nsICODecoder::ReadPNG(const char* aData, uint32_t aLen)
+{
+  if (!WriteToContainedDecoder(aData, aLen)) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
 
-    // Write out the BMP's bitmap info header
-    if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
-      return;
-    }
-
+  if (!HasSize() && mContainedDecoder->HasSize()) {
     nsIntSize size = mContainedDecoder->GetSize();
     PostSize(size.width, size.height);
 
-    // We have the size. If we're doing a metadata decode, we're done.
     if (IsMetadataDecode()) {
-      return;
-    }
-
-    // Sometimes the ICO BPP header field is not filled out
-    // so we should trust the contained resource over our own
-    // information.
-    mBPP = bmpDecoder->GetBitsPerPixel();
-
-    // Check to make sure we have valid color settings
-    uint16_t numColors = GetNumColors();
-    if (numColors == (uint16_t)-1) {
-      PostDataError();
-      return;
+      return Transition::Terminate(ICOState::SUCCESS);
     }
   }
 
-  // If we have a BMP
-  if (!mIsPNG && mContainedDecoder && mPos >= mImageOffset + BITMAPINFOSIZE) {
-    uint16_t numColors = GetNumColors();
-    if (numColors == (uint16_t)-1) {
-      PostDataError();
-      return;
-    }
-    // Feed the actual image data (not including headers) into the BMP decoder
-    uint32_t bmpDataOffset = mDirEntry.mImageOffset + BITMAPINFOSIZE;
-    uint32_t bmpDataEnd = mDirEntry.mImageOffset + BITMAPINFOSIZE +
-                          static_cast<nsBMPDecoder*>(mContainedDecoder.get())->
-                            GetCompressedImageSize() +
-                          4 * numColors;
+  // Raymond Chen says that 32bpp only are valid PNG ICOs
+  // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
+  if (!IsMetadataDecode() &&
+      !static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+
+  return Transition::ContinueUnbuffered(ICOState::READ_PNG);
+}
+
+LexerTransition<ICOState>
+nsICODecoder::ReadBIH(const char* aData)
+{
+  // Buffer the rest of the bitmap information header.
+  memcpy(mBIHraw + PNGSIGNATURESIZE, aData, BITMAPINFOSIZE - PNGSIGNATURESIZE);
 
-    // If we are feeding in the core image data, but we have not yet
-    // reached the ICO's 'AND buffer mask'
-    if (mPos >= bmpDataOffset && mPos < bmpDataEnd) {
+  // Extracting the BPP from the BIH header; it should be trusted over the one
+  // we have from the ICO header.
+  mBPP = ReadBPP(mBIHraw);
+
+  // The ICO format when containing a BMP does not include the 14 byte
+  // bitmap file header. To use the code of the BMP decoder we need to
+  // generate this header ourselves and feed it to the BMP decoder.
+  int8_t bfhBuffer[BMPFILEHEADERSIZE];
+  if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+
+  if (!WriteToContainedDecoder(reinterpret_cast<const char*>(bfhBuffer),
+                               sizeof(bfhBuffer))) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+
+  // Set up the cursor hot spot if one is present.
+  SetHotSpotIfCursor();
 
-      // Figure out how much data the BMP decoder wants
-      uint32_t toFeed = bmpDataEnd - mPos;
-      if (toFeed > aCount) {
-        toFeed = aCount;
-      }
+  // Fix the ICO height from the BIH. It needs to be halved so our BMP decoder
+  // will understand, because the BMP decoder doesn't expect the alpha mask that
+  // follows the BMP data in an ICO.
+  if (!FixBitmapHeight(reinterpret_cast<int8_t*>(mBIHraw))) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
 
-      if (!WriteToContainedDecoder(aBuffer, toFeed)) {
-        return;
-      }
+  // Fix the ICO width from the BIH.
+  if (!FixBitmapWidth(reinterpret_cast<int8_t*>(mBIHraw))) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+
+  // Write out the BMP's bitmap info header.
+  if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+
+  nsIntSize size = mContainedDecoder->GetSize();
+  PostSize(size.width, size.height);
 
-      mPos += toFeed;
-      aCount -= toFeed;
-      aBuffer += toFeed;
-    }
+  // We have the size. If we're doing a metadata decode, we're done.
+  if (IsMetadataDecode()) {
+    return Transition::Terminate(ICOState::SUCCESS);
+  }
+
+  // Sometimes the ICO BPP header field is not filled out so we should trust the
+  // contained resource over our own information.
+  // XXX(seth): Is this ever different than the value we obtained from
+  // ReadBPP() above?
+  nsRefPtr<nsBMPDecoder> bmpDecoder =
+    static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+  mBPP = bmpDecoder->GetBitsPerPixel();
 
-    // If the bitmap is fully processed, treat any left over data as the ICO's
-    // 'AND buffer mask' which appears after the bitmap resource.
-    if (!mIsPNG && mPos >= bmpDataEnd) {
-      nsRefPtr<nsBMPDecoder> bmpDecoder =
-        static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+  // Check to make sure we have valid color settings.
+  uint16_t numColors = GetNumColors();
+  if (numColors == uint16_t(-1)) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+
+  // Do we have an AND mask on this BMP? If so, we need to read it after we read
+  // the BMP data itself.
+  uint32_t bmpDataLength = bmpDecoder->GetCompressedImageSize() + 4 * numColors;
+  bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry.mBytesInRes;
+  ICOState afterBMPState = hasANDMask ? ICOState::PREPARE_FOR_MASK
+                                      : ICOState::FINISHED_RESOURCE;
 
-      // There may be an optional AND bit mask after the data.  This is
-      // only used if the alpha data is not already set. The alpha data
-      // is used for 32bpp bitmaps as per the comment in ICODecoder.h
-      // The alpha mask should be checked in all other cases.
-      if (bmpDecoder->GetBitsPerPixel() != 32 || !bmpDecoder->HasAlphaData()) {
-        uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
-        if (mPos == bmpDataEnd) {
-          mPos++;
-          mRowBytes = 0;
-          mCurLine = GetRealHeight();
-          mRow = (uint8_t*)realloc(mRow, rowSize);
-          if (!mRow) {
-            PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
-            return;
-          }
-        }
+  // Read in the rest of the BMP unbuffered.
+  return Transition::ToUnbuffered(afterBMPState,
+                                  ICOState::READ_BMP,
+                                  bmpDataLength);
+}
+
+LexerTransition<ICOState>
+nsICODecoder::ReadBMP(const char* aData, uint32_t aLen)
+{
+  if (!WriteToContainedDecoder(aData, aLen)) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+
+  return Transition::ContinueUnbuffered(ICOState::READ_BMP);
+}
 
-        // Ensure memory has been allocated before decoding.
-        MOZ_ASSERT(mRow, "mRow is null");
-        if (!mRow) {
-          PostDataError();
-          return;
-        }
+LexerTransition<ICOState>
+nsICODecoder::PrepareForMask()
+{
+  nsRefPtr<nsBMPDecoder> bmpDecoder =
+    static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+
+  uint16_t numColors = GetNumColors();
+  MOZ_ASSERT(numColors != uint16_t(-1));
 
-        uint8_t sawTransparency = 0;
+  // Determine the length of the AND mask.
+  uint32_t bmpLengthWithHeader =
+    BITMAPINFOSIZE + bmpDecoder->GetCompressedImageSize() + 4 * numColors;
+  MOZ_ASSERT(bmpLengthWithHeader < mDirEntry.mBytesInRes);
+  uint32_t maskLength = mDirEntry.mBytesInRes - bmpLengthWithHeader;
+
+  // If we have a 32-bpp BMP with alpha data, we ignore the AND mask. We can
+  // also obviously ignore it if the image has zero width or zero height.
+  if ((bmpDecoder->GetBitsPerPixel() == 32 && bmpDecoder->HasAlphaData()) ||
+      GetRealWidth() == 0 || GetRealHeight() == 0) {
+    return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
+                                    ICOState::SKIP_MASK,
+                                    maskLength);
+  }
 
-        while (mCurLine > 0 && aCount > 0) {
-          uint32_t toCopy = std::min(rowSize - mRowBytes, aCount);
-          if (toCopy) {
-            memcpy(mRow + mRowBytes, aBuffer, toCopy);
-            aCount -= toCopy;
-            aBuffer += toCopy;
-            mRowBytes += toCopy;
-          }
-          if (rowSize == mRowBytes) {
-            mCurLine--;
-            mRowBytes = 0;
+  // Compute the row size for the mask.
+  mMaskRowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
+
+  // If the expected size of the AND mask is larger than its actual size, then
+  // we must have a truncated (and therefore corrupt) AND mask.
+  uint32_t expectedLength = mMaskRowSize * GetRealHeight();
+  if (maskLength < expectedLength) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+
+  mCurrMaskLine = GetRealHeight();
+  return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
+}
+
+
+LexerTransition<ICOState>
+nsICODecoder::ReadMaskRow(const char* aData)
+{
+  mCurrMaskLine--;
 
-            uint32_t* imageData = bmpDecoder->GetImageData();
-            if (!imageData) {
-              PostDataError();
-              return;
-            }
-            uint32_t* decoded = imageData + mCurLine * GetRealWidth();
-            uint32_t* decoded_end = decoded + GetRealWidth();
-            uint8_t* p = mRow;
-            uint8_t* p_end = mRow + rowSize;
-            while (p < p_end) {
-              uint8_t idx = *p++;
-              sawTransparency |= idx;
-              for (uint8_t bit = 0x80; bit && decoded<decoded_end; bit >>= 1) {
-                // Clear pixel completely for transparency.
-                if (idx & bit) {
-                  *decoded = 0;
-                }
-                decoded++;
-              }
-            }
-          }
-        }
+  nsRefPtr<nsBMPDecoder> bmpDecoder =
+    static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+
+  uint32_t* imageData = bmpDecoder->GetImageData();
+  if (!imageData) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
 
-        // If any bits are set in sawTransparency, then we know at least one
-        // pixel was transparent.
-        if (sawTransparency) {
-          PostHasTransparency();
-          bmpDecoder->SetHasAlphaData();
-        }
+  uint8_t sawTransparency = 0;
+  uint32_t* decoded = imageData + mCurrMaskLine * GetRealWidth();
+  uint32_t* decodedRowEnd = decoded + GetRealWidth();
+  const uint8_t* mask = reinterpret_cast<const uint8_t*>(aData);
+  const uint8_t* maskRowEnd = mask + mMaskRowSize;
+
+  // Iterate simultaneously through the AND mask and the image data.
+  while (mask < maskRowEnd) {
+    uint8_t idx = *mask++;
+    sawTransparency |= idx;
+    for (uint8_t bit = 0x80; bit && decoded < decodedRowEnd; bit >>= 1) {
+      // Clear pixel completely for transparency.
+      if (idx & bit) {
+        *decoded = 0;
       }
+      decoded++;
     }
   }
+
+  // If any bits are set in sawTransparency, then we know at least one pixel was
+  // transparent.
+  if (sawTransparency) {
+    PostHasTransparency();
+    bmpDecoder->SetHasAlphaData();
+  }
+
+  if (mCurrMaskLine == 0) {
+    return Transition::To(ICOState::FINISHED_RESOURCE, 0);
+  }
+
+  return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
+}
+
+void
+nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
+{
+  MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
+  MOZ_ASSERT(aBuffer);
+  MOZ_ASSERT(aCount > 0);
+
+  Maybe<ICOState> terminalState =
+    mLexer.Lex(aBuffer, aCount,
+               [=](ICOState aState, const char* aData, size_t aLength) {
+      switch (aState) {
+        case ICOState::HEADER:
+          return ReadHeader(aData);
+        case ICOState::DIR_ENTRY:
+          return ReadDirEntry(aData);
+        case ICOState::SKIP_TO_RESOURCE:
+          return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE);
+        case ICOState::FOUND_RESOURCE:
+          return Transition::To(ICOState::SNIFF_RESOURCE, PNGSIGNATURESIZE);
+        case ICOState::SNIFF_RESOURCE:
+          return SniffResource(aData);
+        case ICOState::READ_PNG:
+          return ReadPNG(aData, aLength);
+        case ICOState::READ_BIH:
+          return ReadBIH(aData);
+        case ICOState::READ_BMP:
+          return ReadBMP(aData, aLength);
+        case ICOState::PREPARE_FOR_MASK:
+          return PrepareForMask();
+        case ICOState::READ_MASK_ROW:
+          return ReadMaskRow(aData);
+        case ICOState::SKIP_MASK:
+          return Transition::ContinueUnbuffered(ICOState::SKIP_MASK);
+        case ICOState::FINISHED_RESOURCE:
+          return Transition::Terminate(ICOState::SUCCESS);
+        default:
+          MOZ_ASSERT_UNREACHABLE("Unknown ICOState");
+          return Transition::Terminate(ICOState::FAILURE);
+      }
+    });
+
+  if (!terminalState) {
+    return;  // Need more data.
+  }
+
+  if (*terminalState == ICOState::FAILURE) {
+    PostDataError();
+    return;
+  }
+
+  MOZ_ASSERT(*terminalState == ICOState::SUCCESS);
 }
 
 bool
 nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
 {
   mContainedDecoder->Write(aBuffer, aCount);
   mProgress |= mContainedDecoder->TakeProgress();
   mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
   if (mContainedDecoder->HasDataError()) {
-    mDataError = mContainedDecoder->HasDataError();
+    PostDataError();
   }
   if (mContainedDecoder->HasDecoderError()) {
     PostDecoderError(mContainedDecoder->GetDecoderError());
   }
   return !HasError();
 }
 
-void
-nsICODecoder::ProcessDirEntry(IconDirEntry& aTarget)
-{
-  memset(&aTarget, 0, sizeof(aTarget));
-  memcpy(&aTarget.mWidth, mDirEntryArray, sizeof(aTarget.mWidth));
-  memcpy(&aTarget.mHeight, mDirEntryArray + 1, sizeof(aTarget.mHeight));
-  memcpy(&aTarget.mColorCount, mDirEntryArray + 2, sizeof(aTarget.mColorCount));
-  memcpy(&aTarget.mReserved, mDirEntryArray + 3, sizeof(aTarget.mReserved));
-  memcpy(&aTarget.mPlanes, mDirEntryArray + 4, sizeof(aTarget.mPlanes));
-  aTarget.mPlanes = LittleEndian::readUint16(&aTarget.mPlanes);
-  memcpy(&aTarget.mBitCount, mDirEntryArray + 6, sizeof(aTarget.mBitCount));
-  aTarget.mBitCount = LittleEndian::readUint16(&aTarget.mBitCount);
-  memcpy(&aTarget.mBytesInRes, mDirEntryArray + 8, sizeof(aTarget.mBytesInRes));
-  aTarget.mBytesInRes = LittleEndian::readUint32(&aTarget.mBytesInRes);
-  memcpy(&aTarget.mImageOffset, mDirEntryArray + 12,
-         sizeof(aTarget.mImageOffset));
-  aTarget.mImageOffset = LittleEndian::readUint32(&aTarget.mImageOffset);
-}
-
 } // namespace image
 } // namespace mozilla
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -3,49 +3,77 @@
  * 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_image_decoders_nsICODecoder_h
 #define mozilla_image_decoders_nsICODecoder_h
 
 #include "nsAutoPtr.h"
+#include "StreamingLexer.h"
 #include "Decoder.h"
 #include "imgFrame.h"
 #include "nsBMPDecoder.h"
 #include "nsPNGDecoder.h"
 #include "ICOFileHeaders.h"
 
 namespace mozilla {
 namespace image {
 
 class RasterImage;
 
+enum class ICOState
+{
+  SUCCESS,
+  FAILURE,
+  HEADER,
+  DIR_ENTRY,
+  SKIP_TO_RESOURCE,
+  FOUND_RESOURCE,
+  SNIFF_RESOURCE,
+  READ_PNG,
+  READ_BIH,
+  READ_BMP,
+  PREPARE_FOR_MASK,
+  READ_MASK_ROW,
+  SKIP_MASK,
+  FINISHED_RESOURCE
+};
+
 class nsICODecoder : public Decoder
 {
 public:
-  virtual ~nsICODecoder();
+  virtual ~nsICODecoder() { }
 
-  // Obtains the width of the icon directory entry
-  uint32_t GetRealWidth() const
+  /// @return the width of the icon directory entry @aEntry.
+  static uint32_t GetRealWidth(const IconDirEntry& aEntry)
   {
-    return mDirEntry.mWidth == 0 ? 256 : mDirEntry.mWidth;
+    return aEntry.mWidth == 0 ? 256 : aEntry.mWidth;
   }
 
-  // Obtains the height of the icon directory entry
-  uint32_t GetRealHeight() const
+  /// @return the width of the selected directory entry (mDirEntry).
+  uint32_t GetRealWidth() const { return GetRealWidth(mDirEntry); }
+
+  /// @return the height of the icon directory entry @aEntry.
+  static uint32_t GetRealHeight(const IconDirEntry& aEntry)
   {
-    return mDirEntry.mHeight == 0 ? 256 : mDirEntry.mHeight;
+    return aEntry.mHeight == 0 ? 256 : aEntry.mHeight;
   }
 
+  /// @return the height of the selected directory entry (mDirEntry).
+  uint32_t GetRealHeight() const { return GetRealHeight(mDirEntry); }
+
   virtual void SetResolution(const gfx::IntSize& aResolution) override
   {
     mResolution = aResolution;
   }
 
+  /// @return The offset from the beginning of the ICO to the first resource.
+  size_t FirstResourceOffset() const;
+
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
   virtual void FinishInternal() override;
   virtual void FinishWithErrorInternal() override;
 
 private:
   friend class DecoderFactory;
 
   // Decoders should only be instantiated via DecoderFactory.
@@ -53,59 +81,57 @@ private:
 
   // Writes to the contained decoder and sets the appropriate errors
   // Returns true if there are no errors.
   bool WriteToContainedDecoder(const char* aBuffer, uint32_t aCount);
 
   // Gets decoder state from the contained decoder so it's visible externally.
   void GetFinalStateFromContainedDecoder();
 
-  // Processes a single dir entry of the icon resource
-  void ProcessDirEntry(IconDirEntry& aTarget);
   // Sets the hotspot property of if we have a cursor
   void SetHotSpotIfCursor();
   // Creates a bitmap file header buffer, returns true if successful
   bool FillBitmapFileHeaderBuffer(int8_t* bfh);
   // Fixes the ICO height to match that of the BIH.
   // and also fixes the BIH height to be /2 of what it was.
   // See definition for explanation.
   // Returns false if invalid information is contained within.
   bool FixBitmapHeight(int8_t* bih);
   // Fixes the ICO width to match that of the BIH.
   // Returns false if invalid information is contained within.
   bool FixBitmapWidth(int8_t* bih);
   // Extract bitmap info header size count from BMP information header
-  int32_t ExtractBIHSizeFromBitmap(int8_t* bih);
+  int32_t ReadBIHSize(const char* aBIH);
   // Extract bit count from BMP information header
-  int32_t ExtractBPPFromBitmap(int8_t* bih);
+  int32_t ReadBPP(const char* aBIH);
   // Calculates the row size in bytes for the AND mask table
   uint32_t CalcAlphaRowSize();
   // Obtains the number of colors from the BPP, mBPP must be filled in
   uint16_t GetNumColors();
 
-  gfx::IntSize mResolution;  // The requested -moz-resolution for this icon.
-  uint16_t mBPP; // Stores the images BPP
-  uint32_t mPos; // Keeps track of the position we have decoded up until
-  uint16_t mNumIcons; // Stores the number of icons in the ICO file
-  uint16_t mCurrIcon; // Stores the current dir entry index we are processing
-  uint32_t mImageOffset; // Stores the offset of the image data we want
-  uint8_t* mRow;      // Holds one raw line of the image
-  int32_t mCurLine;   // Line index of the image that's currently being decoded
-  uint32_t mRowBytes; // How many bytes of the row were already received
-  int32_t mOldLine;   // Previous index of the line
-  nsRefPtr<Decoder> mContainedDecoder; // Contains either a BMP or PNG resource
+  LexerTransition<ICOState> ReadHeader(const char* aData);
+  LexerTransition<ICOState> ReadDirEntry(const char* aData);
+  LexerTransition<ICOState> SniffResource(const char* aData);
+  LexerTransition<ICOState> ReadPNG(const char* aData, uint32_t aLen);
+  LexerTransition<ICOState> ReadBIH(const char* aData);
+  LexerTransition<ICOState> ReadBMP(const char* aData, uint32_t aLen);
+  LexerTransition<ICOState> PrepareForMask();
+  LexerTransition<ICOState> ReadMaskRow(const char* aData);
 
-  char mDirEntryArray[ICODIRENTRYSIZE]; // Holds the current dir entry buffer
-  IconDirEntry mDirEntry; // Holds a decoded dir entry
-  // Holds the potential bytes that can be a PNG signature
-  char mSignature[PNGSIGNATURESIZE];
-  // Holds the potential bytes for a bitmap information header
-  char mBIHraw[40];
-  // Stores whether or not the icon file we are processing has type 1 (icon)
-  bool mIsCursor;
-  // Stores whether or not the contained resource is a PNG
-  bool mIsPNG;
+  StreamingLexer<ICOState, 32> mLexer; // The lexer.
+  nsRefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder.
+  gfx::IntSize mResolution;            // The requested -moz-resolution.
+  char mBIHraw[40];                    // The bitmap information header.
+  IconDirEntry mDirEntry;              // The dir entry for the selected resource.
+  int32_t mBestResourceDelta;          // Used to select the best resource.
+  uint16_t mBestResourceColorDepth;    // Used to select the best resource.
+  uint16_t mNumIcons; // Stores the number of icons in the ICO file.
+  uint16_t mCurrIcon; // Stores the current dir entry index we are processing.
+  uint16_t mBPP;      // The BPP of the resource we're decoding.
+  uint32_t mMaskRowSize;  // The size in bytes of each row in the BMP alpha mask.
+  uint32_t mCurrMaskLine; // The line of the BMP alpha mask we're processing.
+  bool mIsCursor;         // Is this ICO a cursor?
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_decoders_nsICODecoder_h
--- a/image/test/gtest/TestDecoders.cpp
+++ b/image/test/gtest/TestDecoders.cpp
@@ -207,18 +207,17 @@ TEST(ImageDecoders, BMPMultiChunk)
   CheckDecoderMultiChunk(GreenBMPTestCase());
 }
 
 TEST(ImageDecoders, ICOSingleChunk)
 {
   CheckDecoderSingleChunk(GreenICOTestCase());
 }
 
-// XXX(seth): Disabled. We'll fix this in bug 1196066.
-TEST(ImageDecoders, DISABLED_ICOMultiChunk)
+TEST(ImageDecoders, ICOMultiChunk)
 {
   CheckDecoderMultiChunk(GreenICOTestCase());
 }
 
 TEST(ImageDecoders, AnimatedGIFSingleChunk)
 {
   CheckDecoderSingleChunk(GreenFirstFrameAnimatedGIFTestCase());
 }
new file mode 100644
--- /dev/null
+++ b/image/test/gtest/TestStreamingLexer.cpp
@@ -0,0 +1,266 @@
+/* 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 "gtest/gtest.h"
+
+#include "mozilla/Vector.h"
+#include "StreamingLexer.h"
+
+using namespace mozilla;
+using namespace mozilla::image;
+
+enum class TestState
+{
+  ONE,
+  TWO,
+  THREE,
+  UNBUFFERED,
+  SUCCESS,
+  FAILURE
+};
+
+void
+CheckData(const char* aData, size_t aLength)
+{
+  EXPECT_TRUE(aLength == 3);
+  EXPECT_EQ(1, aData[0]);
+  EXPECT_EQ(2, aData[1]);
+  EXPECT_EQ(3, aData[2]);
+}
+
+LexerTransition<TestState>
+DoLex(TestState aState, const char* aData, size_t aLength)
+{
+  switch (aState) {
+    case TestState::ONE:
+      CheckData(aData, aLength);
+      return Transition::To(TestState::TWO, 3);
+    case TestState::TWO:
+      CheckData(aData, aLength);
+      return Transition::To(TestState::THREE, 3);
+    case TestState::THREE:
+      CheckData(aData, aLength);
+      return Transition::Terminate(TestState::SUCCESS);
+    default:
+      EXPECT_TRUE(false);  // Shouldn't get here.
+      return Transition::Terminate(TestState::FAILURE);
+  }
+}
+
+LexerTransition<TestState>
+DoLexWithUnbuffered(TestState aState, const char* aData, size_t aLength,
+                    Vector<char>& aUnbufferedVector)
+{
+  switch (aState) {
+    case TestState::ONE:
+      CheckData(aData, aLength);
+      return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
+    case TestState::UNBUFFERED:
+      EXPECT_TRUE(aLength <= 3);
+      aUnbufferedVector.append(aData, aLength);
+      return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
+    case TestState::TWO:
+      CheckData(aUnbufferedVector.begin(), aUnbufferedVector.length());
+      return Transition::To(TestState::THREE, 3);
+    case TestState::THREE:
+      CheckData(aData, aLength);
+      return Transition::Terminate(TestState::SUCCESS);
+    default:
+      EXPECT_TRUE(false);
+      return Transition::Terminate(TestState::FAILURE);
+  }
+}
+
+LexerTransition<TestState>
+DoLexWithUnbufferedTerminate(TestState aState, const char* aData, size_t aLength)
+{
+  switch (aState) {
+    case TestState::ONE:
+      CheckData(aData, aLength);
+      return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
+    case TestState::UNBUFFERED:
+      return Transition::Terminate(TestState::SUCCESS);
+    default:
+      EXPECT_TRUE(false);
+      return Transition::Terminate(TestState::FAILURE);
+  }
+}
+
+TEST(ImageStreamingLexer, SingleChunk)
+{
+  StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
+  char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
+
+  // Test delivering all the data at once.
+  Maybe<TestState> result = lexer.Lex(data, sizeof(data), DoLex);
+  EXPECT_TRUE(result.isSome());
+  EXPECT_EQ(TestState::SUCCESS, *result);
+}
+
+TEST(ImageStreamingLexer, SingleChunkWithUnbuffered)
+{
+  StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
+  char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
+  Vector<char> unbufferedVector;
+
+  // Test delivering all the data at once.
+  Maybe<TestState> result =
+    lexer.Lex(data, sizeof(data),
+              [&](TestState aState, const char* aData, size_t aLength) {
+      return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
+  });
+  EXPECT_TRUE(result.isSome());
+  EXPECT_EQ(TestState::SUCCESS, *result);
+}
+
+TEST(ImageStreamingLexer, ChunkPerState)
+{
+  StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
+  char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
+
+  // Test delivering in perfectly-sized chunks, one per state.
+  for (unsigned i = 0 ; i < 3 ; ++i) {
+    Maybe<TestState> result = lexer.Lex(data + 3 * i, 3, DoLex);
+
+    if (i == 2) {
+      EXPECT_TRUE(result.isSome());
+      EXPECT_EQ(TestState::SUCCESS, *result);
+    } else {
+      EXPECT_TRUE(result.isNothing());
+    }
+  }
+}
+
+TEST(ImageStreamingLexer, ChunkPerStateWithUnbuffered)
+{
+  StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
+  char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
+  Vector<char> unbufferedVector;
+
+  // Test delivering in perfectly-sized chunks, one per state.
+  for (unsigned i = 0 ; i < 3 ; ++i) {
+    Maybe<TestState> result =
+      lexer.Lex(data + 3 * i, 3,
+                [&](TestState aState, const char* aData, size_t aLength) {
+        return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
+    });
+
+    if (i == 2) {
+      EXPECT_TRUE(result.isSome());
+      EXPECT_EQ(TestState::SUCCESS, *result);
+    } else {
+      EXPECT_TRUE(result.isNothing());
+    }
+  }
+}
+
+TEST(ImageStreamingLexer, OneByteChunks)
+{
+  StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
+  char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
+
+  // Test delivering in one byte chunks.
+  for (unsigned i = 0 ; i < 9 ; ++i) {
+    Maybe<TestState> result = lexer.Lex(data + i, 1, DoLex);
+
+    if (i == 8) {
+      EXPECT_TRUE(result.isSome());
+      EXPECT_EQ(TestState::SUCCESS, *result);
+    } else {
+      EXPECT_TRUE(result.isNothing());
+    }
+  }
+}
+
+TEST(ImageStreamingLexer, OneByteChunksWithUnbuffered)
+{
+  StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
+  char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
+  Vector<char> unbufferedVector;
+
+  // Test delivering in one byte chunks.
+  for (unsigned i = 0 ; i < 9 ; ++i) {
+    Maybe<TestState> result =
+      lexer.Lex(data + i, 1,
+                [&](TestState aState, const char* aData, size_t aLength) {
+        return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
+    });
+
+    if (i == 8) {
+      EXPECT_TRUE(result.isSome());
+      EXPECT_EQ(TestState::SUCCESS, *result);
+    } else {
+      EXPECT_TRUE(result.isNothing());
+    }
+  }
+}
+
+TEST(ImageStreamingLexer, TerminateSuccess)
+{
+  StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
+  char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
+
+  // Test that Terminate is "sticky".
+  Maybe<TestState> result =
+    lexer.Lex(data, sizeof(data),
+              [&](TestState aState, const char* aData, size_t aLength) {
+      EXPECT_TRUE(aState == TestState::ONE);
+      return Transition::Terminate(TestState::SUCCESS);
+  });
+  EXPECT_TRUE(result.isSome());
+  EXPECT_EQ(TestState::SUCCESS, *result);
+
+  result =
+    lexer.Lex(data, sizeof(data),
+              [&](TestState aState, const char* aData, size_t aLength) {
+      EXPECT_TRUE(false);  // Shouldn't get here.
+      return Transition::Terminate(TestState::FAILURE);
+  });
+  EXPECT_TRUE(result.isSome());
+  EXPECT_EQ(TestState::SUCCESS, *result);
+}
+
+TEST(ImageStreamingLexer, TerminateFailure)
+{
+  StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
+  char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
+
+  // Test that Terminate is "sticky".
+  Maybe<TestState> result =
+    lexer.Lex(data, sizeof(data),
+              [&](TestState aState, const char* aData, size_t aLength) {
+      EXPECT_TRUE(aState == TestState::ONE);
+      return Transition::Terminate(TestState::FAILURE);
+  });
+  EXPECT_TRUE(result.isSome());
+  EXPECT_EQ(TestState::FAILURE, *result);
+
+  result =
+    lexer.Lex(data, sizeof(data),
+              [&](TestState aState, const char* aData, size_t aLength) {
+      EXPECT_TRUE(false);  // Shouldn't get here.
+      return Transition::Terminate(TestState::FAILURE);
+  });
+  EXPECT_TRUE(result.isSome());
+  EXPECT_EQ(TestState::FAILURE, *result);
+}
+
+TEST(ImageStreamingLexer, TerminateUnbuffered)
+{
+  StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
+  char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
+
+  // Test that Terminate works during an unbuffered read.
+  for (unsigned i = 0 ; i < 9 ; ++i) {
+    Maybe<TestState> result =
+      lexer.Lex(data + i, 1, DoLexWithUnbufferedTerminate);
+
+    if (i > 2) {
+      EXPECT_TRUE(result.isSome());
+      EXPECT_EQ(TestState::SUCCESS, *result);
+    } else {
+      EXPECT_TRUE(result.isNothing());
+    }
+  }
+}
--- a/image/test/gtest/moz.build
+++ b/image/test/gtest/moz.build
@@ -7,16 +7,17 @@
 Library('imagetest')
 
 UNIFIED_SOURCES = [
     'Common.cpp',
     'TestCopyOnWrite.cpp',
     'TestDecoders.cpp',
     'TestDecodeToSurface.cpp',
     'TestMetadata.cpp',
+    'TestStreamingLexer.cpp',
 ]
 
 TEST_HARNESS_FILES.gtest += [
     'corrupt.jpg',
     'first-frame-green.gif',
     'first-frame-green.png',
     'first-frame-padding.gif',
     'green.bmp',
index 988a6201822f036d1d93c7916f3f3c4f9d7aaaf3..8eb80c7db2ee450eaf16449d307986f7b1736c46
GIT binary patch
literal 92
zc${NkU<5%%1|X@xV8_6~AO^&p0e<ehTvA*>Ca<T5OArG{8i+aAfTX<WkxsCLi(?4K
g%;Xj8!~gR$u$*R6{l?#61e9R#boFyt=akR{0MsB4egFUf
index 3968a13f7222d4eadf4784d71d5703c7f8feb88c..edbaf544349242589fc73db180b49af95c00f229
GIT binary patch
literal 5934
zc${@u2{>EX+mCiiXS7vSw51fIo1*sGJF1jeYOmVbT2gzFNTY~SLoL->s@9B_+LMYk
zLR1w+(bB|{#7+=NER}?aeER>s=X+-6Kl7aD+<V`1?(^K=`JLbUp7*@~0N-Adkpb*o
z&j5~!0{~(G0O09OE7QZjocd+&@vzwqW7{9IKl(xby_;)jna>X^%+}NhfE<uq+B5j?
z0ZokoydUk4*3yi<k)I#ja1H|ij(q&l`LYy_NbC&?fXytg3lIg49n%ppDx<CKeeIQ*
z@o)AI$7uz9p^}(%W4Y(f#S1(h#3b`$it}-N>xnqsLq#q6mW7^#Rz4BbPp|Dog$661
zCH?H=(9)gn;jmWuHmy7DgiX#clZQ-trS2iTW~aw-m!UAlM6>Vn5Q&LqyuDYSf+iJa
zq73TUCxx9>P#n|c8hC%BUbOMYEI*G0d*ha;`aB0w!;UJK6mF}mA9}SSNvoco$d(_6
z+keE4Zb;kM3`3kEh4h(elA+|~OS@&j6mHA8jT(jMSNcxWU1x3bM&ePq{qIW!_5)(}
zxH<#?+}T_2egFWGUyDqK-~7<;qyept+_`Q~YjmoB#Y<v8d{zu=ZcwG}>hDIbq$q?V
zl-q*vkX+`$rF2_Q(|8g->2jqNw}rUzEj1#jWF(MTiEdziXG9Hy92mYDO!fI;5I0}T
zV<8s1Kg^*g`KGi$cyxOw$%tb(3GC)`Rbh4JaYUSa;VkS^M3e0GL$L+{Bi-=f8n;I@
z>F+@H8oVtSL%+nu;hlfc566qXsL^00CHg<X2y7Ru4766vkiVY}C6}<|ZM$YxMFBCB
z|GS(M0Koen`wajXD1f-O{pGWOLbz(MGQ)%H%i#q3J(|V`1ncMHZ##g3mL|-aAG_$P
z(b_n;z(cWlkAgH5>#?G}ESsR<-*ehuHRsF84n#`dTV1!2hm9z%cXjIL=7m)*kO(?u
zE&+YLr1h?mEMf-rSv1jxbAKc+7iw8WGW=%hB}L?qMF9Xi{C~!C830&y)}9S4)XOz!
zCvICq$#U>6Rz;yT8QRSq%SU`)c7%I2c&b%s!65m&Pek6)BbW>N)@$1NFKjfR8xB%7
zGUKz)t$NjzH$MQos=PL2!EQ@j;7gH?#;K9kp4IDH?D&9yh(P}0s#Le#l7(GjCfr7p
zqg*XUP>kp&J##L9@F;g%dRaqus4khZ^$XyAFyQZ6$g0Iv=FSLi4oT1mfx{10wlV#@
z4|wfctEcLh&#*y^%D5?lTPizISVd6;5duBTZ|B?Yr=m`9qtw|yGBKGeH+<vGxiAZu
z(Sb-_-LZoWw0Vs42EVVupCvUxhi3_vll_&bm(uX*sw8&Oww!M6BdYsoeZcRX@|%C&
zzB_=?eKDxM3ngBjDhKTKQKwQfA>@^x7qhy9s$7E2{K*P!;=x!S@u3a{7Sp?A=Ao{4
zBa=`dxVysLNI%Q9tK^K6hJj883}z|2g7C8!>66FL2~B9PATK?H^u{_FR~)w(zGk8^
zLIx+47fNidWluCNMyb=GT4f(mwEK}aPj6(BlM)}E*uN_42r*7XPdS60A35~bz1%a{
zN%vu^>vYwudoZZ23-GLq9s=meNX`T0)>Nu7acwQHiw&{6?>QG=#76lq!>E@^w&IEk
zP_SORlHO=6cq&CMGg;LL%QtwG|BnhDAp!FQT`ePoy9e+gdjy5ngT1``eUkcGrHfb7
zKsL~QWjcdOB{mGva@2vR^c!J6drQV31uox{5_o`56wdS9_(vyD0k!l?Y+SI?3x)=L
zAZ6!;mtaOBH(yuJGTd)9!dn`;!h1W$ve3KxZAZ2A{1A<6BHDw=gC!FMC^s{(p>h6I
zlT32~1EW$wDtPMr>9_|A@;e9x%)HiixEe$MCYJxKf-KM*KN4%qYO$zOR;gZSP!24_
zC->iKE>2CK^WArCNIwr-f=M-orliG4ZVJ(441P*8nH&I|5jyhUgy!aNMorub2@c#9
z)=|dgF(yMq98HyUSR?vk7Y}Eexotvt3|P5?jQ81#sY)@_wl!zRta~ddpAtsV!bPFX
z4Pl)lQ{LMHg=<QvnK5Cf9Q0ICU#tXdm3EpZH`i7d+!%~iIRt6cdx+(k+)2oY8>BZg
zz3U(;kDZ}m0c5C>Ub5a5z^cUmJH#HQR}Z?pdm|=Y;~Rn^8#Ef9S%gQhd`*|Pt#Vcx
zW=%I~%#ufhfZaxOo_brILfeBQV}%U??A_6&7C=e2L3i3#?TESGQmXhBT8B;+OqZ<X
zsAuc5LJsruLMuNq8?uhRXc@FX8;ZB<OZM-m4IQ%IiL)BBw3E^`UZR1_>B)<ngESlX
z=lMAW*%^mZX~L6lX$%#k?lb>JpVDoG68}XjvxRz`gz8w4ogLN_du8AJ#TH#$(JAGs
zTq=QF3RCX~jU647Myo@620vVDFyuHceW*}}MrYC|T%4M~Q<s<CZvii@<glar4zSL>
zz3n&X|M&{5ThzawM(C3^sIAv!N97*CsW+m?h9A*V_r~Pv5WGWfSe^<3`*i<7yUHo&
z(y8a3=hmnG6ksj`0{DJl#hn4|9F5PfET;O%Q|(Lpv3is!M6F$O>L`7iMOaL@F7U?Q
zdG(#|XBss0jl?_gH+M*pd*FOMxWnRxa}Hk+8Om=NcKMy%;Gn@bvW2O_r(vrVYDzV9
zwTXJW@!mKsS;NB6VUd1j{PyStWpl-EB1ad__0yE~l|95F9nawctfIpAo%x2#BHDrO
zIU?colQHIQ7p7H1_Gz~~w#oi{16-QEh<mm~LWp(>VS=>7b@s>Xsp8IG0~BD&Ka`Ni
zbizYCV*Q>{Dsx^;_AfCA+GR8ds>&Zep>4~8Lza&hTS2VOeT`ZitT=XTVnAGavRK)`
zJbQr<WoT3?V)z!vPD%u3a*?0Tiwm5gyq%RB$gC$Y2m);`f*b=hO^BCJ2IB6zOh8>s
zure8L+HND0!2u0HECV-ep-VZlM1QGX?(ahIcvY*!6+n_hpjkw07ZScSN+?zRjN&?N
zMD?iqo~x~f_bagiawyf!@5b?5CKyvLT3yrk5-9H7K5O~efBLvC^wt7h{@ux~K!Mlh
zJ)2ZVkWHzGZgo5ua>O;~Ck(}VrDJEhQjBz0c3zYEP2Bt7&-7EmBQ34#JD>Uxv@PHw
zdP;fWSUGb;?#NwDcCv-{!11V&gAST47Wy%u!v}^Q^ZkX$ABIO-Z8Xhr=eW=W%7%Ms
z$L%#ONRvYMLWjXe57Gu2V*6nTFRTY?l_u|nPN?`L@)LS$u(uhBM)^BYha4UQ&B4+-
zq8JUv*NEK|kJOHL&%umdi^1H<>}Qp@3#VagK@BKL;sK}B-`8%bTqSxj-n}gEN9Zu3
zKDDsb@-3L^CPfD0^MWuHs=@r|FnVg9pnrRP>SV;=znqe4(Vw~IguHK=$v9mK?s`Wy
zqvg%TE}7s$C?Wb(_(??AZN%m^i_0Cocc7%0tuJ8;6n?$_(NgC|qPR*!NZQN9w6ZIf
z&JU(D!spXB(p=l~k4&u3YP|_TLxvqc_&(8N7nuC<ZIpgj?(6B<j)R{I-G9Dq6L^ub
z`+bmN`S|Mm4B&`J+?_7RdVaCk%*6eF#&+UAM&$JYHhp)_u;}Pc@G=X^N{s-dBBN5{
z<4-Y%&pbIq8#onWc5+3{T+d@o6%uMt`u$>;!H28`LI?o0y@7P^rxZ)L(1^)fRkRe^
z@MCYutCI+F%Nw2vWVJcO4H(7f`>ijQ&q!y7(Ma}98&&@BMG_l|3x;frYW2naaQeS;
zcBgAgx~#RjTmsRT_tt@>NVWc6O+kiU83sClG&LjD-FrWvcJ7+r9HF<==IHv(wW7KT
zvo{2(7{uCe!LN43?rGIz^X&EP&0m`l5{+Z+NtYrPPLCMM*3~slin%po?X9A(7q4!l
zUAcGg0yV|1bpJ@QRkG!tLycG3i0R47;><d-s?k%j{6(@uoqWZwT$aF7Np{G*f6maH
zp9_p^q5_xwk_?-`Ja0jcj_*ufcz;hEwx(}|I{JBm)AyFq=yXAR<}y;<ZL{%-ML3rZ
z?=2$wk5VYR4yXuMAGbYW=3xsP&v&{DqA4FxE3*L7Z%PiPxQMZ?!#M$_NiNGD3@ReY
zysx(#H37bxGx)oDrPOG5BR8JGb;+2FikGl8QQ_AN)%vZ<fy97MUX?ZeMN(HQF#Qm|
zLZ)k{<Kl@YRl;%TzZ3mK06YO2f6Agf0;GmAcw|S6B3M;w2W!308t_plxlN<@>XnvY
z5SyNmaew>;PQwL^t&}UaBQqsj$qZasBhmYD?Hx_N+p*+ZT9YwHR1Rr7|HZmoD{29I
z1t_R>$>pmi%M1(x&%}@_a);)D-evetN@d2<J`R0nI`0A%;-X9z-dZ<DOC0rPe;&1Z
zts9gspvC|C<EX>ebRP1QG&i}XT;R`)?|(1$pI9mw+LedYc2{AEI9_*EQT7W$#~;R3
zL4P(Zk-&m;;>1LSM;gfZ=xNtgGb#xp|Ft0G>9=o{&(hE{wYb6a^ZYIDM%yzRGKBfA
z=ub73Qp2p<FKk}_3d0NA2w^DWS*7#s3at5d)6*e6O-Ds}M?<z<&?v!okzYZIzQ1^a
zyHK13vU_8y@ChPK^V!v|6IGw?U@2PNK}g~tS<fXXV1z|)PC)W*BHb8*9((}y;s0pm
z|A>zawc-FH6oNG!jiax>S;LdFgd{_1{US{7VTYk%D}KHAohH;7Sr{#fTgb3(--J!|
z{T0~9oM0nGT%pxLd{BW=xpI^JqGl-LjZIkOr+$y;XB)LS*T&swyWo<>dh6RjaNF-l
z@m0$eOm@#p&u*<+Wz^f?bJ92uF+GX(sX?4{gqyjTTNSmubISasR&UWp+8IFf-=hM%
zIDeC#^?;HJ=LQcnlp?E$m|TO6oR3gNeX3Zi!q<Y^vBcgx{^DMQBA`9!+H2h-c17Xp
zbM5`jRq9(8M%)g=7^<9fmDguE+m}>=35tUq=Jm5M?6(8&#u^+nkGyw(OKNTT+OiYm
zt|aOl9MPK{ero^fJ*C(lORe>(^5Tj>Uz#|qr$y=gW!gRKK<5V<OugM{Owc*Y-OC)b
zRwJm3+tCsXm9m$NnFzz}`&$mSr<V7i&fYjDjhUY$$=_Jx3~cc}l)uM|jEU?X<b$B1
zgvgTd(rRE>_C<I>cS2>bLAbRP2{WFr$G*Fu+0Zo*uMb5w<u;|N$*Ssl`WwPK<&>Po
zc2vFF0`skF10E*vix_7jZ(IX*5Pw=P+h*0@0r-UQXLU{LR~eGre|nKuSTYN!d(tT3
zBJGHW64s`N#lnc10F&GnvEm;5M)tGT=TuFGZhv#%iuQl$<A=U$_i}~%5S<b2!EzqA
z$y6_x6oc>4a17F$rJ%0N*xG(GdF=wUq{O1Qopn@9AP}6b1TQ!%ikWdR&&W<cn<l3;
z{km>w&bzqhjf`bsX(Y%&c}N^ylCCc;%|TAEo->N;^qO#9OTWZ|V_~NTM-WH(dn_@F
zE+rLtQOagET3+aW5?Y^CpzlK7$Sdvviqp=kgpWben9de6|4?|1rBBg&;mX|h1(IYa
zT?+_y0xiR4Lq63<O-36qBy?+Kyb;{M3{NdiFj+-0DtZC>wPtuRNSxxx9r+MqR=Z>r
zt2<CGaak@^^1Dn}6C&DWUWkWUB2`#8uj+gzNvSk&r-fza+7U_@MB>g2`_-#sq=|EL
z6hk@7!neB}iP;z_qw8K3xP&aF5fCj?$^4%cF<%A6yJ}ioHEf+ZU**UP8@h5V)wmJt
zlygW?VT{znFFAZ{xQ*hNHx?u6>;3MHOa_-Eo|P0Z;iNI~G>c95xf!mej&G6h%!^v;
zi8DQMl4j!kKw{_l^!KTHWb;6>^>nf|H5qD)=V)&^=xA}15(bjfvbS(}MtI1mS4u1@
z4KXB*^gDdtW5@RN6UmuZx%oRU_1M=KFnXGs&x-oyKK8$)5Al1{w5YjzzjV|ZZl2|-
z<JhXlNUGJFDS?fH9<)cEpnS9iIJfQ=HCBE3eY6KtG@|3>wKg-+Efi)j-1|}$wZ)fg
zmBYUED(}2tA%i1+MO^9KPM#{H-A>>}@po<M7t6A)rMQFZe@O4fQ|Q2u`Yj>3>8NE0
zv4eeIvg>!g^IFUh6yN<DXi$MWS|8t>%J-boezG(!;%`{@j2PvonF1acw*zgXAxgCw
zi(<%Z@a|wjkMzjyYN&jf!+5|+*lOfqTJnf|PrWf~i6y@<|0S#M0`*H6Fq@w%JKtV{
zRd`kw>fOFK8@ck_cR=)-d4|W<m-Xoxy{4LuPjQBJgQ<e(@cDMyd~mMxg|D>vLN$rd
zFB;75@ZW9})byDJ2oWb<WLT*&j+<!szdG^cnI5}|RZuqxg3>vIKA!Z*1Ar;5zZ~K{
z+I`Jyhb)(70<Dd7guHJ1ZumV^sX7MnTv;|p=A~9vH^gJS$Dy{V5C=+>WAGq}0~W*v
zk_Dj_`tUB7q-Uui=bdtifmA(3f*F{TNlGy5;i7!e>%$&ZQ3J?JP0Y^98aeatQ8`y)
z(Ary*3ndsi*}V$)6l~RkYXx>*ebhaXeb(C9P<BH-bWOP*(w3wf{z-M!s_*f?{S`V^
zJujIrh<iBJ`l13^_-j}oP!;sCp{@zxV%|du@OgQ9ugZB)&aCQIQt%F(YtK7I%qrLz
zdVW?b3rZffrMK=luTy?2+0T>Q4krrIzfWZ`HobGjTuQ>$6$TCxC(ifNA_r8DOv(S=
zlWZ-_yBtbqwf34hVA%=cVWWHAM-*2S+P!y#ic^P?uY?m4Wg^#nfiK%~q3eDr3C|L~
z&ab@?5cuyj({g|<kM2-tQO4CxHdZ}pyqFgC3DnoTWuIFdN?c5z6E=0d7nCKZ<g(<@
z$tM{)*te2wmz<??(8pT2)Ohbi&h%~v8c;_{CL6Vr?=ngGC4N1xD{<t}?Mn@F?Uk~&
zZmaROeS5CZfB1c^R7{3`_~sD#agMZ!vw!Q(-Q<1<2Y4k{eEPV2Psb?(w@2{l^ad|9
zURS%Z8<|9K-WZyfm3rH<W&<j(P|^Iyuq+bhZYAZ4N2O(AK4*=yDt5A+QqIqZCWW<A
zszTpgpTtbD0#mDJ0pJ|U9=ws?Mp>PY;4C_6*)wnhBCWG419nYJ{_T0l>5z|E%T>$l
zgQ9Uar8#E}-vCQFI<VFBO@I<e&Q<cNpJ4i1vKZl&KK6`2;Cx5z+KRqswt|VRAT4L;
zejkJgd5X&$<UrC|AsDEwF!GC;X{ejntMbJH4LKqA$NyM-uD5;Mv1!hqd||ye1u(mA
KWsEes6Z=1B(Q?E9
--- a/js/public/ProfilingStack.h
+++ b/js/public/ProfilingStack.h
@@ -47,17 +47,18 @@ class ProfileEntry
     // General purpose storage describing this frame.
     uint32_t volatile flags_;
 
   public:
     // These traits are bit masks. Make sure they're powers of 2.
     enum Flags {
         // Indicate whether a profile entry represents a CPP frame. If not set,
         // a JS frame is assumed by default. You're not allowed to publicly
-        // change the frame type. Instead, call `setJsFrame` or `setCppFrame`.
+        // change the frame type. Instead, initialize the ProfileEntry as either
+        // a JS or CPP frame with `initJsFrame` or `initCppFrame` respectively.
         IS_CPP_ENTRY = 0x01,
 
         // Indicate that copying the frame label is not necessary when taking a
         // sample of the pseudostack.
         FRAME_LABEL_COPY = 0x02,
 
         // This ProfileEntry is a dummy entry indicating the start of a run
         // of JS pseudostack entries.
@@ -101,22 +102,22 @@ class ProfileEntry
     bool isCpp() const volatile { return hasFlag(IS_CPP_ENTRY); }
     bool isJs() const volatile { return !isCpp(); }
 
     bool isCopyLabel() const volatile { return hasFlag(FRAME_LABEL_COPY); }
 
     void setLabel(const char* aString) volatile { string = aString; }
     const char* label() const volatile { return string; }
 
-    void setJsFrame(JSScript* aScript, jsbytecode* aPc) volatile {
+    void initJsFrame(JSScript* aScript, jsbytecode* aPc) volatile {
         flags_ = 0;
         spOrScript = aScript;
         setPC(aPc);
     }
-    void setCppFrame(void* aSp, uint32_t aLine) volatile {
+    void initCppFrame(void* aSp, uint32_t aLine) volatile {
         flags_ = IS_CPP_ENTRY;
         spOrScript = aSp;
         lineOrPc = static_cast<int32_t>(aLine);
     }
 
     void setFlag(uint32_t flag) volatile {
         MOZ_ASSERT(flag != IS_CPP_ENTRY);
         flags_ |= flag;
@@ -132,16 +133,18 @@ class ProfileEntry
     uint32_t flags() const volatile {
         return flags_;
     }
 
     uint32_t category() const volatile {
         return flags_ & CATEGORY_MASK;
     }
     void setCategory(Category c) volatile {
+        MOZ_ASSERT(c >= Category::FIRST);
+        MOZ_ASSERT(c <= Category::LAST);
         flags_ &= ~CATEGORY_MASK;
         setFlag(static_cast<uint32_t>(c));
     }
 
     void setOSR() volatile {
         MOZ_ASSERT(isJs());
         setFlag(OSR);
     }
--- a/js/src/gc/MemoryProfiler.cpp
+++ b/js/src/gc/MemoryProfiler.cpp
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jsfriendapi.h"
 
 #include "vm/Runtime.h"
 
 using js::gc::Cell;
 
-mozilla::Atomic<int> MemProfiler::sActiveProfilerCount;
+mozilla::Atomic<uint32_t, mozilla::Relaxed> MemProfiler::sActiveProfilerCount;
 NativeProfiler* MemProfiler::sNativeProfiler;
 
 GCHeapProfiler*
 MemProfiler::GetGCHeapProfiler(void* addr)
 {
     JSRuntime* runtime = reinterpret_cast<Cell*>(addr)->runtimeFromAnyThread();
     return runtime->gc.mMemProfiler.mGCHeapProfiler;
 }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1206265.js
@@ -0,0 +1,8 @@
+x = [
+    objectEmulatingUndefined(),
+    function() {}
+];
+x.forEach(function() {});
+this.x.sort(function() {});
+assertEq(x[0] instanceof Function, false);
+assertEq(x[1] instanceof Function, true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1201850.js
@@ -0,0 +1,10 @@
+// |jit-test| error: too much recursion
+var tokenCodes = {
+    get finally() {
+        if (tokenCodes[arr[i]] !== i) {}
+    }
+};
+var arr = ['finally'];
+for (var i = 0; i < arr.length; i++) {
+    if (tokenCodes[arr[i]] !== i) {}
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1203791.js
@@ -0,0 +1,26 @@
+function n(x) {
+    try {
+	Object.create(x);
+    } catch(e){};
+}
+function m() {
+    n();
+}
+var g = newGlobal();
+g.parent = this;
+g.eval(`
+    var dbg = new Debugger();
+    var parentw = dbg.addDebuggee(parent);
+    var pw = parentw.makeDebuggeeValue(parent.p);
+    var scriptw = pw.script;
+`);
+g.dbg.onIonCompilation = function(graph) {
+    if (graph.scripts[0] != g.scriptw)
+        return;
+    m();
+};
+function p() {
+    for (var res = false; !res; res = inIon()) {}
+}
+p();
+(function() {})();
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -3512,17 +3512,17 @@ SetElemAddHasSameShapes(ICSetElem_DenseO
         return false;
 
     JSObject* proto = obj->getProto();
     for (size_t i = 0; i < stub->protoChainDepth(); i++) {
         if (!proto->isNative())
             return false;
         if (proto->as<NativeObject>().lastProperty() != nstub->shape(i + 1))
             return false;
-        proto = obj->getProto();
+        proto = proto->getProto();
         if (!proto) {
             if (i != stub->protoChainDepth() - 1)
                 return false;
             break;
         }
     }
 
     return true;
@@ -3530,32 +3530,63 @@ SetElemAddHasSameShapes(ICSetElem_DenseO
 
 static bool
 DenseOrUnboxedArraySetElemStubExists(JSContext* cx, ICStub::Kind kind,
                                      ICSetElem_Fallback* stub, HandleObject obj)
 {
     MOZ_ASSERT(kind == ICStub::SetElem_DenseOrUnboxedArray ||
                kind == ICStub::SetElem_DenseOrUnboxedArrayAdd);
 
+    if (obj->isSingleton())
+        return false;
+
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
         if (kind == ICStub::SetElem_DenseOrUnboxedArray && iter->isSetElem_DenseOrUnboxedArray()) {
             ICSetElem_DenseOrUnboxedArray* nstub = iter->toSetElem_DenseOrUnboxedArray();
-            if (obj->maybeShape() == nstub->shape() && obj->getGroup(cx) == nstub->group())
+            if (obj->maybeShape() == nstub->shape() && obj->group() == nstub->group())
                 return true;
         }
 
         if (kind == ICStub::SetElem_DenseOrUnboxedArrayAdd && iter->isSetElem_DenseOrUnboxedArrayAdd()) {
             ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd();
-            if (obj->getGroup(cx) == nstub->group() && SetElemAddHasSameShapes(nstub, obj))
+            if (obj->group() == nstub->group() && SetElemAddHasSameShapes(nstub, obj))
                 return true;
         }
     }
     return false;
 }
 
+static void
+RemoveMatchingDenseOrUnboxedArraySetElemAddStub(JSContext* cx,
+                                                ICSetElem_Fallback* stub, HandleObject obj)
+{
+    if (obj->isSingleton())
+        return;
+
+    // Before attaching a new stub to add elements to a dense or unboxed array,
+    // remove any other stub with the same group/shape but different prototype
+    // shapes.
+    for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) {
+        if (!iter->isSetElem_DenseOrUnboxedArrayAdd())
+            continue;
+
+        ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd();
+        if (obj->group() != nstub->group())
+            continue;
+
+        static const size_t MAX_DEPTH = ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH;
+        ICSetElem_DenseOrUnboxedArrayAddImpl<MAX_DEPTH>* nostub = nstub->toImplUnchecked<MAX_DEPTH>();
+
+        if (obj->maybeShape() == nostub->shape(0)) {
+            iter.unlink(cx);
+            return;
+        }
+    }
+}
+
 static bool
 TypedArraySetElemStubExists(ICSetElem_Fallback* stub, HandleObject obj, bool expectOOB)
 {
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
         if (!iter->isSetElem_TypedArray())
             continue;
         ICSetElem_TypedArray* taStub = iter->toSetElem_TypedArray();
         if (obj->maybeShape() == taStub->shape() && taStub->expectOutOfBounds() == expectOOB)
@@ -3727,16 +3758,18 @@ DoSetElemFallback(JSContext* cx, Baselin
             RootedObjectGroup group(cx, obj->getGroup(cx));
             if (!group)
                 return false;
 
             if (addingCase &&
                 !DenseOrUnboxedArraySetElemStubExists(cx, ICStub::SetElem_DenseOrUnboxedArrayAdd,
                                                       stub, obj))
             {
+                RemoveMatchingDenseOrUnboxedArraySetElemAddStub(cx, stub, obj);
+
                 JitSpew(JitSpew_BaselineIC,
                         "  Generating SetElem_DenseOrUnboxedArrayAdd stub "
                         "(shape=%p, group=%p, protoDepth=%u)",
                         shape.get(), group.get(), protoDepth);
                 ICSetElemDenseOrUnboxedArrayAddCompiler compiler(cx, obj, protoDepth);
                 ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(script));
                 if (!newStub)
                     return false;
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -404,22 +404,24 @@ JitCompartment::ensureIonStubsExist(JSCo
     }
 
     return true;
 }
 
 struct OnIonCompilationInfo {
     size_t numBlocks;
     size_t scriptIndex;
+    LifoAlloc alloc;
     LSprinter graph;
 
-    explicit OnIonCompilationInfo(LifoAlloc* alloc)
+    OnIonCompilationInfo()
       : numBlocks(0),
         scriptIndex(0),
-        graph(alloc)
+        alloc(4096),
+        graph(&alloc)
     { }
 
     bool filled() const {
         return numBlocks != 0;
     }
 };
 
 typedef Vector<OnIonCompilationInfo> OnIonCompilationVector;
@@ -589,36 +591,36 @@ jit::LazyLink(JSContext* cx, HandleScrip
         calleeScript->baselineScript()->removePendingIonBuilder(calleeScript);
 
         // Remove from pending.
         builder->removeFrom(HelperThreadState().ionLazyLinkList());
     }
 
     // See PrepareForDebuggerOnIonCompilationHook
     Rooted<ScriptVector> debugScripts(cx, ScriptVector(cx));
-    OnIonCompilationInfo info(builder->alloc().lifoAlloc());
+    OnIonCompilationInfo info;
 
     {
         AutoEnterAnalysis enterTypes(cx);
         if (!LinkBackgroundCodeGen(cx, builder, &debugScripts, &info)) {
             // Silently ignore OOM during code generation. The assembly code
             // doesn't has code to handle it after linking happened. So it's
             // not OK to throw a catchable exception from there.
             cx->clearPendingException();
         }
     }
 
-    if (info.filled())
-        Debugger::onIonCompilation(cx, debugScripts, info.graph);
-
     {
         AutoLockHelperThreadState lock;
         FinishOffThreadBuilder(cx, builder);
     }
 
+    if (info.filled())
+        Debugger::onIonCompilation(cx, debugScripts, info.graph);
+
     MOZ_ASSERT(calleeScript->hasBaselineScript());
     MOZ_ASSERT(calleeScript->baselineOrIonRawPointer());
 }
 
 uint8_t*
 jit::LazyLinkTopActivation(JSContext* cx)
 {
     JitActivationIterator iter(cx->runtime());
@@ -2222,17 +2224,17 @@ IonCompile(JSContext* cx, JSScript* scri
         // processed in the finishedOffThreadCompilations list.
         autoDelete.forget();
 
         return AbortReason_NoAbort;
     }
 
     // See PrepareForDebuggerOnIonCompilationHook
     Rooted<ScriptVector> debugScripts(cx, ScriptVector(cx));
-    OnIonCompilationInfo debugInfo(alloc);
+    OnIonCompilationInfo debugInfo;
 
     ScopedJSDeletePtr<CodeGenerator> codegen;
     {
         AutoEnterAnalysis enter(cx);
         codegen = CompileBackEnd(builder);
         if (!codegen) {
             JitSpew(JitSpew_IonAbort, "Failed during back-end compilation.");
             return AbortReason_Disable;
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -7582,31 +7582,31 @@ ClassHasEffectlessLookup(const Class* cl
            (clasp == &UnboxedArrayObject::class_) ||
            IsTypedObjectClass(clasp) ||
            (clasp->isNative() && !clasp->ops.lookupProperty);
 }
 
 // Return whether an object might have a property for name which is not
 // accounted for by type information.
 static bool
-ObjectHasExtraOwnProperty(CompileCompartment* comp, TypeSet::ObjectKey* object, PropertyName* name)
+ObjectHasExtraOwnProperty(CompileCompartment* comp, TypeSet::ObjectKey* object, jsid id)
 {
     // Some typed object properties are not reflected in type information.
     if (object->isGroup() && object->group()->maybeTypeDescr())
-        return object->group()->typeDescr().hasProperty(comp->runtime()->names(), NameToId(name));
+        return object->group()->typeDescr().hasProperty(comp->runtime()->names(), id);
 
     const Class* clasp = object->clasp();
 
     // Array |length| properties are not reflected in type information.
     if (clasp == &ArrayObject::class_)
-        return name == comp->runtime()->names().length;
+        return JSID_IS_ATOM(id, comp->runtime()->names().length);
 
     // Resolve hooks can install new properties on objects on demand.
     JSObject* singleton = object->isSingleton() ? object->singleton() : nullptr;
-    return ClassMayResolveId(comp->runtime()->names(), clasp, NameToId(name), singleton);
+    return ClassMayResolveId(comp->runtime()->names(), clasp, id, singleton);
 }
 
 void
 IonBuilder::insertRecompileCheck()
 {
     // No need for recompile checks if this is the highest optimization level.
     OptimizationLevel curLevel = optimizationInfo().level();
     if (js_IonOptimizations.isLastLevel(curLevel))
@@ -7624,17 +7624,17 @@ IonBuilder::insertRecompileCheck()
     const OptimizationInfo* info = js_IonOptimizations.get(nextLevel);
     uint32_t warmUpThreshold = info->compilerWarmUpThreshold(topBuilder->script());
     MRecompileCheck* check = MRecompileCheck::New(alloc(), topBuilder->script(), warmUpThreshold,
                                 MRecompileCheck::RecompileCheck_OptimizationLevel);
     current->add(check);
 }
 
 JSObject*
-IonBuilder::testSingletonProperty(JSObject* obj, PropertyName* name)
+IonBuilder::testSingletonProperty(JSObject* obj, jsid id)
 {
     // We would like to completely no-op property/global accesses which can
     // produce only a particular JSObject. When indicating the access result is
     // definitely an object, type inference does not account for the
     // possibility that the property is entirely missing from the input object
     // and its prototypes (if this happens, a semantic trigger would be hit and
     // the pushed types updated, even if there is no type barrier).
     //
@@ -7646,50 +7646,50 @@ IonBuilder::testSingletonProperty(JSObje
     // property will change and trigger invalidation.
 
     while (obj) {
         if (!ClassHasEffectlessLookup(obj->getClass()))
             return nullptr;
 
         TypeSet::ObjectKey* objKey = TypeSet::ObjectKey::get(obj);
         if (analysisContext)
-            objKey->ensureTrackedProperty(analysisContext, NameToId(name));
+            objKey->ensureTrackedProperty(analysisContext, id);
 
         if (objKey->unknownProperties())
             return nullptr;
 
-        HeapTypeSetKey property = objKey->property(NameToId(name));
+        HeapTypeSetKey property = objKey->property(id);
         if (property.isOwnProperty(constraints())) {
             if (obj->isSingleton())
                 return property.singleton(constraints());
             return nullptr;
         }
 
-        if (ObjectHasExtraOwnProperty(compartment, objKey, name))
+        if (ObjectHasExtraOwnProperty(compartment, objKey, id))
             return nullptr;
 
         obj = checkNurseryObject(obj->getProto());
     }
 
     return nullptr;
 }
 
 JSObject*
-IonBuilder::testSingletonPropertyTypes(MDefinition* obj, PropertyName* name)
+IonBuilder::testSingletonPropertyTypes(MDefinition* obj, jsid id)
 {
     // As for TestSingletonProperty, but the input is any value in a type set
     // rather than a specific object.
 
     TemporaryTypeSet* types = obj->resultTypeSet();
     if (types && types->unknownObject())
         return nullptr;
 
     JSObject* objectSingleton = types ? types->maybeSingleton() : nullptr;
     if (objectSingleton)
-        return testSingletonProperty(objectSingleton, name);
+        return testSingletonProperty(objectSingleton, id);
 
     MIRType objType = obj->type();
     if (objType == MIRType_Value && types)
         objType = types->getKnownMIRType();
 
     JSProtoKey key;
     switch (objType) {
       case MIRType_String:
@@ -7717,30 +7717,30 @@ IonBuilder::testSingletonPropertyTypes(M
         // find a prototype common to all the objects; if that prototype
         // has the singleton property, the access will not be on a missing property.
         JSObject* singleton = nullptr;
         for (unsigned i = 0; i < types->getObjectCount(); i++) {
             TypeSet::ObjectKey* key = types->getObject(i);
             if (!key)
                 continue;
             if (analysisContext)
-                key->ensureTrackedProperty(analysisContext, NameToId(name));
+                key->ensureTrackedProperty(analysisContext, id);
 
             const Class* clasp = key->clasp();
-            if (!ClassHasEffectlessLookup(clasp) || ObjectHasExtraOwnProperty(compartment, key, name))
+            if (!ClassHasEffectlessLookup(clasp) || ObjectHasExtraOwnProperty(compartment, key, id))
                 return nullptr;
             if (key->unknownProperties())
                 return nullptr;
-            HeapTypeSetKey property = key->property(NameToId(name));
+            HeapTypeSetKey property = key->property(id);
             if (property.isOwnProperty(constraints()))
                 return nullptr;
 
             if (JSObject* proto = checkNurseryObject(key->proto().toObjectOrNull())) {
                 // Test this type.
-                JSObject* thisSingleton = testSingletonProperty(proto, name);
+                JSObject* thisSingleton = testSingletonProperty(proto, id);
                 if (!thisSingleton)
                     return nullptr;
                 if (singleton) {
                     if (thisSingleton != singleton)
                         return nullptr;
                 } else {
                     singleton = thisSingleton;
                 }
@@ -7752,17 +7752,17 @@ IonBuilder::testSingletonPropertyTypes(M
         return singleton;
       }
       default:
         return nullptr;
     }
 
     JSObject* proto = GetBuiltinPrototypePure(&script()->global(), key);
     if (proto)
-        return testSingletonProperty(proto, name);
+        return testSingletonProperty(proto, id);
 
     return nullptr;
 }
 
 bool
 IonBuilder::pushTypeBarrier(MDefinition* def, TemporaryTypeSet* observed, BarrierKind kind)
 {
     MOZ_ASSERT(def == current->peek(-1));
@@ -7981,17 +7981,17 @@ IonBuilder::getStaticName(JSObject* stat
                                                        name, types, /* updateObserved = */ true);
 
     JSObject* singleton = types->maybeSingleton();
 
     MIRType knownType = types->getKnownMIRType();
     if (barrier == BarrierKind::NoBarrier) {
         // Try to inline properties holding a known constant object.
         if (singleton) {
-            if (testSingletonProperty(staticObject, name) == singleton)
+            if (testSingletonProperty(staticObject, id) == singleton)
                 return pushConstant(ObjectValue(*singleton));
         }
 
         // Try to inline properties that have never been overwritten.
         Value constantValue;
         if (property.constant(constraints(), &constantValue))
             return pushConstant(constantValue);
 
@@ -8216,16 +8216,21 @@ IonBuilder::jsop_getelem()
 
     bool emitted = false;
 
     if (!forceInlineCaches()) {
         trackOptimizationAttempt(TrackedStrategy::GetElem_TypedObject);
         if (!getElemTryTypedObject(&emitted, obj, index) || emitted)
             return emitted;
 
+        // Note: no trackOptimizationAttempt call is needed, getElemTryGetProp
+        // will call it.
+        if (!getElemTryGetProp(&emitted, obj, index) || emitted)
+            return emitted;
+
         trackOptimizationAttempt(TrackedStrategy::GetElem_Dense);
         if (!getElemTryDense(&emitted, obj, index) || emitted)
             return emitted;
 
         trackOptimizationAttempt(TrackedStrategy::GetElem_TypedStatic);
         if (!getElemTryTypedStatic(&emitted, obj, index) || emitted)
             return emitted;
 
@@ -8454,16 +8459,22 @@ IonBuilder::pushScalarLoadFromTypedObjec
     // be valid and unbarriered. Also, need not set resultTypeSet,
     // because knownType is scalar and a resultTypeSet would provide
     // no useful additional info.
     load->setResultType(knownType);
 
     return true;
 }
 
+static bool
+BarrierMustTestTypeTag(BarrierKind kind)
+{
+    return kind == BarrierKind::TypeSet || kind == BarrierKind::TypeTagOnly;
+}
+
 bool
 IonBuilder::pushReferenceLoadFromTypedObject(MDefinition* typedObj,
                                              const LinearSum& byteOffset,
                                              ReferenceTypeDescr::Type type,
                                              PropertyName* name)
 {
     // Find location within the owner object.
     MDefinition* elements;
@@ -8489,17 +8500,17 @@ IonBuilder::pushReferenceLoadFromTypedOb
         break;
       }
       case ReferenceTypeDescr::TYPE_OBJECT: {
         // Make sure the barrier reflects the possibility of reading null. When
         // there is no other barrier needed we include the null bailout with
         // MLoadUnboxedObjectOrNull, which avoids the need to box the result
         // for a type barrier instruction.
         MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
-        if (barrier == BarrierKind::NoBarrier && !observedTypes->hasType(TypeSet::NullType()))
+        if (!observedTypes->hasType(TypeSet::NullType()) && !BarrierMustTestTypeTag(barrier))
             nullBehavior = MLoadUnboxedObjectOrNull::BailOnNull;
         else
             nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
         load = MLoadUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, nullBehavior,
                                              adjustment);
         break;
       }
       case ReferenceTypeDescr::TYPE_STRING: {
@@ -8611,16 +8622,46 @@ IonBuilder::pushDerivedTypedObject(bool*
     }
 
     trackOptimizationSuccess();
     *emitted = true;
     return true;
 }
 
 bool
+IonBuilder::getElemTryGetProp(bool* emitted, MDefinition* obj, MDefinition* index)
+{
+    // If index is a constant string or symbol, try to optimize this GETELEM
+    // as a GETPROP.
+
+    MOZ_ASSERT(*emitted == false);
+
+    if (!index->isConstantValue())
+        return true;
+
+    jsid id;
+    if (!ValueToIdPure(index->constantValue(), &id))
+        return true;
+
+    if (id != IdToTypeId(id))
+        return true;
+
+    TemporaryTypeSet* types = bytecodeTypes(pc);
+
+    trackOptimizationAttempt(TrackedStrategy::GetProp_Constant);
+    if (!getPropTryConstant(emitted, obj, id, types) || *emitted) {
+        if (*emitted)
+            index->setImplicitlyUsedUnchecked();
+        return *emitted;
+    }
+
+    return true;
+}
+
+bool
 IonBuilder::getElemTryDense(bool* emitted, MDefinition* obj, MDefinition* index)
 {
     MOZ_ASSERT(*emitted == false);
 
     JSValueType unboxedType = UnboxedArrayElementType(constraints(), obj, index);
     if (unboxedType == JSVAL_TYPE_MAGIC) {
         if (!ElementAccessIsDenseNative(constraints(), obj, index)) {
             trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
@@ -10184,17 +10225,17 @@ IonBuilder::objectsHaveCommonPrototype(T
         while (key) {
             if (key->unknownProperties())
                 return false;
 
             const Class* clasp = key->clasp();
             if (!ClassHasEffectlessLookup(clasp))
                 return false;
             JSObject* singleton = key->isSingleton() ? key->singleton() : nullptr;
-            if (ObjectHasExtraOwnProperty(compartment, key, name)) {
+            if (ObjectHasExtraOwnProperty(compartment, key, NameToId(name))) {
                 if (!singleton || !singleton->is<GlobalObject>())
                     return false;
                 *guardGlobal = true;
             }
 
             // Look for a getter/setter on the class itself which may need
             // to be called.
             if (isGetter && clasp->ops.getProperty)
@@ -10361,24 +10402,24 @@ IonBuilder::annotateGetPropertyCache(MDe
         if (!group)
             continue;
         TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(group);
         if (key->unknownProperties() || !key->proto().isObject())
             continue;
         JSObject* proto = checkNurseryObject(key->proto().toObject());
 
         const Class* clasp = key->clasp();
-        if (!ClassHasEffectlessLookup(clasp) || ObjectHasExtraOwnProperty(compartment, key, name))
+        if (!ClassHasEffectlessLookup(clasp) || ObjectHasExtraOwnProperty(compartment, key, NameToId(name)))
             continue;
 
         HeapTypeSetKey ownTypes = key->property(NameToId(name));
         if (ownTypes.isOwnProperty(constraints()))
             continue;
 
-        JSObject* singleton = testSingletonProperty(proto, name);
+        JSObject* singleton = testSingletonProperty(proto, NameToId(name));
         if (!singleton || !singleton->is<JSFunction>())
             continue;
 
         // Don't add cases corresponding to non-observed pushes
         if (!pushedTypes->hasType(TypeSet::ObjectType(singleton)))
             continue;
 
         if (!inlinePropTable->addEntry(alloc(), group, &singleton->as<JSFunction>()))
@@ -10619,17 +10660,17 @@ IonBuilder::jsop_getprop(PropertyName* n
         MCallGetProperty* call = MCallGetProperty::New(alloc(), obj, name, *pc == JSOP_CALLPROP);
         current->add(call);
 
         // During the definite properties analysis we can still try to bake in
         // constants read off the prototype chain, to allow inlining later on.
         // In this case we still need the getprop call so that the later
         // analysis knows when the |this| value has been read from.
         if (info().isAnalysis()) {
-            if (!getPropTryConstant(&emitted, obj, name, types) || emitted)
+            if (!getPropTryConstant(&emitted, obj, NameToId(name), types) || emitted)
                 return emitted;
         }
 
         current->push(call);
         return resumeAfter(call) && pushTypeBarrier(call, types, BarrierKind::TypeSet);
     }
 
     // Try to optimize accesses on outer window proxies, for example window.foo.
@@ -10638,17 +10679,17 @@ IonBuilder::jsop_getprop(PropertyName* n
     // even for an outer object.
     trackOptimizationAttempt(TrackedStrategy::GetProp_Innerize);
     if (!getPropTryInnerize(&emitted, obj, name, types) || emitted)
         return emitted;
 
     if (!forceInlineCaches()) {
         // Try to hardcode known constants.
         trackOptimizationAttempt(TrackedStrategy::GetProp_Constant);
-        if (!getPropTryConstant(&emitted, obj, name, types) || emitted)
+        if (!getPropTryConstant(&emitted, obj, NameToId(name), types) || emitted)
             return emitted;
 
         // Try to emit SIMD getter loads
         trackOptimizationAttempt(TrackedStrategy::GetProp_SimdGetter);
         if (!getPropTrySimdGetter(&emitted, obj, name) || emitted)
             return emitted;
 
         // Try to emit loads from known binary data blocks
@@ -10850,29 +10891,28 @@ IonBuilder::getPropTryArgumentsCallee(bo
     current->push(getCallee());
 
     trackOptimizationSuccess();
     *emitted = true;
     return true;
 }
 
 bool
-IonBuilder::getPropTryConstant(bool* emitted, MDefinition* obj, PropertyName* name,
-                               TemporaryTypeSet* types)
+IonBuilder::getPropTryConstant(bool* emitted, MDefinition* obj, jsid id, TemporaryTypeSet* types)
 {
     MOZ_ASSERT(*emitted == false);
 
     if (!types->mightBeMIRType(MIRType_Object)) {
         // If we have not observed an object result here, don't look for a
         // singleton constant.
         trackOptimizationOutcome(TrackedOutcome::NotObject);
         return true;
     }
 
-    JSObject* singleton = testSingletonPropertyTypes(obj, name);
+    JSObject* singleton = testSingletonPropertyTypes(obj, id);
     if (!singleton) {
         trackOptimizationOutcome(TrackedOutcome::NotSingleton);
         return true;
     }
 
     // Property access is a known constant -- safe to emit.
     obj->setImplicitlyUsedUnchecked();
 
@@ -11177,17 +11217,17 @@ IonBuilder::loadUnboxedValue(MDefinition
         break;
 
       case JSVAL_TYPE_STRING:
         load = MLoadUnboxedString::New(alloc(), elements, index, elementsOffset);
         break;
 
       case JSVAL_TYPE_OBJECT: {
         MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
-        if (types->hasType(TypeSet::NullType()) || barrier != BarrierKind::NoBarrier)
+        if (types->hasType(TypeSet::NullType()) || BarrierMustTestTypeTag(barrier))
             nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
         else
             nullBehavior = MLoadUnboxedObjectOrNull::NullNotPossible;
         load = MLoadUnboxedObjectOrNull::New(alloc(), elements, index, nullBehavior,
                                              elementsOffset);
         break;
       }
 
@@ -11574,20 +11614,17 @@ IonBuilder::getPropTryCache(bool* emitte
     if (inspector->hasSeenAccessedGetter(pc))
         barrier = BarrierKind::TypeSet;
 
     // Caches can read values from prototypes, so update the barrier to
     // reflect such possible values.
     if (barrier != BarrierKind::TypeSet) {
         BarrierKind protoBarrier =
             PropertyReadOnPrototypeNeedsTypeBarrier(this, obj, name, types);
-        if (protoBarrier != BarrierKind::NoBarrier) {
-            MOZ_ASSERT(barrier <= protoBarrier);
-            barrier = protoBarrier;
-        }
+        barrier = CombineBarrierKinds(barrier, protoBarrier);
     }
 
     MGetPropertyCache* load = MGetPropertyCache::New(alloc(), obj, name,
                                                      barrier == BarrierKind::TypeSet);
 
     // Try to mark the cache as idempotent.
     if (obj->type() == MIRType_Object && !invalidatedIdempotentCache()) {
         if (PropertyReadIsIdempotent(constraints(), obj, name))
@@ -11684,17 +11721,17 @@ IonBuilder::getPropTryInnerize(bool* emi
         // yet. In this case we'll fall back to getPropTryCache for now.
 
         // Note that it's important that we do this _before_ we'd try to
         // do the optimizations below on obj normally, since some of those
         // optimizations have fallback paths that are slower than the path
         // we'd produce here.
 
         trackOptimizationAttempt(TrackedStrategy::GetProp_Constant);
-        if (!getPropTryConstant(emitted, inner, name, types) || *emitted)
+        if (!getPropTryConstant(emitted, inner, NameToId(name), types) || *emitted)
             return *emitted;
 
         trackOptimizationAttempt(TrackedStrategy::GetProp_StaticName);
         if (!getStaticName(&script()->global(), name, emitted) || *emitted)
             return *emitted;
 
         trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
         if (!getPropTryCommonGetter(emitted, inner, name, types) || *emitted)
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -420,18 +420,17 @@ class IonBuilder
     MDefinition* maybeUnboxForPropertyAccess(MDefinition* def);
 
     // jsop_getprop() helpers.
     bool checkIsDefinitelyOptimizedArguments(MDefinition* obj, bool* isOptimizedArgs);
     bool getPropTryInferredConstant(bool* emitted, MDefinition* obj, PropertyName* name,
                                     TemporaryTypeSet* types);
     bool getPropTryArgumentsLength(bool* emitted, MDefinition* obj);
     bool getPropTryArgumentsCallee(bool* emitted, MDefinition* obj, PropertyName* name);
-    bool getPropTryConstant(bool* emitted, MDefinition* obj, PropertyName* name,
-                            TemporaryTypeSet* types);
+    bool getPropTryConstant(bool* emitted, MDefinition* obj, jsid id, TemporaryTypeSet* types);
     bool getPropTryDefiniteSlot(bool* emitted, MDefinition* obj, PropertyName* name,
                                 BarrierKind barrier, TemporaryTypeSet* types);
     bool getPropTryUnboxed(bool* emitted, MDefinition* obj, PropertyName* name,
                            BarrierKind barrier, TemporaryTypeSet* types);
     bool getPropTryCommonGetter(bool* emitted, MDefinition* obj, PropertyName* name,
                                 TemporaryTypeSet* types);
     bool getPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName* name,
                                 BarrierKind barrier, TemporaryTypeSet* types);
@@ -580,16 +579,17 @@ class IonBuilder
                                            TypedObjectPrediction elemTypeReprs,
                                            int32_t elemSize);
     bool initializeArrayElement(MDefinition* obj, size_t index, MDefinition* value,
                                 JSValueType unboxedType,
                                 bool addResumePointAndIncrementInitializedLength);
 
     // jsop_getelem() helpers.
     bool getElemTryDense(bool* emitted, MDefinition* obj, MDefinition* index);
+    bool getElemTryGetProp(bool* emitted, MDefinition* obj, MDefinition* index);
     bool getElemTryTypedStatic(bool* emitted, MDefinition* obj, MDefinition* index);
     bool getElemTryTypedArray(bool* emitted, MDefinition* obj, MDefinition* index);
     bool getElemTryTypedObject(bool* emitted, MDefinition* obj, MDefinition* index);
     bool getElemTryString(bool* emitted, MDefinition* obj, MDefinition* index);
     bool getElemTryArguments(bool* emitted, MDefinition* obj, MDefinition* index);
     bool getElemTryArgumentsInlined(bool* emitted, MDefinition* obj, MDefinition* index);
     bool getElemTryCache(bool* emitted, MDefinition* obj, MDefinition* index);
     bool getElemTryScalarElemOfTypedObject(bool* emitted,
@@ -955,18 +955,18 @@ class IonBuilder
                                   bool isOwnProperty);
 
     bool annotateGetPropertyCache(MDefinition* obj, MGetPropertyCache* getPropCache,
                                   TemporaryTypeSet* objTypes,
                                   TemporaryTypeSet* pushedTypes);
 
     MGetPropertyCache* getInlineableGetPropertyCache(CallInfo& callInfo);
 
-    JSObject* testSingletonProperty(JSObject* obj, PropertyName* name);
-    JSObject* testSingletonPropertyTypes(MDefinition* obj, PropertyName* name);
+    JSObject* testSingletonProperty(JSObject* obj, jsid id);
+    JSObject* testSingletonPropertyTypes(MDefinition* obj, jsid id);
 
     uint32_t getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_t* pnfixed);
     MDefinition* convertUnboxedObjects(MDefinition* obj);
     MDefinition* convertUnboxedObjects(MDefinition* obj,
                                        const BaselineInspector::ObjectGroupVector& list);
     uint32_t getUnboxedOffset(TemporaryTypeSet* types, PropertyName* name,
                               JSValueType* punboxedType);
     MInstruction* loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -3411,16 +3411,17 @@ GetElementIC::attachGetProp(JSContext* c
 
     if (canCache == GetPropertyIC::CanAttachReadSlot) {
         // OK to attach.
     } else if (canCache == GetPropertyIC::CanAttachCallGetter) {
         if (!output().hasValue()) {
             JitSpew(JitSpew_IonIC, "GETELEM uncacheable property");
             return true;
         }
+        MOZ_ASSERT(monitoredResult());
     } else if (obj->is<UnboxedPlainObject>()) {
         MOZ_ASSERT(canCache == GetPropertyIC::CanAttachNone);
         const UnboxedLayout::Property* property =
             obj->as<UnboxedPlainObject>().layout().lookup(name);
         if (property) {
             // OK to attach.
         } else {
             UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -740,17 +740,40 @@ enum ABIFunctionType
 enum class BarrierKind : uint32_t {
     // No barrier is needed.
     NoBarrier,
 
     // The barrier only has to check the value's type tag is in the TypeSet.
     // Specific object types don't have to be checked.
     TypeTagOnly,
 
+    // The barrier only has to check that object values are in the type set.
+    // Non-object types don't have to be checked.
+    ObjectTypesOnly,
+
     // Check if the value is in the TypeSet, including the object type if it's
     // an object.
     TypeSet
 };
 
+static inline BarrierKind
+CombineBarrierKinds(BarrierKind first, BarrierKind second)
+{
+    // Barrier kinds form the following lattice:
+    //
+    //         TypeSet
+    //          |   |
+    // TypeTagOnly ObjectTypesOnly
+    //          |   |
+    //        NoBarrier
+    //
+    // This function computes the least upper bound of two barrier kinds.
+    if (first == BarrierKind::NoBarrier || first == second)
+        return second;
+    if (second == BarrierKind::NoBarrier)
+        return first;
+    return BarrierKind::TypeSet;
+}
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_IonTypes_h */
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -3298,16 +3298,19 @@ LIRGenerator::visitSetPropertyPolymorphi
     }
 }
 
 void
 LIRGenerator::visitGetElementCache(MGetElementCache* ins)
 {
     MOZ_ASSERT(ins->object()->type() == MIRType_Object);
 
+    if (ins->monitoredResult())
+        gen->setPerformsCall(); // See visitGetPropertyCache.
+
     if (ins->type() == MIRType_Value) {
         MOZ_ASSERT(ins->index()->type() == MIRType_Value);
         LGetElementCacheV* lir = new(alloc()) LGetElementCacheV(useRegister(ins->object()));
         useBox(lir, LGetElementCacheV::Index, ins->index());
         defineBox(lir, ins);
         assignSafepoint(lir, ins);
     } else {
         MOZ_ASSERT(ins->index()->type() == MIRType_Int32);
@@ -3514,16 +3517,18 @@ LIRGenerator::visitSetPropertyCache(MSet
 }
 
 void
 LIRGenerator::visitSetElementCache(MSetElementCache* ins)
 {
     MOZ_ASSERT(ins->object()->type() == MIRType_Object);
     MOZ_ASSERT(ins->index()->type() == MIRType_Value);
 
+    gen->setPerformsCall(); // See visitSetPropertyCache.
+
     // Due to lack of registers on x86, we reuse the object register as a
     // temporary. This register may be used in a 1-byte store, which on x86
     // again has constraints; thus the use of |useByteOpRegister| over
     // |useRegister| below.
     LInstruction* lir;
     if (ins->value()->type() == MIRType_Value) {
         LDefinition tempF32 = hasUnaliasedDouble() ? tempFloat32() : LDefinition::BogusTemp();
         lir = new(alloc()) LSetElementCacheV(useByteOpRegister(ins->object()), tempToUnbox(),
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -4980,16 +4980,22 @@ PropertyReadNeedsTypeBarrier(CompilerCon
     if (property.maybeTypes()) {
         if (!TypeSetIncludes(observed, MIRType_Value, property.maybeTypes())) {
             // If all possible objects have been observed, we don't have to
             // guard on the specific object types.
             if (property.maybeTypes()->objectsAreSubset(observed)) {
                 property.freeze(constraints);
                 return BarrierKind::TypeTagOnly;
             }
+            // If all possible primitives have been observed, we don't have to
+            // guard on those primitives.
+            if (property.maybeTypes()->primitivesAreSubset(observed)) {
+                property.freeze(constraints);
+                return BarrierKind::ObjectTypesOnly;
+            }
             return BarrierKind::TypeSet;
         }
     }
 
     // Type information for global objects is not required to reflect the
     // initial 'undefined' value for properties, in particular global
     // variables declared with 'var'. Until the property is assigned a value
     // other than undefined, a barrier is required.
@@ -5001,44 +5007,16 @@ PropertyReadNeedsTypeBarrier(CompilerCon
             return BarrierKind::TypeSet;
         }
     }
 
     property.freeze(constraints);
     return BarrierKind::NoBarrier;
 }
 
-static bool
-ObjectSubsumes(TypeSet::ObjectKey* first, TypeSet::ObjectKey* second)
-{
-    if (first->isSingleton() ||
-        second->isSingleton() ||
-        first->clasp() != second->clasp() ||
-        first->unknownProperties() ||
-        second->unknownProperties())
-    {
-        return false;
-    }
-
-    if (first->clasp() == &ArrayObject::class_) {
-        HeapTypeSetKey firstElements = first->property(JSID_VOID);
-        HeapTypeSetKey secondElements = second->property(JSID_VOID);
-
-        return firstElements.maybeTypes() && secondElements.maybeTypes() &&
-               firstElements.maybeTypes()->equals(secondElements.maybeTypes());
-    }
-
-    if (first->clasp() == &UnboxedArrayObject::class_) {
-        return first->group()->unboxedLayout().elementType() ==
-               second->group()->unboxedLayout().elementType();
-    }
-
-    return false;
-}
-
 BarrierKind
 jit::PropertyReadNeedsTypeBarrier(JSContext* propertycx,
                                   CompilerConstraintList* constraints,
                                   TypeSet::ObjectKey* key, PropertyName* name,
                                   TemporaryTypeSet* observed, bool updateObserved)
 {
     if (!updateObserved)
         return PropertyReadNeedsTypeBarrier(constraints, key, name, observed);
@@ -5073,40 +5051,16 @@ jit::PropertyReadNeedsTypeBarrier(JSCont
                     }
                 }
             }
 
             obj = obj->getProto();
         }
     }
 
-    // If any objects which could be observed are similar to ones that have
-    // already been observed, add them to the observed type set.
-    if (!key->unknownProperties()) {
-        HeapTypeSetKey property = key->property(name ? NameToId(name) : JSID_VOID);
-
-        if (property.maybeTypes() && !property.maybeTypes()->unknownObject()) {
-            for (size_t i = 0; i < property.maybeTypes()->getObjectCount(); i++) {
-                TypeSet::ObjectKey* key = property.maybeTypes()->getObject(i);
-                if (!key || observed->unknownObject())
-                    continue;
-
-                for (size_t j = 0; j < observed->getObjectCount(); j++) {
-                    TypeSet::ObjectKey* observedKey = observed->getObject(j);
-                    if (observedKey && ObjectSubsumes(observedKey, key)) {
-                        // Note: the return value here is ignored.
-                        observed->addType(TypeSet::ObjectType(key),
-                                          GetJitContext()->temp->lifoAlloc());
-                        break;
-                    }
-                }
-            }
-        }
-    }
-
     return PropertyReadNeedsTypeBarrier(constraints, key, name, observed);
 }
 
 BarrierKind
 jit::PropertyReadNeedsTypeBarrier(JSContext* propertycx,
                                   CompilerConstraintList* constraints,
                                   MDefinition* obj, PropertyName* name,
                                   TemporaryTypeSet* observed)
@@ -5120,25 +5074,19 @@ jit::PropertyReadNeedsTypeBarrier(JSCont
 
     BarrierKind res = BarrierKind::NoBarrier;
 
     bool updateObserved = types->getObjectCount() == 1;
     for (size_t i = 0; i < types->getObjectCount(); i++) {
         if (TypeSet::ObjectKey* key = types->getObject(i)) {
             BarrierKind kind = PropertyReadNeedsTypeBarrier(propertycx, constraints, key, name,
                                                             observed, updateObserved);
-            if (kind == BarrierKind::TypeSet)
+            res = CombineBarrierKinds(res, kind);
+            if (res == BarrierKind::TypeSet)
                 return BarrierKind::TypeSet;
-
-            if (kind == BarrierKind::TypeTagOnly) {
-                MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly);
-                res = BarrierKind::TypeTagOnly;
-            } else {
-                MOZ_ASSERT(kind == BarrierKind::NoBarrier);
-            }
         }
     }
 
     return res;
 }
 
 BarrierKind
 jit::PropertyReadOnPrototypeNeedsTypeBarrier(IonBuilder* builder,
@@ -5162,25 +5110,19 @@ jit::PropertyReadOnPrototypeNeedsTypeBar
             if (!key->hasStableClassAndProto(builder->constraints()))
                 return BarrierKind::TypeSet;
             if (!key->proto().isObject())
                 break;
             JSObject* proto = builder->checkNurseryObject(key->proto().toObject());
             key = TypeSet::ObjectKey::get(proto);
             BarrierKind kind = PropertyReadNeedsTypeBarrier(builder->constraints(),
                                                             key, name, observed);
-            if (kind == BarrierKind::TypeSet)
+            res = CombineBarrierKinds(res, kind);
+            if (res == BarrierKind::TypeSet)
                 return BarrierKind::TypeSet;
-
-            if (kind == BarrierKind::TypeTagOnly) {
-                MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly);
-                res = BarrierKind::TypeTagOnly;
-            } else {
-                MOZ_ASSERT(kind == BarrierKind::NoBarrier);
-            }
         }
     }
 
     return res;
 }
 
 bool
 jit::PropertyReadIsIdempotent(CompilerConstraintList* constraints,
@@ -5395,18 +5337,22 @@ TryAddTypeBarrierForWrite(TempAllocator&
 
     TemporaryTypeSet* types = aggregateProperty->maybeTypes()->clone(alloc.lifoAlloc());
     if (!types)
         return false;
 
     // If all possible objects can be stored without a barrier, we don't have to
     // guard on the specific object types.
     BarrierKind kind = BarrierKind::TypeSet;
-    if ((*pvalue)->resultTypeSet() && (*pvalue)->resultTypeSet()->objectsAreSubset(types))
-        kind = BarrierKind::TypeTagOnly;
+    if ((*pvalue)->resultTypeSet()) {
+        if ((*pvalue)->resultTypeSet()->objectsAreSubset(types))
+            kind = BarrierKind::TypeTagOnly;
+        else if ((*pvalue)->resultTypeSet()->primitivesAreSubset(types))
+            kind = BarrierKind::ObjectTypesOnly;
+    }
 
     MInstruction* ins = MMonitorTypes::New(alloc, *pvalue, types, kind);
     current->add(ins);
     return true;
 }
 
 static MInstruction*
 AddGroupGuard(TempAllocator& alloc, MBasicBlock* current, MDefinition* obj,
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -790,17 +790,17 @@ class MDefinition : public MNode
         return this->to<M##opcode>();     \
     }                                     \
     const M##opcode* to##opcode() const { \
         return this->to<M##opcode>();     \
     }
     MIR_OPCODE_LIST(OPCODE_CASTS)
 #   undef OPCODE_CASTS
 
-    bool isConstantValue() {
+    bool isConstantValue() const {
         return isConstant() || (isBox() && getOperand(0)->isConstant());
     }
     const Value& constantValue();
     const Value* constantVp();
     bool constantToBoolean();
 
     inline MInstruction* toInstruction();
     inline const MInstruction* toInstruction() const;
@@ -12382,17 +12382,17 @@ class MTypeBarrier
     public TypeBarrierPolicy::Data
 {
     BarrierKind barrierKind_;
 
     MTypeBarrier(MDefinition* def, TemporaryTypeSet* types, BarrierKind kind)
       : MUnaryInstruction(def),
         barrierKind_(kind)
     {
-        MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
+        MOZ_ASSERT(kind != BarrierKind::NoBarrier);
 
         MOZ_ASSERT(!types->unknown());
         setResultType(types->getKnownMIRType());
         setResultTypeSet(types);
 
         setGuard();
         setMovable();
     }
@@ -12442,17 +12442,17 @@ class MMonitorTypes
     const TemporaryTypeSet* typeSet_;
     BarrierKind barrierKind_;
 
     MMonitorTypes(MDefinition* def, const TemporaryTypeSet* types, BarrierKind kind)
       : MUnaryInstruction(def),
         typeSet_(types),
         barrierKind_(kind)
     {
-        MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
+        MOZ_ASSERT(kind != BarrierKind::NoBarrier);
 
         setGuard();
         MOZ_ASSERT(!types->unknown());
     }
 
   public:
     INSTRUCTION_HEADER(MonitorTypes)
 
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -30,49 +30,74 @@ using namespace js::jit;
 
 using JS::GenericNaN;
 using JS::ToInt32;
 
 template <typename Source> void
 MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, BarrierKind kind,
                              Register scratch, Label* miss)
 {
-    MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
+    MOZ_ASSERT(kind != BarrierKind::NoBarrier);
     MOZ_ASSERT(!types->unknown());
 
     Label matched;
     TypeSet::Type tests[8] = {
         TypeSet::Int32Type(),
         TypeSet::UndefinedType(),
         TypeSet::BooleanType(),
         TypeSet::StringType(),
         TypeSet::SymbolType(),
         TypeSet::NullType(),
         TypeSet::MagicArgType(),
         TypeSet::AnyObjectType()
     };
 
-    // The double type also implies Int32.
-    // So replace the int32 test with the double one.
-    if (types->hasType(TypeSet::DoubleType())) {
-        MOZ_ASSERT(types->hasType(TypeSet::Int32Type()));
-        tests[0] = TypeSet::DoubleType();
-    }
+    Register tag = extractTag(address, scratch);
+    BranchType lastBranch;
 
-    Register tag = extractTag(address, scratch);
+    if (kind != BarrierKind::ObjectTypesOnly) {
+        // The double type also implies Int32.
+        // So replace the int32 test with the double one.
+        if (types->hasType(TypeSet::DoubleType())) {
+            MOZ_ASSERT(types->hasType(TypeSet::Int32Type()));
+            tests[0] = TypeSet::DoubleType();
+        }
+
+        // Emit all typed tests.
+        for (size_t i = 0; i < mozilla::ArrayLength(tests); i++) {
+            if (!types->hasType(tests[i]))
+                continue;
 
-    // Emit all typed tests.
-    BranchType lastBranch;
-    for (size_t i = 0; i < mozilla::ArrayLength(tests); i++) {
-        if (!types->hasType(tests[i]))
-            continue;
+            if (lastBranch.isInitialized())
+                lastBranch.emit(*this);
+            lastBranch = BranchType(Equal, tag, tests[i], &matched);
+        }
+    } else {
+#ifdef DEBUG
+        // Any non-object will be considered to match the type set. Make sure
+        // such values encountered are actually in the type set.
+
+        if (types->hasType(TypeSet::DoubleType())) {
+            MOZ_ASSERT(types->hasType(TypeSet::Int32Type()));
+            tests[0] = TypeSet::DoubleType();
+        }
 
-        if (lastBranch.isInitialized())
-            lastBranch.emit(*this);
-        lastBranch = BranchType(Equal, tag, tests[i], &matched);
+        Label matchedPrimitive;
+        for (size_t i = 0; i < mozilla::ArrayLength(tests); i++) {
+            if (!types->hasType(tests[i]))
+                continue;
+            BranchType branch(Equal, tag, tests[i], &matchedPrimitive);
+            branch.emit(*this);
+        }
+        branchTestObject(Equal, tag, &matchedPrimitive);
+
+        assumeUnreachable("Unexpected primitive type");
+
+        bind(&matchedPrimitive);
+#endif
     }
 
     // If this is the last check, invert the last branch.
     if (types->hasType(TypeSet::AnyObjectType()) || !types->getObjectCount()) {
         if (!lastBranch.isInitialized()) {
             jump(miss);
             return;
         }
@@ -85,17 +110,17 @@ MacroAssembler::guardTypeSet(const Sourc
         return;
     }
 
     if (lastBranch.isInitialized())
         lastBranch.emit(*this);
 
     // Test specific objects.
     MOZ_ASSERT(scratch != InvalidReg);
-    branchTestObject(NotEqual, tag, miss);
+    branchTestObject(NotEqual, tag, kind == BarrierKind::ObjectTypesOnly ? &matched : miss);
     if (kind != BarrierKind::TypeTagOnly) {
         Register obj = extractObject(address, scratch);
         guardObjectType(obj, types, scratch, miss);
     } else {
 #ifdef DEBUG
         Label fail;
         Register obj = extractObject(address, scratch);
         guardObjectType(obj, types, scratch, &fail);
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2869,17 +2869,17 @@ class GCHeapProfiler
     virtual void sweepTenured() = 0;
     virtual void sweepNursery() = 0;
     virtual void moveNurseryToTenured(void* addrOld, void* addrNew) = 0;
     virtual void reset() = 0;
 };
 
 class MemProfiler
 {
-    static mozilla::Atomic<int> sActiveProfilerCount;
+    static mozilla::Atomic<uint32_t, mozilla::Relaxed> sActiveProfilerCount;
     static NativeProfiler* sNativeProfiler;
 
     static GCHeapProfiler* GetGCHeapProfiler(void* addr);
     static GCHeapProfiler* GetGCHeapProfiler(JSRuntime* runtime);
 
     static NativeProfiler* GetNativeProfiler() {
         return sNativeProfiler;
     }
@@ -2892,115 +2892,115 @@ class MemProfiler
 
     void start(GCHeapProfiler* aGCHeapProfiler);
     void stop();
 
     GCHeapProfiler* getGCHeapProfiler() const {
         return mGCHeapProfiler;
     }
 
-    static bool enabled() {
+    static MOZ_ALWAYS_INLINE bool enabled() {
         return sActiveProfilerCount > 0;
     }
 
     static MemProfiler* GetMemProfiler(JSRuntime* runtime);
 
     static void SetNativeProfiler(NativeProfiler* aProfiler) {
         sNativeProfiler = aProfiler;
     }
 
-    static void SampleNative(void* addr, uint32_t size) {
+    static MOZ_ALWAYS_INLINE void SampleNative(void* addr, uint32_t size) {
         JS::AutoSuppressGCAnalysis nogc;
 
         if (MOZ_LIKELY(!enabled()))
             return;
 
         NativeProfiler* profiler = GetNativeProfiler();
         if (profiler)
             profiler->sampleNative(addr, size);
     }
 
-    static void SampleTenured(void* addr, uint32_t size) {
+    static MOZ_ALWAYS_INLINE void SampleTenured(void* addr, uint32_t size) {
         JS::AutoSuppressGCAnalysis nogc;
 
         if (MOZ_LIKELY(!enabled()))
             return;
 
         GCHeapProfiler* profiler = GetGCHeapProfiler(addr);
         if (profiler)
             profiler->sampleTenured(addr, size);
     }
 
-    static void SampleNursery(void* addr, uint32_t size) {
+    static MOZ_ALWAYS_INLINE void SampleNursery(void* addr, uint32_t size) {
         JS::AutoSuppressGCAnalysis nogc;
 
         if (MOZ_LIKELY(!enabled()))
             return;
 
         GCHeapProfiler* profiler = GetGCHeapProfiler(addr);
         if (profiler)
             profiler->sampleNursery(addr, size);
     }
 
-    static void RemoveNative(void* addr) {
+    static MOZ_ALWAYS_INLINE void RemoveNative(void* addr) {
         JS::AutoSuppressGCAnalysis nogc;
 
         if (MOZ_LIKELY(!enabled()))
             return;
 
         NativeProfiler* profiler = GetNativeProfiler();
         if (profiler)
             profiler->removeNative(addr);
     }
 
-    static void MarkTenuredStart(JSRuntime* runtime) {
+    static MOZ_ALWAYS_INLINE void MarkTenuredStart(JSRuntime* runtime) {
         JS::AutoSuppressGCAnalysis nogc;
 
         if (MOZ_LIKELY(!enabled()))
             return;
 
         GCHeapProfiler* profiler = GetGCHeapProfiler(runtime);
         if (profiler)
             profiler->markTenuredStart();
     }
 
-    static void MarkTenured(void* addr) {
+    static MOZ_ALWAYS_INLINE void MarkTenured(void* addr) {
         JS::AutoSuppressGCAnalysis nogc;
 
         if (MOZ_LIKELY(!enabled()))
             return;
 
         GCHeapProfiler* profiler = GetGCHeapProfiler(addr);
         if (profiler)
             profiler->markTenured(addr);
     }
 
-    static void SweepTenured(JSRuntime* runtime) {
+    static MOZ_ALWAYS_INLINE void SweepTenured(JSRuntime* runtime) {
         JS::AutoSuppressGCAnalysis nogc;
 
         if (MOZ_LIKELY(!enabled()))
             return;
 
         GCHeapProfiler* profiler = GetGCHeapProfiler(runtime);
         if (profiler)
             profiler->sweepTenured();
     }
 
-    static void SweepNursery(JSRuntime* runtime) {
+    static MOZ_ALWAYS_INLINE void SweepNursery(JSRuntime* runtime) {
         JS::AutoSuppressGCAnalysis nogc;
 
         if (MOZ_LIKELY(!enabled()))
             return;
 
         GCHeapProfiler* profiler = GetGCHeapProfiler(runtime);
         if (profiler)
             profiler->sweepNursery();
     }
 
-    static void MoveNurseryToTenured(void* addrOld, void* addrNew) {
+    static MOZ_ALWAYS_INLINE void MoveNurseryToTenured(void* addrOld, void* addrNew) {
         JS::AutoSuppressGCAnalysis nogc;
 
         if (MOZ_LIKELY(!enabled()))
             return;
 
         GCHeapProfiler* profiler = GetGCHeapProfiler(addrOld);
         if (profiler)
             profiler->moveNurseryToTenured(addrOld, addrNew);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -596,16 +596,23 @@ Debugger::slowPathOnLeaveFrame(JSContext
 
     // The onPop handler and associated clean up logic should not run multiple
     // times on the same frame. If slowPathOnLeaveFrame has already been
     // called, the frame will not be present in the Debugger frame maps.
     FrameRange frameRange(frame, global);
     if (frameRange.empty())
         return frameOk;
 
+    auto frameMapsGuard = MakeScopeExit([&] {
+        // Clean up all Debugger.Frame instances. This call creates a fresh
+        // FrameRange, as one debugger's onPop handler could have caused another
+        // debugger to create its own Debugger.Frame instance.
+        removeFromFrameMapsAndClearBreakpointsIn(cx, frame);
+    });
+
     /* Save the frame's completion value. */
     JSTrapStatus status;
     RootedValue value(cx);
     Debugger::resultToCompletion(cx, frameOk, frame.returnValue(), &status, &value);
 
     // This path can be hit via unwinding the stack due to over-recursion or
     // OOM. In those cases, don't fire the frames' onPop handlers, because
     // invoking JS will only trigger the same condition. See
@@ -656,23 +663,16 @@ Debugger::slowPathOnLeaveFrame(JSContext
                 if (nextStatus != JSTRAP_CONTINUE) {
                     status = nextStatus;
                     value = nextValue;
                 }
             }
         }
     }
 
-    /*
-     * Clean up all Debugger.Frame instances. This call creates a fresh
-     * FrameRange, as one debugger's onPop handler could have caused another
-     * debugger to create its own Debugger.Frame instance.
-     */
-    removeFromFrameMapsAndClearBreakpointsIn(cx, frame);
-
     /* Establish (status, value) as our resumption value. */
     switch (status) {
       case JSTRAP_RETURN:
         frame.setReturnValue(value);
         return true;
 
       case JSTRAP_THROW:
         cx->setPendingException(value);
--- a/js/src/vm/SPSProfiler.cpp
+++ b/js/src/vm/SPSProfiler.cpp
@@ -248,17 +248,17 @@ SPSProfiler::beginPseudoJS(const char* s
     /* these operations cannot be re-ordered, so volatile-ize operations */
     volatile ProfileEntry* stack = stack_;
     volatile uint32_t* size = size_;
     uint32_t current = *size;
 
     MOZ_ASSERT(installed());
     if (current < max_) {
         stack[current].setLabel(string);
-        stack[current].setCppFrame(sp, 0);
+        stack[current].initCppFrame(sp, 0);
         stack[current].setFlag(ProfileEntry::BEGIN_PSEUDO_JS);
     }
     *size = current + 1;
 }
 
 void
 SPSProfiler::push(const char* string, void* sp, JSScript* script, jsbytecode* pc, bool copy,
                   ProfileEntry::Category category)
@@ -269,28 +269,29 @@ SPSProfiler::push(const char* string, vo
     /* these operations cannot be re-ordered, so volatile-ize operations */
     volatile ProfileEntry* stack = stack_;
     volatile uint32_t* size = size_;
     uint32_t current = *size;
 
     MOZ_ASSERT(installed());
     if (current < max_) {
         volatile ProfileEntry& entry = stack[current];
-        entry.setLabel(string);
-        entry.setCategory(category);
 
         if (sp != nullptr) {
-            entry.setCppFrame(sp, 0);
+            entry.initCppFrame(sp, 0);
             MOZ_ASSERT(entry.flags() == js::ProfileEntry::IS_CPP_ENTRY);
         }
         else {
-            entry.setJsFrame(script, pc);
+            entry.initJsFrame(script, pc);
             MOZ_ASSERT(entry.flags() == 0);
         }
 
+        entry.setLabel(string);
+        entry.setCategory(category);
+
         // Track if mLabel needs a copy.
         if (copy)
             entry.setFlag(js::ProfileEntry::FRAME_LABEL_COPY);
         else
             entry.unsetFlag(js::ProfileEntry::FRAME_LABEL_COPY);
     }
     *size = current + 1;
 }
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -394,16 +394,23 @@ TypeSet::objectsAreSubset(TypeSet* other
         if (!other->hasType(ObjectType(key)))
             return false;
     }
 
     return true;
 }
 
 bool
+TypeSet::primitivesAreSubset(TypeSet* other)
+{
+    uint32_t primitiveFlags = baseFlags() & TYPE_FLAG_PRIMITIVE;
+    return (primitiveFlags & other->baseFlags()) == primitiveFlags;
+}
+
+bool
 TypeSet::isSubset(const TypeSet* other) const
 {
     if ((baseFlags() & other->baseFlags()) != baseFlags())
         return false;
 
     if (unknownObject()) {
         MOZ_ASSERT(other->unknownObject());
     } else {
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -61,17 +61,17 @@ enum : uint32_t {
     TYPE_FLAG_STRING    =  0x20,
     TYPE_FLAG_SYMBOL    =  0x40,
     TYPE_FLAG_LAZYARGS  =  0x80,
     TYPE_FLAG_ANYOBJECT = 0x100,
 
     /* Mask containing all primitives */
     TYPE_FLAG_PRIMITIVE = TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL | TYPE_FLAG_BOOLEAN |
                           TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_STRING |
-                          TYPE_FLAG_SYMBOL,
+                          TYPE_FLAG_SYMBOL | TYPE_FLAG_LAZYARGS,
 
     /* Mask/shift for the number of objects in objectSet */
     TYPE_FLAG_OBJECT_COUNT_MASK     = 0x3e00,
     TYPE_FLAG_OBJECT_COUNT_SHIFT    = 9,
     TYPE_FLAG_OBJECT_COUNT_LIMIT    = 7,
     TYPE_FLAG_DOMOBJECT_COUNT_LIMIT =
         TYPE_FLAG_OBJECT_COUNT_MASK >> TYPE_FLAG_OBJECT_COUNT_SHIFT,
 
@@ -478,21 +478,20 @@ class TypeSet
     bool mightBeMIRType(jit::MIRType type);
 
     /*
      * Get whether this type set is known to be a subset of other.
      * This variant doesn't freeze constraints. That variant is called knownSubset
      */
     bool isSubset(const TypeSet* other) const;
 
-    /*
-     * Get whether the objects in this TypeSet are a subset of the objects
-     * in other.
-     */
+    // Return whether this is a subset of other, ignoring primitive or object
+    // types respectively.
     bool objectsAreSubset(TypeSet* other);
+    bool primitivesAreSubset(TypeSet* other);
 
     /* Whether this TypeSet contains exactly the same types as other. */
     bool equals(const TypeSet* other) const {
         return this->isSubset(other) && other->isSubset(this);
     }
 
     bool objectsIntersect(const TypeSet* other) const;
 
--- a/js/src/vm/UnboxedObject-inl.h
+++ b/js/src/vm/UnboxedObject-inl.h
@@ -497,20 +497,20 @@ SetOrExtendBoxedOrUnboxedDenseElements(E
     // Overwrite any existing elements covered by the new range. If we fail
     // after this point due to some incompatible type being written to the
     // object's elements, afterwards the contents will be different from when
     // we started. The caller must retry the operation using a generic path,
     // which will overwrite the already-modified elements as well as the ones
     // that were left alone.
     size_t i = 0;
     if (updateTypes == ShouldUpdateTypes::DontUpdate) {
-        for (size_t j = start; i < count && j < oldInitlen; i++)
+        for (size_t j = start; i < count && j < oldInitlen; i++, j++)
             nobj->setElementNoTypeChangeSpecific<Type>(j, vp[i]);
     } else {
-        for (size_t j = start; i < count && j < oldInitlen; i++) {
+        for (size_t j = start; i < count && j < oldInitlen; i++, j++) {
             if (!nobj->setElementSpecific<Type>(cx, j, vp[i]))
                 return DenseElementResult::Incomplete;
         }
     }
 
     if (i != count) {
         obj->as<UnboxedArrayObject>().setInitializedLength(start + count);
         if (updateTypes == ShouldUpdateTypes::DontUpdate) {
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -1969,16 +1969,22 @@ js::TryConvertToUnboxedLayout(ExclusiveC
     }
 
     size_t layoutSize = 0;
     if (isArray) {
         // Don't use an unboxed representation if we couldn't determine an
         // element type for the objects.
         if (UnboxedTypeSize(elementType) == 0)
             return true;
+
+        // Don't use an unboxed representation if objects in the group have
+        // ever had holes in the past. Even if they have been filled in, future
+        // objects that are created might be given holes as well.
+        if (group->flags() & OBJECT_FLAG_NON_PACKED)
+            return true;
     } else {
         if (objectCount <= 1) {
             // If only one of the objects has been created, it is more likely
             // to have new properties added later. This heuristic is not used
             // for array objects, where we might want an unboxed representation
             // even if there is only one large array.
             return true;
         }
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1503,20 +1503,16 @@ already_AddRefed<LayerManager> nsDisplay
 
   FrameLayerBuilder *layerBuilder = new FrameLayerBuilder();
   layerBuilder->Init(aBuilder, layerManager);
 
   if (aFlags & PAINT_COMPRESSED) {
     layerBuilder->SetLayerTreeCompressionMode();
   }
 
-  if (aFlags & PAINT_FLUSH_LAYERS) {
-    FrameLayerBuilder::InvalidateAllLayers(layerManager);
-  }
-
   if (doBeginTransaction) {
     if (aCtx) {
       layerManager->BeginTransactionWithTarget(aCtx->ThebesContext());
     } else {
       layerManager->BeginTransaction();
     }
   }
   if (widgetTransaction) {
@@ -1707,20 +1703,16 @@ already_AddRefed<LayerManager> nsDisplay
         }
         presContext->NotifyInvalidation(bounds, 0);
       }
     } else if (shouldInvalidate) {
       view->GetViewManager()->InvalidateView(view);
     }
   }
 
-  if (aFlags & PAINT_FLUSH_LAYERS) {
-    FrameLayerBuilder::InvalidateAllLayers(layerManager);
-  }
-
   layerManager->SetUserData(&gLayerManagerLayerBuilder, oldBuilder);
   return layerManager.forget();
 }
 
 uint32_t nsDisplayList::Count() const {
   uint32_t count = 0;
   for (nsDisplayItem* i = GetBottom(); i; i = i->GetAbove()) {
     ++count;
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -1977,36 +1977,32 @@ public:
    * dirty rect used to construct the display list.
    * 
    * If aFlags contains PAINT_USE_WIDGET_LAYERS and
    * ShouldUseWidgetLayerManager() is set, then we will paint using
    * the reference frame's widget's layer manager (and ctx may be null),
    * otherwise we will use a temporary BasicLayerManager and ctx must
    * not be null.
    * 
-   * If PAINT_FLUSH_LAYERS is set, we'll force a completely new layer
-   * tree to be created for this paint *and* the next paint.
-   * 
    * If PAINT_EXISTING_TRANSACTION is set, the reference frame's widget's
    * layer manager has already had BeginTransaction() called on it and
    * we should not call it again.
    *
    * If PAINT_COMPRESSED is set, the FrameLayerBuilder should be set to compressed mode
    * to avoid short cut optimizations.
    *
    * This must only be called on the root display list of the display list
    * tree.
    *
    * We return the layer manager used for painting --- mainly so that
    * callers can dump its layer tree if necessary.
    */
   enum {
     PAINT_DEFAULT = 0,
     PAINT_USE_WIDGET_LAYERS = 0x01,
-    PAINT_FLUSH_LAYERS = 0x02,
     PAINT_EXISTING_TRANSACTION = 0x04,
     PAINT_NO_COMPOSITE = 0x08,
     PAINT_COMPRESSED = 0x10
   };
   already_AddRefed<LayerManager> PaintRoot(nsDisplayListBuilder* aBuilder,
                                            nsRenderingContext* aCtx,
                                            uint32_t aFlags);
   /**
@@ -2406,50 +2402,21 @@ protected:
  * drawing of the background color into this class (from nsDisplayBackground
  * via nsDisplayCanvasBackground). This is done so that we can always draw a
  * background color to avoid ugly flashes of white when we can't draw a full
  * frame tree (ie when a page is loading). The bounds can differ from the
  * frame's bounds -- this is needed when a frame/iframe is loading and there
  * is not yet a frame tree to go in the frame/iframe so we use the subdoc
  * frame of the parent document as a standin.
  */
-class nsDisplaySolidColor : public nsDisplayItem {
+class nsDisplaySolidColorBase : public nsDisplayItem {
 public:
-  nsDisplaySolidColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
-                      const nsRect& aBounds, nscolor aColor)
-    : nsDisplayItem(aBuilder, aFrame), mBounds(aBounds), mColor(aColor)
-  {
-    NS_ASSERTION(NS_GET_A(aColor) > 0, "Don't create invisible nsDisplaySolidColors!");
-    MOZ_COUNT_CTOR(nsDisplaySolidColor);
-  }
-#ifdef NS_BUILD_REFCNT_LOGGING
-  virtual ~nsDisplaySolidColor() {
-    MOZ_COUNT_DTOR(nsDisplaySolidColor);
-  }
-#endif
-
-  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
-
-  virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override {
-    *aSnap = false;
-    nsRegion result;
-    if (NS_GET_A(mColor) == 255) {
-      result = GetBounds(aBuilder, aSnap);
-    }
-    return result;
-  }
-
-  virtual bool IsUniform(nsDisplayListBuilder* aBuilder, nscolor* aColor) override
-  {
-    *aColor = mColor;
-    return true;
-  }
-
-  virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+  nsDisplaySolidColorBase(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nscolor aColor)
+    : nsDisplayItem(aBuilder, aFrame), mColor(aColor)
+  {}
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
   {
     return new nsDisplaySolidColorGeometry(this, aBuilder, mColor);
   }
 
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
@@ -2460,23 +2427,61 @@ public:
     if (mColor != geometry->mColor) {
       bool dummy;
       aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &dummy));
       return;
     }
     ComputeInvalidationRegionDifference(aBuilder, geometry, aInvalidRegion);
   }
 
+  virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+                                   bool* aSnap) override {
+    *aSnap = false;
+    nsRegion result;
+    if (NS_GET_A(mColor) == 255) {
+      result = GetBounds(aBuilder, aSnap);
+    }
+    return result;
+  }
+
+  virtual bool IsUniform(nsDisplayListBuilder* aBuilder, nscolor* aColor) override
+  {
+    *aColor = mColor;
+    return true;
+  }
+
+protected:
+  nscolor mColor;
+};
+
+class nsDisplaySolidColor : public nsDisplaySolidColorBase {
+public:
+  nsDisplaySolidColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+                      const nsRect& aBounds, nscolor aColor)
+    : nsDisplaySolidColorBase(aBuilder, aFrame, aColor), mBounds(aBounds)
+  {
+    NS_ASSERTION(NS_GET_A(aColor) > 0, "Don't create invisible nsDisplaySolidColors!");
+    MOZ_COUNT_CTOR(nsDisplaySolidColor);
+  }
+#ifdef NS_BUILD_REFCNT_LOGGING
+  virtual ~nsDisplaySolidColor() {
+    MOZ_COUNT_DTOR(nsDisplaySolidColor);
+  }
+#endif
+
+  virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+
+  virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+
   virtual void WriteDebugInfo(std::stringstream& aStream) override;
 
   NS_DISPLAY_DECL_NAME("SolidColor", TYPE_SOLID_COLOR)
 
 private:
   nsRect  mBounds;
-  nscolor mColor;
 };
 
 /**
  * A display item to paint one background-image for a frame. Each background
  * image layer gets its own nsDisplayBackgroundImage.
  */
 class nsDisplayBackgroundImage : public nsDisplayImageContainer {
 public:
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3131,27 +3131,21 @@ nsLayoutUtils::PaintFrame(nsRenderingCon
       visibleRegion = aFrame->GetVisualOverflowRectRelativeToSelf();
     } else {
       visibleRegion = displayport;
     }
   } else {
     visibleRegion = aDirtyRegion;
   }
 
-  // If we're going to display something different from what we'd normally
-  // paint in a window then we will flush out any retained layer trees before
-  // *and after* we draw.
-  bool willFlushRetainedLayers = (aFlags & PAINT_HIDE_CARET) != 0;
-
   nsDisplayList list;
 
   // If the root has embedded plugins, flag the builder so we know we'll need
   // to update plugin geometry after painting.
   if ((aFlags & PAINT_WIDGET_LAYERS) &&
-      !willFlushRetainedLayers &&
       !(aFlags & PAINT_DOCUMENT_RELATIVE) &&
       rootPresContext->NeedToComputePluginGeometryUpdates()) {
     builder.SetWillComputePluginGeometry(true);
   }
 
   nsRect canvasArea(nsPoint(0, 0), aFrame->GetSize());
   bool ignoreViewportScrolling =
     aFrame->GetParent() ? false : presShell->IgnoringViewportScrolling();
@@ -3257,43 +3251,22 @@ nsLayoutUtils::PaintFrame(nsRenderingCon
     // Add the canvas background color to the bottom of the list. This
     // happens after we've built the list so that AddCanvasBackgroundColorItem
     // can monkey with the contents if necessary.
     canvasArea.IntersectRect(canvasArea, visibleRegion.GetBounds());
     nsDisplayListBuilder::AutoBuildingDisplayList
       buildingDisplayList(&builder, aFrame, canvasArea, false);
     presShell->AddCanvasBackgroundColorItem(
            builder, list, aFrame, canvasArea, aBackstop);
-
-    // If the passed in backstop color makes us draw something different from
-    // normal, we need to flush layers.
-    if ((aFlags & PAINT_WIDGET_LAYERS) && !willFlushRetainedLayers) {
-      nsView* view = aFrame->GetView();
-      if (view) {
-        nscolor backstop = presShell->ComputeBackstopColor(view);
-        // The PresShell's canvas background color doesn't get updated until
-        // EnterPresShell, so this check has to be done after that.
-        nscolor canvasColor = presShell->GetCanvasBackground();
-        if (NS_ComposeColors(aBackstop, canvasColor) !=
-            NS_ComposeColors(backstop, canvasColor)) {
-          willFlushRetainedLayers = true;
-        }
-      }
-    }
   }
 
   builder.LeavePresShell(aFrame);
   Telemetry::AccumulateTimeDelta(Telemetry::PAINT_BUILD_DISPLAYLIST_TIME,
                                  startBuildDisplayList);
 
-  if (builder.GetHadToIgnorePaintSuppression()) {
-    willFlushRetainedLayers = true;
-  }
-
-
   bool profilerNeedsDisplayList = profiler_feature_active("displaylistdump");
   bool consoleNeedsDisplayList = gfxUtils::DumpDisplayList() || gfxUtils::sDumpPainting;
 #ifdef MOZ_DUMP_PAINTING
   FILE* savedDumpFile = gfxUtils::sDumpPaintFile;
 #endif
 
   UniquePtr<std::stringstream> ss;
   if (consoleNeedsDisplayList || profilerNeedsDisplayList) {
@@ -3327,26 +3300,17 @@ nsLayoutUtils::PaintFrame(nsRenderingCon
       }
       ss = MakeUnique<std::stringstream>();
     }
   }
 
   uint32_t flags = nsDisplayList::PAINT_DEFAULT;
   if (aFlags & PAINT_WIDGET_LAYERS) {
     flags |= nsDisplayList::PAINT_USE_WIDGET_LAYERS;
-    if (willFlushRetainedLayers) {
-      // The caller wanted to paint from retained layers, but set up
-      // the paint in such a way that we can't use them.  We're going
-      // to display something different from what we'd normally paint
-      // in a window, so make sure we flush out any retained layer
-      // trees before *and after* we draw.  Callers should be fixed to
-      // not do this.
-      NS_WARNING("Flushing retained layers!");
-      flags |= nsDisplayList::PAINT_FLUSH_LAYERS;
-    } else if (!(aFlags & PAINT_DOCUMENT_RELATIVE)) {
+    if (!(aFlags & PAINT_DOCUMENT_RELATIVE)) {
       nsIWidget *widget = aFrame->GetNearestWidget();
       if (widget) {
         // If we're finished building display list items for painting of the outermost
         // pres shell, notify the widget about any toolbars we've encountered.
         widget->UpdateThemeGeometries(builder.GetThemeGeometries());
       }
     }
   }
@@ -3411,17 +3375,16 @@ nsLayoutUtils::PaintFrame(nsRenderingCon
     print_stderr(ss);
   }
 #endif
 
   // Update the widget's opaque region information. This sets
   // glass boundaries on Windows. Also set up the window dragging region
   // and plugin clip regions and bounds.
   if ((aFlags & PAINT_WIDGET_LAYERS) &&
-      !willFlushRetainedLayers &&
       !(aFlags & PAINT_DOCUMENT_RELATIVE)) {
     nsIWidget *widget = aFrame->GetNearestWidget();
     if (widget) {
       nsRegion opaqueRegion;
       opaqueRegion.And(builder.GetWindowExcludeGlassRegion(), builder.GetWindowOpaqueRegion());
       widget->UpdateOpaqueRegion(
         opaqueRegion.ToNearestPixels(presContext->AppUnitsPerDevPixel()));
 
--- a/layout/generic/nsCanvasFrame.h
+++ b/layout/generic/nsCanvasFrame.h
@@ -190,83 +190,53 @@ protected:
 };
 
 /**
  * Override nsDisplayBackground methods so that we pass aBGClipRect to
  * PaintBackground, covering the whole overflow area.
  * We can also paint an "extra background color" behind the normal
  * background.
  */
-class nsDisplayCanvasBackgroundColor : public nsDisplayItem {
+class nsDisplayCanvasBackgroundColor : public nsDisplaySolidColorBase {
 public:
   nsDisplayCanvasBackgroundColor(nsDisplayListBuilder* aBuilder, nsIFrame *aFrame)
-    : nsDisplayItem(aBuilder, aFrame)
-    , mColor(NS_RGBA(0,0,0,0))
+    : nsDisplaySolidColorBase(aBuilder, aFrame, NS_RGBA(0,0,0,0))
   {
   }
 
   virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder,
                                  nsRegion* aVisibleRegion) override
   {
     return NS_GET_A(mColor) > 0;
   }
-  virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
-                                   bool* aSnap) override
-  {
-    if (NS_GET_A(mColor) == 255) {
-      return nsRegion(GetBounds(aBuilder, aSnap));
-    }
-    return nsRegion();
-  }
-  virtual bool IsUniform(nsDisplayListBuilder* aBuilder, nscolor* aColor) override
-  {
-    *aColor = mColor;
-    return true;
-  }
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override
   {
     nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
     *aSnap = true;
     return frame->CanvasArea() + ToReferenceFrame();
   }
   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                        HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override
   {
     // We need to override so we don't consider border-radius.
     aOutFrames->AppendElement(mFrame);
   }
 
-  virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
-  {
-    return new nsDisplayItemBoundsGeometry(this, aBuilder);
-  }
-
-  virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
-                                         const nsDisplayItemGeometry* aGeometry,
-                                         nsRegion* aInvalidRegion) override
-  {
-    const nsDisplayItemBoundsGeometry* geometry = static_cast<const nsDisplayItemBoundsGeometry*>(aGeometry);
-    ComputeInvalidationRegionDifference(aBuilder, geometry, aInvalidRegion);
-  }
-
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      nsRenderingContext* aCtx) override;
 
   void SetExtraBackgroundColor(nscolor aColor)
   {
     mColor = aColor;
   }
 
   NS_DISPLAY_DECL_NAME("CanvasBackgroundColor", TYPE_CANVAS_BACKGROUND_COLOR)
 #ifdef MOZ_DUMP_PAINTING
   virtual void WriteDebugInfo(std::stringstream& aStream) override;
 #endif
-
-private:
-  nscolor mColor;
 };
 
 class nsDisplayCanvasBackgroundImage : public nsDisplayBackgroundImage {
 public:
   nsDisplayCanvasBackgroundImage(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                                  uint32_t aLayer, const nsStyleBackground* aBg)
     : nsDisplayBackgroundImage(aBuilder, aFrame, aLayer, aBg)
   {}
--- a/mobile/android/base/GeckoJavaSampler.java
+++ b/mobile/android/base/GeckoJavaSampler.java
@@ -17,16 +17,17 @@ import java.util.Set;
 public class GeckoJavaSampler {
     private static final String LOGTAG = "JavaSampler";
     private static Thread sSamplingThread;
     private static SamplingThread sSamplingRunnable;
     private static Thread sMainThread;
 
     // Use the same timer primitive as the profiler
     // to get a perfect sample syncing.
+    @WrapForJNI
     private static native double getProfilerTime();
 
     private static class Sample {
         public Frame[] mFrames;
         public double mTime;
         public long mJavaTime; // non-zero if Android system time is used
         public Sample(StackTraceElement[] aStack) {
             mFrames = new Frame[aStack.length];
@@ -203,11 +204,8 @@ public class GeckoJavaSampler {
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             sSamplingThread = null;
             sSamplingRunnable = null;
         }
     }
 }
-
-
-
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -512,19 +512,16 @@ pref("media.video-queue.default-size", 1
 
 // The maximum number of queued frames to send to the compositor.
 // By default, send all of them.
 pref("media.video-queue.send-to-compositor-size", 9999);
 
 // Whether to disable the video stats to prevent fingerprinting
 pref("media.video_stats.enabled", true);
 
-// Whether to enable the audio writing APIs on the audio element
-pref("media.audio_data.enabled", false);
-
 // Whether to use async panning and zooming
 pref("layers.async-pan-zoom.enabled", false);
 
 // Whether to enable event region building during painting
 pref("layout.event-regions.enabled", false);
 
 // APZ preferences. For documentation/details on what these prefs do, check
 // gfx/layers/apz/src/AsyncPanZoomController.cpp.
--- a/toolkit/components/gfx/SanityTest.js
+++ b/toolkit/components/gfx/SanityTest.js
@@ -15,17 +15,17 @@ const FRAME_SCRIPT_URL = "chrome://gfxsa
 
 const PAGE_WIDTH=92;
 const PAGE_HEIGHT=166;
 const DRIVER_PREF="sanity-test.driver-version";
 const DEVICE_PREF="sanity-test.device-id";
 const VERSION_PREF="sanity-test.version";
 const DISABLE_VIDEO_PREF="media.hardware-video-decoding.failed";
 const RUNNING_PREF="sanity-test.running";
-const TIMEOUT_SEC=6;
+const TIMEOUT_SEC=20;
 
 // GRAPHICS_SANITY_TEST histogram enumeration values
 const TEST_PASSED=0;
 const TEST_FAILED_RENDER=1;
 const TEST_FAILED_VIDEO=2;
 const TEST_CRASHED=3;
 const TEST_TIMEOUT=4;
 
@@ -215,21 +215,24 @@ var listener = {
       return;
     }
 
     this.win.ownerGlobal.close();
     this.win = null;
     this.utils = null;
     this.canvas = null;
 
-    this.messages.forEach((msgName) => {
-      this.mm.removeMessageListener(msgName, this);
-    });
+    if (this.mm) {
+      // We don't have a MessageManager if onWindowLoaded never fired.
+      this.messages.forEach((msgName) => {
+        this.mm.removeMessageListener(msgName, this);
+      });
 
-    this.mm = null;
+      this.mm = null;
+    }
   
     // Remove the annotation after we've cleaned everything up, to catch any
     // incidental crashes from having performed the sanity test.
     annotateCrashReport(false);
   }
 };
 
 function SanityTest() {}
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -30,24 +30,44 @@
 #include "nsThreadUtils.h"
 #endif
 #include "ProfilerMarkers.h"
 
 #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
   #include "AndroidBridge.h"
 #endif
 
+#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
+#include "GeneratedJNINatives.h"
+#endif
+
 #ifndef SPS_STANDALONE
 #if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_x86_linux)
 # define USE_LUL_STACKWALK
 # include "lul/LulMain.h"
 # include "lul/platform-linux-lul.h"
 #endif
 #endif
 
+#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
+class GeckoJavaSampler : public widget::GeckoJavaSampler::Natives<GeckoJavaSampler>
+{
+private:
+  GeckoJavaSampler();
+
+public:
+  static double GetProfilerTime() {
+    if (!profiler_is_active()) {
+      return 0.0;
+    }
+    return profiler_time();
+  };
+};
+#endif
+
 mozilla::ThreadLocal<PseudoStack *> tlsPseudoStack;
 mozilla::ThreadLocal<GeckoSampler *> tlsTicker;
 mozilla::ThreadLocal<void *> tlsStackTop;
 // We need to track whether we've been initialized otherwise
 // we end up using tlsStack without initializing it.
 // Because tlsStack is totally opaque to us we can't reuse
 // it as the flag itself.
 bool stack_key_initialized;
@@ -468,16 +488,22 @@ void mozilla_sampler_init(void* stackTop
 
   // platform specific initialization
   OS::Startup();
 
 #ifndef SPS_STANDALONE
   set_stderr_callback(mozilla_sampler_log);
 #endif
 
+#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
+  if (mozilla::jni::IsAvailable()) {
+    GeckoJavaSampler::Init();
+  }
+#endif
+
   // We can't open pref so we use an environment variable
   // to know if we should trigger the profiler on startup
   // NOTE: Default
   const char *val = getenv("MOZ_PROFILER_STARTUP");
   if (!val || !*val) {
     return;
   }
 
--- a/tools/profiler/public/PseudoStack.h
+++ b/tools/profiler/public/PseudoStack.h
@@ -271,26 +271,20 @@ public:
     if (mStackPointer == 0) {
       ref();
     }
 
     volatile StackEntry &entry = mStack[mStackPointer];
 
     // Make sure we increment the pointer after the name has
     // been written such that mStack is always consistent.
+    entry.initCppFrame(aStackAddress, line);
     entry.setLabel(aName);
-    entry.setCppFrame(aStackAddress, line);
     MOZ_ASSERT(entry.flags() == js::ProfileEntry::IS_CPP_ENTRY);
-
-    uint32_t uint_category = static_cast<uint32_t>(aCategory);
-    MOZ_ASSERT(
-      uint_category >= static_cast<uint32_t>(js::ProfileEntry::Category::FIRST) &&
-      uint_category <= static_cast<uint32_t>(js::ProfileEntry::Category::LAST));
-
-    entry.setFlag(uint_category);
+    entry.setCategory(aCategory);
 
     // Track if mLabel needs a copy.
     if (aCopy)
       entry.setFlag(js::ProfileEntry::FRAME_LABEL_COPY);
     else
       entry.unsetFlag(js::ProfileEntry::FRAME_LABEL_COPY);
 
     // Prevent the optimizer from re-ordering these instructions
@@ -461,9 +455,8 @@ public:
     int newValue = --mRefCnt;
     if (newValue == 0) {
       delete this;
     }
   }
 };
 
 #endif
-
--- a/widget/android/AndroidJNI.cpp
+++ b/widget/android/AndroidJNI.cpp
@@ -675,31 +675,31 @@ Java_org_mozilla_gecko_GeckoAppShell_com
     return nsWindow::ComputeRenderIntegrity();
 }
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult(JNIEnv* jenv, jclass, jstring filePath, jlong callback)
 {
     class NotifyFilePickerResultRunnable : public nsRunnable {
     public:
-        NotifyFilePickerResultRunnable(nsString& fileDir, long callback) : 
+        NotifyFilePickerResultRunnable(nsString& fileDir, long callback) :
             mFileDir(fileDir), mCallback(callback) {}
 
         NS_IMETHODIMP Run() {
             nsFilePickerCallback* handler = (nsFilePickerCallback*)mCallback;
             handler->handleResult(mFileDir);
             handler->Release();
             return NS_OK;
         }
     private:
         nsString mFileDir;
         long mCallback;
     };
     nsString path = nsJNIString(filePath, jenv);
-    
+
     nsCOMPtr<nsIRunnable> runnable =
         new NotifyFilePickerResultRunnable(path, (long)callback);
     NS_DispatchToMainThread(runnable);
 }
 
 #define MAX_LOCK_ATTEMPTS 10
 
 static bool LockWindowWithRetry(void* window, unsigned char** bits, int* width, int* height, int* format, int* stride)
@@ -762,17 +762,17 @@ Java_org_mozilla_gecko_GeckoAppShell_get
     dstHeight = mozilla::RoundUpPow2(srcHeight);
     dstSize = dstWidth * dstHeight * bpp;
 
     bitsCopy = (unsigned char*)malloc(dstSize);
     bzero(bitsCopy, dstSize);
     for (int i = 0; i < srcHeight; i++) {
         memcpy(bitsCopy + ((dstHeight - i - 1) * dstWidth * bpp), bits + (i * srcStride * bpp), srcStride * bpp);
     }
-    
+
     if (!jSurfaceBitsClass) {
         jSurfaceBitsClass = (jclass)jenv->NewGlobalRef(jenv->FindClass("org/mozilla/gecko/SurfaceBits"));
         jSurfaceBitsCtor = jenv->GetMethodID(jSurfaceBitsClass, "<init>", "()V");
 
         jSurfaceBitsWidth = jenv->GetFieldID(jSurfaceBitsClass, "width", "I");
         jSurfaceBitsHeight = jenv->GetFieldID(jSurfaceBitsClass, "height", "I");
         jSurfaceBitsFormat = jenv->GetFieldID(jSurfaceBitsClass, "format", "I");
         jSurfaceBitsBuffer = jenv->GetFieldID(jSurfaceBitsClass, "buffer", "Ljava/nio/ByteBuffer;");
@@ -847,25 +847,16 @@ Java_org_mozilla_gecko_GeckoAppShell_onS
 }
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_dispatchMemoryPressure(JNIEnv* jenv, jclass)
 {
     NS_DispatchMemoryPressure(MemPressure_New);
 }
 
-NS_EXPORT jdouble JNICALL
-Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime(JNIEnv *jenv, jclass jc)
-{
-  if (!profiler_is_active()) {
-    return 0.0;
-  }
-  return profiler_time();
-}
-
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_gfx_NativePanZoomController_abortAnimation(JNIEnv* env, jobject instance)
 {
     APZCTreeManager *controller = nsWindow::GetAPZCTreeManager();
     if (controller) {
         // TODO: Pass in correct values for presShellId and viewId.
         controller->CancelAnimation(ScrollableLayerGuid(nsWindow::RootLayerTreeId(), 0, 0));
     }
--- a/widget/android/GeneratedJNINatives.h
+++ b/widget/android/GeneratedJNINatives.h
@@ -32,16 +32,31 @@ public:
                 ::template Wrap<&Impl::RequestNativeStack>)
     };
 };
 
 template<class Impl>
 constexpr JNINativeMethod ANRReporter::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>)
+    };
+};
+
+template<class Impl>
+constexpr JNINativeMethod GeckoJavaSampler::Natives<Impl>::methods[];
+
+template<class Impl>
 class GeckoThread::Natives : public mozilla::jni::NativeImpl<GeckoThread, Impl>
 {
 public:
     static constexpr JNINativeMethod methods[] = {
 
         mozilla::jni::MakeNativeMethod<GeckoThread::SpeculativeConnect_t>(
                 mozilla::jni::NativeStub<GeckoThread::SpeculativeConnect_t, Impl>
                 ::template Wrap<&Impl::SpeculativeConnect>)
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -710,16 +710,19 @@ constexpr char GeckoJavaSampler::name[];
 constexpr char GeckoJavaSampler::GetFrameNameJavaProfilingWrapper_t::name[];
 constexpr char GeckoJavaSampler::GetFrameNameJavaProfilingWrapper_t::signature[];
 
 auto GeckoJavaSampler::GetFrameNameJavaProfilingWrapper(int32_t a0, int32_t a1, int32_t a2) -> mozilla::jni::String::LocalRef
 {
     return mozilla::jni::Method<GetFrameNameJavaProfilingWrapper_t>::Call(nullptr, nullptr, a0, a1, a2);
 }
 
+constexpr char GeckoJavaSampler::GetProfilerTime_t::name[];
+constexpr char GeckoJavaSampler::GetProfilerTime_t::signature[];
+
 constexpr char GeckoJavaSampler::GetSampleTimeJavaProfiling_t::name[];
 constexpr char GeckoJavaSampler::GetSampleTimeJavaProfiling_t::signature[];
 
 auto GeckoJavaSampler::GetSampleTimeJavaProfiling(int32_t a0, int32_t a1) -> double
 {
     return mozilla::jni::Method<GetSampleTimeJavaProfiling_t>::Call(nullptr, nullptr, a0, a1);
 }
 
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -1690,16 +1690,31 @@ public:
         static const bool isMultithreaded = true;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     static auto GetFrameNameJavaProfilingWrapper(int32_t, int32_t, int32_t) -> mozilla::jni::String::LocalRef;
 
 public:
+    struct GetProfilerTime_t {
+        typedef GeckoJavaSampler Owner;
+        typedef double ReturnType;
+        typedef double SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "getProfilerTime";
+        static constexpr char signature[] =
+                "()D";
+        static const bool isStatic = true;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+public:
     struct GetSampleTimeJavaProfiling_t {
         typedef GeckoJavaSampler Owner;
         typedef double ReturnType;
         typedef double SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 int32_t> Args;
         static constexpr char name[] = "getSampleTime";
@@ -1796,16 +1811,18 @@ public:
         static const bool isStatic = true;
         static const bool isMultithreaded = true;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     static auto UnpauseJavaProfiling() -> void;
 
+public:
+    template<class Impl> class Natives;
 };
 
 class GeckoThread : public mozilla::jni::Class<GeckoThread>
 {
 public:
     typedef mozilla::jni::Ref<GeckoThread> Ref;
     typedef mozilla::jni::LocalRef<GeckoThread> LocalRef;
     typedef mozilla::jni::GlobalRef<GeckoThread> GlobalRef;
--- a/widget/android/moz.build
+++ b/widget/android/moz.build
@@ -14,16 +14,17 @@ XPIDL_SOURCES += [
 ]
 
 XPIDL_MODULE = 'widget_android'
 
 EXPORTS += [
     'AndroidBridge.h',
     'AndroidJavaWrappers.h',
     'AndroidJNIWrapper.h',
+    'GeneratedJNINatives.h',
     'GeneratedJNIWrappers.h',
 ]
 
 UNIFIED_SOURCES += [
     'AndroidBridge.cpp',
     'AndroidContentController.cpp',
     'AndroidDirectTexture.cpp',
     'AndroidGraphicBuffer.cpp',
--- a/widget/cocoa/TextInputHandler.h
+++ b/widget/cocoa/TextInputHandler.h
@@ -695,38 +695,16 @@ class IMEInputHandler : public TextInput
 public:
   virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget);
 
   virtual void OnFocusChangeInGecko(bool aFocus);
 
   void OnSelectionChange(const IMENotification& aIMENotification);
 
   /**
-   * DispatchCompositionChangeEvent() dispatches a compositionchange event on
-   * mWidget.
-   *
-   * @param aText                 User text input.
-   * @param aAttrString           An NSAttributedString instance which indicates
-   *                              current composition string.
-   * @param aSelectedRange        Current selected range (or caret position).
-   */
-  bool DispatchCompositionChangeEvent(const nsString& aText,
-                                      NSAttributedString* aAttrString,
-                                      NSRange& aSelectedRange);
-
-  /**
-   * DispatchCompositionCommitEvent() dispatches a compositioncommit event or
-   * compositioncommitasis event.  If aCommitString is null, dispatches
-   * compositioncommitasis event.  I.e., if aCommitString is null, this
-   * commits the composition with the last data.  Otherwise, commits the
-   * composition with aCommitString value.
-   */
-  bool DispatchCompositionCommitEvent(const nsAString* aCommitString = nullptr);
-
-  /**
    * SetMarkedText() is a handler of setMarkedText of NSTextInput.
    *
    * @param aAttrString           This mut be an instance of NSAttributedString.
    *                              If the aString parameter to
    *                              [ChildView setMarkedText:setSelectedRange:]
    *                              isn't an instance of NSAttributedString,
    *                              create an NSAttributedString from it and pass
    *                              that instead.
@@ -882,19 +860,18 @@ protected:
    *                              aAttrString instead of current selection.
    */
   void InsertTextAsCommittingComposition(NSAttributedString* aAttrString,
                                          NSRange* aReplacementRange);
 
 private:
   // If mIsIMEComposing is true, the composition string is stored here.
   NSString* mIMECompositionString;
-  // mLastDispatchedCompositionString stores the lastest dispatched composition
-  // string by compositionupdate event.
-  nsString mLastDispatchedCompositionString;
+  // If mIsIMEComposing is true, the start offset of the composition string.
+  uint32_t mIMECompositionStart;
 
   NSRange mMarkedRange;
   NSRange mSelectedRange;
 
   NSRange mRangeForWritingMode; // range within which mWritingMode applies
   mozilla::WritingMode mWritingMode;
 
   bool mIsIMEComposing;
@@ -969,29 +946,53 @@ private:
    * InitCompositionEvent() initializes aCompositionEvent.
    *
    * @param aCompositionEvent     A composition event which you want to
    *                              initialize.
    */
   void InitCompositionEvent(WidgetCompositionEvent& aCompositionEvent);
 
   /**
-   * When a composition starts, OnStartIMEComposition() is called.
+   * DispatchCompositionStartEvent() dispatches a compositionstart event and
+   * initializes the members indicating composition state.
+   *
+   * @return                      true if it can continues handling composition.
+   *                              Otherwise, e.g., canceled by the web page,
+   *                              this returns false.
    */
-  void OnStartIMEComposition();
+  bool DispatchCompositionStartEvent();
 
   /**
-   * When a composition is updated, OnUpdateIMEComposition() is called.
+   * DispatchCompositionChangeEvent() dispatches a compositionchange event on
+   * mWidget and modifies the members indicating composition state.
+   *
+   * @param aText                 User text input.
+   * @param aAttrString           An NSAttributedString instance which indicates
+   *                              current composition string.
+   * @param aSelectedRange        Current selected range (or caret position).
+   *
+   * @return                      true if it can continues handling composition.
+   *                              Otherwise, e.g., canceled by the web page,
+   *                              this returns false.
    */
-  void OnUpdateIMEComposition(NSString* aIMECompositionString);
+  bool DispatchCompositionChangeEvent(const nsString& aText,
+                                      NSAttributedString* aAttrString,
+                                      NSRange& aSelectedRange);
 
   /**
-   * When a composition is finished, OnEndIMEComposition() is called.
+   * DispatchCompositionCommitEvent() dispatches a compositioncommit event or
+   * compositioncommitasis event.  If aCommitString is null, dispatches
+   * compositioncommitasis event.  I.e., if aCommitString is null, this
+   * commits the composition with the last data.  Otherwise, commits the
+   * composition with aCommitString value.
+   *
+   * @return                      true if the widget isn't destroyed.
+   *                              Otherwise, false.
    */
-  void OnEndIMEComposition();
+  bool DispatchCompositionCommitEvent(const nsAString* aCommitString = nullptr);
 
   // The focused IME handler.  Please note that the handler might lost the
   // actual focus by deactivating the application.  If we are active, this
   // must have the actual focused handle.
   // We cannot access to the NSInputManager during we aren't active, so, the
   // focused handler can have an IME transaction even if we are deactive.
   static IMEInputHandler* sFocusedIMEHandler;
 
--- a/widget/cocoa/TextInputHandler.mm
+++ b/widget/cocoa/TextInputHandler.mm
@@ -1,20 +1,20 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 sw=2 et tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "mozilla/ArrayUtils.h"
-
 #include "TextInputHandler.h"
 
 #include "mozilla/Logging.h"
 
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextEvents.h"
 
 #include "nsChildView.h"
 #include "nsObjCExceptions.h"
 #include "nsBidiUtils.h"
 #include "nsToolkit.h"
@@ -2691,67 +2691,167 @@ IMEInputHandler::CreateTextRangeArray(NS
      GetRangeTypeName(range.mRangeType)));
 
   return textRangeArray.forget();
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
 }
 
 bool
+IMEInputHandler::DispatchCompositionStartEvent()
+{
+  MOZ_LOG(gLog, LogLevel::Info,
+    ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+     "mSelectedRange={ location=%llu, length=%llu }, Destroyed()=%s, "
+     "mView=%p, mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
+     this,  SelectedRange().location, mSelectedRange.length,
+     TrueOrFalse(Destroyed()), mView, mWidget,
+     mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
+
+  WidgetCompositionEvent compositionStartEvent(true, eCompositionStart,
+                                               mWidget);
+  InitCompositionEvent(compositionStartEvent);
+
+  NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
+  mIsIMEComposing = true;
+
+  DispatchEvent(compositionStartEvent);
+
+  if (Destroyed()) {
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+       "destroyed by compositionstart event", this));
+    return false;
+  }
+
+  // FYI: compositionstart may cause committing composition by the webapp.
+  if (!mIsIMEComposing) {
+    return false;
+  }
+
+  // FYI: The selection range might have been modified by a compositionstart
+  //      event handler.
+  mIMECompositionStart = SelectedRange().location;
+  return true;
+}
+
+bool
 IMEInputHandler::DispatchCompositionChangeEvent(const nsString& aText,
                                                 NSAttributedString* aAttrString,
                                                 NSRange& aSelectedRange)
 {
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
      "aText=\"%s\", aAttrString=\"%s\", "
-     "aSelectedRange={ location=%llu, length=%llu }, "
-     "Destroyed()=%s",
+     "aSelectedRange={ location=%llu, length=%llu }, Destroyed()=%s, mView=%p, "
+     "mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
      this, NS_ConvertUTF16toUTF8(aText).get(),
      GetCharacters([aAttrString string]),
      aSelectedRange.location, aSelectedRange.length,
-     TrueOrFalse(Destroyed())));
+     TrueOrFalse(Destroyed()), mView, mWidget,
+     mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
 
   NS_ENSURE_TRUE(!Destroyed(), false);
 
+  NS_ASSERTION(mIsIMEComposing, "We're not in composition");
+
   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
 
   WidgetCompositionEvent compositionChangeEvent(true, eCompositionChange,
                                                 mWidget);
   compositionChangeEvent.time = PR_IntervalNow();
   compositionChangeEvent.mData = aText;
   compositionChangeEvent.mRanges =
     CreateTextRangeArray(aAttrString, aSelectedRange);
-  return DispatchEvent(compositionChangeEvent);
+
+  mSelectedRange.location = mIMECompositionStart + aSelectedRange.location;
+  mSelectedRange.length = aSelectedRange.length;
+
+  if (mIMECompositionString) {
+    [mIMECompositionString release];
+  }
+  mIMECompositionString = [[aAttrString string] retain];
+
+  DispatchEvent(compositionChangeEvent);
+
+  if (Destroyed()) {
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+       "destroyed by compositionchange event", this));
+    return false;
+  }
+
+  // FYI: compositionstart may cause committing composition by the webapp.
+  return mIsIMEComposing;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
 }
 
 bool
 IMEInputHandler::DispatchCompositionCommitEvent(const nsAString* aCommitString)
 {
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
-     "aCommitString=0x%p (\"%s\"), Destroyed()=%s",
+     "aCommitString=0x%p (\"%s\"), Destroyed()=%s, mView=%p, mWidget=%p, "
+     "inputContext=%p, mIsIMEComposing=%s",
      this, aCommitString,
      aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : "",
-     TrueOrFalse(Destroyed())));
-
-  if (NS_WARN_IF(Destroyed())) {
+     TrueOrFalse(Destroyed()), mView, mWidget,
+     mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
+
+  NS_ASSERTION(mIsIMEComposing, "We're not in composition");
+
+  nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+  if (!Destroyed()) {
+    EventMessage message =
+      aCommitString ? eCompositionCommit : eCompositionCommitAsIs;
+    WidgetCompositionEvent compositionCommitEvent(true, message, mWidget);
+    compositionCommitEvent.time = PR_IntervalNow();
+    if (aCommitString) {
+      compositionCommitEvent.mData = *aCommitString;
+    }
+
+    // IME may query selection immediately after this, however, in e10s mode,
+    // OnSelectionChange() will be called asynchronously.  Until then, we
+    // should emulate expected selection range if the webapp does nothing.
+    mSelectedRange.location = mIMECompositionStart;
+    if (message == eCompositionCommit) {
+      mSelectedRange.location += compositionCommitEvent.mData.Length();
+    } else if (mIMECompositionString) {
+      nsAutoString commitString;
+      nsCocoaUtils::GetStringForNSString(mIMECompositionString, commitString);
+      mSelectedRange.location += commitString.Length();
+    }
+    mSelectedRange.length = 0;
+
+    DispatchEvent(compositionCommitEvent);
+  }
+
+  mIsIMEComposing = false;
+  mIMECompositionStart = UINT32_MAX;
+  if (mIMECompositionString) {
+    [mIMECompositionString release];
+    mIMECompositionString = nullptr;
+  }
+
+  if (Destroyed()) {
+    MOZ_LOG(gLog, LogLevel::Info,
+      ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+       "destroyed by compositioncommit event", this));
     return false;
   }
 
-  nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
-
-  EventMessage message =
-    aCommitString ? eCompositionCommit : eCompositionCommitAsIs;
-  WidgetCompositionEvent compositionCommitEvent(true, message, mWidget);
-  compositionCommitEvent.time = PR_IntervalNow();
-  if (aCommitString) {
-    compositionCommitEvent.mData = *aCommitString;
-  }
-  return DispatchEvent(compositionCommitEvent);
+  return true;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
 }
 
 void
 IMEInputHandler::InitCompositionEvent(WidgetCompositionEvent& aCompositionEvent)
 {
   aCompositionEvent.time = PR_IntervalNow();
 }
 
@@ -2782,66 +2882,53 @@ IMEInputHandler::InsertTextAsCommittingC
     return;
   }
 
   // First, commit current composition with the latest composition string if the
   // replacement range is different from marked range.
   if (IsIMEComposing() && aReplacementRange &&
       aReplacementRange->location != NSNotFound &&
       !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
-    DispatchCompositionCommitEvent();
-    if (Destroyed()) {
+    if (!DispatchCompositionCommitEvent()) {
       MOZ_LOG(gLog, LogLevel::Info,
         ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
          "destroyed by commiting composition for setting replacement range",
          this));
       return;
     }
-    OnEndIMEComposition();
   }
 
   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
 
   nsString str;
   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
 
   if (!IsIMEComposing()) {
     // If there is no selection and replacement range is specified, set the
     // range as selection.
     if (aReplacementRange && aReplacementRange->location != NSNotFound &&
         !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
       NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
     }
 
-    // XXXmnakano Probably, we shouldn't emulate composition in this case.
-    // I think that we should just fire DOM3 textInput event if we implement it.
-    WidgetCompositionEvent compStart(true, eCompositionStart, mWidget);
-    InitCompositionEvent(compStart);
-
-    DispatchEvent(compStart);
-    if (Destroyed()) {
+    if (!DispatchCompositionStartEvent()) {
       MOZ_LOG(gLog, LogLevel::Info,
         ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
-         "destroyed by compositionstart event", this));
+         "cannot continue handling composition after compositionstart", this));
       return;
     }
-
-    OnStartIMEComposition();
   }
 
-  DispatchCompositionCommitEvent(&str);
-  if (Destroyed()) {
+  if (!DispatchCompositionCommitEvent(&str)) {
     MOZ_LOG(gLog, LogLevel::Info,
       ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
        "destroyed by compositioncommit event", this));
     return;
   }
 
-  OnEndIMEComposition();
-
   mMarkedRange = NSMakeRange(NSNotFound, 0);
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 void
 IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString,
                                NSRange& aSelectedRange,
@@ -2869,28 +2956,25 @@ IMEInputHandler::SetMarkedText(NSAttribu
 
   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
 
   // First, commit current composition with the latest composition string if the
   // replacement range is different from marked range.
   if (IsIMEComposing() && aReplacementRange &&
       aReplacementRange->location != NSNotFound &&
       !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
-    bool ignoreIMECommit = mIgnoreIMECommit;
+    AutoRestore<bool> ignoreIMECommit(mIgnoreIMECommit);
     mIgnoreIMECommit = false;
-    DispatchCompositionCommitEvent();
-    mIgnoreIMECommit = ignoreIMECommit;
-    if (Destroyed()) {
+    if (!DispatchCompositionCommitEvent()) {
       MOZ_LOG(gLog, LogLevel::Info,
         ("%p IMEInputHandler::SetMarkedText, "
          "destroyed by commiting composition for setting replacement range",
          this));
       return;
     }
-    OnEndIMEComposition();
   }
 
   nsString str;
   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
 
   mMarkedRange.length = str.Length();
 
   if (!IsIMEComposing() && !str.IsEmpty()) {
@@ -2898,63 +2982,40 @@ IMEInputHandler::SetMarkedText(NSAttribu
     // range as selection.
     if (aReplacementRange && aReplacementRange->location != NSNotFound &&
         !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
       NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
     }
 
     mMarkedRange.location = SelectedRange().location;
 
-    WidgetCompositionEvent compStart(true, eCompositionStart, mWidget);
-    InitCompositionEvent(compStart);
-
-    DispatchEvent(compStart);
-    if (Destroyed()) {
+    if (!DispatchCompositionStartEvent()) {
       MOZ_LOG(gLog, LogLevel::Info,
-        ("%p IMEInputHandler::SetMarkedText, "
-         "destroyed by compositionstart event", this));
+        ("%p IMEInputHandler::SetMarkedText, cannot continue handling "
+         "composition after dispatching compositionstart", this));
       return;
     }
-
-    OnStartIMEComposition();
-  }
-
-  if (!IsIMEComposing()) {
-    return;
   }
 
   if (!str.IsEmpty()) {
-    OnUpdateIMEComposition([aAttrString string]);
-
-    // Set temprary range for Apple Japanese IME with e10s because
-    // SelectedRange may return invalid range until OnSelectionChange is
-    // called from content process.
-    // This value will be updated by OnSelectionChange soon.
-    mSelectedRange.location = aSelectedRange.location + mMarkedRange.location;
-    mSelectedRange.length = aSelectedRange.length;
-
-    DispatchCompositionChangeEvent(str, aAttrString, aSelectedRange);
-    if (Destroyed()) {
+    if (!DispatchCompositionChangeEvent(str, aAttrString, aSelectedRange)) {
       MOZ_LOG(gLog, LogLevel::Info,
-        ("%p IMEInputHandler::SetMarkedText, "
-         "destroyed by compositionchange event", this));
+        ("%p IMEInputHandler::SetMarkedText, cannot continue handling "
+         "composition after dispatching compositionchange", this));
     }
     return;
   }
 
   // If the composition string becomes empty string, we should commit
   // current composition.
-  DispatchCompositionCommitEvent(&EmptyString());
-  if (Destroyed()) {
+  if (!DispatchCompositionCommitEvent(&EmptyString())) {
     MOZ_LOG(gLog, LogLevel::Info,
       ("%p IMEInputHandler::SetMarkedText, "
        "destroyed by compositioncommit event", this));
-    return;
   }
-  OnEndIMEComposition();
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 NSInteger
 IMEInputHandler::ConversationIdentifier()
 {
   MOZ_LOG(gLog, LogLevel::Info,
@@ -2998,16 +3059,50 @@ IMEInputHandler::GetAttributedSubstringF
   }
 
   if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
     return nil;
   }
 
   nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
 
+  // If we're in composing, the queried range may be in the composition string.
+  // In such case, we should use mIMECompositionString since if the composition
+  // string is handled by a remote process, the content cache may be out of
+  // date.
+  NSUInteger compositionLength =
+    mIMECompositionString ? [mIMECompositionString length] : 0;
+  if (mIMECompositionStart != UINT32_MAX &&
+      mIMECompositionStart >= aRange.location &&
+      mIMECompositionStart + compositionLength <=
+        aRange.location + aRange.length) {
+    NSRange range =
+      NSMakeRange(aRange.location - mIMECompositionStart, aRange.length);
+    NSString* nsstr = [mIMECompositionString substringWithRange:range];
+    NSMutableAttributedString* result =
+      [[[NSMutableAttributedString alloc] initWithString:nsstr
+                                              attributes:nil] autorelease];
+    // XXX We cannot return font information in this case.  However, this
+    //     case must occur only when IME tries to confirm if composing string
+    //     is handled as expected.
+    if (aActualRange) {
+      *aActualRange = aRange;
+    }
+
+    if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+      nsAutoString str;
+      nsCocoaUtils::GetStringForNSString(nsstr, str);
+      MOZ_LOG(gLog, LogLevel::Info,
+        ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
+         "computed with mIMECompositionString (result string=\"%s\")",
+         this, NS_ConvertUTF16toUTF8(str).get()));
+    }
+    return result;
+  }
+
   nsAutoString str;
   WidgetQueryContentEvent textContent(true, eQueryTextContent, mWidget);
   textContent.InitForQueryTextContent(aRange.location, aRange.length);
   textContent.RequestFontRanges();
   DispatchEvent(textContent);
 
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
@@ -3302,22 +3397,27 @@ IMEInputHandler::GetValidAttributesForMa
 
 /******************************************************************************
  *
  *  IMEInputHandler implementation #2
  *
  ******************************************************************************/
 
 IMEInputHandler::IMEInputHandler(nsChildView* aWidget,
-                                 NSView<mozView> *aNativeView) :
-  TextInputHandlerBase(aWidget, aNativeView),
-  mPendingMethods(0), mIMECompositionString(nullptr),
-  mIsIMEComposing(false), mIsIMEEnabled(true),
-  mIsASCIICapableOnly(false), mIgnoreIMECommit(false),
-  mIsInFocusProcessing(false), mIMEHasFocus(false)
+                                 NSView<mozView> *aNativeView)
+  : TextInputHandlerBase(aWidget, aNativeView)
+  , mPendingMethods(0)
+  , mIMECompositionString(nullptr)
+  , mIMECompositionStart(UINT32_MAX)
+  , mIsIMEComposing(false)
+  , mIsIMEEnabled(true)
+  , mIsASCIICapableOnly(false)
+  , mIgnoreIMECommit(false)
+  , mIsInFocusProcessing(false)
+  , mIMEHasFocus(false)
 {
   InitStaticMembers();
 
   mMarkedRange.location = NSNotFound;
   mMarkedRange.length = 0;
   mSelectedRange.location = NSNotFound;
   mSelectedRange.length = 0;
 }
@@ -3326,16 +3426,20 @@ IMEInputHandler::~IMEInputHandler()
 {
   if (mTimer) {
     mTimer->Cancel();
     mTimer = nullptr;
   }
   if (sFocusedIMEHandler == this) {
     sFocusedIMEHandler = nullptr;
   }
+  if (mIMECompositionString) {
+    [mIMECompositionString release];
+    mIMECompositionString = nullptr;
+  }
 }
 
 void
 IMEInputHandler::OnFocusChangeInGecko(bool aFocus)
 {
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, "
      "sFocusedIMEHandler=%p",
@@ -3378,86 +3482,25 @@ IMEInputHandler::OnDestroyWidget(nsChild
 
   if (!TextInputHandlerBase::OnDestroyWidget(aDestroyingWidget)) {
     return false;
   }
 
   if (IsIMEComposing()) {
     // If our view is in the composition, we should clean up it.
     CancelIMEComposition();
-    OnEndIMEComposition();
   }
 
   mSelectedRange.location = NSNotFound; // Marking dirty
   mIMEHasFocus = false;
 
   return true;
 }
 
 void
-IMEInputHandler::OnStartIMEComposition()
-{
-  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
-
-  MOZ_LOG(gLog, LogLevel::Info,
-    ("%p IMEInputHandler::OnStartIMEComposition, mView=%p, mWidget=%p"
-     "inputContext=%p, mIsIMEComposing=%s",
-     this, mView, mWidget, mView ? [mView inputContext] : nullptr,
-     TrueOrFalse(mIsIMEComposing)));
-
-  NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
-  mIsIMEComposing = true;
-
-  NS_OBJC_END_TRY_ABORT_BLOCK;
-}
-
-void
-IMEInputHandler::OnUpdateIMEComposition(NSString* aIMECompositionString)
-{
-  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
-
-  MOZ_LOG(gLog, LogLevel::Info,
-    ("%p IMEInputHandler::OnUpdateIMEComposition, mView=%p, mWidget=%p, "
-     "inputContext=%p, mIsIMEComposing=%s, aIMECompositionString=\"%s\"",
-     this, mView, mWidget, mView ? [mView inputContext] : nullptr,
-     TrueOrFalse(mIsIMEComposing), GetCharacters(aIMECompositionString)));
-
-  NS_ASSERTION(mIsIMEComposing, "We're not in composition");
-
-  if (mIMECompositionString)
-    [mIMECompositionString release];
-  mIMECompositionString = [aIMECompositionString retain];
-
-  NS_OBJC_END_TRY_ABORT_BLOCK;
-}
-
-void
-IMEInputHandler::OnEndIMEComposition()
-{
-  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
-
-  MOZ_LOG(gLog, LogLevel::Info,
-    ("%p IMEInputHandler::OnEndIMEComposition, mView=%p, mWidget=%p, "
-     "inputContext=%p, mIsIMEComposing=%s",
-     this, mView, mWidget, mView ? [mView inputContext] : nullptr,
-     TrueOrFalse(mIsIMEComposing)));
-
-  NS_ASSERTION(mIsIMEComposing, "We're not in composition");
-
-  mIsIMEComposing = false;
-
-  if (mIMECompositionString) {
-    [mIMECompositionString release];
-    mIMECompositionString = nullptr;
-  }
-
-  NS_OBJC_END_TRY_ABORT_BLOCK;
-}
-
-void
 IMEInputHandler::SendCommittedText(NSString *aString)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, "
      "inputContext=%p, mIsIMEComposing=%s",
      this, mView, mWidget, mView ? [mView inputContext] : nullptr,
--- a/widget/gtk/nsAppShell.cpp
+++ b/widget/gtk/nsAppShell.cpp
@@ -111,17 +111,19 @@ nsAppShell::Init()
     GSList* pixbufFormats = gdk_pixbuf_get_formats();
     for (GSList* iter = pixbufFormats; iter; iter = iter->next) {
         GdkPixbufFormat* format = static_cast<GdkPixbufFormat*>(iter->data);
         gchar* name = gdk_pixbuf_format_get_name(format);
         if (strcmp(name, "jpeg") &&
             strcmp(name, "png") &&
             strcmp(name, "gif") &&
             strcmp(name, "bmp") &&
-            strcmp(name, "ico")) {
+            strcmp(name, "ico") &&
+            strcmp(name, "xpm") &&
+            strcmp(name, "svg")) {
           gdk_pixbuf_format_set_disabled(format, TRUE);
         }
         g_free(name);
     }
     g_slist_free(pixbufFormats);
 
     int err = pipe(mPipeFDs);
     if (err)
--- a/widget/windows/TSFTextStore.cpp
+++ b/widget/windows/TSFTextStore.cpp
@@ -811,16 +811,26 @@ public:
        mActiveTIPKeyboardDescription.Equals(
          NS_LITERAL_STRING("Microsoft \xC785\xB825\xAE30")) ||
        mActiveTIPKeyboardDescription.Equals(
          NS_LITERAL_STRING("\x5FAE\x8F6F\x8F93\x5165\x6CD5")) ||
        mActiveTIPKeyboardDescription.Equals(
          NS_LITERAL_STRING("\x5FAE\x8EDF\x8F38\x5165\x6CD5")));
   }
 
+  bool IsMSOfficeJapaneseIME2010Active() const
+  {
+    // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64}
+    static const GUID kGUID = {
+      0x54EDCC94, 0x1524, 0x4BB1,
+        { 0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64 }
+    };
+    return mActiveTIPGUID == kGUID;
+  }
+
   bool IsATOKActive() const
   {
     // FYI: Name of ATOK includes the release year like "ATOK 2015".
     return StringBeginsWith(mActiveTIPKeyboardDescription,
                             NS_LITERAL_STRING("ATOK "));
   }
 
   /****************************************************************************
@@ -914,27 +924,31 @@ private:
 
   nsRefPtr<ITfThreadMgr> mThreadMgr;
   nsRefPtr<ITfInputProcessorProfiles> mInputProcessorProfiles;
 
   // Active TIP keyboard's description.  If active language profile isn't TIP,
   // i.e., IMM-IME or just a keyboard layout, this is empty.
   nsString mActiveTIPKeyboardDescription;
 
+  // Active TIP's GUID
+  GUID mActiveTIPGUID;
+
   static StaticRefPtr<TSFStaticSink> sInstance;
 };
 
 StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance;
 
 TSFStaticSink::TSFStaticSink()
   : mIPProfileCookie(TF_INVALID_COOKIE)
   , mLangProfileCookie(TF_INVALID_COOKIE)
   , mLangID(0)
   , mIsIMM_IME(false)
   , mOnActivatedCalled(false)
+  , mActiveTIPGUID(GUID_NULL)
 {
 }
 
 bool
 TSFStaticSink::Init(ITfThreadMgr* aThreadMgr,
                     ITfInputProcessorProfiles* aInputProcessorProfiles)
 {
   MOZ_ASSERT(!mThreadMgr && !mInputProcessorProfiles,
@@ -1039,16 +1053,17 @@ TSFStaticSink::Destroy()
 STDMETHODIMP
 TSFStaticSink::OnActivated(REFCLSID clsid, REFGUID guidProfile,
                            BOOL fActivated)
 {
   // NOTE: This is installed only on XP or Server 2003.
   if (fActivated) {
     // TODO: We should check if the profile's category is keyboard or not.
     mOnActivatedCalled = true;
+    mActiveTIPGUID = guidProfile;
     mIsIMM_IME = IsIMM_IME(::GetKeyboardLayout(0));
 
     HRESULT hr = mInputProcessorProfiles->GetCurrentLanguage(&mLangID);
     if (FAILED(hr)) {
       MOZ_LOG(sTextStoreLog, LogLevel::Error,
              ("TSF: TSFStaticSink::OnActivated() FAILED due to "
               "GetCurrentLanguage() failure, hr=0x%08X", hr));
     } else if (IsTIPCategoryKeyboard(clsid, mLangID, guidProfile)) {
@@ -1081,16 +1096,17 @@ TSFStaticSink::OnActivated(DWORD dwProfi
 {
   // NOTE: This is installed only on Vista or later.  However, this may be
   //       called by EnsureInitActiveLanguageProfile() even on XP or Server
   //       2003.
   if ((dwFlags & TF_IPSINK_FLAG_ACTIVE) &&
       (dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT ||
        catid == GUID_TFCAT_TIP_KEYBOARD)) {
     mOnActivatedCalled = true;
+    mActiveTIPGUID = guidProfile;
     mLangID = langid;
     mIsIMM_IME = IsIMM_IME(hkl);
     GetTIPDescription(rclsid, mLangID, guidProfile,
                       mActiveTIPKeyboardDescription);
   }
   MOZ_LOG(sTextStoreLog, LogLevel::Info,
          ("TSF: 0x%p TSFStaticSink::OnActivated(dwProfileType=%s (0x%08X), "
           "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%08X, "
@@ -3502,43 +3518,52 @@ TSFTextStore::GetTextExt(TsViewCookie vc
   // NOTE: TSF (at least on Win 8.1) doesn't return TS_E_NOLAYOUT to the
   // caller even if we return it.  It's converted to just E_FAIL.
   // However, this is fixed on Win 10.
 
   const TSFStaticSink* kSink = TSFStaticSink::GetInstance();
   if (mComposition.IsComposing() && mComposition.mStart < acpEnd &&
       mLockedContent.IsLayoutChangedAfter(acpEnd)) {
     const Selection& currentSel = CurrentSelection();
-    if ((sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar ||
-         sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret) &&
-        kSink->IsMSJapaneseIMEActive()) {
+    // The bug of Microsoft Office IME 2010 for Japanese is similar to
+    // MS-IME for Win 8.1 and Win 10.  Newer version of MS Office IME is not
+    // released yet.  So, we can hack it without prefs  because there must be
+    // no developers who want to disable this hack for tests.
+    const bool kIsMSOfficeJapaneseIME2010 =
+      kSink->IsMSOfficeJapaneseIME2010Active();
+    if (kIsMSOfficeJapaneseIME2010 ||
+        ((sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar ||
+          sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret) &&
+         kSink->IsMSJapaneseIMEActive())) {
       // MS IME for Japanese doesn't support asynchronous handling at deciding
       // its suggest list window position.  The feature was implemented
       // starting from Windows 8.
-      if (IsWin8OrLater()) {
+      if (IsWin8OrLater() || kIsMSOfficeJapaneseIME2010) {
         // Basically, MS-IME tries to retrieve whole composition string rect
         // at deciding suggest window immediately after unlocking the document.
         // However, in e10s mode, the content hasn't updated yet in most cases.
         // Therefore, if the first character at the retrieving range rect is
         // available, we should use it as the result.
-        if (sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar &&
+        if ((kIsMSOfficeJapaneseIME2010 ||
+             sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar) &&
             !mLockedContent.IsLayoutChangedAfter(acpStart) &&
             acpStart < acpEnd) {
           acpEnd = acpStart;
           MOZ_LOG(sTextStoreLog, LogLevel::Debug,
                  ("TSF: 0x%p   TSFTextStore::GetTextExt() hacked the offsets "
                   "of the first character of changing range of the composition "
                   "string for TIP acpStart=%d, acpEnd=%d",
                   this, acpStart, acpEnd));
         }
         // Although, the condition is not clear, MS-IME sometimes retrieves the
         // caret rect immediately after modifying the composition string but
         // before unlocking the document.  In such case, we should return the
         // nearest character rect.
-        else if (sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret &&
+        else if ((kIsMSOfficeJapaneseIME2010 ||
+                  sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret) &&
                  acpStart == acpEnd &&
                  currentSel.IsCollapsed() && currentSel.EndOffset() == acpEnd) {
           acpEnd = acpStart = mLockedContent.MinOffsetOfLayoutChanged();
           MOZ_LOG(sTextStoreLog, LogLevel::Debug,
                  ("TSF: 0x%p   TSFTextStore::GetTextExt() hacked the offsets "
                   "of the caret of the composition string for TIP acpStart=%d, "
                   "acpEnd=%d", this, acpStart, acpEnd));
         }
--- a/xpcom/base/ErrorList.h
+++ b/xpcom/base/ErrorList.h
@@ -783,16 +783,17 @@
   ERROR(NS_ERROR_XPATH_BAD_BANG,                      FAILURE(24)),
   ERROR(NS_ERROR_XPATH_ILLEGAL_CHAR,                  FAILURE(25)),
   ERROR(NS_ERROR_XPATH_BINARY_EXPECTED,               FAILURE(26)),
   ERROR(NS_ERROR_XSLT_LOAD_BLOCKED_ERROR,             FAILURE(27)),
   ERROR(NS_ERROR_XPATH_INVALID_EXPRESSION_EVALUATED,  FAILURE(28)),
   ERROR(NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE,        FAILURE(29)),
   ERROR(NS_ERROR_XSLT_BAD_NODE_NAME,                  FAILURE(30)),
   ERROR(NS_ERROR_XSLT_VAR_ALREADY_SET,                FAILURE(31)),
+  ERROR(NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED,        FAILURE(32)),
 
   ERROR(NS_XSLT_GET_NEW_HANDLER,  SUCCESS(1)),
 #undef MODULE
 
 
   /* ======================================================================= */
   /* 28: NS_ERROR_MODULE_IPC */
   /* ======================================================================= */