Merge m-i to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 19 Sep 2015 21:07:50 -0700
changeset 296045 ccd6b5f5e544c1d708849144943a776941bd3794
parent 295987 e035c0741781683d6f7ae0389adf7f724264e565 (current diff)
parent 296044 c0d3f6e2a3e33f8b45bf4039126cbbe004448e3e (diff)
child 296046 b275a63010de18c1fd5d522c0801239ef16f40b5
child 296061 d1ae9771dd0619c82f1f30d4a69f3b177dc742a0
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
ccd6b5f5e544 / 43.0a1 / 20150920030217 / files
nightly linux64
ccd6b5f5e544 / 43.0a1 / 20150920030217 / files
nightly mac
ccd6b5f5e544 / 43.0a1 / 20150920030217 / files
nightly win32
ccd6b5f5e544 / 43.0a1 / 20150920030217 / files
nightly win64
ccd6b5f5e544 / 43.0a1 / 20150920030217 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-i to m-c, a=merge
dom/push/PushServiceHttp2Crypto.jsm
dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys.js
image/test/reftest/ico/ico-bmp-corrupted/invalid_ico_height.ico
image/test/reftest/ico/ico-bmp-corrupted/invalid_ico_width.ico
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -57,17 +57,16 @@ pref("browser.cache.disk.smart_size.firs
 
 pref("browser.cache.memory.enable", true);
 pref("browser.cache.memory.capacity", 1024); // kilobytes
 
 pref("browser.cache.memory_limit", 2048); // 2 MB
 
 /* image cache prefs */
 pref("image.cache.size", 1048576); // bytes
-pref("image.high_quality_downscaling.enabled", false);
 pref("canvas.image.cache.limit", 20971520); // 20 MB
 
 /* offline cache prefs */
 pref("browser.offline-apps.notify", false);
 pref("browser.cache.offline.enable", true);
 pref("offline-apps.allow_by_default", true);
 
 /* protocol warning prefs */
--- 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());
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -265,19 +265,16 @@ private:
 
   DECL_GFX_PREF(Live, "gl.msaa-level",                         MSAALevel, uint32_t, 2);
   DECL_GFX_PREF(Live, "gl.require-hardware",                   RequireHardwareGL, bool, false);
 
   DECL_GFX_PREF(Once, "image.cache.size",                      ImageCacheSize, int32_t, 5*1024*1024);
   DECL_GFX_PREF(Once, "image.cache.timeweight",                ImageCacheTimeWeight, int32_t, 500);
   DECL_GFX_PREF(Live, "image.decode-immediately.enabled",      ImageDecodeImmediatelyEnabled, bool, false);
   DECL_GFX_PREF(Live, "image.downscale-during-decode.enabled", ImageDownscaleDuringDecodeEnabled, bool, true);
-  DECL_GFX_PREF(Live, "image.high_quality_downscaling.enabled", ImageHQDownscalingEnabled, bool, false);
-  DECL_GFX_PREF(Live, "image.high_quality_downscaling.min_factor", ImageHQDownscalingMinFactor, uint32_t, 1000);
-  DECL_GFX_PREF(Live, "image.high_quality_upscaling.max_size", ImageHQUpscalingMaxSize, uint32_t, 20971520);
   DECL_GFX_PREF(Live, "image.infer-src-animation.threshold-ms", ImageInferSrcAnimationThresholdMS, uint32_t, 2000);
   DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
   DECL_GFX_PREF(Live, "image.mem.discardable",                 ImageMemDiscardable, bool, false);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb",    ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor",    ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
   DECL_GFX_PREF(Live, "image.mozsamplesize.enabled",           ImageMozSampleSizeEnabled, bool, false);
--- a/image/ClippedImage.cpp
+++ b/image/ClippedImage.cpp
@@ -215,16 +215,26 @@ ClippedImage::GetIntrinsicRatio(nsSize* 
 
 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
 ClippedImage::GetFrame(uint32_t aWhichFrame,
                        uint32_t aFlags)
 {
   return GetFrameInternal(mClip.Size(), Nothing(), aWhichFrame, aFlags);
 }
 
+NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
+ClippedImage::GetFrameAtSize(const IntSize& aSize,
+                             uint32_t aWhichFrame,
+                             uint32_t aFlags)
+{
+  // XXX(seth): It'd be nice to support downscale-during-decode for this case,
+  // but right now we just fall back to the intrinsic size.
+  return GetFrame(aWhichFrame, aFlags);
+}
+
 already_AddRefed<SourceSurface>
 ClippedImage::GetFrameInternal(const nsIntSize& aSize,
                                const Maybe<SVGImageContext>& aSVGContext,
                                uint32_t aWhichFrame,
                                uint32_t aFlags)
 {
   if (!ShouldClip()) {
     return InnerImage()->GetFrame(aWhichFrame, aFlags);
--- a/image/ClippedImage.h
+++ b/image/ClippedImage.h
@@ -32,16 +32,20 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_IMETHOD GetWidth(int32_t* aWidth) override;
   NS_IMETHOD GetHeight(int32_t* aHeight) override;
   NS_IMETHOD GetIntrinsicSize(nsSize* aSize) override;
   NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override;
   NS_IMETHOD_(already_AddRefed<SourceSurface>)
     GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
+  NS_IMETHOD_(already_AddRefed<SourceSurface>)
+    GetFrameAtSize(const gfx::IntSize& aSize,
+                   uint32_t aWhichFrame,
+                   uint32_t aFlags) override;
   NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager,
                                               uint32_t aFlags) override;
   NS_IMETHOD_(already_AddRefed<layers::ImageContainer>)
     GetImageContainer(layers::LayerManager* aManager,
                       uint32_t aFlags) override;
   NS_IMETHOD_(DrawResult) Draw(gfxContext* aContext,
                                const nsIntSize& aSize,
                                const ImageRegion& aRegion,
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -240,16 +240,30 @@ Decoder::CompleteDecode()
         !(mDecoderFlags & DecoderFlags::IMAGE_IS_TRANSIENT) &&
         mCurrentFrame) {
       mCurrentFrame->SetOptimizable();
     }
   }
 }
 
 nsresult
+Decoder::SetTargetSize(const nsIntSize& aSize)
+{
+  // Make sure the size is reasonable.
+  if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Create a downscaler that we'll filter our output through.
+  mDownscaler.emplace(aSize);
+
+  return NS_OK;
+}
+
+nsresult
 Decoder::AllocateFrame(uint32_t aFrameNum,
                        const nsIntSize& aTargetSize,
                        const nsIntRect& aFrameRect,
                        gfx::SurfaceFormat aFormat,
                        uint8_t aPaletteDepth)
 {
   mCurrentFrame = AllocateFrameInternal(aFrameNum, aTargetSize, aFrameRect,
                                         aFormat, aPaletteDepth,
@@ -319,18 +333,17 @@ Decoder::AllocateFrameInternal(uint32_t 
     return RawAccessFrameRef();
   }
 
   if (ShouldUseSurfaceCache()) {
     InsertOutcome outcome =
       SurfaceCache::Insert(frame, ImageKey(mImage.get()),
                            RasterSurfaceKey(aTargetSize,
                                             mSurfaceFlags,
-                                            aFrameNum),
-                           Lifetime::Persistent);
+                                            aFrameNum));
     if (outcome == InsertOutcome::FAILURE) {
       // We couldn't insert the surface, almost certainly due to low memory. We
       // treat this as a permanent error to help the system recover; otherwise,
       // we might just end up attempting to decode this image again immediately.
       ref->Abort();
       return RawAccessFrameRef();
     } else if (outcome == InsertOutcome::FAILURE_ALREADY_PRESENT) {
       // Another decoder beat us to decoding this frame. We abort this decoder
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -6,16 +6,17 @@
 #ifndef mozilla_image_Decoder_h
 #define mozilla_image_Decoder_h
 
 #include "FrameAnimator.h"
 #include "RasterImage.h"
 #include "mozilla/RefPtr.h"
 #include "DecodePool.h"
 #include "DecoderFlags.h"
+#include "Downscaler.h"
 #include "ImageMetadata.h"
 #include "Orientation.h"
 #include "SourceBuffer.h"
 #include "SurfaceFlags.h"
 
 namespace mozilla {
 
 namespace Telemetry {
@@ -107,29 +108,24 @@ public:
     mMetadataDecode = aMetadataDecode;
   }
   bool IsMetadataDecode() const { return mMetadataDecode; }
 
   /**
    * If this decoder supports downscale-during-decode, sets the target size that
    * this image should be decoded to.
    *
-   * If this decoder *doesn't* support downscale-during-decode, returns
-   * NS_ERROR_NOT_AVAILABLE. If the provided size is unacceptable, returns
-   * another error.
+   * If the provided size is unacceptable, an error is returned.
    *
    * Returning NS_OK from this method is a promise that the decoder will decode
    * the image to the requested target size unless it encounters an error.
    *
    * This must be called before Init() is called.
    */
-  virtual nsresult SetTargetSize(const nsIntSize& aSize)
-  {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
+  nsresult SetTargetSize(const nsIntSize& aSize);
 
   /**
    * Set the requested sample size for this decoder. Used to implement the
    * -moz-sample-size media fragment.
    *
    *  XXX(seth): Support for -moz-sample-size will be removed in bug 1120056.
    */
   virtual void SetSampleSize(int aSampleSize) { }
@@ -398,16 +394,18 @@ protected:
   RawAccessFrameRef AllocateFrameInternal(uint32_t aFrameNum,
                                           const nsIntSize& aTargetSize,
                                           const nsIntRect& aFrameRect,
                                           gfx::SurfaceFormat aFormat,
                                           uint8_t aPaletteDepth,
                                           imgFrame* aPreviousFrame);
 
 protected:
+  Maybe<Downscaler> mDownscaler;
+
   uint8_t* mImageData;  // Pointer to image data in either Cairo or 8bit format
   uint32_t mImageDataLength;
   uint32_t* mColormap;  // Current colormap to be used in Cairo format
   uint32_t mColormapSize;
 
 private:
   nsRefPtr<RasterImage> mImage;
   Maybe<SourceBufferIterator> mIterator;
--- a/image/DecoderFactory.cpp
+++ b/image/DecoderFactory.cpp
@@ -128,18 +128,16 @@ DecoderFactory::CreateDecoder(DecoderTyp
   decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::FIRST_FRAME_ONLY);
   decoder->SetSurfaceFlags(aSurfaceFlags);
   decoder->SetSampleSize(aSampleSize);
   decoder->SetResolution(aResolution);
 
   // Set a target size for downscale-during-decode if applicable.
   if (aTargetSize) {
     DebugOnly<nsresult> rv = decoder->SetTargetSize(*aTargetSize);
-    MOZ_ASSERT(nsresult(rv) != NS_ERROR_NOT_AVAILABLE,
-               "We're downscale-during-decode but decoder doesn't support it?");
     MOZ_ASSERT(NS_SUCCEEDED(rv), "Bad downscale-during-decode target size?");
   }
 
   decoder->Init();
   if (NS_FAILED(decoder->GetDecoderError())) {
     return nullptr;
   }
 
--- a/image/DynamicImage.cpp
+++ b/image/DynamicImage.cpp
@@ -163,28 +163,36 @@ DynamicImage::GetAnimated(bool* aAnimate
   return NS_OK;
 }
 
 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
 DynamicImage::GetFrame(uint32_t aWhichFrame,
                        uint32_t aFlags)
 {
   gfxIntSize size(mDrawable->Size());
+  return GetFrameAtSize(IntSize(size.width, size.height),
+                        aWhichFrame,
+                        aFlags);
+}
 
+NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
+DynamicImage::GetFrameAtSize(const IntSize& aSize,
+                             uint32_t aWhichFrame,
+                             uint32_t aFlags)
+{
   RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
-    CreateOffscreenContentDrawTarget(IntSize(size.width, size.height),
-                                     SurfaceFormat::B8G8R8A8);
+    CreateOffscreenContentDrawTarget(aSize, SurfaceFormat::B8G8R8A8);
   if (!dt) {
     gfxWarning() <<
       "DynamicImage::GetFrame failed in CreateOffscreenContentDrawTarget";
     return nullptr;
   }
   nsRefPtr<gfxContext> context = new gfxContext(dt);
 
-  auto result = Draw(context, size, ImageRegion::Create(size),
+  auto result = Draw(context, aSize, ImageRegion::Create(aSize),
                      aWhichFrame, GraphicsFilter::FILTER_NEAREST,
                      Nothing(), aFlags);
 
   return result == DrawResult::SUCCESS ? dt->Snapshot() : nullptr;
 }
 
 NS_IMETHODIMP_(bool)
 DynamicImage::IsOpaque()
--- a/image/FrozenImage.cpp
+++ b/image/FrozenImage.cpp
@@ -39,16 +39,24 @@ FrozenImage::GetAnimated(bool* aAnimated
 
 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
 FrozenImage::GetFrame(uint32_t aWhichFrame,
                       uint32_t aFlags)
 {
   return InnerImage()->GetFrame(FRAME_FIRST, aFlags);
 }
 
+NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
+FrozenImage::GetFrameAtSize(const IntSize& aSize,
+                            uint32_t aWhichFrame,
+                            uint32_t aFlags)
+{
+  return InnerImage()->GetFrameAtSize(aSize, FRAME_FIRST, aFlags);
+}
+
 NS_IMETHODIMP_(bool)
 FrozenImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
 {
   return false;
 }
 
 NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
 FrozenImage::GetImageContainer(layers::LayerManager* aManager, uint32_t aFlags)
--- a/image/FrozenImage.h
+++ b/image/FrozenImage.h
@@ -32,16 +32,20 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual void IncrementAnimationConsumers() override;
   virtual void DecrementAnimationConsumers() override;
 
   NS_IMETHOD GetAnimated(bool* aAnimated) override;
   NS_IMETHOD_(already_AddRefed<SourceSurface>)
     GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
+  NS_IMETHOD_(already_AddRefed<SourceSurface>)
+    GetFrameAtSize(const gfx::IntSize& aSize,
+                   uint32_t aWhichFrame,
+                   uint32_t aFlags) override;
   NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager,
                                               uint32_t aFlags) override;
   NS_IMETHOD_(already_AddRefed<layers::ImageContainer>)
     GetImageContainer(layers::LayerManager* aManager,
                       uint32_t aFlags) override;
   NS_IMETHOD_(DrawResult) Draw(gfxContext* aContext,
                                const nsIntSize& aSize,
                                const ImageRegion& aRegion,
--- a/image/Image.h
+++ b/image/Image.h
@@ -142,29 +142,24 @@ public:
    * possible, regardless of what our heuristics say.
    *
    * INIT_FLAG_TRANSIENT: The container is likely to exist for only a short time
    * before being destroyed. (For example, containers for
    * multipart/x-mixed-replace image parts fall into this category.) If this
    * flag is set, INIT_FLAG_DISCARDABLE and INIT_FLAG_DECODE_ONLY_ON_DRAW must
    * not be set.
    *
-   * INIT_FLAG_DOWNSCALE_DURING_DECODE: The container should attempt to
-   * downscale images during decoding instead of decoding them to their
-   * intrinsic size.
-   *
    * INIT_FLAG_SYNC_LOAD: The container is being loaded synchronously, so
    * it should avoid relying on async workers to get the container ready.
    */
   static const uint32_t INIT_FLAG_NONE                     = 0x0;
   static const uint32_t INIT_FLAG_DISCARDABLE              = 0x1;
   static const uint32_t INIT_FLAG_DECODE_IMMEDIATELY       = 0x2;
   static const uint32_t INIT_FLAG_TRANSIENT                = 0x4;
-  static const uint32_t INIT_FLAG_DOWNSCALE_DURING_DECODE  = 0x8;
-  static const uint32_t INIT_FLAG_SYNC_LOAD                = 0x10;
+  static const uint32_t INIT_FLAG_SYNC_LOAD                = 0x8;
 
   virtual already_AddRefed<ProgressTracker> GetProgressTracker() = 0;
   virtual void SetProgressTracker(ProgressTracker* aProgressTracker) {}
 
   /**
    * The size, in bytes, occupied by the compressed source data of the image.
    * If MallocSizeOf does not work on this platform, uses a fallback approach to
    * ensure that something reasonable is always returned.
@@ -212,18 +207,17 @@ public:
    * @param aLastPart Whether this is the final part of the underlying request.
    */
   virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
                                        nsISupports* aContext,
                                        nsresult aStatus,
                                        bool aLastPart) = 0;
 
   /**
-   * Called when the SurfaceCache discards a persistent surface belonging to
-   * this image.
+   * Called when the SurfaceCache discards a surface belonging to this image.
    */
   virtual void OnSurfaceDiscarded() = 0;
 
   virtual void SetInnerWindowID(uint64_t aInnerWindowId) = 0;
   virtual uint64_t InnerWindowID() const = 0;
 
   virtual bool HasError() = 0;
   virtual void SetHasError() = 0;
--- a/image/ImageFactory.cpp
+++ b/image/ImageFactory.cpp
@@ -27,77 +27,57 @@
 
 namespace mozilla {
 namespace image {
 
 /*static*/ void
 ImageFactory::Initialize()
 { }
 
-static bool
-ShouldDownscaleDuringDecode(const nsCString& aMimeType)
-{
-  DecoderType type = DecoderFactory::GetDecoderType(aMimeType.get());
-  return type == DecoderType::JPEG ||
-         type == DecoderType::ICON ||
-         type == DecoderType::PNG ||
-         type == DecoderType::BMP ||
-         type == DecoderType::GIF;
-}
-
 static uint32_t
 ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart)
 {
   nsresult rv;
 
   // We default to the static globals.
   bool isDiscardable = gfxPrefs::ImageMemDiscardable();
   bool doDecodeImmediately = gfxPrefs::ImageDecodeImmediatelyEnabled();
-  bool doDownscaleDuringDecode = gfxPrefs::ImageDownscaleDuringDecodeEnabled();
 
   // We want UI to be as snappy as possible and not to flicker. Disable
   // discarding for chrome URLS.
   bool isChrome = false;
   rv = uri->SchemeIs("chrome", &isChrome);
   if (NS_SUCCEEDED(rv) && isChrome) {
     isDiscardable = false;
   }
 
   // We don't want resources like the "loading" icon to be discardable either.
   bool isResource = false;
   rv = uri->SchemeIs("resource", &isResource);
   if (NS_SUCCEEDED(rv) && isResource) {
     isDiscardable = false;
   }
 
-  // Downscale-during-decode is only enabled for certain content types.
-  if (doDownscaleDuringDecode && !ShouldDownscaleDuringDecode(aMimeType)) {
-    doDownscaleDuringDecode = false;
-  }
-
   // For multipart/x-mixed-replace, we basically want a direct channel to the
   // decoder. Disable everything for this case.
   if (isMultiPart) {
-    isDiscardable = doDownscaleDuringDecode = false;
+    isDiscardable = false;
   }
 
   // We have all the information we need.
   uint32_t imageFlags = Image::INIT_FLAG_NONE;
   if (isDiscardable) {
     imageFlags |= Image::INIT_FLAG_DISCARDABLE;
   }
   if (doDecodeImmediately) {
     imageFlags |= Image::INIT_FLAG_DECODE_IMMEDIATELY;
   }
   if (isMultiPart) {
     imageFlags |= Image::INIT_FLAG_TRANSIENT;
   }
-  if (doDownscaleDuringDecode) {
-    imageFlags |= Image::INIT_FLAG_DOWNSCALE_DURING_DECODE;
-  }
 
   return imageFlags;
 }
 
 /* static */ already_AddRefed<Image>
 ImageFactory::CreateImage(nsIRequest* aRequest,
                           ProgressTracker* aProgressTracker,
                           const nsCString& aMimeType,
--- a/image/ImageWrapper.cpp
+++ b/image/ImageWrapper.cpp
@@ -169,16 +169,24 @@ ImageWrapper::GetAnimated(bool* aAnimate
 
 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
 ImageWrapper::GetFrame(uint32_t aWhichFrame,
                        uint32_t aFlags)
 {
   return mInnerImage->GetFrame(aWhichFrame, aFlags);
 }
 
+NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
+ImageWrapper::GetFrameAtSize(const IntSize& aSize,
+                             uint32_t aWhichFrame,
+                             uint32_t aFlags)
+{
+  return mInnerImage->GetFrameAtSize(aSize, aWhichFrame, aFlags);
+}
+
 NS_IMETHODIMP_(bool)
 ImageWrapper::IsOpaque()
 {
   return mInnerImage->IsOpaque();
 }
 
 NS_IMETHODIMP_(bool)
 ImageWrapper::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
--- a/image/OrientedImage.cpp
+++ b/image/OrientedImage.cpp
@@ -117,16 +117,26 @@ OrientedImage::GetFrame(uint32_t aWhichF
   ctx->Multiply(OrientationMatrix(size));
   gfxUtils::DrawPixelSnapped(ctx, drawable, size,
                              ImageRegion::Create(size),
                              surfaceFormat, GraphicsFilter::FILTER_FAST);
 
   return target->Snapshot();
 }
 
+NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
+OrientedImage::GetFrameAtSize(const IntSize& aSize,
+                              uint32_t aWhichFrame,
+                              uint32_t aFlags)
+{
+  // XXX(seth): It'd be nice to support downscale-during-decode for this case,
+  // but right now we just fall back to the intrinsic size.
+  return GetFrame(aWhichFrame, aFlags);
+}
+
 NS_IMETHODIMP_(bool)
 OrientedImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
 {
   if (mOrientation.IsIdentity()) {
     return InnerImage()->IsImageContainerAvailable(aManager, aFlags);
   }
   return false;
 }
--- a/image/OrientedImage.h
+++ b/image/OrientedImage.h
@@ -29,16 +29,20 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_IMETHOD GetWidth(int32_t* aWidth) override;
   NS_IMETHOD GetHeight(int32_t* aHeight) override;
   NS_IMETHOD GetIntrinsicSize(nsSize* aSize) override;
   NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override;
   NS_IMETHOD_(already_AddRefed<SourceSurface>)
     GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
+  NS_IMETHOD_(already_AddRefed<SourceSurface>)
+    GetFrameAtSize(const gfx::IntSize& aSize,
+                   uint32_t aWhichFrame,
+                   uint32_t aFlags) override;
   NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager,
                                               uint32_t aFlags) override;
   NS_IMETHOD_(already_AddRefed<layers::ImageContainer>)
     GetImageContainer(layers::LayerManager* aManager,
                       uint32_t aFlags) override;
   NS_IMETHOD_(DrawResult) Draw(gfxContext* aContext,
                                const nsIntSize& aSize,
                                const ImageRegion& aRegion,
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -60,152 +60,16 @@ namespace image {
 
 using std::ceil;
 using std::min;
 
 // The maximum number of times any one RasterImage was decoded.  This is only
 // used for statistics.
 static int32_t sMaxDecodeCount = 0;
 
-class ScaleRunner : public nsRunnable
-{
-  enum ScaleState
-  {
-    eNew,
-    eReady,
-    eFinish,
-    eFinishWithError
-  };
-
-public:
-  ScaleRunner(RasterImage* aImage,
-              uint32_t aImageFlags,
-              const IntSize& aSize,
-              RawAccessFrameRef&& aSrcRef)
-    : mImage(aImage)
-    , mSrcRef(Move(aSrcRef))
-    , mDstSize(aSize)
-    , mImageFlags(aImageFlags)
-    , mState(eNew)
-  {
-    MOZ_ASSERT(!mSrcRef->GetIsPaletted());
-    MOZ_ASSERT(aSize.width > 0 && aSize.height > 0);
-  }
-
-  bool Init()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mState == eNew, "Calling Init() twice?");
-
-    // We'll need a destination frame. It's unconditionally ARGB32 because
-    // that's what the scaler outputs.
-    nsRefPtr<imgFrame> tentativeDstFrame = new imgFrame();
-    nsresult rv =
-      tentativeDstFrame->InitForDecoder(mDstSize, SurfaceFormat::B8G8R8A8);
-    if (NS_FAILED(rv)) {
-      return false;
-    }
-
-    // We need a strong reference to the raw data for the destination frame.
-    // (We already got one for the source frame in the constructor.)
-    RawAccessFrameRef tentativeDstRef = tentativeDstFrame->RawAccessRef();
-    if (!tentativeDstRef) {
-      return false;
-    }
-
-    // Everything worked, so commit to these objects and mark ourselves ready.
-    mDstRef = Move(tentativeDstRef);
-    mState = eReady;
-
-    // Insert the new surface into the cache immediately. We need to do this so
-    // that we won't start multiple scaling jobs for the same size.
-    SurfaceCache::Insert(mDstRef.get(), ImageKey(mImage.get()),
-                         RasterSurfaceKey(mDstSize,
-                                          ToSurfaceFlags(mImageFlags),
-                                          /* aFrameNum = */ 0),
-                         Lifetime::Transient);
-
-    return true;
-  }
-
-  NS_IMETHOD Run() override
-  {
-    if (mState == eReady) {
-      // Collect information from the frames that we need to scale.
-      ScalingData srcData = mSrcRef->GetScalingData();
-      ScalingData dstData = mDstRef->GetScalingData();
-
-      // Actually do the scaling.
-      bool succeeded =
-        gfx::Scale(srcData.mRawData, srcData.mSize.width, srcData.mSize.height,
-                   srcData.mBytesPerRow, dstData.mRawData, mDstSize.width,
-                   mDstSize.height, dstData.mBytesPerRow, srcData.mFormat);
-
-      if (succeeded) {
-        // Mark the frame as complete and discardable.
-        mDstRef->ImageUpdated(mDstRef->GetRect());
-        MOZ_ASSERT(mDstRef->IsImageComplete(),
-                   "Incomplete, but just updated the entire frame");
-      }
-
-      // We need to send notifications and release our references on the main
-      // thread, so finish up there.
-      mState = succeeded ? eFinish : eFinishWithError;
-      NS_DispatchToMainThread(this);
-    } else if (mState == eFinish) {
-      MOZ_ASSERT(NS_IsMainThread());
-      MOZ_ASSERT(mDstRef, "Should have a valid scaled frame");
-
-      // Notify, so observers can redraw.
-      nsRefPtr<RasterImage> image = mImage.get();
-      if (image) {
-        image->NotifyNewScaledFrame();
-      }
-
-      // We're done, so release everything.
-      mSrcRef.reset();
-      mDstRef.reset();
-    } else if (mState == eFinishWithError) {
-      MOZ_ASSERT(NS_IsMainThread());
-      NS_WARNING("HQ scaling failed");
-
-      // Remove the frame from the cache since we know we don't need it.
-      SurfaceCache::RemoveSurface(ImageKey(mImage.get()),
-                                  RasterSurfaceKey(mDstSize,
-                                                   ToSurfaceFlags(mImageFlags),
-                                                   /* aFrameNum = */ 0));
-
-      // Release everything we're holding, too.
-      mSrcRef.reset();
-      mDstRef.reset();
-    } else {
-      // mState must be eNew, which is invalid in Run().
-      MOZ_ASSERT(false, "Need to call Init() before dispatching");
-    }
-
-    return NS_OK;
-  }
-
-private:
-  virtual ~ScaleRunner()
-  {
-    MOZ_ASSERT(!mSrcRef && !mDstRef,
-               "Should have released strong refs in Run()");
-  }
-
-  WeakPtr<RasterImage> mImage;
-  RawAccessFrameRef    mSrcRef;
-  RawAccessFrameRef    mDstRef;
-  const IntSize      mDstSize;
-  uint32_t             mImageFlags;
-  ScaleState           mState;
-};
-
-static nsCOMPtr<nsIThread> sScaleWorkerThread = nullptr;
-
 #ifndef DEBUG
 NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties)
 #else
 NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties,
                   imgIContainerDebug)
 #endif
 
 //******************************************************************************
@@ -222,17 +86,16 @@ RasterImage::RasterImage(ImageURL* aURI 
   mSourceBuffer(new SourceBuffer()),
   mFrameCount(0),
   mHasSize(false),
   mTransient(false),
   mSyncLoad(false),
   mDiscardable(false),
   mHasSourceData(false),
   mHasBeenDecoded(false),
-  mDownscaleDuringDecode(false),
   mPendingAnimation(false),
   mAnimationFinished(false),
   mWantFullDecode(false)
 {
   Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
 }
 
 //******************************************************************************
@@ -258,33 +121,25 @@ RasterImage::Init(const char* aMimeType,
   }
 
   // Not sure an error can happen before init, but be safe
   if (mError) {
     return NS_ERROR_FAILURE;
   }
 
   // We want to avoid redecodes for transient images.
-  MOZ_ASSERT(!(aFlags & INIT_FLAG_TRANSIENT) ||
-               (!(aFlags & INIT_FLAG_DISCARDABLE) &&
-                !(aFlags & INIT_FLAG_DOWNSCALE_DURING_DECODE)),
-             "Illegal init flags for transient image");
+  MOZ_ASSERT_IF(aFlags & INIT_FLAG_TRANSIENT,
+                !(aFlags & INIT_FLAG_DISCARDABLE));
 
   // Store initialization data
   mDiscardable = !!(aFlags & INIT_FLAG_DISCARDABLE);
   mWantFullDecode = !!(aFlags & INIT_FLAG_DECODE_IMMEDIATELY);
   mTransient = !!(aFlags & INIT_FLAG_TRANSIENT);
-  mDownscaleDuringDecode = !!(aFlags & INIT_FLAG_DOWNSCALE_DURING_DECODE);
   mSyncLoad = !!(aFlags & INIT_FLAG_SYNC_LOAD);
 
-#ifndef MOZ_ENABLE_SKIA
-  // Downscale-during-decode requires Skia.
-  mDownscaleDuringDecode = false;
-#endif
-
   // Use the MIME type to select a decoder type, and make sure there *is* a
   // decoder for this MIME type.
   NS_ENSURE_ARG_POINTER(aMimeType);
   mDecoderType = DecoderFactory::GetDecoderType(aMimeType);
   if (mDecoderType == DecoderType::UNKNOWN) {
     return NS_ERROR_FAILURE;
   }
 
@@ -693,54 +548,68 @@ RasterImage::CopyFrame(uint32_t aWhichFr
 
 //******************************************************************************
 /* [noscript] SourceSurface getFrame(in uint32_t aWhichFrame,
  *                                   in uint32_t aFlags); */
 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
 RasterImage::GetFrame(uint32_t aWhichFrame,
                       uint32_t aFlags)
 {
-  return GetFrameInternal(aWhichFrame, aFlags).second().forget();
+  return GetFrameInternal(mSize, aWhichFrame, aFlags).second().forget();
+}
+
+NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
+RasterImage::GetFrameAtSize(const IntSize& aSize,
+                            uint32_t aWhichFrame,
+                            uint32_t aFlags)
+{
+  return GetFrameInternal(aSize, aWhichFrame, aFlags).second().forget();
 }
 
 Pair<DrawResult, RefPtr<SourceSurface>>
-RasterImage::GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags)
+RasterImage::GetFrameInternal(const IntSize& aSize,
+                              uint32_t aWhichFrame,
+                              uint32_t aFlags)
 {
   MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
 
+  if (aSize.IsEmpty()) {
+    return MakePair(DrawResult::BAD_ARGS, RefPtr<SourceSurface>());
+  }
+
   if (aWhichFrame > FRAME_MAX_VALUE) {
     return MakePair(DrawResult::BAD_ARGS, RefPtr<SourceSurface>());
   }
 
   if (mError) {
     return MakePair(DrawResult::BAD_IMAGE, RefPtr<SourceSurface>());
   }
 
   // Get the frame. If it's not there, it's probably the caller's fault for
   // not waiting for the data to be loaded from the network or not passing
   // FLAG_SYNC_DECODE
   DrawableFrameRef frameRef =
-    LookupFrame(GetRequestedFrameIndex(aWhichFrame), mSize, aFlags);
+    LookupFrame(GetRequestedFrameIndex(aWhichFrame), aSize, aFlags);
   if (!frameRef) {
     // The OS threw this frame away and we couldn't redecode it.
     return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
   }
 
   // If this frame covers the entire image, we can just reuse its existing
   // surface.
   RefPtr<SourceSurface> frameSurf;
-  IntRect frameRect = frameRef->GetRect();
-  if (frameRect.x == 0 && frameRect.y == 0 &&
-      frameRect.width == mSize.width &&
-      frameRect.height == mSize.height) {
+  if (!frameRef->NeedsPadding() &&
+      frameRef->GetSize() == aSize) {
     frameSurf = frameRef->GetSurface();
   }
 
   // The image doesn't have a usable surface because it's been optimized away or
-  // because it's a partial update frame from an animation. Create one.
+  // because it's a partial update frame from an animation. Create one. (In this
+  // case we fall back to returning a surface at our intrinsic size, even if a
+  // different size was originally specified.)
   if (!frameSurf) {
     frameSurf = CopyFrame(aWhichFrame, aFlags);
   }
 
   if (!frameRef->IsImageComplete()) {
     return MakePair(DrawResult::INCOMPLETE, Move(frameSurf));
   }
 
@@ -751,17 +620,17 @@ Pair<DrawResult, nsRefPtr<layers::Image>
 RasterImage::GetCurrentImage(ImageContainer* aContainer, uint32_t aFlags)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aContainer);
 
   DrawResult drawResult;
   RefPtr<SourceSurface> surface;
   Tie(drawResult, surface) =
-    GetFrameInternal(FRAME_CURRENT, aFlags | FLAG_ASYNC_NOTIFY);
+    GetFrameInternal(mSize, FRAME_CURRENT, aFlags | FLAG_ASYNC_NOTIFY);
   if (!surface) {
     // The OS threw out some or all of our buffer. We'll need to wait for the
     // redecode (which was automatically triggered by GetFrame) to complete.
     return MakePair(drawResult, nsRefPtr<layers::Image>());
   }
 
   CairoImage::Data cairoData;
   GetWidth(&cairoData.mSize.width);
@@ -1324,33 +1193,29 @@ RasterImage::RequestDecodeForSize(const 
     return NS_ERROR_FAILURE;
   }
 
   if (!mHasSize) {
     mWantFullDecode = true;
     return NS_OK;
   }
 
-  // Fall back to our intrinsic size if we don't support
-  // downscale-during-decode.
-  IntSize targetSize = mDownscaleDuringDecode ? aSize : mSize;
-
   // Decide whether to sync decode images we can decode quickly. Here we are
   // explicitly trading off flashing for responsiveness in the case that we're
   // redecoding an image (see bug 845147).
   bool shouldSyncDecodeIfFast =
     !mHasBeenDecoded && (aFlags & FLAG_SYNC_DECODE_IF_FAST);
 
   uint32_t flags = shouldSyncDecodeIfFast
                  ? aFlags
                  : aFlags & ~FLAG_SYNC_DECODE_IF_FAST;
 
   // Look up the first frame of the image, which will implicitly start decoding
   // if it's not available right now.
-  LookupFrame(0, targetSize, flags);
+  LookupFrame(0, aSize, flags);
 
   return NS_OK;
 }
 
 static void
 LaunchDecoder(Decoder* aDecoder,
               RasterImage* aImage,
               uint32_t aFlags,
@@ -1390,30 +1255,24 @@ RasterImage::Decode(const IntSize& aSize
   }
 
   // If we don't have a size yet, we can't do any other decoding.
   if (!mHasSize) {
     mWantFullDecode = true;
     return NS_OK;
   }
 
-  if (mDownscaleDuringDecode) {
-    // We're about to decode again, which may mean that some of the previous
-    // sizes we've decoded at aren't useful anymore. We can allow them to
-    // expire from the cache by unlocking them here. When the decode finishes,
-    // it will send an invalidation that will cause all instances of this image
-    // to redraw. If this image is locked, any surfaces that are still useful
-    // will become locked again when LookupFrame touches them, and the remainder
-    // will eventually expire.
-    SurfaceCache::UnlockSurfaces(ImageKey(this));
-  }
-
-  MOZ_ASSERT(mDownscaleDuringDecode || aSize == mSize,
-             "Can only decode to our intrinsic size if we're not allowed to "
-             "downscale-during-decode");
+  // We're about to decode again, which may mean that some of the previous sizes
+  // we've decoded at aren't useful anymore. We can allow them to expire from
+  // the cache by unlocking them here. When the decode finishes, it will send an
+  // invalidation that will cause all instances of this image to redraw. If this
+  // image is locked, any surfaces that are still useful will become locked
+  // again when LookupFrame touches them, and the remainder will eventually
+  // expire.
+  SurfaceCache::UnlockSurfaces(ImageKey(this));
 
   Maybe<IntSize> targetSize = mSize != aSize ? Some(aSize) : Nothing();
 
   // Determine which flags we need to decode this image with.
   DecoderFlags decoderFlags = DefaultDecoderFlags();
   if (aFlags & FLAG_ASYNC_NOTIFY) {
     decoderFlags |= DecoderFlags::ASYNC_NOTIFY;
   }
@@ -1528,85 +1387,34 @@ RasterImage::RecoverFromInvalidFrames(co
     ResetAnimation();
     return;
   }
 
   // For non-animated images, it's fine to recover using an async decode.
   Decode(aSize, aFlags);
 }
 
-bool
-RasterImage::CanScale(GraphicsFilter aFilter,
-                      const IntSize& aSize,
-                      uint32_t aFlags)
+static bool
+HaveSkia()
 {
-#ifndef MOZ_ENABLE_SKIA
-  // The high-quality scaler requires Skia.
-  return false;
+#ifdef MOZ_ENABLE_SKIA
+  return true;
 #else
-  // Check basic requirements: HQ downscaling is enabled, we have all the source
-  // data and know our size, the flags allow us to do it, and a 'good' filter is
-  // being used. The flags may ask us not to scale because the caller isn't
-  // drawing to the window. If we're drawing to something else (e.g. a canvas)
-  // we usually have no way of updating what we've drawn, so HQ scaling is
-  // useless.
-  if (!gfxPrefs::ImageHQDownscalingEnabled() || !mHasSize || !mHasSourceData ||
-      !(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) ||
-      aFilter != GraphicsFilter::FILTER_GOOD) {
-    return false;
-  }
-
-  // We don't HQ scale images that we can downscale during decode.
-  if (mDownscaleDuringDecode) {
-    return false;
-  }
-
-  // We don't use the scaler for animated or transient images to avoid doing a
-  // bunch of work on an image that just gets thrown away.
-  if (mAnim || mTransient) {
-    return false;
-  }
-
-  // If target size is 1:1 with original, don't scale.
-  if (aSize == mSize) {
-    return false;
-  }
-
-  // To save memory, don't quality upscale images bigger than the limit.
-  if (aSize.width > mSize.width || aSize.height > mSize.height) {
-    uint32_t scaledSize = static_cast<uint32_t>(aSize.width * aSize.height);
-    if (scaledSize > gfxPrefs::ImageHQUpscalingMaxSize()) {
-      return false;
-    }
-  }
-
-  // There's no point in scaling if we can't store the result.
-  if (!SurfaceCache::CanHold(aSize)) {
-    return false;
-  }
-
-  // XXX(seth): It's not clear what this check buys us over
-  // gfxPrefs::ImageHQUpscalingMaxSize().
-  // The default value of this pref is 1000, which means that we never upscale.
-  // If that's all it's getting us, I'd rather we just forbid that explicitly.
-  gfx::Size scale(double(aSize.width) / mSize.width,
-                  double(aSize.height) / mSize.height);
-  gfxFloat minFactor = gfxPrefs::ImageHQDownscalingMinFactor() / 1000.0;
-  return (scale.width < minFactor || scale.height < minFactor);
+  return false;
 #endif
 }
 
 bool
 RasterImage::CanDownscaleDuringDecode(const IntSize& aSize, uint32_t aFlags)
 {
-  // Check basic requirements: downscale-during-decode is enabled for this
-  // image, we have all the source data and know our size, the flags allow us to
-  // do it, and a 'good' filter is being used.
-  if (!mDownscaleDuringDecode || !mHasSize ||
-      !gfxPrefs::ImageHQDownscalingEnabled() ||
+  // Check basic requirements: downscale-during-decode is enabled, Skia is
+  // available, this image isn't transient, we have all the source data and know
+  // our size, and the flags allow us to do it.
+  if (!mHasSize || mTransient || !HaveSkia() ||
+      !gfxPrefs::ImageDownscaleDuringDecodeEnabled() ||
       !(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING)) {
     return false;
   }
 
   // We don't downscale animated images during decode.
   if (mAnim) {
     return false;
   }
@@ -1624,109 +1432,42 @@ RasterImage::CanDownscaleDuringDecode(co
   // There's no point in scaling if we can't store the result.
   if (!SurfaceCache::CanHold(aSize)) {
     return false;
   }
 
   return true;
 }
 
-void
-RasterImage::NotifyNewScaledFrame()
-{
-  // Send an invalidation so observers will repaint and can take advantage of
-  // the new scaled frame if possible.
-  NotifyProgress(NoProgress, IntRect(0, 0, mSize.width, mSize.height));
-}
-
-void
-RasterImage::RequestScale(imgFrame* aFrame,
-                          uint32_t aFlags,
-                          const IntSize& aSize)
+DrawResult
+RasterImage::DrawInternal(DrawableFrameRef&& aFrameRef,
+                          gfxContext* aContext,
+                          const IntSize& aSize,
+                          const ImageRegion& aRegion,
+                          GraphicsFilter aFilter,
+                          uint32_t aFlags)
 {
-  // We don't scale frames which aren't fully decoded.
-  if (!aFrame->IsImageComplete()) {
-    return;
-  }
-
-  // We can't scale frames that need padding or are single pixel.
-  if (aFrame->NeedsPadding() || aFrame->IsSinglePixel()) {
-    return;
-  }
-
-  // We also can't scale if we can't lock the image data for this frame.
-  RawAccessFrameRef frameRef = aFrame->RawAccessRef();
-  if (!frameRef) {
-    return;
-  }
-
-  nsRefPtr<ScaleRunner> runner =
-    new ScaleRunner(this, aFlags, aSize, Move(frameRef));
-  if (runner->Init()) {
-    if (!sScaleWorkerThread) {
-      NS_NewNamedThread("Image Scaler", getter_AddRefs(sScaleWorkerThread));
-      ClearOnShutdown(&sScaleWorkerThread);
-    }
-
-    sScaleWorkerThread->Dispatch(runner, NS_DISPATCH_NORMAL);
-  }
-}
-
-DrawResult
-RasterImage::DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
-                                          gfxContext* aContext,
-                                          const IntSize& aSize,
-                                          const ImageRegion& aRegion,
-                                          GraphicsFilter aFilter,
-                                          uint32_t aFlags)
-{
-  DrawableFrameRef frameRef;
-
-  if (CanScale(aFilter, aSize, aFlags)) {
-    LookupResult result =
-      SurfaceCache::Lookup(ImageKey(this),
-                           RasterSurfaceKey(aSize,
-                                            ToSurfaceFlags(aFlags),
-                                            /* aFrameNum = */ 0));
-    if (!result) {
-      // We either didn't have a matching scaled frame or the OS threw it away.
-      // Request a new one so we'll be ready next time. For now, we'll fall back
-      // to aFrameRef below.
-      RequestScale(aFrameRef.get(), aFlags, aSize);
-    }
-    if (result && result.DrawableRef()->IsImageComplete()) {
-      frameRef = Move(result.DrawableRef());  // The scaled version is ready.
-    }
-  }
-
   gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
   ImageRegion region(aRegion);
-  bool frameIsComplete = true;  // We already checked HQ scaled frames.
-  if (!frameRef) {
-    // There's no HQ scaled frame available, so we'll have to use the frame
-    // provided by the caller.
-    frameRef = Move(aFrameRef);
-    frameIsComplete = frameRef->IsImageComplete();
-  }
+  bool frameIsComplete = aFrameRef->IsImageComplete();
 
   // By now we may have a frame with the requested size. If not, we need to
   // adjust the drawing parameters accordingly.
-  IntSize finalSize = frameRef->GetImageSize();
+  IntSize finalSize = aFrameRef->GetImageSize();
   bool couldRedecodeForBetterFrame = false;
   if (finalSize != aSize) {
     gfx::Size scale(double(aSize.width) / finalSize.width,
                     double(aSize.height) / finalSize.height);
     aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height));
     region.Scale(1.0 / scale.width, 1.0 / scale.height);
 
-    couldRedecodeForBetterFrame = mDownscaleDuringDecode &&
-                                  CanDownscaleDuringDecode(aSize, aFlags);
+    couldRedecodeForBetterFrame = CanDownscaleDuringDecode(aSize, aFlags);
   }
 
-  if (!frameRef->Draw(aContext, region, aFilter, aFlags)) {
+  if (!aFrameRef->Draw(aContext, region, aFilter, aFlags)) {
     RecoverFromInvalidFrames(aSize, aFlags);
     return DrawResult::TEMPORARY_ERROR;
   }
   if (!frameIsComplete) {
     return DrawResult::INCOMPLETE;
   }
   if (couldRedecodeForBetterFrame) {
     return DrawResult::WRONG_SIZE;
@@ -1790,18 +1531,18 @@ RasterImage::Draw(gfxContext* aContext,
       mDrawStartTime = TimeStamp::Now();
     }
     return DrawResult::NOT_READY;
   }
 
   bool shouldRecordTelemetry = !mDrawStartTime.IsNull() &&
                                ref->IsImageComplete();
 
-  auto result = DrawWithPreDownscaleIfNeeded(Move(ref), aContext, aSize,
-                                             aRegion, aFilter, flags);
+  auto result = DrawInternal(Move(ref), aContext, aSize,
+                             aRegion, aFilter, flags);
 
   if (shouldRecordTelemetry) {
       TimeDuration drawLatency = TimeStamp::Now() - mDrawStartTime;
       Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY,
                             int32_t(drawLatency.ToMicroseconds()));
       mDrawStartTime = TimeStamp();
   }
 
@@ -2105,35 +1846,16 @@ RasterImage::OptimalImageSizeForDest(con
     return IntSize(0, 0);
   }
 
   IntSize destSize(ceil(aDest.width), ceil(aDest.height));
 
   if (aFilter == GraphicsFilter::FILTER_GOOD &&
       CanDownscaleDuringDecode(destSize, aFlags)) {
     return destSize;
-  } else if (CanScale(aFilter, destSize, aFlags)) {
-    LookupResult result =
-      SurfaceCache::Lookup(ImageKey(this),
-                           RasterSurfaceKey(destSize,
-                                            ToSurfaceFlags(aFlags),
-                                            /* aFrameNum = */ 0));
-
-    if (result && result.DrawableRef()->IsImageComplete()) {
-      return destSize;  // We have an existing HQ scale for this size.
-    }
-    if (!result) {
-      // We could HQ scale to this size, but we haven't. Request a scale now.
-      DrawableFrameRef ref = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
-                                         mSize, aFlags);
-      if (ref) {
-        RequestScale(ref.get(), aFlags, destSize);
-      }
-    }
   }
 
-  // We either can't HQ scale to this size or the scaled version isn't ready
-  // yet. Use our intrinsic size for now.
+  // We can't scale to this size. Use our intrinsic size for now.
   return mSize;
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/RasterImage.h
+++ b/image/RasterImage.h
@@ -249,28 +249,30 @@ public:
       GetURI()->GetSpec(spec);
     }
     return spec;
   }
 
 private:
   nsresult Init(const char* aMimeType, uint32_t aFlags);
 
-  DrawResult DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
-                                          gfxContext* aContext,
-                                          const nsIntSize& aSize,
-                                          const ImageRegion& aRegion,
-                                          GraphicsFilter aFilter,
-                                          uint32_t aFlags);
+  DrawResult DrawInternal(DrawableFrameRef&& aFrameRef,
+                          gfxContext* aContext,
+                          const nsIntSize& aSize,
+                          const ImageRegion& aRegion,
+                          GraphicsFilter aFilter,
+                          uint32_t aFlags);
 
   already_AddRefed<gfx::SourceSurface> CopyFrame(uint32_t aWhichFrame,
                                              uint32_t aFlags);
 
   Pair<DrawResult, RefPtr<gfx::SourceSurface>>
-    GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags);
+    GetFrameInternal(const gfx::IntSize& aSize,
+                     uint32_t aWhichFrame,
+                     uint32_t aFlags);
 
   LookupResult LookupFrameInternal(uint32_t aFrameNum,
                                    const gfx::IntSize& aSize,
                                    uint32_t aFlags);
   DrawableFrameRef LookupFrame(uint32_t aFrameNum,
                                const nsIntSize& aSize,
                                uint32_t aFlags);
   uint32_t GetCurrentFrameIndex() const;
@@ -298,20 +300,16 @@ private:
 
   /**
    * Creates and runs a decoder, either synchronously or asynchronously
    * according to @aFlags. Decodes at the provided target size @aSize, using
    * decode flags @aFlags.
    *
    * It's an error to call Decode() before this image's intrinsic size is
    * available. A metadata decode must successfully complete first.
-   *
-   * If downscale-during-decode is not enabled for this image (i.e., if
-   * mDownscaleDuringDecode is false), it is an error to pass an @aSize value
-   * different from this image's intrinsic size.
    */
   NS_IMETHOD Decode(const gfx::IntSize& aSize, uint32_t aFlags);
 
   /**
    * Creates and runs a metadata decoder, either synchronously or
    * asynchronously according to @aFlags.
    */
   NS_IMETHOD DecodeMetadata(uint32_t aFlags);
@@ -391,17 +389,16 @@ private: // data
 
   // Boolean flags (clustered together to conserve space):
   bool                       mHasSize:1;       // Has SetSize() been called?
   bool                       mTransient:1;     // Is the image short-lived?
   bool                       mSyncLoad:1;      // Are we loading synchronously?
   bool                       mDiscardable:1;   // Is container discardable?
   bool                       mHasSourceData:1; // Do we have source data?
   bool                       mHasBeenDecoded:1; // Decoded at least once?
-  bool                       mDownscaleDuringDecode:1;
 
   // Whether we're waiting to start animation. If we get a StartAnimation() call
   // but we don't yet have more than one frame, mPendingAnimation is set so that
   // we know to start animation later if/when we have more frames.
   bool                       mPendingAnimation:1;
 
   // Whether the animation can stop, due to running out
   // of frames, or no more owning request
@@ -413,32 +410,20 @@ private: // data
 
   TimeStamp mDrawStartTime;
 
 
   //////////////////////////////////////////////////////////////////////////////
   // Scaling.
   //////////////////////////////////////////////////////////////////////////////
 
-  // Initiates an HQ scale for the given frame, if possible.
-  void RequestScale(imgFrame* aFrame, uint32_t aFlags, const nsIntSize& aSize);
-
-  // Determines whether we can perform an HQ scale with the given parameters.
-  bool CanScale(GraphicsFilter aFilter, const nsIntSize& aSize,
-                uint32_t aFlags);
-
   // Determines whether we can downscale during decode with the given
   // parameters.
   bool CanDownscaleDuringDecode(const nsIntSize& aSize, uint32_t aFlags);
 
-  // Called by the HQ scaler when a new scaled frame is ready.
-  void NotifyNewScaledFrame();
-
-  friend class ScaleRunner;
-
 
   // Error handling.
   void DoError();
 
   class HandleErrorWorker : public nsRunnable
   {
   public:
     /**
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/SurfaceCache.cpp
+++ b/image/SurfaceCache.cpp
@@ -130,27 +130,24 @@ class CachedSurface
   ~CachedSurface() { }
 public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(CachedSurface)
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CachedSurface)
 
   CachedSurface(imgFrame*          aSurface,
                 const Cost         aCost,
                 const ImageKey     aImageKey,
-                const SurfaceKey&  aSurfaceKey,
-                const Lifetime     aLifetime)
+                const SurfaceKey&  aSurfaceKey)
     : mSurface(aSurface)
     , mCost(aCost)
     , mImageKey(aImageKey)
     , mSurfaceKey(aSurfaceKey)
-    , mLifetime(aLifetime)
   {
-    MOZ_ASSERT(!IsPlaceholder() ||
-               (mCost == sPlaceholderCost && mLifetime == Lifetime::Transient),
-               "Placeholder should have trivial cost and transient lifetime");
+    MOZ_ASSERT(!IsPlaceholder() || mCost == sPlaceholderCost,
+               "Placeholder should have trivial cost");
     MOZ_ASSERT(mImageKey, "Must have a valid image key");
   }
 
   DrawableFrameRef DrawableRef() const
   {
     if (MOZ_UNLIKELY(IsPlaceholder())) {
       MOZ_ASSERT_UNREACHABLE("Shouldn't call DrawableRef() on a placeholder");
       return DrawableFrameRef();
@@ -160,34 +157,33 @@ public:
   }
 
   void SetLocked(bool aLocked)
   {
     if (IsPlaceholder()) {
       return;  // Can't lock a placeholder.
     }
 
-    if (aLocked && mLifetime == Lifetime::Persistent) {
+    if (aLocked) {
       // This may fail, and that's OK. We make no guarantees about whether
       // locking is successful if you call SurfaceCache::LockImage() after
       // SurfaceCache::Insert().
       mDrawableRef = mSurface->DrawableRef();
     } else {
       mDrawableRef.reset();
     }
   }
 
   bool IsPlaceholder() const { return !bool(mSurface); }
   bool IsLocked() const { return bool(mDrawableRef); }
 
   ImageKey GetImageKey() const { return mImageKey; }
   SurfaceKey GetSurfaceKey() const { return mSurfaceKey; }
   CostEntry GetCostEntry() { return image::CostEntry(this, mCost); }
   nsExpirationState* GetExpirationState() { return &mExpirationState; }
-  Lifetime GetLifetime() const { return mLifetime; }
 
   bool IsDecoded() const
   {
     return !IsPlaceholder() && mSurface->IsImageComplete();
   }
 
   // A helper type used by SurfaceCacheImpl::CollectSizeOfSurfaces.
   struct MOZ_STACK_CLASS SurfaceMemoryReport
@@ -225,17 +221,16 @@ public:
 
 private:
   nsExpirationState  mExpirationState;
   nsRefPtr<imgFrame> mSurface;
   DrawableFrameRef   mDrawableRef;
   const Cost         mCost;
   const ImageKey     mImageKey;
   const SurfaceKey   mSurfaceKey;
-  const Lifetime     mLifetime;
 };
 
 /**
  * An ImageSurfaceCache is a per-image surface cache. For correctness we must be
  * able to remove all surfaces associated with an image when the image is
  * destroyed or invalidated. Since this will happen frequently, it makes sense
  * to make it cheap by storing the surfaces for each image separately.
  *
@@ -254,19 +249,18 @@ public:
   typedef
     nsRefPtrHashtable<nsGenericHashKey<SurfaceKey>, CachedSurface> SurfaceTable;
 
   bool IsEmpty() const { return mSurfaces.Count() == 0; }
 
   void Insert(const SurfaceKey& aKey, CachedSurface* aSurface)
   {
     MOZ_ASSERT(aSurface, "Should have a surface");
-    MOZ_ASSERT(!mLocked || aSurface->GetLifetime() != Lifetime::Persistent ||
-               aSurface->IsLocked(),
-               "Inserting an unlocked persistent surface for a locked image");
+    MOZ_ASSERT(!mLocked || aSurface->IsPlaceholder() || aSurface->IsLocked(),
+               "Inserting an unlocked surface for a locked image");
     mSurfaces.Put(aKey, aSurface);
   }
 
   void Remove(CachedSurface* aSurface)
   {
     MOZ_ASSERT(aSurface, "Should have a surface");
     MOZ_ASSERT(mSurfaces.GetWeak(aSurface->GetSurfaceKey()),
         "Should not be removing a surface we don't have");
@@ -465,18 +459,17 @@ private:
 public:
   void InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
 
   Mutex& GetMutex() { return mMutex; }
 
   InsertOutcome Insert(imgFrame*         aSurface,
                        const Cost        aCost,
                        const ImageKey    aImageKey,
-                       const SurfaceKey& aSurfaceKey,
-                       Lifetime          aLifetime)
+                       const SurfaceKey& aSurfaceKey)
   {
     // If this is a duplicate surface, refuse to replace the original.
     // XXX(seth): Calling Lookup() and then RemoveSurface() does the lookup
     // twice. We'll make this more efficient in bug 1185137.
     LookupResult result = Lookup(aImageKey, aSurfaceKey, /* aMarkUsed = */ false);
     if (MOZ_UNLIKELY(result)) {
       return InsertOutcome::FAILURE_ALREADY_PRESENT;
     }
@@ -508,22 +501,22 @@ public:
     // for this image, create it.
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache) {
       cache = new ImageSurfaceCache;
       mImageCaches.Put(aImageKey, cache);
     }
 
     nsRefPtr<CachedSurface> surface =
-      new CachedSurface(aSurface, aCost, aImageKey, aSurfaceKey, aLifetime);
+      new CachedSurface(aSurface, aCost, aImageKey, aSurfaceKey);
 
-    // We require that locking succeed if the image is locked and the surface is
-    // persistent; the caller may need to know this to handle errors correctly.
-    if (cache->IsLocked() && aLifetime == Lifetime::Persistent) {
-      MOZ_ASSERT(!surface->IsPlaceholder(), "Placeholders should be transient");
+    // We require that locking succeed if the image is locked and we're not
+    // inserting a placeholder; the caller may need to know this to handle
+    // errors correctly.
+    if (cache->IsLocked() && !surface->IsPlaceholder()) {
       surface->SetLocked(true);
       if (!surface->IsLocked()) {
         return InsertOutcome::FAILURE;
       }
     }
 
     // Insert.
     MOZ_ASSERT(aCost <= mAvailableCost, "Inserting despite too large a cost");
@@ -536,18 +529,18 @@ public:
   void Remove(CachedSurface* aSurface)
   {
     MOZ_ASSERT(aSurface, "Should have a surface");
     ImageKey imageKey = aSurface->GetImageKey();
 
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey);
     MOZ_ASSERT(cache, "Shouldn't try to remove a surface with no image cache");
 
-    // If the surface was persistent, tell its image that we discarded it.
-    if (aSurface->GetLifetime() == Lifetime::Persistent) {
+    // If the surface was not a placeholder, tell its image that we discarded it.
+    if (!aSurface->IsPlaceholder()) {
       static_cast<Image*>(imageKey)->OnSurfaceDiscarded();
     }
 
     StopTracking(aSurface);
     cache->Remove(aSurface);
 
     // Remove the per-image cache if it's unneeded now. (Keep it if the image is
     // locked, since the per-image cache is where we store that state.)
@@ -772,19 +765,18 @@ public:
     // The per-image cache isn't needed anymore, so remove it as well.
     // This implicitly unlocks the image if it was locked.
     mImageCaches.Remove(aImageKey);
   }
 
   void DiscardAll()
   {
     // Remove in order of cost because mCosts is an array and the other data
-    // structures are all hash tables. Note that locked surfaces (persistent
-    // surfaces belonging to locked images) are not removed, since they aren't
-    // present in mCosts.
+    // structures are all hash tables. Note that locked surfaces are not
+    // removed, since they aren't present in mCosts.
     while (!mCosts.IsEmpty()) {
       Remove(mCosts.LastElement().GetSurface());
     }
   }
 
   void DiscardForMemoryPressure()
   {
     // Compute our discardable cost. Since locked surfaces aren't discardable,
@@ -808,18 +800,17 @@ public:
     while (mAvailableCost < targetCost) {
       MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and still not done");
       Remove(mCosts.LastElement().GetSurface());
     }
   }
 
   void LockSurface(CachedSurface* aSurface)
   {
-    if (aSurface->GetLifetime() == Lifetime::Transient ||
-        aSurface->IsLocked()) {
+    if (aSurface->IsPlaceholder() || aSurface->IsLocked()) {
       return;
     }
 
     StopTracking(aSurface);
 
     // Lock the surface. This can fail.
     aSurface->SetLocked(true);
     StartTracking(aSurface);
@@ -832,18 +823,17 @@ public:
     static_cast<SurfaceCacheImpl*>(aCache)->StopTracking(aSurface);
     return PL_DHASH_NEXT;
   }
 
   static PLDHashOperator DoUnlockSurface(const SurfaceKey&,
                                          CachedSurface*    aSurface,
                                          void*             aCache)
   {
-    if (aSurface->GetLifetime() == Lifetime::Transient ||
-        !aSurface->IsLocked()) {
+    if (aSurface->IsPlaceholder() || !aSurface->IsLocked()) {
       return PL_DHASH_NEXT;
     }
 
     auto cache = static_cast<SurfaceCacheImpl*>(aCache);
     cache->StopTracking(aSurface);
 
     aSurface->SetLocked(false);
     cache->StartTracking(aSurface);
@@ -916,19 +906,19 @@ private:
   {
     nsRefPtr<ImageSurfaceCache> imageCache;
     mImageCaches.Get(aImageKey, getter_AddRefs(imageCache));
     return imageCache.forget();
   }
 
   // This is similar to CanHold() except that it takes into account the costs of
   // locked surfaces. It's used internally in Insert(), but it's not exposed
-  // publicly because if we start permitting multithreaded access to the surface
-  // cache, which seems likely, then the result would be meaningless: another
-  // thread could insert a persistent surface or lock an image at any time.
+  // publicly because we permit multithreaded access to the surface cache, which
+  // means that the result would be meaningless: another thread could insert a
+  // surface or lock an image at any time.
   bool CanHoldAfterDiscarding(const Cost aCost) const
   {
     return aCost <= mMaxCost - mLockedCost;
   }
 
   void MarkUsed(CachedSurface* aSurface, ImageSurfaceCache* aCache)
   {
     if (aCache->IsLocked()) {
@@ -1091,44 +1081,42 @@ SurfaceCache::LookupBestMatch(const Imag
 
   MutexAutoLock lock(sInstance->GetMutex());
   return sInstance->LookupBestMatch(aImageKey, aSurfaceKey, aAlternateFlags);
 }
 
 /* static */ InsertOutcome
 SurfaceCache::Insert(imgFrame*         aSurface,
                      const ImageKey    aImageKey,
-                     const SurfaceKey& aSurfaceKey,
-                     Lifetime          aLifetime)
+                     const SurfaceKey& aSurfaceKey)
 {
   if (!sInstance) {
     return InsertOutcome::FAILURE;
   }
 
   // Refuse null surfaces.
   if (!aSurface) {
     MOZ_CRASH("Don't pass null surfaces to SurfaceCache::Insert");
   }
 
   MutexAutoLock lock(sInstance->GetMutex());
   Cost cost = ComputeCost(aSurface->GetSize(), aSurface->GetBytesPerPixel());
-  return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey, aLifetime);
+  return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey);
 }
 
 /* static */ InsertOutcome
 SurfaceCache::InsertPlaceholder(const ImageKey    aImageKey,
                                 const SurfaceKey& aSurfaceKey)
 {
   if (!sInstance) {
     return InsertOutcome::FAILURE;
   }
 
   MutexAutoLock lock(sInstance->GetMutex());
-  return sInstance->Insert(nullptr, sPlaceholderCost, aImageKey, aSurfaceKey,
-                           Lifetime::Transient);
+  return sInstance->Insert(nullptr, sPlaceholderCost, aImageKey, aSurfaceKey);
 }
 
 /* static */ bool
 SurfaceCache::CanHold(const IntSize& aSize, uint32_t aBytesPerPixel /* = 4 */)
 {
   if (!sInstance) {
     return false;
   }
--- a/image/SurfaceCache.h
+++ b/image/SurfaceCache.h
@@ -116,21 +116,16 @@ VectorSurfaceKey(const gfx::IntSize& aSi
                  float aAnimationTime)
 {
   // We don't care about aFlags for VectorImage because none of the flags we
   // have right now influence VectorImage's rendering. If we add a new flag that
   // *does* affect how a VectorImage renders, we'll have to change this.
   return SurfaceKey(aSize, aSVGContext, aAnimationTime, DefaultSurfaceFlags());
 }
 
-enum class Lifetime : uint8_t {
-  Transient,
-  Persistent
-};
-
 enum class InsertOutcome : uint8_t {
   SUCCESS,                 // Success (but see Insert documentation).
   FAILURE,                 // Couldn't insert (e.g., for capacity reasons).
   FAILURE_ALREADY_PRESENT  // A surface with the same key is already present.
 };
 
 /**
  * SurfaceCache is an imagelib-global service that allows caching of temporary
@@ -141,19 +136,18 @@ enum class InsertOutcome : uint8_t {
  * objects, which hold surfaces but also layer on additional features specific
  * to imagelib's needs like animation, padding support, and transparent support
  * for volatile buffers.
  *
  * Sometime it's useful to temporarily prevent surfaces from expiring from the
  * cache. This is most often because losing the data could harm the user
  * experience (for example, we often don't want to allow surfaces that are
  * currently visible to expire) or because it's not possible to rematerialize
- * the surface. SurfaceCache supports this through the use of image locking and
- * surface lifetimes; see the comments for Insert() and LockImage() for more
- * details.
+ * the surface. SurfaceCache supports this through the use of image locking; see
+ * the comments for Insert() and LockImage() for more details.
  *
  * Any image which stores surfaces in the SurfaceCache *must* ensure that it
  * calls RemoveImage() before it is destroyed. See the comments for
  * RemoveImage() for more details.
  */
 struct SurfaceCache
 {
   typedef gfx::IntSize IntSize;
@@ -173,18 +167,18 @@ struct SurfaceCache
    * drawable reference to that imgFrame.
    *
    * If the image associated with the surface is locked, then the surface will
    * be locked before it is returned.
    *
    * If the imgFrame was found in the cache, but had stored its surface in a
    * volatile buffer which was discarded by the OS, then it is automatically
    * removed from the cache and an empty LookupResult is returned. Note that
-   * this will never happen to persistent surfaces associated with a locked
-   * image; the cache keeps a strong reference to such surfaces internally.
+   * this will never happen to surfaces associated with a locked image; the
+   * cache keeps a strong reference to such surfaces internally.
    *
    * @param aImageKey       Key data identifying which image the surface belongs
    *                        to.
    *
    * @param aSurfaceKey     Key data which uniquely identifies the requested
    *                        surface.
    *
    * @param aAlternateFlags If not Nothing(), a different set of flags than the
@@ -231,64 +225,58 @@ struct SurfaceCache
                                       const Maybe<SurfaceFlags>& aAlternateFlags
                                         = Nothing());
 
   /**
    * Insert a surface into the cache. If a surface with the same ImageKey and
    * SurfaceKey is already in the cache, Insert returns FAILURE_ALREADY_PRESENT.
    * If a matching placeholder is already present, the placeholder is removed.
    *
-   * Each surface in the cache has a lifetime, either Transient or Persistent.
-   * Transient surfaces can expire from the cache at any time. Persistent
-   * surfaces, on the other hand, will never expire as long as they remain
-   * locked, but if they become unlocked, can expire just like transient
-   * surfaces. When it is first inserted, a persistent surface is locked if its
-   * associated image is locked. When that image is later unlocked, the surface
-   * becomes unlocked too. To become locked again at that point, two things must
-   * happen: the image must become locked again (via LockImage()), and the
-   * surface must be touched again (via one of the Lookup() functions).
+   * Surfaces will never expire as long as they remain locked, but if they
+   * become unlocked, they can expire either because the SurfaceCache runs out
+   * of capacity or because they've gone too long without being used.  When it
+   * is first inserted, a surface is locked if its associated image is locked.
+   * When that image is later unlocked, the surface becomes unlocked too. To
+   * become locked again at that point, two things must happen: the image must
+   * become locked again (via LockImage()), and the surface must be touched
+   * again (via one of the Lookup() functions).
    *
    * All of this means that a very particular procedure has to be followed for
    * surfaces which cannot be rematerialized. First, they must be inserted
-   * with a persistent lifetime *after* the image is locked with LockImage(); if
-   * you use the other order, the surfaces might expire before LockImage() gets
-   * called or before the surface is touched again by Lookup(). Second, the
-   * image they are associated with must never be unlocked.
+   * *after* the image is locked with LockImage(); if you use the other order,
+   * the surfaces might expire before LockImage() gets called or before the
+   * surface is touched again by Lookup(). Second, the image they are associated
+   * with must never be unlocked.
    *
    * If a surface cannot be rematerialized, it may be important to know whether
    * it was inserted into the cache successfully. Insert() returns FAILURE if it
    * failed to insert the surface, which could happen because of capacity
-   * reasons, or because it was already freed by the OS. If you aren't inserting
-   * a surface with persistent lifetime, or if the surface isn't associated with
-   * a locked image, checking for SUCCESS or FAILURE is useless: the surface
-   * might expire immediately after being inserted, even though Insert()
-   * returned SUCCESS. Thus, many callers do not need to check the result of
-   * Insert() at all.
+   * reasons, or because it was already freed by the OS. If the surface isn't
+   * associated with a locked image, checking for SUCCESS or FAILURE is useless:
+   * the surface might expire immediately after being inserted, even though
+   * Insert() returned SUCCESS. Thus, many callers do not need to check the
+   * result of Insert() at all.
    *
    * @param aTarget      The new surface (wrapped in an imgFrame) to insert into
    *                     the cache.
    * @param aImageKey    Key data identifying which image the surface belongs
    *                     to.
    * @param aSurfaceKey  Key data which uniquely identifies the requested
    *                     surface.
-   * @param aLifetime    Whether this is a transient surface that can always be
-   *                     allowed to expire, or a persistent surface that
-   *                     shouldn't expire if the image is locked.
    * @return SUCCESS if the surface was inserted successfully. (But see above
    *           for more information about when you should check this.)
    *         FAILURE if the surface could not be inserted, e.g. for capacity
    *           reasons. (But see above for more information about when you
    *           should check this.)
    *         FAILURE_ALREADY_PRESENT if a surface with the same ImageKey and
    *           SurfaceKey already exists in the cache.
    */
   static InsertOutcome Insert(imgFrame*         aSurface,
                               const ImageKey    aImageKey,
-                              const SurfaceKey& aSurfaceKey,
-                              Lifetime          aLifetime);
+                              const SurfaceKey& aSurfaceKey);
 
   /**
    * Insert a placeholder for a surface into the cache. If a surface with the
    * same ImageKey and SurfaceKey is already in the cache, InsertPlaceholder()
    * returns FAILURE_ALREADY_PRESENT.
    *
    * Placeholders exist to allow lazy allocation of surfaces. The Lookup*()
    * methods will report whether a placeholder for an exactly matching surface
@@ -328,27 +316,26 @@ struct SurfaceCache
    *                        images.
    *
    * @return false if the surface cache can't hold a surface of that size.
    */
   static bool CanHold(const IntSize& aSize, uint32_t aBytesPerPixel = 4);
   static bool CanHold(size_t aSize);
 
   /**
-   * Locks an image. Any of the image's persistent surfaces which are either
-   * inserted or accessed while the image is locked will not expire.
+   * Locks an image. Any of the image's surfaces which are either inserted or
+   * accessed while the image is locked will not expire.
    *
    * Locking an image does not automatically lock that image's existing
-   * surfaces. A call to LockImage() guarantees that persistent surfaces which
-   * are inserted afterward will not expire before the next call to
-   * UnlockImage() or UnlockSurfaces() for that image. Surfaces that are
-   * accessed via Lookup() or LookupBestMatch() after a LockImage() call will
-   * also not expire until the next UnlockImage() or UnlockSurfaces() call for
-   * that image. Any other surfaces owned by the image may expire at any time,
-   * whether they are persistent or transient.
+   * surfaces. A call to LockImage() guarantees that surfaces which are inserted
+   * afterward will not expire before the next call to UnlockImage() or
+   * UnlockSurfaces() for that image. Surfaces that are accessed via Lookup() or
+   * LookupBestMatch() after a LockImage() call will also not expire until the
+   * next UnlockImage() or UnlockSurfaces() call for that image. Any other
+   * surfaces owned by the image may expire at any time.
    *
    * Regardless of locking, any of an image's surfaces may be removed using
    * RemoveSurface(), and all of an image's surfaces are removed by
    * RemoveImage(), whether the image is locked or not.
    *
    * It's safe to call LockImage() on an image that's already locked; this has
    * no effect.
    *
@@ -376,20 +363,20 @@ struct SurfaceCache
    * Unlocks the existing surfaces of an image, allowing them to expire at any
    * time.
    *
    * This does not unlock the image itself, so accessing the surfaces via
    * Lookup() or LookupBestMatch() will lock them again, and prevent them from
    * expiring.
    *
    * This is intended to be used in situations where it's no longer clear that
-   * all of the persistent surfaces owned by an image are needed. Calling
-   * UnlockSurfaces() and then taking some action that will cause Lookup() to
-   * touch any surfaces that are still useful will permit the remaining surfaces
-   * to expire from the cache.
+   * all of the surfaces owned by an image are needed. Calling UnlockSurfaces()
+   * and then taking some action that will cause Lookup() to touch any surfaces
+   * that are still useful will permit the remaining surfaces to expire from the
+   * cache.
    *
    * If the image is unlocked, this has no effect.
    *
    * @param aImageKey    The image which should have its existing surfaces
    *                     unlocked.
    */
   static void UnlockSurfaces(const ImageKey aImageKey);
 
@@ -420,19 +407,19 @@ struct SurfaceCache
    *
    * @param aImageKey  The image which should be removed from the cache.
    */
   static void RemoveImage(const ImageKey aImageKey);
 
   /**
    * Evicts all evictable surfaces from the cache.
    *
-   * All surfaces are evictable except for persistent surfaces associated with
-   * locked images. Non-evictable surfaces can only be removed by
-   * RemoveSurface() or RemoveImage().
+   * All surfaces are evictable except for surfaces associated with locked
+   * images. Non-evictable surfaces can only be removed by RemoveSurface() or
+   * RemoveImage().
    */
   static void DiscardAll();
 
   /**
    * Collects an accounting of the surfaces contained in the SurfaceCache for
    * the given image, along with their size and various other metadata.
    *
    * This is intended for use with memory reporting.
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -663,58 +663,67 @@ VectorImage::IsOpaque()
 {
   return false; // In general, SVG content is not opaque.
 }
 
 //******************************************************************************
 /* [noscript] SourceSurface getFrame(in uint32_t aWhichFrame,
  *                                   in uint32_t aFlags; */
 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
-VectorImage::GetFrame(uint32_t aWhichFrame,
-                      uint32_t aFlags)
+VectorImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags)
 {
-  MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
-
-  if (aWhichFrame > FRAME_MAX_VALUE) {
-    return nullptr;
-  }
-
-  if (mError || !mIsFullyLoaded) {
-    return nullptr;
-  }
-
   // Look up height & width
   // ----------------------
   SVGSVGElement* svgElem = mSVGDocumentWrapper->GetRootSVGElem();
   MOZ_ASSERT(svgElem, "Should have a root SVG elem, since we finished "
                       "loading without errors");
   nsIntSize imageIntSize(svgElem->GetIntrinsicWidth(),
                          svgElem->GetIntrinsicHeight());
 
   if (imageIntSize.IsEmpty()) {
     // We'll get here if our SVG doc has a percent-valued or negative width or
     // height.
     return nullptr;
   }
 
+  return GetFrameAtSize(imageIntSize, aWhichFrame, aFlags);
+}
+
+NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
+VectorImage::GetFrameAtSize(const IntSize& aSize,
+                            uint32_t aWhichFrame,
+                            uint32_t aFlags)
+{
+  MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
+
+  if (aSize.IsEmpty()) {
+    return nullptr;
+  }
+
+  if (aWhichFrame > FRAME_MAX_VALUE) {
+    return nullptr;
+  }
+
+  if (mError || !mIsFullyLoaded) {
+    return nullptr;
+  }
+
   // Make our surface the size of what will ultimately be drawn to it.
   // (either the full image size, or the restricted region)
   RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
-    CreateOffscreenContentDrawTarget(IntSize(imageIntSize.width,
-                                             imageIntSize.height),
-                                     SurfaceFormat::B8G8R8A8);
+    CreateOffscreenContentDrawTarget(aSize, SurfaceFormat::B8G8R8A8);
   if (!dt) {
     NS_ERROR("Could not create a DrawTarget");
     return nullptr;
   }
 
   nsRefPtr<gfxContext> context = new gfxContext(dt);
 
-  auto result = Draw(context, imageIntSize,
-                     ImageRegion::Create(imageIntSize),
+  auto result = Draw(context, aSize,
+                     ImageRegion::Create(aSize),
                      aWhichFrame, GraphicsFilter::FILTER_NEAREST,
                      Nothing(), aFlags);
 
   return result == DrawResult::SUCCESS ? dt->Snapshot() : nullptr;
 }
 
 NS_IMETHODIMP_(bool)
 VectorImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
@@ -906,18 +915,17 @@ VectorImage::CreateSurfaceAndShow(const 
   if (!surface) {
     return Show(svgDrawable, aParams);
   }
 
   // Attempt to cache the frame.
   SurfaceCache::Insert(frame, ImageKey(this),
                        VectorSurfaceKey(aParams.size,
                                         aParams.svgContext,
-                                        aParams.animationTime),
-                       Lifetime::Persistent);
+                                        aParams.animationTime));
 
   // Draw.
   nsRefPtr<gfxDrawable> drawable =
     new gfxSurfaceDrawable(surface, aParams.size);
   Show(drawable, aParams);
 
   // Send out an invalidation so that surfaces that are still in use get
   // re-locked. See the discussion of the UnlockSurfaces call above.
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -59,30 +59,16 @@ nsBMPDecoder::nsBMPDecoder(RasterImage* 
 nsBMPDecoder::~nsBMPDecoder()
 {
   delete[] mColors;
   if (mRow) {
       free(mRow);
   }
 }
 
-nsresult
-nsBMPDecoder::SetTargetSize(const nsIntSize& aSize)
-{
-  // Make sure the size is reasonable.
-  if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // Create a downscaler that we'll filter our output through.
-  mDownscaler.emplace(aSize);
-
-  return NS_OK;
-}
-
 // Sets whether or not the BMP will use alpha data
 void
 nsBMPDecoder::SetUseAlphaData(bool useAlphaData)
 {
   mUseAlphaData = useAlphaData;
 }
 
 // Obtains the bits per pixel from the internal BIH header
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -4,34 +4,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 #ifndef mozilla_image_decoders_nsBMPDecoder_h
 #define mozilla_image_decoders_nsBMPDecoder_h
 
 #include "BMPFileHeaders.h"
 #include "Decoder.h"
-#include "Downscaler.h"
 #include "gfxColor.h"
 #include "nsAutoPtr.h"
 
 namespace mozilla {
 namespace image {
 
 class RasterImage;
 
 /// Decoder for BMP-Files, as used by Windows and OS/2
 
 class nsBMPDecoder : public Decoder
 {
 public:
     ~nsBMPDecoder();
 
-    nsresult SetTargetSize(const nsIntSize& aSize) override;
-
     // Specifies whether or not the BMP file will contain alpha data
     // If set to true and the BMP is 32BPP, the alpha data will be
     // retrieved from the 4th byte of image data per pixel
     void SetUseAlphaData(bool useAlphaData);
 
     // Obtains the bits per pixel from the internal BIH header
     int32_t GetBitsPerPixel() const;
 
@@ -72,18 +69,16 @@ private:
 
     uint32_t mPos; //< Number of bytes read from aBuffer in WriteInternal()
 
     BMPFILEHEADER mBFH;
     BITMAPV5HEADER mBIH;
     char mRawBuf[BIH_INTERNAL_LENGTH::WIN_V3]; //< If this is changed,
                                                // WriteInternal() MUST be updated
 
-    Maybe<Downscaler> mDownscaler;
-
     uint32_t mLOH; //< Length of the header
 
     uint32_t mNumColors; //< The number of used colors, i.e. the number of
                          // entries in mColors
     colorTable* mColors;
 
     bitFields mBitFields;
 
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -94,30 +94,16 @@ nsGIFDecoder2::nsGIFDecoder2(RasterImage
 }
 
 nsGIFDecoder2::~nsGIFDecoder2()
 {
   free(mGIFStruct.local_colormap);
   free(mGIFStruct.hold);
 }
 
-nsresult
-nsGIFDecoder2::SetTargetSize(const nsIntSize& aSize)
-{
-  // Make sure the size is reasonable.
-  if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // Create a downscaler that we'll filter our output through.
-  mDownscaler.emplace(aSize);
-
-  return NS_OK;
-}
-
 uint8_t*
 nsGIFDecoder2::GetCurrentRowBuffer()
 {
   if (!mDownscaler) {
     MOZ_ASSERT(!mDeinterlacer, "Deinterlacer without downscaler?");
     uint32_t bpp = mGIFStruct.images_decoded == 0 ? sizeof(uint32_t)
                                                   : sizeof(uint8_t);
     return mImageData + mGIFStruct.irow * mGIFStruct.width * bpp;
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -3,35 +3,32 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_decoders_nsGIFDecoder2_h
 #define mozilla_image_decoders_nsGIFDecoder2_h
 
 #include "Decoder.h"
-#include "Downscaler.h"
 
 #include "GIF2.h"
 #include "nsCOMPtr.h"
 
 namespace mozilla {
 namespace image {
 class RasterImage;
 
 //////////////////////////////////////////////////////////////////////
 // nsGIFDecoder2 Definition
 
 class nsGIFDecoder2 : public Decoder
 {
 public:
   ~nsGIFDecoder2();
 
-  nsresult SetTargetSize(const nsIntSize& aSize) override;
-
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
   virtual void FinishInternal() override;
   virtual Telemetry::ID SpeedHistogram() override;
 
 private:
   friend class DecoderFactory;
 
   // Decoders should only be instantiated via DecoderFactory.
@@ -68,16 +65,15 @@ private:
 
   uint8_t mCurrentPass;
   uint8_t mLastFlushedPass;
   uint8_t mColorMask;        // Apply this to the pixel to keep within colormap
   bool mGIFOpen;
   bool mSawTransparency;
 
   gif_struct mGIFStruct;
-  Maybe<Downscaler> mDownscaler;
   Maybe<Deinterlacer> mDeinterlacer;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_decoders_nsGIFDecoder2_h
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -9,23 +9,24 @@
 #include <stdlib.h>
 
 #include "mozilla/Endian.h"
 #include "mozilla/Move.h"
 #include "nsICODecoder.h"
 
 #include "RasterImage.h"
 
+using namespace mozilla::gfx;
+
 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;
 
 // ----------------------------------------
 // Actual Data Processing
 // ----------------------------------------
 
 uint32_t
 nsICODecoder::CalcAlphaRowSize()
 {
@@ -52,68 +53,64 @@ 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))
+  , mBiggestResourceColorDepth(0)
+  , 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 +198,481 @@ 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()
+LexerTransition<ICOState>
+nsICODecoder::ReadHeader(const char* aData)
+{
+  // 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);
+
+  // 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.
+  }
+
+  // Downscale-during-decode can end up decoding different resources in the ICO
+  // file depending on the target size. Since the resources are not necessarily
+  // scaled versions of the same image, some may be transparent and some may not
+  // be. We could be precise about transparency if we decoded the metadata of
+  // every resource, but for now we don't and it's safest to assume that
+  // transparency could be present.
+  PostHasTransparency();
+
+  return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
+}
+
+size_t
+nsICODecoder::FirstResourceOffset() const
+{
+  MOZ_ASSERT(mNumIcons > 0,
+             "Calling FirstResourceOffset before processing header");
+
+  // 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)
 {
-  if (!mIsCursor) {
-    return;
+  mCurrIcon++;
+
+  // 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);
+
+  // Determine if this is the biggest resource we've seen so far. We always use
+  // the biggest resource for the intrinsic size, and if we're not downscaling,
+  // we select it as the best resource as well.
+  IntSize entrySize(GetRealWidth(e), GetRealHeight(e));
+  if (e.mBitCount >= mBiggestResourceColorDepth &&
+      entrySize.width * entrySize.height >=
+        mBiggestResourceSize.width * mBiggestResourceSize.height) {
+    mBiggestResourceSize = entrySize;
+    mBiggestResourceColorDepth = e.mBitCount;
+    mBiggestResourceHotSpot = IntSize(e.mXHotspot, e.mYHotspot);
+
+    if (!mDownscaler) {
+      mDirEntry = e;
+    }
+  }
+
+  if (mDownscaler) {
+    // Calculate the delta between this resource'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 resources, 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 downscaler's target size. We will
+    // choose the smallest resource that is >= the target size (i.e. we assume
+    // it's better to downscale a larger icon than to upscale a smaller one).
+    IntSize desiredSize = mDownscaler->TargetSize();
+    int32_t delta = entrySize.width - desiredSize.width +
+                    entrySize.height - desiredSize.height;
+    if (e.mBitCount >= mBestResourceColorDepth &&
+        ((mBestResourceDelta < 0 && delta >= mBestResourceDelta) ||
+         (delta >= 0 && delta <= mBestResourceDelta))) {
+      mBestResourceDelta = delta;
+      mBestResourceColorDepth = e.mBitCount;
+      mDirEntry = e;
+    }
+  }
+
+  if (mCurrIcon == mNumIcons) {
+    // Ensure the resource we selected has an offset past the ICO headers.
+    if (mDirEntry.mImageOffset < FirstResourceOffset()) {
+      return Transition::Terminate(ICOState::FAILURE);
+    }
+
+    // If this is a cursor, set the hotspot. We use the hotspot from the biggest
+    // resource since we also use that resource for the intrinsic size.
+    if (mIsCursor) {
+      mImageMetadata.SetHotspot(mBiggestResourceHotSpot.width,
+                                mBiggestResourceHotSpot.height);
+    }
+
+    // We always report the biggest resource's size as the intrinsic size; this
+    // is necessary for downscale-during-decode to work since we won't even
+    // attempt to *upscale* while decoding.
+    PostSize(mBiggestResourceSize.width, mBiggestResourceSize.height);
+    if (IsMetadataDecode()) {
+      return Transition::Terminate(ICOState::SUCCESS);
+    }
+
+    // If the resource we selected matches the downscaler's target size
+    // perfectly, we don't need to do any downscaling.
+    if (mDownscaler && GetRealSize() == mDownscaler->TargetSize()) {
+      mDownscaler.reset();
+    }
+
+    size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset();
+    return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
+                                    ICOState::SKIP_TO_RESOURCE,
+                                    offsetToResource);
   }
 
-  mImageMetadata.SetHotspot(mDirEntry.mXHotspot, mDirEntry.mYHotspot);
+  return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
+}
+
+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());
+    if (mDownscaler) {
+      mContainedDecoder->SetTargetSize(mDownscaler->TargetSize());
+    }
+    mContainedDecoder->Init();
+
+    if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) {
+      return Transition::Terminate(ICOState::FAILURE);
+    }
+
+    if (mDirEntry.mBytesInRes <= PNGSIGNATURESIZE) {
+      return Transition::Terminate(ICOState::FAILURE);
+    }
+
+    // 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());
+    if (mDownscaler) {
+      mContainedDecoder->SetTargetSize(mDownscaler->TargetSize());
+    }
+    mContainedDecoder->Init();
+
+    // 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);
+    }
+
+    // Buffer the first part of the bitmap information header.
+    memcpy(mBIHraw, aData, PNGSIGNATURESIZE);
+
+    // Read in the rest of the bitmap information header.
+    return Transition::To(ICOState::READ_BIH,
+                          BITMAPINFOSIZE - PNGSIGNATURESIZE);
+  }
+}
+
+LexerTransition<ICOState>
+nsICODecoder::ReadPNG(const char* aData, uint32_t aLen)
+{
+  if (!WriteToContainedDecoder(aData, aLen)) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+
+  // Raymond Chen says that 32bpp only are valid PNG ICOs
+  // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
+  if (!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);
+
+  // 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);
+  }
+
+  // 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);
+  }
+
+  // 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);
+  }
+
+  // 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();
+
+  // 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;
+
+  // 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);
+}
+
+LexerTransition<ICOState>
+nsICODecoder::PrepareForMask()
+{
+  nsRefPtr<nsBMPDecoder> bmpDecoder =
+    static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+
+  uint16_t numColors = GetNumColors();
+  MOZ_ASSERT(numColors != uint16_t(-1));
+
+  // 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);
+  }
+
+  // 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--;
+
+  nsRefPtr<nsBMPDecoder> bmpDecoder =
+    static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+
+  uint32_t* imageData = bmpDecoder->GetImageData();
+  if (!imageData) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+
+  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);
+}
+
+LexerTransition<ICOState>
+nsICODecoder::FinishResource()
+{
+  // Make sure the actual size of the resource matches the size in the directory
+  // entry. If not, we consider the image corrupt.
+  if (mContainedDecoder->HasSize() &&
+      mContainedDecoder->GetSize() != GetRealSize()) {
+    return Transition::Terminate(ICOState::FAILURE);
+  }
+
+  return Transition::Terminate(ICOState::SUCCESS);
 }
 
 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);
 
-  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;
+  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 FinishResource();
+        default:
+          MOZ_ASSERT_UNREACHABLE("Unknown ICOState");
+          return Transition::Terminate(ICOState::FAILURE);
       }
-      mIsCursor = (*aBuffer == 2);
-    }
-    mPos++; aBuffer++; aCount--;
-  }
+    });
 
-  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);
+  if (!terminalState) {
+    return;  // Need more data.
   }
 
-  // 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;
-
-  // 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
-    }
-
-    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;
-        }
-
-        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;
-    }
-
-    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;
-    }
-
-    memcpy(mSignature + (mPos - mImageOffset), aBuffer, toCopy);
-    mPos += toCopy;
-    aCount -= toCopy;
-    aBuffer += toCopy;
-
-    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 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;
-    }
-
-    if (!HasSize() && mContainedDecoder->HasSize()) {
-      nsIntSize size = mContainedDecoder->GetSize();
-      PostSize(size.width, size.height);
-    }
-
-    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();
-    }
+  if (*terminalState == ICOState::FAILURE) {
+    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;
-    }
-
-    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
-    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;
-    }
-
-    // Setup the cursor hot spot if one is present
-    SetHotSpotIfCursor();
-
-    // 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;
-    }
-
-    // Fix the ICO width from the BIH.
-    if (!FixBitmapWidth(reinterpret_cast<int8_t*>(mBIHraw))) {
-      PostDataError();
-      return;
-    }
-
-    // Write out the BMP's bitmap info header
-    if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
-      return;
-    }
-
-    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;
-    }
-  }
-
-  // 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;
-
-    // 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) {
-
-      // Figure out how much data the BMP decoder wants
-      uint32_t toFeed = bmpDataEnd - mPos;
-      if (toFeed > aCount) {
-        toFeed = aCount;
-      }
-
-      if (!WriteToContainedDecoder(aBuffer, toFeed)) {
-        return;
-      }
-
-      mPos += toFeed;
-      aCount -= toFeed;
-      aBuffer += toFeed;
-    }
-
-    // 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());
-
-      // 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;
-          }
-        }
-
-        // Ensure memory has been allocated before decoding.
-        MOZ_ASSERT(mRow, "mRow is null");
-        if (!mRow) {
-          PostDataError();
-          return;
-        }
-
-        uint8_t sawTransparency = 0;
-
-        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;
-
-            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++;
-              }
-            }
-          }
-        }
-
-        // If any bits are set in sawTransparency, then we know at least one
-        // pixel was transparent.
-        if (sawTransparency) {
-          PostHasTransparency();
-          bmpDecoder->SetHasAlphaData();
-        }
-      }
-    }
-  }
+  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,78 @@
  * 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;
   }
 
-  virtual void SetResolution(const gfx::IntSize& aResolution) override
+  /// @return the height of the selected directory entry (mDirEntry).
+  uint32_t GetRealHeight() const { return GetRealHeight(mDirEntry); }
+
+  /// @return the size of the selected directory entry (mDirEntry).
+  gfx::IntSize GetRealSize() const
   {
-    mResolution = aResolution;
+    return gfx::IntSize(GetRealWidth(), GetRealHeight());
   }
 
+  /// @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 +82,58 @@ 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);
+  LexerTransition<ICOState> FinishResource();
 
-  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.
+  char mBIHraw[40];                    // The bitmap information header.
+  IconDirEntry mDirEntry;              // The dir entry for the selected resource.
+  IntSize mBiggestResourceSize;        // Used to select the intrinsic size.
+  IntSize mBiggestResourceHotSpot;     // Used to select the intrinsic size.
+  uint16_t mBiggestResourceColorDepth; // Used to select the intrinsic size.
+  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/decoders/nsIconDecoder.cpp
+++ b/image/decoders/nsIconDecoder.cpp
@@ -28,30 +28,16 @@ nsIconDecoder::nsIconDecoder(RasterImage
  , mHeight(-1)
 {
   // Nothing to do
 }
 
 nsIconDecoder::~nsIconDecoder()
 { }
 
-nsresult
-nsIconDecoder::SetTargetSize(const nsIntSize& aSize)
-{
-  // Make sure the size is reasonable.
-  if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // Create a downscaler that we'll filter our output through.
-  mDownscaler.emplace(aSize);
-
-  return NS_OK;
-}
-
 void
 nsIconDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
 
   // Loop until the input data is gone
   while (aCount > 0) {
     switch (mState) {
--- a/image/decoders/nsIconDecoder.h
+++ b/image/decoders/nsIconDecoder.h
@@ -34,28 +34,24 @@ class RasterImage;
 //
 ////////////////////////////////////////////////////////////////////////////////
 
 class nsIconDecoder : public Decoder
 {
 public:
   virtual ~nsIconDecoder();
 
-  virtual nsresult SetTargetSize(const nsIntSize& aSize) override;
-
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
 
 private:
   friend class DecoderFactory;
 
   // Decoders should only be instantiated via DecoderFactory.
   explicit nsIconDecoder(RasterImage* aImage);
 
-  Maybe<Downscaler> mDownscaler;
-
   uint32_t mExpectedDataLength;
   uint32_t mPixBytesRead;
   uint32_t mState;
   uint8_t mWidth;
   uint8_t mHeight;
 };
 
 enum {
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -131,30 +131,16 @@ nsJPEGDecoder::~nsJPEGDecoder()
 }
 
 Telemetry::ID
 nsJPEGDecoder::SpeedHistogram()
 {
   return Telemetry::IMAGE_DECODE_SPEED_JPEG;
 }
 
-nsresult
-nsJPEGDecoder::SetTargetSize(const nsIntSize& aSize)
-{
-  // Make sure the size is reasonable.
-  if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // Create a downscaler that we'll filter our output through.
-  mDownscaler.emplace(aSize);
-
-  return NS_OK;
-}
-
 void
 nsJPEGDecoder::InitInternal()
 {
   mCMSMode = gfxPlatform::GetCMSMode();
   if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
     mCMSMode = eCMSMode_Off;
   }
 
--- a/image/decoders/nsJPEGDecoder.h
+++ b/image/decoders/nsJPEGDecoder.h
@@ -10,17 +10,16 @@
 #include "RasterImage.h"
 // On Windows systems, RasterImage.h brings in 'windows.h', which defines INT32.
 // But the jpeg decoder has its own definition of INT32. To avoid build issues,
 // we need to undefine the version from 'windows.h'.
 #undef INT32
 
 #include "Decoder.h"
 
-#include "Downscaler.h"
 #include "nsAutoPtr.h"
 
 #include "nsIInputStream.h"
 #include "nsIPipe.h"
 #include "qcms.h"
 
 extern "C" {
 #include "jpeglib.h"
@@ -50,36 +49,32 @@ typedef enum {
 class RasterImage;
 struct Orientation;
 
 class nsJPEGDecoder : public Decoder
 {
 public:
   virtual ~nsJPEGDecoder();
 
-  virtual nsresult SetTargetSize(const nsIntSize& aSize) override;
-
   virtual void SetSampleSize(int aSampleSize) override
   {
     mSampleSize = aSampleSize;
   }
 
   virtual void InitInternal() override;
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
   virtual void FinishInternal() override;
 
   virtual Telemetry::ID SpeedHistogram() override;
   void NotifyDone();
 
 protected:
   Orientation ReadOrientationFromEXIF();
   void OutputScanlines(bool* suspend);
 
-  Maybe<Downscaler> mDownscaler;
-
 private:
   friend class DecoderFactory;
 
   // Decoders should only be instantiated via DecoderFactory.
   nsJPEGDecoder(RasterImage* aImage, Decoder::DecodeStyle aDecodeStyle);
 
 public:
   struct jpeg_decompress_struct mInfo;
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -136,30 +136,16 @@ nsPNGDecoder::~nsPNGDecoder()
 
     // mTransform belongs to us only if mInProfile is non-null
     if (mTransform) {
       qcms_transform_release(mTransform);
     }
   }
 }
 
-nsresult
-nsPNGDecoder::SetTargetSize(const nsIntSize& aSize)
-{
-  // Make sure the size is reasonable.
-  if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // Create a downscaler that we'll filter our output through.
-  mDownscaler.emplace(aSize);
-
-  return NS_OK;
-}
-
 void
 nsPNGDecoder::CheckForTransparency(SurfaceFormat aFormat,
                                    const IntRect& aFrameRect)
 {
   // Check if the image has a transparent color in its palette.
   if (aFormat == SurfaceFormat::B8G8R8A8) {
     PostHasTransparency();
   }
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -3,17 +3,16 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_decoders_nsPNGDecoder_h
 #define mozilla_image_decoders_nsPNGDecoder_h
 
 #include "Decoder.h"
-#include "Downscaler.h"
 
 #include "gfxTypes.h"
 
 #include "nsCOMPtr.h"
 
 #include "png.h"
 
 #include "qcms.h"
@@ -22,18 +21,16 @@ namespace mozilla {
 namespace image {
 class RasterImage;
 
 class nsPNGDecoder : public Decoder
 {
 public:
   virtual ~nsPNGDecoder();
 
-  virtual nsresult SetTargetSize(const nsIntSize& aSize) override;
-
   virtual void InitInternal() override;
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
   virtual Telemetry::ID SpeedHistogram() override;
 
   nsresult CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset,
                        int32_t aWidth, int32_t aHeight,
                        gfx::SurfaceFormat aFormat);
   void EndImageFrame();
@@ -80,17 +77,16 @@ private:
   // XXX(seth): nsICODecoder is temporarily an exception to this rule.
   explicit nsPNGDecoder(RasterImage* aImage);
 
   void PostPartialInvalidation(const IntRect& aInvalidRegion);
   void PostFullInvalidation();
 
 public:
   png_structp mPNG;
-  Maybe<Downscaler> mDownscaler;
   png_infop mInfo;
   nsIntRect mFrameRect;
   uint8_t* mCMSLine;
   uint8_t* interlacebuf;
   qcms_profile* mInProfile;
   qcms_transform* mTransform;
 
   gfx::SurfaceFormat format;
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -791,98 +791,18 @@ imgFrame::LockImageData()
     return NS_OK;
   }
 
   // Paletted images don't have surfaces, so there's nothing to do.
   if (mPalettedImageData) {
     return NS_OK;
   }
 
-  return Deoptimize();
-}
-
-nsresult
-imgFrame::Deoptimize()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  mMonitor.AssertCurrentThreadOwns();
-  MOZ_ASSERT(!mImageSurface);
-
-  if (!mImageSurface) {
-    if (mVBuf) {
-      VolatileBufferPtr<uint8_t> ref(mVBuf);
-      if (ref.WasBufferPurged()) {
-        return NS_ERROR_FAILURE;
-      }
-
-      mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat);
-      if (!mImageSurface) {
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-    }
-    if (mOptSurface || mSinglePixel || mFormat == SurfaceFormat::R5G6B5) {
-      SurfaceFormat format = mFormat;
-      if (mFormat == SurfaceFormat::R5G6B5) {
-        format = SurfaceFormat::B8G8R8A8;
-      }
-
-      // Recover the pixels
-      RefPtr<VolatileBuffer> buf =
-        AllocateBufferForImage(mSize, format);
-      if (!buf) {
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-
-      RefPtr<DataSourceSurface> surf =
-        CreateLockedSurface(buf, mSize, format);
-      if (!surf) {
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-
-      DataSourceSurface::MappedSurface mapping;
-      if (!surf->Map(DataSourceSurface::MapType::WRITE, &mapping)) {
-        gfxCriticalError() << "imgFrame::Deoptimize failed to map surface";
-        return NS_ERROR_FAILURE;
-      }
-
-      RefPtr<DrawTarget> target =
-        Factory::CreateDrawTargetForData(BackendType::CAIRO,
-                                         mapping.mData,
-                                         mSize,
-                                         mapping.mStride,
-                                         format);
-      if (!target) {
-        gfxWarning() <<
-          "imgFrame::Deoptimize failed in CreateDrawTargetForData";
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-
-      Rect rect(0, 0, mSize.width, mSize.height);
-      if (mSinglePixel) {
-        target->FillRect(rect, ColorPattern(mSinglePixelColor),
-                         DrawOptions(1.0f, CompositionOp::OP_SOURCE));
-      } else if (mFormat == SurfaceFormat::R5G6B5) {
-        target->DrawSurface(mImageSurface, rect, rect);
-      } else {
-        target->DrawSurface(mOptSurface, rect, rect,
-                            DrawSurfaceOptions(),
-                            DrawOptions(1.0f, CompositionOp::OP_SOURCE));
-      }
-      target->Flush();
-      surf->Unmap();
-
-      mFormat = format;
-      mVBuf = buf;
-      mImageSurface = surf;
-      mOptSurface = nullptr;
-    }
-  }
-
-  mVBufPtr = mVBuf;
-  return NS_OK;
+  MOZ_ASSERT_UNREACHABLE("It's illegal to re-lock an optimized imgFrame");
+  return NS_ERROR_FAILURE;
 }
 
 void
 imgFrame::AssertImageDataLocked() const
 {
 #ifdef DEBUG
   MonitorAutoLock lock(mMonitor);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
--- a/image/imgFrame.h
+++ b/image/imgFrame.h
@@ -269,17 +269,16 @@ public:
 
 private: // methods
 
   ~imgFrame();
 
   nsresult LockImageData();
   nsresult UnlockImageData();
   nsresult Optimize();
-  nsresult Deoptimize();
 
   void AssertImageDataLocked() const;
 
   bool IsImageCompleteInternal() const;
   nsresult ImageUpdatedInternal(const nsIntRect& aUpdateRect);
   void GetImageDataInternal(uint8_t** aData, uint32_t* length) const;
   uint32_t GetImageBytesPerRow() const;
   uint32_t GetImageDataLength() const;
@@ -443,16 +442,19 @@ private:
  * |ref| and |if (ref)| is true, then calls to GetImageData(), GetPaletteData(),
  * and GetDrawTarget() are guaranteed to succeed. This guarantee is stronger
  * than DrawableFrameRef, so everything that a valid DrawableFrameRef guarantees
  * is also guaranteed by a valid RawAccessFrameRef.
  *
  * This may be considerably more expensive than is necessary just for drawing,
  * so only use this when you need to read or write the raw underlying image data
  * that the imgFrame holds.
+ *
+ * Once all an imgFrame's RawAccessFrameRefs go out of scope, new
+ * RawAccessFrameRefs cannot be created.
  */
 class RawAccessFrameRef final
 {
 public:
   RawAccessFrameRef() { }
 
   explicit RawAccessFrameRef(imgFrame* aFrame)
     : mFrame(aFrame)
--- a/image/imgIContainer.idl
+++ b/image/imgIContainer.idl
@@ -114,17 +114,17 @@ native nsIntSizeByVal(nsIntSize);
 
 /**
  * imgIContainer is the interface that represents an image. It allows
  * access to frames as Thebes surfaces. It also allows drawing of images
  * onto Thebes contexts.
  *
  * Internally, imgIContainer also manages animation of images.
  */
-[scriptable, builtinclass, uuid(4880727a-5673-44f7-b248-f6c86e22a434)]
+[scriptable, builtinclass, uuid(4e5a0547-6c54-4051-8b52-1f2fdd667696)]
 interface imgIContainer : nsISupports
 {
   /**
    * The width of the container rectangle.  In the case of any error,
    * zero is returned, and an exception will be thrown.
    */
   readonly attribute int32_t width;
 
@@ -263,16 +263,31 @@ interface imgIContainer : nsISupports
    *
    * @param aWhichFrame Frame specifier of the FRAME_* variety.
    * @param aFlags Flags of the FLAG_* variety
    */
   [noscript, notxpcom] TempRefSourceSurface getFrame(in uint32_t aWhichFrame,
                                                      in uint32_t aFlags);
 
   /**
+   * Get a surface for the given frame at the specified size. Matching the
+   * requested size is best effort; it's not guaranteed that the surface you get
+   * will be a perfect match. (Some reasons you may get a surface of a different
+   * size include: if you requested upscaling, if downscale-during-decode is
+   * disabled, or if you didn't request the first frame.)
+   *
+   * @param aSize The desired size.
+   * @param aWhichFrame Frame specifier of the FRAME_* variety.
+   * @param aFlags Flags of the FLAG_* variety
+   */
+  [noscript, notxpcom] TempRefSourceSurface getFrameAtSize([const] in nsIntSize aSize,
+                                                           in uint32_t aWhichFrame,
+                                                           in uint32_t aFlags);
+
+  /**
    * Whether this image is opaque (i.e., needs a background painted behind it).
    */
   [notxpcom] boolean isOpaque();
 
   /**
    * @return true if getImageContainer() is expected to return a valid
    *         ImageContainer when passed the given @Manager and @Flags
    *         parameters.
--- a/image/imgTools.cpp
+++ b/image/imgTools.cpp
@@ -193,36 +193,37 @@ imgTools::EncodeScaledImage(imgIContaine
   NS_ENSURE_ARG(aScaledWidth >= 0 && aScaledHeight >= 0);
 
   // If no scaled size is specified, we'll just encode the image at its
   // original size (no scaling).
   if (aScaledWidth == 0 && aScaledHeight == 0) {
     return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
   }
 
-  // Use frame 0 from the image container.
-  RefPtr<SourceSurface> frame =
-    aContainer->GetFrame(imgIContainer::FRAME_FIRST,
-                         imgIContainer::FLAG_SYNC_DECODE);
-  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
-
-  int32_t frameWidth = frame->GetSize().width;
-  int32_t frameHeight = frame->GetSize().height;
+  // Retrieve the image's size.
+  int32_t imageWidth = 0;
+  int32_t imageHeight = 0;
+  aContainer->GetWidth(&imageWidth);
+  aContainer->GetHeight(&imageHeight);
 
   // If the given width or height is zero we'll replace it with the image's
   // original dimensions.
-  if (aScaledWidth == 0) {
-    aScaledWidth = frameWidth;
-  } else if (aScaledHeight == 0) {
-    aScaledHeight = frameHeight;
-  }
+  IntSize scaledSize(aScaledWidth == 0 ? imageWidth : aScaledWidth,
+                     aScaledHeight == 0 ? imageHeight : aScaledHeight);
+
+  // Use frame 0 from the image container.
+  RefPtr<SourceSurface> frame =
+    aContainer->GetFrameAtSize(scaledSize,
+                               imgIContainer::FRAME_FIRST,
+                               imgIContainer::FLAG_HIGH_QUALITY_SCALING |
+                               imgIContainer::FLAG_SYNC_DECODE);
+  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
 
   RefPtr<DataSourceSurface> dataSurface =
-    Factory::CreateDataSourceSurface(IntSize(aScaledWidth, aScaledHeight),
-                                     SurfaceFormat::B8G8R8A8);
+    Factory::CreateDataSourceSurface(scaledSize, SurfaceFormat::B8G8R8A8);
   if (NS_WARN_IF(!dataSurface)) {
     return NS_ERROR_FAILURE;
   }
 
   DataSourceSurface::MappedSurface map;
   if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) {
     return NS_ERROR_FAILURE;
   }
@@ -233,19 +234,20 @@ imgTools::EncodeScaledImage(imgIContaine
                                      dataSurface->GetSize(),
                                      map.mStride,
                                      SurfaceFormat::B8G8R8A8);
   if (!dt) {
     gfxWarning() << "imgTools::EncodeImage failed in CreateDrawTargetForData";
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
+  IntSize frameSize = frame->GetSize();
   dt->DrawSurface(frame,
-                  Rect(0, 0, aScaledWidth, aScaledHeight),
-                  Rect(0, 0, frameWidth, frameHeight),
+                  Rect(0, 0, scaledSize.width, scaledSize.height),
+                  Rect(0, 0, frameSize.width, frameSize.height),
                   DrawSurfaceOptions(),
                   DrawOptions(1.0f, CompositionOp::OP_SOURCE));
 
   dataSurface->Unmap();
 
   return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream);
 }
 
--- a/image/test/crashtests/crashtests.list
+++ b/image/test/crashtests/crashtests.list
@@ -47,8 +47,13 @@ load multiple-png-hassize.ico
 load 856616.gif
 
 skip-if(AddressSanitizer) skip-if(B2G) load 944353.jpg
 
 # Bug 1160801: Ensure that we handle invalid disposal types.
 load invalid-disposal-method-1.gif
 load invalid-disposal-method-2.gif
 load invalid-disposal-method-3.gif
+
+# Ensure we handle ICO directory entries which specify the wrong size for the
+# contained resource.
+load invalid_ico_height.ico
+load invalid_ico_width.ico
rename from image/test/reftest/ico/ico-bmp-corrupted/invalid_ico_height.ico
rename to image/test/crashtests/invalid_ico_height.ico
rename from image/test/reftest/ico/ico-bmp-corrupted/invalid_ico_width.ico
rename to image/test/crashtests/invalid_ico_width.ico
--- a/image/test/gtest/TestDecodeToSurface.cpp
+++ b/image/test/gtest/TestDecodeToSurface.cpp
@@ -17,81 +17,87 @@
 #include "mozilla/nsRefPtr.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::image;
 
-
 TEST(ImageDecodeToSurface, ImageModuleAvailable)
 {
   // We can run into problems if XPCOM modules get initialized in the wrong
   // order. It's important that this test run first, both as a sanity check and
   // to ensure we get the module initialization order we want.
   nsCOMPtr<imgITools> imgTools =
     do_CreateInstance("@mozilla.org/image/tools;1");
   EXPECT_TRUE(imgTools != nullptr);
 }
 
 class DecodeToSurfaceRunnable : public nsRunnable
 {
 public:
-  DecodeToSurfaceRunnable(nsIInputStream* aInputStream,
+  DecodeToSurfaceRunnable(nsRefPtr<SourceSurface>& aSurface,
+                          nsIInputStream* aInputStream,
                           const ImageTestCase& aTestCase)
-    : mInputStream(aInputStream)
+    : mSurface(aSurface)
+    , mInputStream(aInputStream)
     , mTestCase(aTestCase)
   { }
 
   NS_IMETHOD Run()
   {
     Go();
     return NS_OK;
   }
 
   void Go()
   {
-    nsRefPtr<SourceSurface> surface =
+    mSurface =
       ImageOps::DecodeToSurface(mInputStream,
                                 nsAutoCString(mTestCase.mMimeType),
                                 imgIContainer::DECODE_FLAGS_DEFAULT);
-    ASSERT_TRUE(surface != nullptr);
+    ASSERT_TRUE(mSurface != nullptr);
 
-    EXPECT_EQ(SurfaceType::DATA, surface->GetType());
-    EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::B8G8R8X8 ||
-                surface->GetFormat() == SurfaceFormat::B8G8R8A8);
-    EXPECT_EQ(mTestCase.mSize, surface->GetSize());
+    EXPECT_EQ(SurfaceType::DATA, mSurface->GetType());
+    EXPECT_TRUE(mSurface->GetFormat() == SurfaceFormat::B8G8R8X8 ||
+                mSurface->GetFormat() == SurfaceFormat::B8G8R8A8);
+    EXPECT_EQ(mTestCase.mSize, mSurface->GetSize());
 
-    EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(),
+    EXPECT_TRUE(IsSolidColor(mSurface, BGRAColor::Green(),
                              mTestCase.mFlags & TEST_CASE_IS_FUZZY));
   }
 
 private:
+  nsRefPtr<SourceSurface>& mSurface;
   nsCOMPtr<nsIInputStream> mInputStream;
   ImageTestCase mTestCase;
 };
 
 static void
 RunDecodeToSurface(const ImageTestCase& aTestCase)
 {
   nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
   ASSERT_TRUE(inputStream != nullptr);
 
   nsCOMPtr<nsIThread> thread;
   nsresult rv = NS_NewThread(getter_AddRefs(thread), nullptr);
   ASSERT_TRUE(NS_SUCCEEDED(rv));
 
   // We run the DecodeToSurface tests off-main-thread to ensure that
   // DecodeToSurface doesn't require any main-thread-only code.
+  nsRefPtr<SourceSurface> surface;
   nsCOMPtr<nsIRunnable> runnable =
-    new DecodeToSurfaceRunnable(inputStream, aTestCase);
+    new DecodeToSurfaceRunnable(surface, inputStream, aTestCase);
   thread->Dispatch(runnable, nsIThread::DISPATCH_SYNC);
 
   thread->Shutdown();
+
+  // Explicitly release the SourceSurface on the main thread.
+  surface = nullptr;
 }
 
 TEST(ImageDecodeToSurface, PNG) { RunDecodeToSurface(GreenPNGTestCase()); }
 TEST(ImageDecodeToSurface, GIF) { RunDecodeToSurface(GreenGIFTestCase()); }
 TEST(ImageDecodeToSurface, JPG) { RunDecodeToSurface(GreenJPGTestCase()); }
 TEST(ImageDecodeToSurface, BMP) { RunDecodeToSurface(GreenBMPTestCase()); }
 TEST(ImageDecodeToSurface, ICO) { RunDecodeToSurface(GreenICOTestCase()); }
 
--- 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',
--- a/image/test/mochitest/test_has_transparency.html
+++ b/image/test/mochitest/test_has_transparency.html
@@ -52,18 +52,19 @@ function testFiles() {
   yield ["damon.jpg", false];
 
   // Most BMPs are not transparent. (The TestMetadata GTest, which will
   // eventually replace this test totally, has coverage for the kinds that can be
   // transparent.)
   yield ["opaque.bmp", false];
 
   // ICO files which contain BMPs have an additional type of transparency - the
-  // AND mask - that warrants separate testing.
-  yield ["ico-bmp-opaque.ico", false];
+  // AND mask - that warrants separate testing. (Although, after bug 1201796,
+  // all ICOs are considered transparent.)
+  yield ["ico-bmp-opaque.ico", true];
   yield ["ico-bmp-transparent.ico", true];
 
   // SVGs are always transparent.
   yield ["lime100x100.svg", true];
 }
 
 function loadNext() {
   var currentFile = "";
--- a/image/test/reftest/downscaling/reftest.list
+++ b/image/test/reftest/downscaling/reftest.list
@@ -19,28 +19,28 @@
 # sufficient.
 #
 # Also note that Mac OS X has its own system-level downscaling algorithm, so
 # tests here may need Mac-specific "fuzzy-if(cocoaWidget,...)" annotations.
 # Similarly, modern versions of Windows have slightly different downscaling
 # behavior than other platforms, and may require "fuzzy-if(winWidget,...)".
 
 
-# RUN TESTS NOT AFFECTED BY HIGH QUALITY DOWNSCALING:
-# ===================================================
+# RUN TESTS NOT AFFECTED BY DOWNSCALE-DURING-DECODE:
+# ==================================================
 == downscale-svg-1a.html downscale-svg-1-ref.html?80
 fuzzy(80,468) == downscale-svg-1b.html downscale-svg-1-ref.html?72
 == downscale-svg-1c.html downscale-svg-1-ref.html?64
 fuzzy(17,208) fuzzy-if(B2G,255,207) == downscale-svg-1d.html downscale-svg-1-ref.html?53 # right side is 1 pixel off for B2G, probably regression from 974242
 fuzzy(78,216) == downscale-svg-1e.html downscale-svg-1-ref.html?40
 fuzzy(51,90) == downscale-svg-1f.html downscale-svg-1-ref.html?24
 
-# RUN TESTS WITH HIGH QUALITY DOWNSCALING DISABLED:
-# =================================================
-default-preferences pref(image.high_quality_downscaling.enabled,false)
+# RUN TESTS WITH DOWNSCALE-DURING-DECODE DISABLED:
+# ================================================
+default-preferences pref(image.downscale-during-decode.enabled,false)
 
 fuzzy-if(winWidget,16,20) fuzzy-if(cocoaWidget,106,31) == downscale-1.html downscale-1-ref.html
 
 fuzzy(20,999) != downscale-2a.html?203,52,left about:blank
 fuzzy(20,999) != downscale-2b.html?203,52,left about:blank
 fuzzy(20,999) != downscale-2c.html?203,52,left about:blank
 fuzzy(20,999) != downscale-2d.html?203,52,left about:blank
 fuzzy(20,999) != downscale-2e.html?203,52,left about:blank
@@ -85,20 +85,19 @@ fuzzy(20,999) != downscale-2a.html?205,5
 fuzzy(20,999) != downscale-2b.html?205,53,bottom about:blank
 fuzzy(20,999) != downscale-2c.html?205,53,bottom about:blank
 fuzzy(20,999) != downscale-2d.html?205,53,bottom about:blank
 fuzzy(20,999) fails-if(OSX>=1008) != downscale-2e.html?205,53,bottom about:blank
 
 == downscale-png.html?16,16,interlaced downscale-png.html?16,16,normal
 == downscale-png.html?24,24,interlaced downscale-png.html?24,24,normal
 
-# RUN TESTS WITH HIGH QUALITY DOWNSCALING ENABLED:
-# ================================================
-# High-quality downscaling enabled:
-default-preferences pref(image.high_quality_downscaling.enabled,true)
+# RUN TESTS WITH DOWNSCALE-DURING-DECODE ENABLED:
+# ===============================================
+default-preferences pref(image.downscale-during-decode.enabled,true)
 
 fuzzy(31,127) fuzzy-if(d2d,31,147) == downscale-1.html downscale-1-ref.html # intermittently 147 pixels on win7 accelerated only (not win8)
 
 fuzzy(20,999) != downscale-2a.html?203,52,left about:blank
 fuzzy(20,999) != downscale-2b.html?203,52,left about:blank
 fuzzy(20,999) != downscale-2c.html?203,52,left about:blank
 fuzzy(20,999) != downscale-2d.html?203,52,left about:blank
 fuzzy(20,999) != downscale-2e.html?203,52,left about:blank
index d37f2f1cc0534d9aec717a087a13dd4eb5b83a8f..025ebaed1ff12279d4e07cfcaf3bd1cbbeb0884b
GIT binary patch
literal 4286
zc%1E%KTE?v7{=dHP&Ye?bg`qWQ^xuQ{7ilmCk4T!L){cgx1vzWK~0Gw1dWm*5HJvk
z;q^J&lWS;Igv&YHQ-0-mkM{Rm{tz|r>2yeb(|L<%hlut8Ms!H?`Da?>;^N}s;_|Q4
z-e|VL9XJ687X1C;V8B8M#`@Nv=Nh~LPn&u2?7r`_?11&ep#LS9%~<as&wknKaS#M@
z-Umk}{aKc=-T{a(4Ebg>k~3k-t^9eOv)<w2?3AM@lItszXIK8BC|K{1BniiH%!_AZ
z2^0RZELrc6rs-D)c^$NSVZ_hKcO53<vAP!C&;R&0|GEzTbZX39^;bJQKHM91ukx!7
zPq^o%-Ru0X(Q_^CRsK5NRe#-X-EYIK{I=Z6Z_BOut+-{X@7{0n-kq}xwg4S3*w)ws
SY7f6@fF{@k*k)`qw)q3j(ZA6E
--- a/image/test/reftest/ico/ico-bmp-corrupted/reftest.list
+++ b/image/test/reftest/ico/ico-bmp-corrupted/reftest.list
@@ -3,13 +3,8 @@
 # Invalid value for bits per pixel (BPP) - detected when decoding the header.
 == wrapper.html?invalid-bpp.ico about:blank
 # Invalid BPP values for RLE4 - detected when decoding the image data.
 == wrapper.html?invalid-compression-RLE4.ico about:blank
 # Invalid BPP values for RLE8 - detected when decoding the image data.
 == wrapper.html?invalid-compression-RLE8.ico about:blank
 # Invalid compression value - detected when decoding the image data.
 == wrapper.html?invalid-compression.ico about:blank
-
-# Invalid ICO width and heigth should be ignored if the
-# contained BMP is correct.
-== invalid_ico_height.ico 16x16.png
-== invalid_ico_width.ico 16x16.png
--- a/image/test/reftest/ico/ico-mixed/reftest.list
+++ b/image/test/reftest/ico/ico-mixed/reftest.list
@@ -1,14 +1,3 @@
 # ICO BMP and PNG mixed tests
 
-== mixed-bmp-png.ico mixed-bmp-png.png
-
-# Using media fragments to select different resolutions
-
-== mixed-bmp-png.ico#-moz-resolution=8,8 mixed-bmp-png.png
-== mixed-bmp-png.ico#test=true&-moz-resolution=8,8&other mixed-bmp-png.png
-== mixed-bmp-png.ico#-moz-resolution=32,32 mixed-bmp-png32.png
-== mixed-bmp-png.ico#-moz-resolution=39,39 mixed-bmp-png48.png
-== mixed-bmp-png.ico#-moz-resolution=40,40 mixed-bmp-png48.png
-== mixed-bmp-png.ico#-moz-resolution=48,48 mixed-bmp-png48.png
-== mixed-bmp-png.ico#-moz-resolution=64,64 mixed-bmp-png48.png
-== mixed-bmp-png.ico#-moz-resolution=64 mixed-bmp-png.png # Bad syntax will fall back to lowest resolution
+== mixed-bmp-png.ico mixed-bmp-png48.png
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..ecb88edf3ca8ff586d009cac9d2b1cb40aff8999
GIT binary patch
literal 5934
zc${@u2{>EX+mCiiXS7vSw51fIo1*sGJF1jeYOmVbT2gzFNGYP!P)oH))tbLnYELTG
z2vJoOMN7q!#7+=NER}?ad^+Fve9z4MXYP6KbI*J3d7k?_zw>+F^S&1V;M;3IE<V5+
zz)^7kKy0u7^rn^R;a^Vuve!LqcEi{f@W&iJz(M}Khiho5&krli*3<}q>X%&FGx+ZT
zO^pG(AMHOaC7F98KR>wP90mX!`S|1JgDV`7*c%i8n^|5LAPO8irXylhN?Y6e+AA~T
z-|Qcb(+YY+C9xUCa?hQM7kE5~N!G_y=i|dIC*pMv6*cQy7J3p|_(ae@y|xz>8mM@d
z{Iioob638H!&=4L^se+1HaSB~9xC~jx`*(ZogT+shQbsR&A!h=Bqf>g_NG1sO)AVp
z8`QB+3OlW!Ii}0iu)YSp7~_v{KaT}_<L0OOJO@$3_DYvjZi}oRX0<$7tB#(;mLG@N
zf5eS$NZZ&9L7bw5^qJ|Bq2%RDyQRQXZu7Z~YK53r`cBkcXKnIE(ownn?@I*s17i2M
zIs^dR*<0^^005C+gUW#2{Lts50j-MKxo%Hua4L^Tki>oXtQgi*uS(t3-;G*HRR~8Y
zw+0PEa+wF0GHg9f6G+3!mn*Ee&BTpwX^}z5kw9hzrk?qo5j_NQVEAe<)#rym+<YyM
zg*e>)Fo*7xo6-W|F>M_rBaY!Du#3-Ch1HSA5pnW`v2afjjk4Dd#Tf*QbiszI-5$}T
zzXRE8u-0HK{Sp^H?EH&<_^{}UY7JI$lK&H|z;?k(e@poc`TOZmGLj{4+c~o;3W%Nj
z-{qVD0N(%DZvenR0mQZKE1Lxr!c>Em86IR`4ky^}(e!XYuzvpVZ3j@$(u7&lV;5aD
zS}O-1cqlIKQILjW9Zs}|WfK(qdrsS{rhGZs{wV2ttLrxM5hIH0ogMnQd0`a`B!W(<
zOF(ZAX}xm<PRyh}iy_)@?vLc<LM<yvhTlxRq=@`+XaHb`|Ic_X0|2Ye+OvU$dbtK|
z#BFORSq|38DlfDqL%X<R`H1h!jxf)9PqlKb2uS|!6OniHNaljR^_q763mXmShJ%!i
z%=qkcs~$Dw%@4rNO0NxBu-g(B_)?_3VQQqMd-eJjJ0Tz-GLXNxGR<ulxv)#jg4u|2
zl&i!DijjS!XU=609_4OJFKfsS)}~OlegV7>2K-$M@EUwY?u_8(pahK&IP_p;8{5bG
zFsyxR^;F&R88(Pf5kEz6OJgSqt0;;fLZFBF?R?w(RMZJ>lv?{oCMI)bhHtz%7iIx7
zIuOaLJ9ZKMtsdjNf$yuZXUUDwp;?0EWM2jPr8I21GMSycEvK9Ni0VFC7w~(B{N|sx
z?+##eUo5)!0@BM<<$%3D`czsLguL?eBD^c8(k0l;pRCX-9*px5A8c1(F};y94|Tm8
zn1lks-4*Ue##ydi1!tTz1avxJFiY7Lgq^)epFDm}XhM4hb?G6bC(g;Z{J6!?H4}{y
zGB~lUP-1f}d!k`6TAdEnD*cG2-H*C?dLxURob>R-{#98=h;b5T${F<h$f3XP<(|P#
zz8|r=PFIEBi-6j?0MEMUA%LEYlsr&wb%h!e-`f1T*buk-o^$a<T(tjk1oaYfE54`z
z9noWl?1{mFr&8szQdEs_d;>@M|5MH*Bx0Xns$_)l_W(X*kD$;xu$Py=PjYXIbn$9B
z$OgKvRA)d5X~PgLLmzlbzY+Gcw`43z;PO2wfd|7$!g-z>|L6oNpyob_jSE(K!O)-&
zr0l$KBz7cf^L5oM!~IqRthv53yr+E(j@jLBJF2DUhiF(6(H=+{Ku#2(-ORv-#`#xG
zvdjexj7kKl;HmSc;~y-@?;sSg^IF^CY7G6GIR3K=vOw?QkvL;kvqi14O4UNWa$w<b
zO5d%f;<Su8-+k8x_49B@Y??7NH9b~xQ-~&G@Kd_UWIy1H(2@TpG&g@Udg4|{aNw@6
zjxs)vF&QG_XsV>c8qpWKcsR?<Z4<&{M3gzmc%QwPrW8wUU2}GX-&;xjlsJkJE(&FC
z2<se~^4{(*TvI~Nj0rpCV5W+C<0K+hX{UK|bFH<(4Z%2-Ly!i&hd7?eoy5%e0eTbD
zyB3oA*cloYK!z&mrRZG&tV;a9L+oLC^`O(cCvws?p*|?8UZdfeMR+93*K~Q?DrcpB
z)^wA`L_Q(}>^7M5)LUy6S|1!4E36M-?~X1t1CU(?UFlmjBj$cfY2sIC?K<!XU9y&=
zo~_RcIn2)sqx{Hh&^qR#WzYg`Fu|@j#lO8KbkKe$-fGO!PD<B!i3T#Krz~;~(rjR#
z=jRk;XB<wY3s1hKF;tAY&iosFO1Bk|{)<*-3w3x2)v+QwJDex(%D(xF&ARxaQ_7XO
zR06pqLcI?(c62}*qYmvJ_;9V>kmI=Yp<EprlSQ9!acTrlU0!;>1-!J9!;a}az&iK#
zw%>sN<0}zeqW*m}La($zO`R?~I`;rxy#Y-&{D_gdHzrq$;2m<q@l+7Fr~41uRZKaT
zOg;BJw?6f!0CO1-!1n_y{tRg6XhMEPG1W(&YG2Za)1yQqYV4ZQM(Nuu!eZiefj9Qf
ztM7b2)1aYmB;JX?xkHND1Ly0(9TGR3bNGVDRDR2_%kSs{2MxTDEld+W9kE)jrc_N=
zo2auJ?}^uvH7pDr66s?mY>!@0Hdp*6a&+NbA5B?b*+VSK@f<$DDmr}MnQwSF(GGOa
z5e1{4j5T+=Fs&N0PrLcCP4?#-;F63*{IewzLbO8&8>Ahsvp;rE6?gs`pa4_;p@cl9
z(=fy%&hIIuBIm_q-x7nMT}p$XEB#>;+O{khWchfp6~yY?*XYH8@?*y)`o*Oui<J$`
zvlj@_hDIeKhHvrg<RoAg7xn49xWF08+gZ8(tU3aNAkgX}$T7gsgm{UiAnvZq1k}X@
zCzI)>?KUzQ98e#`GH}Bcx|A`I`b%|ke;0zss~RP)0E!#}g%fd|DA>{{p+xmFn(MR?
z-L39>uBHmsr^E`#p;R@!8z1H}!Pqj<s_NdCKymN3S<BD<)5mq8w-)H~?@n$73cNP&
z-lRH$Y)V9Qs}jJFBd$3=VJY4#?K{&IVx+sW^P1Fe;@$^;rk@fXX>M8H`P7S`Z2=cC
zQ_2g+%9tB+NA7B}Q!Kpuk4KLjbkJ<J(2oTjJ}~&0?=M9DFg((0VrYgt$Au=)Hrz`)
zZm(%UniRShCJZ)ukk(%x*B62C!g-KZY4To}#PVOFK4GQ?dYVuew7(N|(BU!A94xIP
ziq&9zjoeN3NNa!h9L(sk7|5N>epZ3Ma5`cws2(jzJm8e}``Rs)t3)rxyO(8s2pvZB
zr)IWVz6Dd=q{v`=UNAz1YA`=KgqfNr=-*zSIvF|eFQ=qh%xA7SA@5sO3SO6jzuunB
zXnu3CQzp0&N{BfXei9LO8?kxK;&OY>9VjVw>r0pdg<r34w8XiAD6Uc;lKwI&z4VHu
z^MmQk@cE35bl0~0BNOYhT5m!ykRitpzEAYn1t$ObHcG!M_x1E_`@zqJ?myqQ3A{+z
z{XRgke0+6&25>|q{!XW39luyyR?_}IV>|JO5p}(vP2Zg}EIPUqyv%~K(jozAsOYqW
zgj39+GfxiD`cH+Jom^2f*Yj9Yg@hWEe81Rf@BzL+2mzqCH&E_<lwt`N8Zl+7l9oyv
zdh9KEbrL~te#0|?tTu(X0izkczxBrP8R_gW8pyusqskw?Na8~A!H|tnt={+_PXAZV
z?sRTRm$p=uNg#Uj-a4=psn*}CD5%gYLqG?Rre>77d(Q{-&Rz4HBlPB)99_S;7IbG}
z_J$x8i&z^f_|>l1J-v!-p1q#E`D+tGqG7Bp`BLP<=@CQO+S<lRF}Eh1y;aQh;?<4x
zEB6jwpr+cD>>o+7O0nE?sPPILF+EvXyjeTE3Nt0kUnD!&!B_msWeGf$Y=_GG=M2sH
zxxmOKI&j%9*{~7J^A_aj_|D{o_jSkPs(V+cqn{Tzy>A%}P8Y;yE~C`lHXEK;gmdYz
zo+6_ED21}?fR1$aaoZDS9<H$Ae22RrhVlWuG7BL6rsQCXj~wefoD*Q0?6Um9pgfAq
z`+B=U6X3f!GkjOCgc{>+<i<0&E*YCy{xV`sRQNSRwQj4TKPljoS4Fjdk<`_4Y#)TL
zkm=gtxOn18rEomv??nF)08fC%pSmcI0I8u39@!D2304)_!CLR({XPoF+cbKQUI`M5
z*z|;q`ww5>)L+2bO1a|Nvr@yA%)k}Z5<MT+-qGZ{9FgDB8;v=lawyyRFV<yR(F?dM
zKtZibE?+fSW?&F_CYDs5J2(&YE*<`)RB9~k<IsDi<1SDkKH6mAt#wn3#8Ged=TWQI
zx<MHNTKum+jyinJ;Gs@Qb5p9z1pdtU{`X>k#8Sb~&ODU1y9!Ih@w%&uvR@D;;V`}u
z`m-TY0te2C7ZVj8sV5J|OuMd{QArT_uLU7bzkRECmX4XJ!4I6D=Wlj5+Md~vA<TEi
ze5$UH8e-jkVe|Ueh+$zHAuMGaUNYaNz?yF}Jsr~BcvO^kG-TTagBE-j^%bP(`->;I
z6U|v5yEmi>pCHmSpIz-dQTgc(j-u5Sgdz@*^<07iMp*QwL=^8P%8eoD!3SU;{zEJO
zM|@PM6$cop5UlBF9CQ86+AtX|BpFiU7ioGAHv|n^@$0$oG@;IbW3?!5Aw#;o6E;=%
zS0X;<1RE*h3#|?g2Nf8VDL2|LYKAi2*n~xW>hpMhwn3Y7ZQPBv3r057S>FbNTYpE1
zuUf8Pv%6n<c4^fpqu&Oflg4|9=}D|l4dA6C-OR<@Dyd~1Q|2$VdWtsE&j4cn9u?Td
z`I~h314<f<8{A)Cf~q89a}73fK0+1ssbVb(Ukh%>5qs|Vi+d4@fcBtkuXT^u6@{zM
zwe>Yss&8EwaXTErP~~K(ygtj>zN8XNP#kDCubYj)eLL`OtlmNM$b0v<q?YEd%{xKv
zN}|rekv-Yrr}nSjQ;O@h)LNe^D=rW8rHMy$H!HorOuJ_t==?x~skb|g4LWDJdzpjL
zY5;X|+na--QudOu6Jhv$f6Kx4)bbwG(G%~aG4qoo`5SATfh*pJ_V;*^Ig#CsdJt5U
z7=;`!sRD*&UxXEOB~}C*gj-9Iu;cl9?7It^^_~3*`cPD3ZeyC7tg5c3zagwcPRUtp
zN7cJEFyFc+;9)Yqh;bI`#x-C&@u&6DZC2eKfKLcNymL~&(val-(~G=9<Sd}}NrQxo
zwBs<8ur@s;7Dm(rnB+E#6?YGBWIt<pPSs@S_BHjcX#bZ!e(1YqFITt^(HYSmDC2P(
zP4$9FvBTXOjzM~}6!euDTib6YuU&walsGiEqn3&d1cI}bU<GGIu`>?lnb{d<)8&+=
zU)K)Kc^7xTk+Cc+i2^w&4~oN(8T!)F9MlBsIit8%uMzLH^h+E#F5=X{2;wMzw<UJb
z1zDaKt!!qa<%Q`ZVf0xA`Yz;+yy9-4IPJVj_!uOe>1;9c4~5rQ#uU8=rp#?yAW4SO
zwSZtJ&~n6V$fvsK$ruBMgl>(DH-a0O>8ZsDCaWk$$1Fg<Ru3%(iBlZ8BOgM{YL<-R
zbo<LBF3Y7!ewPVrM8vqv3-QoPq;d=ARh`cyDV2Kew6M%v8$#)VNc@>0zdCiSG;wZ@
zVkl=>_;$BFDH|(gbls~Qp9ohP0nxIQ%>P*t^OewHS51qnhAlJaD;;@ZgIA8F88?8P
zat<jfjFGzeC5Mg;wNf1O#$rW%z2Ci&$>fs6;mH9LP8t(W;cU9k&2TmK;bsZXyy&Iw
zc+(RnX(rAOBzB%pf1j#DHT9=hPp4Q@Q=qoP9PKR!9W8EhVt-0{_7;Aa5gsz?l^Tam
zM+{1%{0`su*s(qRL~`a;ZvM_oJ@z$51U=o&XGMK;ANya@hlD+9n$_IBUpi_HHO=zW
z@od#&B-N_T)WC)T585M7P(H>2oLhT~8mGSeKE{J78rlBxTC17p78*Mc?tQ6}+U!fV
z%3<Gnm3LmSkiikZBChmqCr=g9W+!l?_`A0Bi)C5Y68ypS|48p9Q0Tyrx-B8O>F8w$
zv7LQivh#Po^IFUhG~fLj7*K&bMt`^|jqf?7?PN)O<lnIF8L`SwvjjXYZU@@NK$L1S
z7sXK7;N5}5Zt0QT)lm6Thw*@u5vx&$X(=P}-F3#SC6@fc{1<rd1?rbDU^YKjcD@aX
zQ+QSy>fN^YHfrU$Z@=g@^GuJeFYD7YdX3fXpW+Se2GRsE;qz^@`QTjX3twsTg=!L^
zUo@Cq;lJG|sO~ij5F$>z$h1;p95>PMe|6%?Gd*@AtDtrg1f_EZd_3t<2LMxAe>udv
zwR@Y^4p}bE1X>&E2zlN1-SB&;Qh5yGxw34I%1f)Ls!zark3((KAP$sh$KU}H2P}vS
zBnv_<^kJPY$<NY4&O7B21F3q71T!!vi<D^A%|-iS)`vVQqx(^p8krpx)pF+FqjRpr
zVzjp=7m!#v*}V$)RK%(U*9z>s`lxFn`>eIIq3niw=$djLq%~PL{FCacRqx|}`zv&;
zYF;v55dUzj<wZHF@Yk?FpepENeQhJc#k`vm;PdkIUX}Bpj9J;Gq~IMm*OqsT2rt+e
ze12974keG;(pz?%*D1dt_wyvT!-<0Q?^AHbrgyHG3o>k7q5mLp;(Q-1s$ccUl>G1A
zDb~Wg%b|2uOOKfYj-4nTHoE71L~%u--Fru<c=ZVKm2g6mOw^h$@MUW*blopC@mb>6
z`L!1U0{@+6TK2Q$G3^S?%J|yJhRP=m7t^CZfqI*^>~o7liHjL?!lthGg5YvWE=vv_
ze3GF9y(=kpDR7m8KGw=5#(O7nmUkP_fI5PlY|u`*%OvF^{kmUQ;K`%gm+I%*Dr9Zl
zRugP{_gtax@cSC6*i8NK%|Y_x9BC70|CXJ*DSZ$Q@Jg=u^l|y__EQFKk6_ao^<J1^
zUG0W0R5HPNV{l$p>TUCy4XCVKMe`rSvPhV_m6Yo+Iz0>f89vS`-^q4LJwG3s9M(pu
z41ITf5<A5TOskp&fO9B&@J4<cWpzA)vFM~_&%h0cw9c*!*flBTx91_JLq5WnE0@^^
zMdNQubIuyR0hVxdB33gt0ZJe_SIMh>f*EhgVuV-vxHAHQ^X)ZjEBc<<3MRIKw4A~F
zy$~YgDL!w214(azV4=3cs4r%wp>ATY$`%VW<b>QG|6}pF-uiLJrYV2&h4qhr<7U^b
Kj8R5+;{FG3NOFPz
index df422207e7b9918f582896c7d7889c5103adf8f7..cc8a4a31db973df11dd9c21b56d12eabab61cb62
GIT binary patch
literal 1150
zc$|fkyGsK>5XM)uH-*?(c&LSqg4hIn{t2Sut2#uZrEjqEkyeOl?1ESavGoP9N(6-y
zAF&bCXdyv7&qjjCINu~2HYd>uU&v18_s#6yo>CS32?P{>TWY#WsamB}GmBH4$>$}i
z)W2~NQZ_5WBA<}6|KP0axf<tgqnA!*nnYJK0lx?2pw?uG$ofbhxtC{2>g?zttG-dx
z+R{eL69aS|?e}}QrU&zm=Xp{OZef0jHY3rZVzF(?z1|aMGv_F<HjCo%-C|#8WO$6=
ziGT;ZZ*J0*J&u?j_H8=1r)x|WGeGFAf5QWwhDRd2@+Lh{r)Sr3aBxVe)M;_8y%6_X
zed5s_e3mt418>&*1HDo6^z<y}0luN`flia3@;}ZVJ<EmefjJPFZCiNE;CbJ2hS+<#
z!0h1H*TS;KIWV7q$Kvd(<iNwHTyH2eIjz%s%-Z0gsqrlD%@q&So2<SY5S~>b<UTX&
zXb<e=a6em|!_VsT<8bu-W6ol^+5;LLe4){y9cH^q?lU|yk6$kHCGSAN!!bR~nYx1K
l;@RGkK}`wk^Re}r`h?uC_xHV8sgp{jRx9%QFLWIkus;g)Rv-WX
index 645bc114c8de46cc0a4f6afb02c190f3aea9c7a3..ea14dbedede96973213586f1754a29c774e72993
GIT binary patch
literal 1051
zc%1ux<NpH&0WUXCHwH#V1_nkTWcYuZ!I^=X2?RhSGZ3(_v48*v8yhPdCkGc7CkH1d
z7Y{!V7dIa_Cnv8UFCV{vfS>>ukC3pCfH06P05XITq?4J21E^7eo0D6BWbpq0gCGZk
z0D}NCqaXv5AS1IN<NqTJ@<4a8GJ*jE6fiO|v#_$Ub8vET0~Kr)U|<5;&BVgY%F4n5
zl&uBIGq4D<3Mm>ovIz$!vMUve7&T5@$f4}C@t|nX#SbdRNkvVZTw>x9l2WQ_>Kd9_
zCZ=ZQ7M51dF0O9w9-dyoA)#U65s^{JDXD4c8JStdC8cHM6_r)ZEv;?s9i3g1CQq3<
zZTgIvvlcC0vUJ(<6)RV5+Pr1!w(UE1?mBe%$kAiRPn<k;>GGAU*RJ2VdF$b$$4{O<
zd;a3(tB;>PfBE|D`;VW$K>lK6U}l5?to{N90wWU(Fi6=!{$gY*2PqI_VO2C_6LJh>
zPb?HxGHT=yahkYr<3Ubk<Dd_sNktdA#8gZks(u7{4eT@GJk~^(&)^<I`0Ew}4>Kb$
z@|Xn~>={1iH`p`%6R)w_c%jDekv+%DsOug2YjX4S7F~_Ap0TL5@&22SJ8pU2S-8XE
zISbcMKDoy|ar)-}PA_Z=?w&K<D(*jn-b&rt8%w?x-Cb+G@vDsgfgd$yZlAMetPGlx
zdGyo%=9xG5w-%q=x+Hm1)n2`@AJacLKbU`9`@T$c1+&xEi+chWW?nb{__v9F<CGm+
zdrqnpML$1Xp~4y(>$Gdfx!5A@mo=_O9$Dl?<lGF6I~A(zRdOq8W!&YSsY}mFXNy;C
z-8%DwjBwbpRIduHFZYeQ=cJeYTC(8Ut?i3n6xegsXV`O8Og<!)*7>OW`K3L+OK+}i
z%93*_X6v<koin-Z9NPoSbN-?~w;8LPHPSg3@n&1lx1!uzMpI;$esrH@wO)PYU&+bO
zB=g0dax-^Lwa=QgCre|0m6hgEuT!zNc4gkpP5b3pr(a|CgZIPlM`}Bl|M*jJ{E_()
zZR6$9TMAX&Zdh%SoU8m((c|Kc)^jgq)-#68>N)z{rg&Duhp!*bP2=X)tFQCRJ(;s}
w>3hGZ{8Wjxv3Dna__>am@z9r5!G}YG*9ZN%vuBm2ZO&p@<#qd;KK;K50H9=^rvLx|
index c3a4aee616675d212178f70a97d8ce73fc1813e2..5722223c26fe0024d8c4d18f198114d5fde708bb
GIT binary patch
literal 950
zc$@*Y14;aeP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV000AhNkl<ZIE_Wq
zeJq>@0LSr1x`a;5GDT;MVN#7Q+G@9E>!LFx#7w1~R@tVV5e<5mb0OH`Z5gEtr4tps
zSrf0-Rp*9zxeMFHxC*5l*K^hz(TrtMnz`rs?vLBc@84hY{qG|nfh2)wwes&KjVm<u
z>S!7mqpoL^Z~Al?mRA1zMXQJ;NdO3vB(Y=t3jo9FAIO_DeAF<4;?^%T=uMPs=SXkX
zl9*G@SCy4KURkt7B#<Nt0m;gSK}+YiHwa0)!l@cH)%_!U-aSa;;wm+>tH{;&$-G=m
zbNRdcI+VcLd>f*58%Y8|+Sz38S{mPF9HXK5B0Y6k+$nvJ&sti^8=d3)?VrfkEs?J3
zAu=wEf%fC9PegLBuLN=DDZ<Y7I{M;BmJ9q@E(*e^^riP>PpZn56m)iTX?Pmh=mOb&
zGrUvS%t?6$lbr{6qz+|mc?@A=L4%?A2n&~7Sjck2l)aaki!Y-oileYBpAS@O!fU=~
zUq&@&R6mn)t&5h+NBLdzI_Bwmgh%Q!X7l`bD05>;;l;AThsBb&sI5?vdi^%hHFtS7
zA_w763ddwBqU2Q!wS*%YV;S!{jbK(~GNbTfDaV7kPd({)+l7*I;e1k7PkikF3H4e6
z&s8A!ox(9Nk<gd~rn?SfHHR^I^8~^~VIotR9$0dH=nr+G(cg}ur1wcwT;Xi)Rb&;{
ziAentJNF0#hY(7YXV~fwU{n7JGe2BF7_H1@;{1zPa=e&2?aXA<F7BniN>5fOL&`8T
z#c?FX#Uj`R;piAdM|}c}1o3#}4W>q`5KP*B24h~rBKO3U?S?tknTdF3s$X*?C)kri
zu6_u@UUm!K$YO$-Z4Kn<*g@9jlbO_YAc)&r+&LS;_{rz6D7`Vs+*nq4qE7N6C3qii
z`b2QlBZ!LV{Ve4BVs3PRwJ7wO1lCp_AOM($e?%3qo9SqGj2C>EPj%zBJbxY*9OOan
z5e)eum~;J@&9-A}+!v$nFuH-OfP`SRiU8>AYpHvF7hNs_gFg059CzV<tSiQ3cT8z7
zuqd}@y>$<UyWaHPOlN!RPn!iv6cPW4%(S)8cwj#_oE&I&dWIHPdu|2T({o}s+Vnm2
ze-Xl9TPaUAH*BCp(WX%pMMTjSd1A4k`?`+qlw{hY!fB6>pj+`F!_8Hg=k))R7Hy>e
Y1{)a08Fp#&*8l(j07*qoM6N<$f@Rjr9RL6T
--- a/image/test/unit/test_imgtools.js
+++ b/image/test/unit/test_imgtools.js
@@ -164,17 +164,17 @@ testdesc = "test encoding a scaled JPEG"
 // we'll reuse the container from the previous test
 istream = imgTools.encodeScaledImage(container, "image/jpeg", 16, 16);
 
 var encodedBytes = streamToArray(istream);
 // Get bytes for exected result
 var refName = "image1png16x16.jpg";
 var refFile = do_get_file(refName);
 istream = getFileInputStream(refFile);
-do_check_eq(istream.available(), 1078);
+do_check_eq(istream.available(), 1051);
 var referenceBytes = streamToArray(istream);
 
 // compare the encoder's output to the reference file.
 compareArrays(encodedBytes, referenceBytes);
 
 
 /* ========== 3 ========== */
 testnum++;
@@ -223,17 +223,17 @@ if (!isWindows) {
 // we'll reuse the container from the previous test
 istream = imgTools.encodeScaledImage(container, "image/png", 16, 16);
 
 encodedBytes = streamToArray(istream);
 // Get bytes for exected result
 refName = isWindows ? "image2jpg16x16-win.png" : "image2jpg16x16.png";
 refFile = do_get_file(refName);
 istream = getFileInputStream(refFile);
-do_check_eq(istream.available(), 948);
+do_check_eq(istream.available(), 950);
 referenceBytes = streamToArray(istream);
 
 // compare the encoder's output to the reference file.
 compareArrays(encodedBytes, referenceBytes);
 }
 
 
 /* ========== 6 ========== */
@@ -686,30 +686,32 @@ imgFile = do_get_file(imgName);
 
 istream = getFileInputStream(imgFile);
 do_check_eq(istream.available(), 17759);
 var errsrc = "none";
 
 try {
   container = imgTools.decodeImage(istream, inMimeType);
 
-  // We should never hit this - decodeImage throws an assertion because the
-  // image decoded doesn't have enough frames.
+  // We expect to hit an error during encoding because the ICO header of the
+  // image is fine, but the actual resources are corrupt. Since decodeImage()
+  // only performs a metadata decode, it doesn't decode far enough to realize
+  // this, but we'll find out when we do a full decode during encodeImage().
   try {
       istream = imgTools.encodeImage(container, "image/png");
   } catch (e) {
       err = e;
       errsrc = "encode";
   }
 } catch (e) {
   err = e;
   errsrc = "decode";
 }
 
-do_check_eq(errsrc, "decode");
+do_check_eq(errsrc, "encode");
 checkExpectedError(/NS_ERROR_FAILURE/, err);
 
 
 /* ========== bug 815359  ========== */
 testnum = 815359;
 testdesc = "test correct ico hotspots (bug 815359)";
 
 imgName = "bug815359.ico";
--- 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/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;
 
@@ -8611,16 +8616,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 +10219,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 +10396,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 +10654,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 +10673,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 +10885,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();
 
@@ -11684,17 +11718,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/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.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;
--- 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/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/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
@@ -956,18 +956,18 @@ GetDisplayPortFromMarginsData(nsIContent
 
     ScreenPoint scrollPosScreen = LayoutDevicePoint::FromAppUnits(scrollPos, auPerDevPixel)
                                 * res;
 
     screenRect += scrollPosScreen;
     // Round-out the display port to the nearest alignment (tiles)
     float x = alignmentX * floor(screenRect.x / alignmentX);
     float y = alignmentY * floor(screenRect.y / alignmentY);
-    float w = alignmentX * ceil(screenRect.width / alignmentX);
-    float h = alignmentY * ceil(screenRect.height / alignmentY);
+    float w = alignmentX * ceil(screenRect.width / alignmentX + 1);
+    float h = alignmentY * ceil(screenRect.height / alignmentY + 1);
     screenRect = ScreenRect(x, y, w, h);
     screenRect -= scrollPosScreen;
 
     ScreenRect screenExpScrollableRect =
       LayoutDeviceRect::FromAppUnits(expandedScrollableRect,
                                      auPerDevPixel) * res;
 
     // Make sure the displayport remains within the scrollable rect.
@@ -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/layout/reftests/pixel-rounding/reftest.list
+++ b/layout/reftests/pixel-rounding/reftest.list
@@ -119,17 +119,17 @@ fails == collapsed-border-top-6.html bor
 == image-height-top-4.html image-height-4.html
 == image-height-top-5.html image-height-5.html
 == image-height-top-6.html image-height-6.html
 == image-width-left-4.html image-width-4.html
 == image-width-left-5.html image-width-5.html
 == image-width-left-6.html image-width-6.html
 
 
-skip pref(image.high_quality_downscaling.enabled,true) == image-high-quality-scaling-1.html image-high-quality-scaling-1-ref.html
+skip pref(image.downscale-during-decode.enabled,true) == image-high-quality-scaling-1.html image-high-quality-scaling-1-ref.html
 
 
 != offscreen-0-ref.html offscreen-10-ref.html
 == offscreen-background-color-pos-4.html offscreen-0-ref.html
 == offscreen-background-color-pos-5.html offscreen-10-ref.html
 == offscreen-background-color-pos-6.html offscreen-10-ref.html
 == offscreen-background-color-size-4.html offscreen-0-ref.html
 == offscreen-background-color-size-5.html offscreen-10-ref.html
--- a/layout/tools/reftest/reftest-preferences.js
+++ b/layout/tools/reftest/reftest-preferences.js
@@ -18,18 +18,18 @@
     branch.setBoolPref("app.update.enabled", false);
     // Disable addon updates and prefetching so we don't leak them
     branch.setBoolPref("extensions.update.enabled", false);
     branch.setBoolPref("extensions.getAddons.cache.enabled", false);
     // Disable blocklist updates so we don't have them reported as leaks
     branch.setBoolPref("extensions.blocklist.enabled", false);
     // Make url-classifier updates so rare that they won't affect tests
     branch.setIntPref("urlclassifier.updateinterval", 172800);
-    // Disable high-quality downscaling, since it makes reftests more difficult.
-    branch.setBoolPref("image.high_quality_downscaling.enabled", false);
+    // Disable downscale-during-decode, since it makes reftests more difficult.
+    branch.setBoolPref("image.downscale-during-decode.enabled", false);
     // Disable the single-color optimization, since it can cause intermittent
     // oranges and it causes many of our tests to test a different code path
     // than the one that normal images on the web use.
     branch.setBoolPref("image.single-color-optimization.enabled", false);
     // Checking whether two files are the same is slow on Windows.
     // Setting this pref makes tests run much faster there.
     branch.setBoolPref("security.fileuri.strict_origin_policy", false);
     // Disable the thumbnailing service
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -64,17 +64,16 @@ pref("browser.cache.memory.enable", fals
 pref("browser.cache.memory.enable", true);
 #endif
 pref("browser.cache.memory.capacity", 1024); // kilobytes
 
 pref("browser.cache.memory_limit", 5120); // 5 MB
 
 /* image cache prefs */
 pref("image.cache.size", 1048576); // bytes
-pref("image.high_quality_downscaling.enabled", false);
 
 /* offline cache prefs */
 pref("browser.offline-apps.notify", true);
 pref("browser.cache.offline.enable", true);
 pref("browser.cache.offline.capacity", 5120); // kilobytes
 pref("offline-apps.quota.warn", 1024); // kilobytes
 
 // cache compression turned off for now - see bug #715198
--- a/mobile/android/b2gdroid/app/b2gdroid.js
+++ b/mobile/android/b2gdroid/app/b2gdroid.js
@@ -63,17 +63,16 @@ pref("browser.cache.memory.enable", fals
 pref("browser.cache.memory.enable", true);
 #endif
 pref("browser.cache.memory.capacity", 1024); // kilobytes
 
 pref("browser.cache.memory_limit", 5120); // 5 MB
 
 /* image cache prefs */
 pref("image.cache.size", 1048576); // bytes
-pref("image.high_quality_downscaling.enabled", false);
 
 /* offline cache prefs */
 pref("browser.offline-apps.notify", true);
 pref("browser.cache.offline.enable", true);
 pref("browser.cache.offline.capacity", 5120); // kilobytes
 pref("offline-apps.quota.warn", 1024); // kilobytes
 
 // cache compression turned off for now - see bug #715198
--- 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