Merge inbound to mozilla-central. a=merge
authorCosmin Sabou <csabou@mozilla.com>
Wed, 28 Feb 2018 23:48:29 +0200
changeset 761344 17e47288c2249ac21bd2d0585b19c835984ed32b
parent 761343 b3c95e78fd757c06527180822b4f500511485331 (current diff)
parent 761244 f558cf2238e47aa1b4f2b92e7bd2171fd5c48720 (diff)
child 761345 25624bafcfca22288ccd81aff942eba280a67772
child 761347 3a612030a1275545487d141d4ee8630404d7ae22
child 761349 b3539762286841a3711a254f0e1a9bed3ed011b2
child 761353 3fa481af8f5b761082671b482079f0a6c6929d51
child 761373 0e1ca79b33cf7de2562308d7dab90f481f8247c1
child 761384 aeb33e789f6c0bd9fa38218b4f2b188bda88e3dc
child 761398 7ba8cf3ae689f1291c4b21d281179f0c65501d87
child 761460 a3b38afcb7d3fe787f96b3e791c4c395395b3ea0
child 761509 1033a7b622724ab4e2b1807bc36c9df28a8a5343
child 761561 b996cabc7ef54bbe050d647494bf00d668ec52e6
child 761819 3dfa7321454df8531207a36fc44c7d541584dccc
child 761919 bed6daad5d88dd78efdf798214d6b7ad97b040f0
child 761933 3a0e3c49b8b3ff3f1feaff25f110e3cb21c0fb7b
child 762704 165916a0bb97565e30ee0a61858d10d292ff1788
child 763473 b13e6f457d90c3624461015bfeaab56938d6c475
child 763589 2b1cd326d4c3a0cd54e7f80e9642cff403fd6a35
child 764575 ef9326cf3e5499b132e649831877c6fc782b3f10
child 765577 13881f6442acbf4a451601d15fa04c98446051e7
push id100940
push userbmo:ntim.bugs@gmail.com
push dateWed, 28 Feb 2018 23:05:18 +0000
reviewersmerge
milestone60.0a1
Merge inbound to mozilla-central. a=merge
security/nss/cmd/certcgi/HOWTO.txt
security/nss/cmd/certcgi/Makefile
security/nss/cmd/certcgi/ca.html
security/nss/cmd/certcgi/ca_form.html
security/nss/cmd/certcgi/certcgi.c
security/nss/cmd/certcgi/certcgi.gyp
security/nss/cmd/certcgi/index.html
security/nss/cmd/certcgi/main.html
security/nss/cmd/certcgi/manifest.mn
security/nss/cmd/certcgi/nscp_ext_form.html
security/nss/cmd/certcgi/stnd_ext_form.html
security/nss/lib/freebl/chacha20.c
security/nss/lib/freebl/chacha20.h
security/nss/lib/freebl/chacha20_vec.c
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -14492,8 +14492,36 @@ nsDocShell::SetDisplayMode(uint32_t aDis
     if (NS_SUCCEEDED(GetPresContext(getter_AddRefs(presContext)))) {
       presContext->MediaFeatureValuesChangedAllDocuments({
         MediaFeatureChangeReason::DisplayModeChange });
     }
   }
 
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsDocShell::SetColorMatrix(float* aMatrix, uint32_t aMatrixLen)
+{
+  if (aMatrixLen == 20) {
+    mColorMatrix.reset(new gfx::Matrix5x4());
+    MOZ_ASSERT(aMatrixLen * sizeof(*aMatrix) == sizeof(mColorMatrix->components));
+    memcpy(mColorMatrix->components, aMatrix, sizeof(mColorMatrix->components));
+  } else if (aMatrixLen == 0) {
+    mColorMatrix.reset();
+  } else {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsIPresShell* presShell = GetPresShell();
+  if (!presShell) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsIFrame* frame = presShell->GetRootFrame();
+  if (!frame) {
+    return NS_ERROR_FAILURE;
+  }
+
+  frame->SchedulePaint();
+
+  return NS_OK;
+}
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -11,16 +11,17 @@
 #include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "mozilla/gfx/Matrix.h"
 
 #include "nsIAuthPromptProvider.h"
 #include "nsIBaseWindow.h"
 #include "nsIClipboardCommands.h"
 #include "nsIDeprecationWarner.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellLoadInfo.h"
 #include "nsIDocShellTreeItem.h"
@@ -355,16 +356,20 @@ public:
   bool HasHistoryEntry(nsISHEntry* aEntry) const
   {
     return aEntry && (aEntry == mOSHE || aEntry == mLSHE);
   }
 
   // Update any pointers (mOSHE or mLSHE) to aOldEntry to point to aNewEntry
   void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry);
 
+  mozilla::gfx::Matrix5x4* GetColorMatrix() {
+    return mColorMatrix.get();
+  }
+
   static bool SandboxFlagsImplyCookies(const uint32_t &aSandboxFlags);
 
   // Tell the favicon service that aNewURI has the same favicon as aOldURI.
   static void CopyFavicon(nsIURI* aOldURI,
                           nsIURI* aNewURI,
                           nsIPrincipal* aLoadingPrincipal,
                           bool aInPrivateBrowsing);
 
@@ -984,16 +989,18 @@ private: // data members
   nsCOMPtr<nsIURI> mFailedURI;
   nsCOMPtr<nsIChannel> mFailedChannel;
 
   // Set in DoURILoad when either the LOAD_RELOAD_ALLOW_MIXED_CONTENT flag or
   // the LOAD_NORMAL_ALLOW_MIXED_CONTENT flag is set.
   // Checked in nsMixedContentBlocker, to see if the channels match.
   nsCOMPtr<nsIChannel> mMixedContentChannel;
 
+  mozilla::UniquePtr<mozilla::gfx::Matrix5x4> mColorMatrix;
+
   const mozilla::Encoding* mForcedCharset;
   const mozilla::Encoding* mParentCharset;
 
   // WEAK REFERENCES BELOW HERE.
   // Note these are intentionally not addrefd. Doing so will create a cycle.
   // For that reasons don't use nsCOMPtr.
 
   nsIDocShellTreeOwner* mTreeOwner; // Weak Reference
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -1178,16 +1178,19 @@ interface nsIDocShell : nsIDocShellTreeI
    * this document if no code calls GetDocument(), but we still need a
    * ClientSource object to represent the about:blank window.  This may return
    * nullptr; for example if the docshell has created a real window and document
    * already.
    */
   [noscript, nostdcall, notxpcom]
   UniqueClientSource TakeInitialClientSource();
 
+  void setColorMatrix([array, size_is(aMatrixLen)] in float aMatrix,
+                      [optional] in unsigned long aMatrixLen);
+
 %{C++
   /**
    * These methods call nsDocShell::GetHTMLEditorInternal() and
    * nsDocShell::SetHTMLEditorInternal() with static_cast.
    */
   mozilla::HTMLEditor* GetHTMLEditor();
   nsresult SetHTMLEditor(mozilla::HTMLEditor* aHTMLEditor);
 %}
--- a/dom/clients/manager/ClientManagerService.cpp
+++ b/dom/clients/manager/ClientManagerService.cpp
@@ -5,73 +5,38 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ClientManagerService.h"
 
 #include "ClientManagerParent.h"
 #include "ClientNavigateOpParent.h"
 #include "ClientOpenWindowOpParent.h"
 #include "ClientOpenWindowUtils.h"
+#include "ClientPrincipalUtils.h"
 #include "ClientSourceParent.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SystemGroup.h"
 #include "nsIAsyncShutdown.h"
 #include "nsIXULRuntime.h"
 #include "nsProxyRelease.h"
 
 namespace mozilla {
 namespace dom {
 
 using mozilla::ipc::AssertIsOnBackgroundThread;
-using mozilla::ipc::ContentPrincipalInfo;
 using mozilla::ipc::PrincipalInfo;
 
 namespace {
 
 ClientManagerService* sClientManagerServiceInstance = nullptr;
 bool sClientManagerServiceShutdownRegistered = false;
 
-bool
-MatchPrincipalInfo(const PrincipalInfo& aLeft, const PrincipalInfo& aRight)
-{
-  if (aLeft.type() != aRight.type()) {
-    return false;
-  }
-
-  switch (aLeft.type()) {
-    case PrincipalInfo::TContentPrincipalInfo:
-    {
-      const ContentPrincipalInfo& leftContent = aLeft.get_ContentPrincipalInfo();
-      const ContentPrincipalInfo& rightContent = aRight.get_ContentPrincipalInfo();
-      return leftContent.attrs() == rightContent.attrs() &&
-             leftContent.originNoSuffix() == rightContent.originNoSuffix();
-    }
-    case PrincipalInfo::TSystemPrincipalInfo:
-    {
-      // system principal always matches
-      return true;
-    }
-    case PrincipalInfo::TNullPrincipalInfo:
-    {
-      // null principal never matches
-      return false;
-    }
-    default:
-    {
-      break;
-    }
-  }
-
-  // Clients (windows/workers) should never have an expanded principal type.
-  MOZ_CRASH("unexpected principal type!");
-}
-
 class ClientShutdownBlocker final : public nsIAsyncShutdownBlocker
 {
   RefPtr<GenericPromise::Private> mPromise;
 
   ~ClientShutdownBlocker() = default;
 
 public:
   explicit ClientShutdownBlocker(GenericPromise::Private* aPromise)
@@ -273,17 +238,17 @@ ClientManagerService::FindSource(const n
 
   auto entry = mSourceTable.Lookup(aID);
   if (!entry) {
     return nullptr;
   }
 
   ClientSourceParent* source = entry.Data();
   if (source->IsFrozen() ||
-      !MatchPrincipalInfo(source->Info().PrincipalInfo(), aPrincipalInfo)) {
+      !ClientMatchPrincipalInfo(source->Info().PrincipalInfo(), aPrincipalInfo)) {
     return nullptr;
   }
 
   return source;
 }
 
 void
 ClientManagerService::AddManager(ClientManagerParent* aManager)
@@ -450,17 +415,17 @@ ClientManagerService::MatchAll(const Cli
       continue;
     }
 
     if (aArgs.type() != ClientType::All &&
         source->Info().Type() != aArgs.type()) {
       continue;
     }
 
-    if (!MatchPrincipalInfo(source->Info().PrincipalInfo(), principalInfo)) {
+    if (!ClientMatchPrincipalInfo(source->Info().PrincipalInfo(), principalInfo)) {
       continue;
     }
 
     if (!aArgs.includeUncontrolled()) {
       const Maybe<ServiceWorkerDescriptor>& controller =
         source->GetController();
       if (controller.isNothing()) {
         continue;
@@ -496,17 +461,17 @@ ClientManagerService::Claim(const Client
   for (auto iter = mSourceTable.Iter(); !iter.Done(); iter.Next()) {
     ClientSourceParent* source = iter.UserData();
     MOZ_DIAGNOSTIC_ASSERT(source);
 
     if (source->IsFrozen()) {
       continue;
     }
 
-    if (!MatchPrincipalInfo(source->Info().PrincipalInfo(), principalInfo)) {
+    if (!ClientMatchPrincipalInfo(source->Info().PrincipalInfo(), principalInfo)) {
       continue;
     }
 
     const Maybe<ServiceWorkerDescriptor>& controller = source->GetController();
     if (controller.isSome() &&
         controller.ref().Scope() == serviceWorker.scope() &&
         controller.ref().Id() == serviceWorker.id()) {
       continue;
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/ClientPrincipalUtils.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ClientPrincipalUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+using mozilla::ipc::ContentPrincipalInfo;
+using mozilla::ipc::PrincipalInfo;
+
+bool
+ClientMatchPrincipalInfo(const PrincipalInfo& aLeft, const PrincipalInfo& aRight)
+{
+  if (aLeft.type() != aRight.type()) {
+    return false;
+  }
+
+  switch (aLeft.type()) {
+    case PrincipalInfo::TContentPrincipalInfo:
+    {
+      const ContentPrincipalInfo& leftContent = aLeft.get_ContentPrincipalInfo();
+      const ContentPrincipalInfo& rightContent = aRight.get_ContentPrincipalInfo();
+      return leftContent.attrs() == rightContent.attrs() &&
+             leftContent.originNoSuffix() == rightContent.originNoSuffix();
+    }
+    case PrincipalInfo::TSystemPrincipalInfo:
+    {
+      // system principal always matches
+      return true;
+    }
+    case PrincipalInfo::TNullPrincipalInfo:
+    {
+      // null principal never matches
+      return false;
+    }
+    default:
+    {
+      break;
+    }
+  }
+
+  // Clients (windows/workers) should never have an expanded principal type.
+  MOZ_CRASH("unexpected principal type!");
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/ClientPrincipalUtils.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _mozilla_dom_ClientPrincipaltils_h
+#define _mozilla_dom_ClientPrincipaltils_h
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+bool
+ClientMatchPrincipalInfo(const mozilla::ipc::PrincipalInfo& aLeft,
+                         const mozilla::ipc::PrincipalInfo& aRight);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientPrincipalUtils_h
--- a/dom/clients/manager/ClientSource.cpp
+++ b/dom/clients/manager/ClientSource.cpp
@@ -3,16 +3,17 @@
 /* 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 "ClientSource.h"
 
 #include "ClientManager.h"
 #include "ClientManagerChild.h"
+#include "ClientPrincipalUtils.h"
 #include "ClientSourceChild.h"
 #include "ClientState.h"
 #include "ClientValidation.h"
 #include "mozilla/dom/ClientIPCTypes.h"
 #include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/dom/MessageEvent.h"
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/Navigator.h"
@@ -202,17 +203,19 @@ ClientSource::WorkerExecutionReady(Worke
     return;
   }
 
   // A client without access to storage should never be controlled by
   // a service worker.  Check this here in case we were controlled before
   // execution ready.  We can't reliably determine what our storage policy
   // is before execution ready, unfortunately.
   if (mController.isSome()) {
-    MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate->IsStorageAllowed());
+    MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate->IsStorageAllowed() ||
+                          StringBeginsWith(aWorkerPrivate->ScriptURL(),
+                                           NS_LITERAL_STRING("blob:")));
   }
 
   // Its safe to store the WorkerPrivate* here because the ClientSource
   // is explicitly destroyed by WorkerPrivate before exiting its run loop.
   MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
   mOwner = AsVariant(aWorkerPrivate);
 
   ClientSourceExecutionReadyArgs args(
@@ -230,44 +233,46 @@ ClientSource::WindowExecutionReady(nsPID
   MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->IsCurrentInnerWindow());
   MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->HasActiveDocument());
 
   if (IsShutdown()) {
     return NS_OK;
   }
 
   nsIDocument* doc = aInnerWindow->GetExtantDoc();
-  if (NS_WARN_IF(!doc)) {
-    return NS_ERROR_UNEXPECTED;
-  }
+  NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
+
+  nsIURI* uri = doc->GetOriginalURI();
+  NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
+
+  // Don't use nsAutoCString here since IPC requires a full nsCString anyway.
+  nsCString spec;
+  nsresult rv = uri->GetSpec(spec);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // A client without access to storage should never be controlled by
   // a service worker.  Check this here in case we were controlled before
   // execution ready.  We can't reliably determine what our storage policy
   // is before execution ready, unfortunately.
+  //
+  // Note, explicitly avoid checking storage policy for windows that inherit
+  // service workers from their parent.  If a user opens a controlled window
+  // and then blocks storage, that window will continue to be controlled by
+  // the SW until the window is closed.  Any about:blank or blob URL should
+  // continue to inherit the SW as well.  We need to avoid triggering the
+  // assertion in this corner case.
   if (mController.isSome()) {
-    MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::StorageAllowedForWindow(aInnerWindow) ==
+    MOZ_DIAGNOSTIC_ASSERT(spec.LowerCaseEqualsLiteral("about:blank") ||
+                          StringBeginsWith(spec, NS_LITERAL_CSTRING("blob:")) ||
+                          nsContentUtils::StorageAllowedForWindow(aInnerWindow) ==
                           nsContentUtils::StorageAccess::eAllow);
   }
 
-  // Don't use nsAutoCString here since IPC requires a full nsCString anyway.
-  nsCString spec;
-
-  nsIURI* uri = doc->GetOriginalURI();
-  if (uri) {
-    nsresult rv = uri->GetSpec(spec);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
   nsPIDOMWindowOuter* outer = aInnerWindow->GetOuterWindow();
-  if (NS_WARN_IF(!outer)) {
-    return NS_ERROR_UNEXPECTED;
-  }
+  NS_ENSURE_TRUE(outer, NS_ERROR_UNEXPECTED);
 
   FrameType frameType = FrameType::Top_level;
   if (!outer->IsTopLevelWindow()) {
     frameType = FrameType::Nested;
   } else if(outer->HadOriginalOpener()) {
     frameType = FrameType::Auxiliary;
   }
 
@@ -367,29 +372,42 @@ ClientSource::WorkerSyncPing(WorkerPriva
   GetActor()->SendWorkerSyncPing();
 }
 
 void
 ClientSource::SetController(const ServiceWorkerDescriptor& aServiceWorker)
 {
   NS_ASSERT_OWNINGTHREAD(ClientSource);
 
+  // We should never have a cross-origin controller.  Since this would be
+  // same-origin policy violation we do a full release assertion here.
+  MOZ_RELEASE_ASSERT(ClientMatchPrincipalInfo(mClientInfo.PrincipalInfo(),
+                                              aServiceWorker.PrincipalInfo()));
+
   // A client in private browsing mode should never be controlled by
   // a service worker.  The principal origin attributes should guarantee
   // this invariant.
   MOZ_DIAGNOSTIC_ASSERT(!mClientInfo.IsPrivateBrowsing());
 
   // A client without access to storage should never be controlled a
   // a service worker.  If we are already execution ready with a real
   // window or worker, then verify assert the storage policy is correct.
+  //
+  // Note, explicitly avoid checking storage policy for clients that inherit
+  // service workers from their parent.  This basically means blob: URLs
+  // and about:blank windows.
   if (GetInnerWindow()) {
-    MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::StorageAllowedForWindow(GetInnerWindow()) ==
+    MOZ_DIAGNOSTIC_ASSERT(Info().URL().LowerCaseEqualsLiteral("about:blank") ||
+                          StringBeginsWith(Info().URL(), NS_LITERAL_CSTRING("blob:")) ||
+                          nsContentUtils::StorageAllowedForWindow(GetInnerWindow()) ==
                           nsContentUtils::StorageAccess::eAllow);
-  } else if (GetWorkerPrivate()) {
-    MOZ_DIAGNOSTIC_ASSERT(GetWorkerPrivate()->IsStorageAllowed());
+  } else if (WorkerPrivate* wp = GetWorkerPrivate()) {
+    MOZ_DIAGNOSTIC_ASSERT(wp->IsStorageAllowed() ||
+                          StringBeginsWith(wp->ScriptURL(),
+                                           NS_LITERAL_STRING("blob:")));
   }
 
   if (mController.isSome() && mController.ref() == aServiceWorker) {
     return;
   }
 
   mController.reset();
   mController.emplace(aServiceWorker);
--- a/dom/clients/manager/moz.build
+++ b/dom/clients/manager/moz.build
@@ -35,16 +35,17 @@ UNIFIED_SOURCES += [
   'ClientManagerService.cpp',
   'ClientNavigateOpChild.cpp',
   'ClientNavigateOpParent.cpp',
   'ClientOpenWindowOpActors.cpp',
   'ClientOpenWindowOpChild.cpp',
   'ClientOpenWindowOpParent.cpp',
   'ClientOpenWindowUtils.cpp',
   'ClientPrefs.cpp',
+  'ClientPrincipalUtils.cpp',
   'ClientSource.cpp',
   'ClientSourceChild.cpp',
   'ClientSourceOpChild.cpp',
   'ClientSourceOpParent.cpp',
   'ClientSourceParent.cpp',
   'ClientState.cpp',
   'ClientValidation.cpp',
 ]
--- a/dom/serviceworkers/ServiceWorkerManager.cpp
+++ b/dom/serviceworkers/ServiceWorkerManager.cpp
@@ -34,16 +34,17 @@
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/ErrorNames.h"
 #include "mozilla/LoadContext.h"
 #include "mozilla/SystemGroup.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/ClientHandle.h"
 #include "mozilla/dom/ClientManager.h"
+#include "mozilla/dom/ClientSource.h"
 #include "mozilla/dom/ConsoleUtils.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DOMPrefs.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/Navigator.h"
 #include "mozilla/dom/NotificationEvent.h"
@@ -2484,16 +2485,48 @@ ServiceWorkerManager::DispatchFetchEvent
       //       is no way for the controller to be cleared from a client in
       //       the spec or our implementation.  We may want to force a
       //       new inner window to be created instead of reusing the
       //       initial about:blank global.  See bug 1419620 and the spec
       //       issue here: https://github.com/w3c/ServiceWorker/issues/1232
     }
 
     if (clientInfo.isSome()) {
+      // ClientChannelHelper is not called for STS upgrades that get
+      // intercepted by a service worker when interception occurs in
+      // the content process.  Therefore the reserved client is not
+      // properly cleared in that case leading to a situation where
+      // a ClientSource with an http:// principal is controlled by
+      // a ServiceWorker with an https:// principal.
+      //
+      // This does not occur when interception is handled by the
+      // simpler InterceptedHttpChannel approach in the parent.
+      //
+      // As a temporary work around check for this principal mismatch
+      // here and perform the ClientChannelHelper's replacement of
+      // reserved client automatically.
+      if (!XRE_IsParentProcess()) {
+        nsCOMPtr<nsIPrincipal> clientPrincipal = clientInfo.ref().GetPrincipal();
+        if (!clientPrincipal || !clientPrincipal->Equals(principal)) {
+          UniquePtr<ClientSource> reservedClient =
+            loadInfo->TakeReservedClientSource();
+
+          nsCOMPtr<nsISerialEventTarget> target =
+            reservedClient ? reservedClient->EventTarget()
+                           : SystemGroup::EventTargetFor(TaskCategory::Other);
+
+          reservedClient.reset();
+          reservedClient = ClientManager::CreateSource(ClientType::Window,
+                                                       target,
+                                                       principal);
+
+          loadInfo->GiveReservedClientSource(Move(reservedClient));
+        }
+      }
+
       // First, attempt to mark the reserved client controlled directly.  This
       // will update the controlled status in the ClientManagerService in the
       // parent.  It will also eventually propagate back to the ClientSource.
       StartControllingClient(clientInfo.ref(), registration);
     }
 
     // But we also note the reserved state on the LoadInfo.  This allows the
     // ClientSource to be updated immediately after the nsIChannel starts.
--- a/dom/serviceworkers/test/browser_storage_permission.js
+++ b/dom/serviceworkers/test/browser_storage_permission.js
@@ -90,16 +90,158 @@ add_task(async function test_session_per
   });
 
   is(controller, null, "page should be not controlled with session storage");
 
   await BrowserTestUtils.removeTab(tab);
   Services.perms.remove(Services.io.newURI(PAGE_URI), "cookie");
 });
 
+// Test to verify an about:blank iframe successfully inherits the
+// parent's controller when storage is blocked between opening the
+// parent page and creating the iframe.
+add_task(async function test_block_storage_before_blank_iframe() {
+  Services.perms.remove(Services.io.newURI(PAGE_URI), "cookie");
+
+  let tab = BrowserTestUtils.addTab(gBrowser, SCOPE);
+  let browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  let controller = await ContentTask.spawn(browser, null, async function() {
+    return content.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller, "page should be controlled with storage allowed");
+
+  let controller2 = await ContentTask.spawn(browser, null, async function() {
+    let f = content.document.createElement("iframe");
+    content.document.body.appendChild(f);
+    await new Promise(resolve => f.onload = resolve);
+    return !!f.contentWindow.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller2, "page should be controlled with storage allowed");
+
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT],
+  ]});
+
+  let controller3 = await ContentTask.spawn(browser, null, async function() {
+    let f = content.document.createElement("iframe");
+    content.document.body.appendChild(f);
+    await new Promise(resolve => f.onload = resolve);
+    return !!f.contentWindow.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller3, "page should be controlled with storage allowed");
+
+  await SpecialPowers.popPrefEnv();
+  await BrowserTestUtils.removeTab(tab);
+});
+
+// Test to verify a blob URL iframe successfully inherits the
+// parent's controller when storage is blocked between opening the
+// parent page and creating the iframe.
+add_task(async function test_block_storage_before_blob_iframe() {
+  Services.perms.remove(Services.io.newURI(PAGE_URI), "cookie");
+
+  let tab = BrowserTestUtils.addTab(gBrowser, SCOPE);
+  let browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  let controller = await ContentTask.spawn(browser, null, async function() {
+    return content.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller, "page should be controlled with storage allowed");
+
+  let controller2 = await ContentTask.spawn(browser, null, async function() {
+    let b = new content.Blob(["<!DOCTYPE html><html></html>"], { type: "text/html" });
+    let f = content.document.createElement("iframe");
+    // No need to call revokeObjectURL() since the window will be closed shortly.
+    f.src = content.URL.createObjectURL(b);
+    content.document.body.appendChild(f);
+    await new Promise(resolve => f.onload = resolve);
+    return !!f.contentWindow.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller2, "page should be controlled with storage allowed");
+
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT],
+  ]});
+
+  let controller3 = await ContentTask.spawn(browser, null, async function() {
+    let b = new content.Blob(["<!DOCTYPE html><html></html>"], { type: "text/html" });
+    let f = content.document.createElement("iframe");
+    // No need to call revokeObjectURL() since the window will be closed shortly.
+    f.src = content.URL.createObjectURL(b);
+    content.document.body.appendChild(f);
+    await new Promise(resolve => f.onload = resolve);
+    return !!f.contentWindow.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller3, "page should be controlled with storage allowed");
+
+  await SpecialPowers.popPrefEnv();
+  await BrowserTestUtils.removeTab(tab);
+});
+
+// Test to verify a blob worker script does not hit our service
+// worker storage assertions when storage is blocked between opening
+// the parent page and creating the worker.  Note, we cannot
+// explicitly check if the worker is controlled since we don't expose
+// WorkerNavigator.serviceWorkers.controller yet.
+add_task(async function test_block_storage_before_blob_worker() {
+  Services.perms.remove(Services.io.newURI(PAGE_URI), "cookie");
+
+  let tab = BrowserTestUtils.addTab(gBrowser, SCOPE);
+  let browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  let controller = await ContentTask.spawn(browser, null, async function() {
+    return content.navigator.serviceWorker.controller;
+  });
+
+  ok(!!controller, "page should be controlled with storage allowed");
+
+  let scriptURL = await ContentTask.spawn(browser, null, async function() {
+    let b = new content.Blob(["self.postMessage(self.location.href);self.close()"],
+                             { type: "application/javascript" });
+    // No need to call revokeObjectURL() since the window will be closed shortly.
+    let u = content.URL.createObjectURL(b);
+    let w = new content.Worker(u);
+    return await new Promise(resolve => {
+      w.onmessage = e => resolve(e.data);
+    });
+  });
+
+  ok(scriptURL.startsWith("blob:"), "blob URL worker should run");
+
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT],
+  ]});
+
+  let scriptURL2 = await ContentTask.spawn(browser, null, async function() {
+    let b = new content.Blob(["self.postMessage(self.location.href);self.close()"],
+                             { type: "application/javascript" });
+    // No need to call revokeObjectURL() since the window will be closed shortly.
+    let u = content.URL.createObjectURL(b);
+    let w = new content.Worker(u);
+    return await new Promise(resolve => {
+      w.onmessage = e => resolve(e.data);
+    });
+  });
+
+  ok(scriptURL2.startsWith("blob:"), "blob URL worker should run");
+
+  await SpecialPowers.popPrefEnv();
+  await BrowserTestUtils.removeTab(tab);
+});
+
 add_task(async function cleanup() {
   Services.perms.remove(Services.io.newURI(PAGE_URI), "cookie");
 
   let tab = BrowserTestUtils.addTab(gBrowser, PAGE_URI);
   let browser = gBrowser.getBrowserForTab(tab);
   await BrowserTestUtils.browserLoaded(browser);
 
   await ContentTask.spawn(browser, SCOPE, async function(uri) {
--- a/dom/tests/mochitest/fetch/test_fetch_basic.js
+++ b/dom/tests/mochitest/fetch/test_fetch_basic.js
@@ -108,17 +108,27 @@ function testRequestMozErrors() {
   const r = new Request("http://localhost:4/should/fail", { mozErrors: true });
   return fetch(r).then(res => {
     ok(false, "Request should not succeed");
   }).catch(err => {
     ok(err instanceof TypeError);
   });
 }
 
+function testViewSourceURL() {
+  var p2 = fetch('view-source:/').then(function(res) {
+    ok(false, "view-source: URL should fail");
+  }, function(e) {
+    ok(e instanceof TypeError, "view-source: URL should fail");
+  });
+}
+
 function runTest() {
   return Promise.resolve()
     .then(testAboutURL)
     .then(testDataURL)
     .then(testSameOriginBlobURL)
     .then(testNonGetBlobURL)
     .then(testMozErrors)
+    .then(testRequestMozErrors)
+    .then(testViewSourceURL)
     // Put more promise based tests here.
 }
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -1,9 +1,9 @@
-52491
+52537
 0/nm
 0th/pt
 1/n1
 1st/p
 1th/tc
 2/nm
 2nd/p
 2th/tc
@@ -3886,16 +3886,17 @@ Elyssa/M
 Elysée/M
 Ema/M
 Emacs/M
 Emanuel/M
 Emanuele/M
 Emeline/M
 Emerson/M
 Emery/M
+Emeryville/M
 Emil/M
 Emile/M
 Emilia/M
 Emilie/M
 Emilio/M
 Emily/M
 Eminem/M
 Eminence
@@ -9363,17 +9364,19 @@ PET/M
 PFC
 PG
 PGP
 PIN
 PJ's
 PLO/M
 PM/SMDG
 PMS/M
+PNG/MS
 PO
+POTUS/M
 POW/M
 PP
 PPS
 PR
 PRC/M
 PRNewswire/M
 PRO
 PS/M
@@ -9751,16 +9754,17 @@ Pisces/M
 Pisistratus/M
 Pissaro/M
 Pitcairn/M
 Pitt/SM
 Pittman/M
 Pitts/M
 Pittsburgh/M
 Pius/M
+Pixar/M
 Pizarro/M
 Pkwy
 Pl
 Place
 Planck/M
 Plano
 Plantagenet/M
 Plasticine/M
@@ -10673,16 +10677,17 @@ SAC
 SALT/M
 SAM/M
 SAP/M
 SARS/M
 SASE
 SAT
 SBA
 SC/M
+SCOTUS/M
 SCSI/M
 SD
 SDI
 SE/M
 SEATO
 SEC/M
 SF
 SGML/M
@@ -11728,16 +11733,18 @@ TELNET/S
 TELNETTed
 TELNETTing
 TESL
 TESOL
 TEirtza/M
 TGIF
 THC
 THz/M
+TIF/MS
+TIFF/MS
 TKO/M
 TLC/M
 TM
 TN
 TNT/M
 TOEFL
 TQM
 TV/SM
@@ -13037,16 +13044,18 @@ Wynn/M
 Wynne/M
 Wyo
 Wyoming/M
 Wyomingite/SM
 X/M
 XBL/M
 XEmacs/M
 XL/M
+XLS/MS
+XLSX/MS
 XML
 XPCOM/M
 XPConnect/M
 XPInstall/M
 XS
 XUL/M
 XULRunner/M
 XXL
@@ -13663,16 +13672,17 @@ ad/SM
 adage/MS
 adagio/MS
 adamant/MY
 adapt/BZGVDRS
 adaptability/M
 adaptation/MS
 adapter/M
 adaption/S
+add-on/S
 add/SDRBZG
 addend/MS
 addenda
 addendum/M
 adder/M
 addible
 addict/GVMDS
 addiction/SM
@@ -17848,16 +17858,17 @@ bunkhouse/SM
 bunko/SMDG
 bunkum/M
 bunny/SM
 bunt/MDGSJ
 bunting/M
 buoy/MDGS
 buoyancy/M
 buoyant/Y
+bupkis
 bur/SMY
 burble/DSMG
 burbs/M
 burden's
 burden/USGD
 burdensome
 burdock/M
 bureau/SM
@@ -21166,16 +21177,17 @@ counterrevolution/SM
 counterrevolutionary/SM
 countersign/GSMD
 countersignature/MS
 countersink/GSM
 counterspy/SM
 counterstroke/SM
 countersunk
 countertenor/MS
+countertop/S
 countervail/GSD
 counterweight/MS
 countess/MS
 countless
 countrified
 country/SM
 countryman/M
 countrymen
@@ -21325,16 +21337,17 @@ crank/SMDG
 crankcase/SM
 crankily
 crankiness/M
 crankshaft/MS
 cranky/PRT
 cranny/DSM
 crap/MS
 crape/SM
+crapola
 crapped
 crapper/S
 crappie/M
 crapping
 crappy/RSPT
 craps/M
 crapshooter/MS
 crash/MDSG
@@ -21550,16 +21563,17 @@ croup/M
 croupier/M
 croupy/ZTR
 crouton/MS
 crow/MDGS
 crowbar/MS
 crowd/SMDG
 crowded/U
 crowdfund/SDG
+crowdsource/SDG
 crowfeet
 crowfoot/SM
 crown/SMDG
 crowned/U
 crozier/MS
 croûton/MS
 crucial/Y
 crucible/SM
@@ -21615,16 +21629,17 @@ crybaby/SM
 cryogenic/S
 cryogenics/M
 cryonic/S
 cryosurgery/M
 crypt's
 crypt/S
 cryptic
 cryptically
+cryptocurrency/S
 cryptogram/SM
 cryptographer/SM
 cryptographic
 cryptography/M
 cryptologist/MS
 cryptosystem/S
 crystal/SM
 crystalline
@@ -21827,16 +21842,17 @@ cyan/M
 cyanide/M
 cyber
 cyberbully/SM
 cybercafe/S
 cybercafé/S
 cybernetic/S
 cybernetics/M
 cyberpunk/SM
+cybersecurity
 cybersex
 cyberspace/MS
 cyborg/SM
 cyclamen/MS
 cycle/ADSMG
 cyclic
 cyclical/Y
 cyclist/MS
@@ -22172,16 +22188,17 @@ declension/SM
 declination/M
 decline/DRSMZG
 decliner/M
 declivity/SM
 decolletage/SM
 decollete
 decongestant/MS
 deconstructionism
+decontextualize/DGS
 decor/MS
 decorate/AGNVDS
 decorating/M
 decoration/AM
 decorations
 decorative/Y
 decorator/MS
 decorous/IY
@@ -23669,19 +23686,23 @@ dreamland/M
 dreamless
 dreamlike
 dreamworld/SM
 dreamy/RPT
 drear
 drearily
 dreariness/M
 dreary/RPT
+dreck
+dreckish
+drecky
 dredge/DRSMZG
 dredger/M
 dregs/M
+drek
 drench/GDS
 dress/AUGSDM
 dressage/M
 dresser/MS
 dressiness/M
 dressing/SM
 dressmaker/SM
 dressmaking/M
@@ -24477,16 +24498,18 @@ emoticon/SM
 emotion/M
 emotional/UY
 emotionalism/M
 emotionalize/GDS
 emotionless
 emotive/Y
 empanel/GDS
 empathetic
+empathic
+empathically
 empathize/DSG
 empathy/M
 emperor/MS
 emphases
 emphasis/M
 emphasize/AGDS
 emphatic/U
 emphatically
@@ -25289,16 +25312,18 @@ exemplification/M
 exemplify/GDSXN
 exempt/SGD
 exemption/SM
 exercise/DRSMZG
 exerciser/M
 exert/SDG
 exertion/MS
 exeunt
+exfiltrate/DGS
+exfiltration/S
 exfoliate/GNDS
 exhalation/MS
 exhale/DSG
 exhaust/GVMDS
 exhaustible/I
 exhaustion/M
 exhaustive/YP
 exhaustiveness/M
@@ -28640,16 +28665,17 @@ gunpoint/M
 gunpowder/M
 gunrunner/MS
 gunrunning/M
 gunship/MS
 gunshot/MS
 gunslinger/SM
 gunsmith/M
 gunsmiths
+gunsmoke
 gunwale/MS
 guppy/SM
 gurgle/MGDS
 gurney/MS
 guru/MS
 gush/MDRSZG
 gusher/M
 gushing/Y
@@ -30779,16 +30805,17 @@ inconstant/Y
 incontestability/M
 incontestably
 incontinent
 incontrovertibly
 inconvenience/GD
 incorporate/ADSGN
 incorporated/U
 incorporation/AM
+incorporator/S
 incorporeal
 incorrect/Y
 incorrigible/P
 incorrigibly
 increasing/Y
 increment/SMD
 incremental/Y
 incrementalism
@@ -31395,16 +31422,19 @@ interne/GDL
 internecine
 internee/SM
 interneship/S
 internet
 internist/MS
 internment/M
 internship/MS
 interoffice
+interoperability
+interoperable
+interoperate
 interpenetrate/DSGN
 interpersonal
 interplanetary
 interplay/M
 interpolate/XDSGN
 interpolation/M
 interpose/GDS
 interposition/M
@@ -34263,16 +34293,17 @@ mawkishness/M
 max/GMDS
 maxi/MS
 maxilla/M
 maxillae
 maxillary
 maxim/SM
 maxima
 maximal/Y
+maximalist/MS
 maximization/M
 maximize/GDS
 maximum/SM
 may/M
 maybe/SM
 mayday/MS
 mayflower/MS
 mayfly/SM
@@ -37182,17 +37213,17 @@ oilmen
 oilskin/MS
 oilskins/M
 oily/RPT
 oink/MDSG
 ointment/SM
 okapi/SM
 okay/MDSG
 okra/MS
-old/TMNRP
+old/TMNRPS
 oldie/SM
 oldish
 oldness/M
 oldster/MS
 ole/SMV
 oleaginous
 oleander/MS
 oleo/M
@@ -37952,16 +37983,18 @@ oxidize/ZGDRS
 oxidizer/M
 oxtail/S
 oxyacetylene/M
 oxygen/M
 oxygenate/DSGN
 oxygenation/M
 oxymora
 oxymoron/M
+oyes
+oyez
 oyster/SM
 oz
 ozone/M
 p/NRXTGJ
 pH
 pa/SMH
 pablum/M
 pabulum/M
@@ -41761,16 +41794,17 @@ rebate/M
 rebel/MS
 rebellion/MS
 rebellious/YP
 rebelliousness/M
 rebid/S
 rebidding
 rebirth/M
 reboil/SDG
+rebrand/DG
 rebuild/SG
 rebuke/DSMG
 rebuking/Y
 rebuttal/MS
 rec'd
 rec/M
 recalcitrance/M
 recalcitrant
@@ -41879,17 +41913,17 @@ recurrent/Y
 recurring
 recurse/XNV
 recusal/S
 recuse/DSG
 recyclable/SM
 recycling/M
 red/PSM
 redact/SDG
-redaction/M
+redaction/MS
 redactor/SM
 redbird/SM
 redbreast/MS
 redbrick
 redcap/SM
 redcoat/SM
 redcurrant/S
 redden/SDG
@@ -41897,17 +41931,17 @@ redder
 reddest
 reddish
 redeem/RZB
 redeemer/M
 redemption/M
 redemptive
 redesign/DSG
 redhead/SMD
-redirection
+redirection/S
 redistrict/GD
 redivide/GDS
 redlining/M
 redneck/SM
 redness/M
 redo/G
 redolence/M
 redolent
@@ -42079,17 +42113,17 @@ reiterate/V
 reject/GSMD
 rejection/SM
 rejoice/JGDS
 rejoicing/M
 rejoinder/SM
 rejuvenate/DSGN
 rejuvenation/M
 rel
-relate/DRSXZGNV
+relate/DRSXZGNVB
 relatedness/M
 relater/M
 relation/M
 relational
 relationship/MS
 relative/MYS
 relativism/M
 relativist/S
@@ -42481,16 +42515,17 @@ returnee/SM
 rev/ZVM
 revamping/M
 reveal/GJSD
 revealed/U
 revealing/Y
 reveille/M
 revel/JMDRSZG
 revelation/SM
+revelatory
 reveler/M
 revelry/SM
 revenge/MGDS
 revenuer/SM
 reverb
 reverberate/DSGNX
 reverberation/M
 revere/DSG
@@ -42804,16 +42839,17 @@ role/MS
 roll/MDRZGJS
 rollback/SM
 roller/M
 rollerblading
 rollerskating/M
 rollick/SDG
 rollicking/M
 rollmop/S
+rollout
 rollover/SM
 romaine/MS
 roman/M
 romance/MZGDRS
 romancer/M
 romantic/MS
 romantically
 romanticism/M
@@ -45508,16 +45544,17 @@ sodded
 sodden/Y
 sodding
 sodium/M
 sodomite/MS
 sodomize/GDS
 sodomy/M
 soever
 sofa/MS
+soffit/S
 soft/NRYXTP
 softback
 softball/MS
 softbound
 softcover
 soften/DRZG
 softener/M
 softhearted
@@ -45993,17 +46030,18 @@ spiritual/MYS
 spiritualism/M
 spiritualist/MS
 spiritualistic
 spirituality/M
 spirituous
 spirochete/SM
 spiry
 spit/MDGS
-spitball/SM
+spitball/SMG
+spitballer/S
 spite/ASM
 spiteful/PY
 spitefuller
 spitefullest
 spitefulness/M
 spitfire/SM
 spitted
 spitting
@@ -46059,17 +46097,17 @@ spokeswoman/M
 spokeswomen
 spoliation/CM
 sponge/DRSMZG
 spongecake/M
 sponger/M
 sponginess/M
 spongy/RPT
 sponsor/MDGS
-sponsorship/M
+sponsorship/MS
 spontaneity/M
 spontaneous/Y
 spoof/SMDG
 spook/SMDG
 spookiness/M
 spooky/RPT
 spool/SMDG
 spoon/SMDG
@@ -46743,16 +46781,17 @@ streaky/TR
 stream/MDRSZG
 streamer/M
 streamline/DSG
 street/MS
 streetcar/MS
 streetlamp/S
 streetlight/SM
 streetwalker/SM
+streetwalking
 streetwise
 strength/M
 strengthen/AGDS
 strengthener/MS
 strengths
 strenuous/PY
 strenuousness/M
 strep/M
@@ -49902,16 +49941,18 @@ unbeknown
 unbeknownst
 unbend/SG
 unbent
 unbid
 unblinking/Y
 unblushing/Y
 unbosom/DG
 unbound/D
+unbox/DS
+unboxing/S
 unbreakable
 unbroken
 uncanny/T
 uncap/S
 uncaring
 unceasing/Y
 unchangeable
 uncharacteristic
@@ -50200,16 +50241,18 @@ unless
 unlike/PB
 unlikely/T
 unlit
 unlock/DSG
 unlovable
 unlovely/TR
 unloving
 unlucky/T
+unmaintainable
+unmaintained
 unman/S
 unmanly/T
 unmarried
 unmeaning
 unmentionable/MS
 unmentionables/M
 unmet
 unmindful
@@ -50238,16 +50281,17 @@ unprofessional/Y
 unpromising
 unpropitious
 unquestioning/Y
 unquiet/TR
 unread/B
 unready
 unreal
 unreasoning
+unredacted
 unregenerate
 unrelated
 unrelenting/Y
 unrelieved/Y
 unremarkable
 unremitting/Y
 unrepentant
 unreported
@@ -50844,17 +50888,17 @@ videlicet
 video/GSMD
 videocassette/SM
 videoconferencing
 videodisc/MS
 videophone/MS
 videotape/DSMG
 videotex
 vie/DS
-view/AMDRSZG
+view/AMDRSZGB
 viewer/AM
 viewership/M
 viewfinder/SM
 viewing/SM
 viewpoint/MS
 vigesimal
 vigil/SM
 vigilance/M
@@ -51662,16 +51706,17 @@ whistle/MZGDRS
 whistler/M
 whit/MDNRSXTGJ
 white/SPM
 whitebait
 whiteboard/S
 whitecap/SM
 whitefish/MS
 whitehead/MS
+whitelist/DGS
 whiten/ZGDRJ
 whitener/M
 whiteness/M
 whitening/M
 whiteout/SM
 whitepaper/MS
 whitetail/MS
 whitewall/SM
@@ -52297,16 +52342,17 @@ yarmulke/SM
 yarn/MS
 yarrow/M
 yashmak/S
 yaw/SGMD
 yawl/MS
 yawn/MDRSZG
 yawner/M
 yaws/M
+yay
 yd
 ye/RST
 yea/SM
 yeah/M
 yeahs
 year/MYS
 yearbook/MS
 yearling/MS
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -516,16 +516,18 @@ private:
   DECL_GFX_PREF(Live, "gl.ignore-dx-interop2-blacklist",       IgnoreDXInterop2Blacklist, bool, false);
   DECL_GFX_PREF(Live, "gl.msaa-level",                         MSAALevel, uint32_t, 2);
 #if defined(XP_MACOSX)
   DECL_GFX_PREF(Live, "gl.multithreaded",                      GLMultithreaded, bool, false);
 #endif
   DECL_GFX_PREF(Live, "gl.require-hardware",                   RequireHardwareGL, bool, false);
   DECL_GFX_PREF(Live, "gl.use-tls-is-current",                 UseTLSIsCurrent, int32_t, 0);
 
+  DECL_GFX_PREF(Live, "image.animated.decode-on-demand.threshold-kb", ImageAnimatedDecodeOnDemandThresholdKB, uint32_t, 20480);
+  DECL_GFX_PREF(Live, "image.animated.decode-on-demand.batch-size", ImageAnimatedDecodeOnDemandBatchSize, uint32_t, 6);
   DECL_GFX_PREF(Live, "image.cache.factor2.threshold-surfaces", ImageCacheFactor2ThresholdSurfaces, int32_t, -1);
   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.infer-src-animation.threshold-ms", ImageInferSrcAnimationThresholdMS, uint32_t, 2000);
   DECL_GFX_PREF(Live, "image.layout_network_priority",         ImageLayoutNetworkPriority, bool, true);
   DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
new file mode 100644
--- /dev/null
+++ b/image/AnimationFrameBuffer.cpp
@@ -0,0 +1,292 @@
+/* -*- 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/. */
+
+#include "AnimationFrameBuffer.h"
+#include "mozilla/Move.h"             // for Move
+
+namespace mozilla {
+namespace image {
+
+AnimationFrameBuffer::AnimationFrameBuffer()
+  : mThreshold(0)
+  , mBatch(0)
+  , mPending(0)
+  , mAdvance(0)
+  , mInsertIndex(0)
+  , mGetIndex(0)
+  , mSizeKnown(false)
+{ }
+
+void
+AnimationFrameBuffer::Initialize(size_t aThreshold,
+                                 size_t aBatch,
+                                 size_t aStartFrame)
+{
+  MOZ_ASSERT(mThreshold == 0);
+  MOZ_ASSERT(mBatch == 0);
+  MOZ_ASSERT(mPending == 0);
+  MOZ_ASSERT(mAdvance == 0);
+  MOZ_ASSERT(mFrames.IsEmpty());
+
+  mThreshold = aThreshold;
+  mBatch = aBatch;
+  mAdvance = aStartFrame;
+
+  if (mBatch > SIZE_MAX/4) {
+    // Batch size is so big, we will just end up decoding the whole animation.
+    mBatch = SIZE_MAX/4;
+  } else if (mBatch < 1) {
+    // Never permit a batch size smaller than 1. We always want to be asking for
+    // at least one frame to start.
+    mBatch = 1;
+  }
+
+  // To simplify the code, we have the assumption that the threshold for
+  // entering discard-after-display mode is at least twice the batch size (since
+  // that is the most frames-pending-decode we will request) + 1 for the current
+  // frame. That way the redecoded frames being inserted will never risk
+  // overlapping the frames we will discard due to the animation progressing.
+  // That may cause us to use a little more memory than we want but that is an
+  // acceptable tradeoff for simplicity.
+  size_t minThreshold = 2 * mBatch + 1;
+  if (mThreshold < minThreshold) {
+    mThreshold = minThreshold;
+  }
+
+  // The maximum number of frames we should ever have decoded at one time is
+  // twice the batch. That is a good as number as any to start our decoding at.
+  mPending = mBatch * 2;
+}
+
+bool
+AnimationFrameBuffer::Insert(RawAccessFrameRef&& aFrame)
+{
+  // We should only insert new frames if we actually asked for them.
+  MOZ_ASSERT(mPending > 0);
+
+  if (mSizeKnown) {
+    // We only insert after the size is known if we are repeating the animation
+    // and we did not keep all of the frames. Replace whatever is there
+    // (probably an empty frame) with the new frame.
+    MOZ_ASSERT(MayDiscard());
+    MOZ_ASSERT(mInsertIndex < mFrames.Length());
+
+    if (mInsertIndex > 0) {
+      MOZ_ASSERT(!mFrames[mInsertIndex]);
+      mFrames[mInsertIndex] = Move(aFrame);
+    }
+  } else {
+    if (mInsertIndex == mFrames.Length()) {
+      // We are still on the first pass of the animation decoding, so this is
+      // the first time we have seen this frame.
+      mFrames.AppendElement(Move(aFrame));
+    } else if (mInsertIndex > 0) {
+      // We were forced to restart an animation before we decoded the last
+      // frame. Thus we might need to insert, even on a "first pass."
+      MOZ_ASSERT(mInsertIndex < mFrames.Length());
+      MOZ_ASSERT(!mFrames[mInsertIndex]);
+      mFrames[mInsertIndex] = Move(aFrame);
+    }
+
+    if (mInsertIndex == mThreshold) {
+      // We just tripped over the threshold, and on the first pass of the
+      // decoding; this is our chance to do any clearing of already displayed
+      // frames. After this, we only need to release as we advance.
+      MOZ_ASSERT(MayDiscard());
+      MOZ_ASSERT(mGetIndex < mInsertIndex);
+      for (size_t i = 1; i < mGetIndex; ++i) {
+        RawAccessFrameRef discard = Move(mFrames[i]);
+      }
+    }
+  }
+
+  MOZ_ASSERT(mFrames[mInsertIndex]);
+  ++mInsertIndex;
+
+  // Ensure we only request more decoded frames if we actually need them. If we
+  // need to advance to a certain point in the animation on behalf of the owner,
+  // then do so. This ensures we keep decoding. If the batch size is really
+  // small (i.e. 1), it is possible advancing will request the decoder to
+  // "restart", but we haven't told it to stop yet. Note that we skip the first
+  // insert because we actually start "advanced" to the first frame anyways.
+  bool continueDecoding = --mPending > 0;
+  if (mAdvance > 0 && mInsertIndex > 1) {
+    continueDecoding |= AdvanceInternal();
+    --mAdvance;
+  }
+  return continueDecoding;
+}
+
+bool
+AnimationFrameBuffer::MarkComplete()
+{
+  // We reached the end of the animation, the next frame we get, if we get
+  // another, will be the first frame again.
+  MOZ_ASSERT(mInsertIndex == mFrames.Length());
+  mInsertIndex = 0;
+
+  // Since we only request advancing when we want to resume at a certain point
+  // in the animation, we should never exceed the number of frames.
+  MOZ_ASSERT(mAdvance == 0);
+
+  if (!mSizeKnown) {
+    // We just received the last frame in the animation. Compact the frame array
+    // because we know we won't need to grow beyond here.
+    mSizeKnown = true;
+    mFrames.Compact();
+
+    if (!MayDiscard()) {
+      // If we did not meet the threshold, then we know we want to keep all of the
+      // frames. If we also hit the last frame, we don't want to ask for more.
+      mPending = 0;
+    }
+  }
+
+  return mPending > 0;
+}
+
+DrawableFrameRef
+AnimationFrameBuffer::Get(size_t aFrame)
+{
+  // We should not have asked for a frame if we never inserted.
+  if (mFrames.IsEmpty()) {
+    MOZ_ASSERT_UNREACHABLE("Calling Get() when we have no frames");
+    return DrawableFrameRef();
+  }
+
+  // If we don't have that frame, return an empty frame ref.
+  if (aFrame >= mFrames.Length()) {
+    return DrawableFrameRef();
+  }
+
+  // We've got the requested frame because we are not discarding frames. While
+  // we typically should have not run out of frames since we ask for more before
+  // we want them, it is possible the decoder is behind.
+  if (!mFrames[aFrame]) {
+    MOZ_ASSERT(MayDiscard());
+    return DrawableFrameRef();
+  }
+
+  // If we are advancing on behalf of the animation, we don't expect it to be
+  // getting any frames (besides the first) until we get the desired frame.
+  MOZ_ASSERT(aFrame == 0 || mAdvance == 0);
+  return mFrames[aFrame]->DrawableRef();
+}
+
+bool
+AnimationFrameBuffer::AdvanceTo(size_t aExpectedFrame)
+{
+  // The owner should only be advancing once it has reached the requested frame
+  // in the animation.
+  MOZ_ASSERT(mAdvance == 0);
+  bool restartDecoder = AdvanceInternal();
+  // Advancing should always be successful, as it should only happen after the
+  // owner has accessed the next (now current) frame.
+  MOZ_ASSERT(mGetIndex == aExpectedFrame);
+  return restartDecoder;
+}
+
+bool
+AnimationFrameBuffer::AdvanceInternal()
+{
+  // We should not have advanced if we never inserted.
+  if (mFrames.IsEmpty()) {
+    MOZ_ASSERT_UNREACHABLE("Calling Advance() when we have no frames");
+    return false;
+  }
+
+  // We only want to change the current frame index if we have advanced. This
+  // means either a higher frame index, or going back to the beginning.
+  size_t framesLength = mFrames.Length();
+  // We should never have advanced beyond the frame buffer.
+  MOZ_ASSERT(mGetIndex < framesLength);
+  // We should never advance if the current frame is null -- it needs to know
+  // the timeout from it at least to know when to advance.
+  MOZ_ASSERT(mFrames[mGetIndex]);
+  if (++mGetIndex == framesLength) {
+    MOZ_ASSERT(mSizeKnown);
+    mGetIndex = 0;
+  }
+  // The owner should have already accessed the next frame, so it should also
+  // be available.
+  MOZ_ASSERT(mFrames[mGetIndex]);
+
+  // If we moved forward, that means we can remove the previous frame, assuming
+  // that frame is not the first frame. If we looped and are back at the first
+  // frame, we can remove the last frame.
+  if (MayDiscard()) {
+    RawAccessFrameRef discard;
+    if (mGetIndex > 1) {
+      discard = Move(mFrames[mGetIndex - 1]);
+    } else if (mGetIndex == 0) {
+      MOZ_ASSERT(mSizeKnown && framesLength > 1);
+      discard = Move(mFrames[framesLength - 1]);
+    }
+  }
+
+  if (!mSizeKnown || MayDiscard()) {
+    // Calculate how many frames we have requested ahead of the current frame.
+    size_t buffered = mPending;
+    if (mGetIndex > mInsertIndex) {
+      // It wrapped around and we are decoding the beginning again before the
+      // the display has finished the loop.
+      MOZ_ASSERT(mSizeKnown);
+      buffered += mInsertIndex + framesLength - mGetIndex - 1;
+    } else {
+      buffered += mInsertIndex - mGetIndex - 1;
+    }
+
+    if (buffered < mBatch) {
+      // If we have fewer frames than the batch size, then ask for more. If we
+      // do not have any pending, then we know that there is no active decoding.
+      mPending += mBatch;
+      return mPending == mBatch;
+    }
+  }
+
+  return false;
+}
+
+bool
+AnimationFrameBuffer::Reset()
+{
+  // The animation needs to start back at the beginning.
+  mGetIndex = 0;
+  mAdvance = 0;
+
+  if (!MayDiscard()) {
+    // If we haven't crossed the threshold, then we know by definition we have
+    // not discarded any frames. If we previously requested more frames, but
+    // it would have been more than we would have buffered otherwise, we can
+    // stop the decoding after one more frame.
+    if (mPending > 1 && mInsertIndex - 1 >= mBatch * 2) {
+      MOZ_ASSERT(!mSizeKnown);
+      mPending = 1;
+    }
+
+    // Either the decoder is still running, or we have enough frames already.
+    // No need for us to restart it.
+    return false;
+  }
+
+  // If we are over the threshold, then we know we will have missing frames in
+  // our buffer. The easiest thing to do is to drop everything but the first
+  // frame and go back to the initial state.
+  bool restartDecoder = mPending == 0;
+  mInsertIndex = 0;
+  mPending = 2 * mBatch;
+
+  // Discard all frames besides the first, because the decoder always expects
+  // that when it re-inserts a frame, it is not present. (It doesn't re-insert
+  // the first frame.)
+  for (size_t i = 1; i < mFrames.Length(); ++i) {
+    RawAccessFrameRef discard = Move(mFrames[i]);
+  }
+
+  return restartDecoder;
+}
+
+} // namespace image
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/image/AnimationFrameBuffer.h
@@ -0,0 +1,195 @@
+/* -*- 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/. */
+
+#ifndef mozilla_image_AnimationFrameBuffer_h
+#define mozilla_image_AnimationFrameBuffer_h
+
+#include "ISurfaceProvider.h"
+
+namespace mozilla {
+namespace image {
+
+/**
+ * An AnimationFrameBuffer owns the frames outputted by an animated image
+ * decoder as well as directing its owner on how to drive the decoder,
+ * whether to produce more or to stop.
+ *
+ * Based upon its given configuration parameters, it will retain up to a
+ * certain number of frames in the buffer before deciding to discard previous
+ * frames, and relying upon the decoder to recreate older frames when the
+ * animation loops. It will also request that the decoder stop producing more
+ * frames when the display of the frames are far behind -- this allows other
+ * tasks and images which require decoding to take execution priority.
+ *
+ * The desire is that smaller animated images should be kept completely in
+ * memory while larger animated images should only keep a certain number of
+ * frames to minimize our memory footprint at the cost of CPU.
+ */
+class AnimationFrameBuffer final
+{
+public:
+  AnimationFrameBuffer();
+
+  /**
+   * Configure the frame buffer with a particular threshold and batch size. Note
+   * that the frame buffer may adjust the given values.
+   *
+   * @param aThreshold  Maximum number of frames that may be stored in the frame
+   *                    buffer before it may discard already displayed frames.
+   *                    Once exceeded, it will discard the previous frame to the
+   *                    current frame whenever Advance is called. It always
+   *                    retains the first frame.
+   *
+   * @param aBatch      Number of frames we request to be decoded each time it
+   *                    decides we need more.
+   *
+   * @param aStartFrame The starting frame for the animation. The frame buffer
+   *                    will auto-advance (and thus keep the decoding pipeline
+   *                    going) until it has reached this frame. Useful when the
+   *                    animation was progressing, but the surface was
+   *                    discarded, and we had to redecode.
+   */
+  void Initialize(size_t aThreshold, size_t aBatch, size_t aStartFrame);
+
+  /**
+   * Access a specific frame from the frame buffer. It should generally access
+   * frames in sequential order, increasing in tandem with AdvanceTo calls. The
+   * first frame may be accessed at any time. The access order should start with
+   * the same value as that given in Initialize (aStartFrame).
+   *
+   * @param aFrame      The frame index to access.
+   *
+   * @returns The frame, if available.
+   */
+  DrawableFrameRef Get(size_t aFrame);
+
+  /**
+   * Inserts a frame into the frame buffer. If it has yet to fully decode the
+   * animated image yet, then it will append the frame to its internal buffer.
+   * If it has been fully decoded, it will replace the next frame in its buffer
+   * with the given frame.
+   *
+   * Once we have a sufficient number of frames buffered relative to the
+   * currently displayed frame, it will return false to indicate the caller
+   * should stop decoding.
+   *
+   * @param aFrame      The frame to insert into the buffer.
+   *
+   * @returns True if the decoder should decode another frame.
+   */
+  bool Insert(RawAccessFrameRef&& aFrame);
+
+  /**
+   * This should be called after the last frame has been inserted. If the buffer
+   * is discarding old frames, it may request more frames to be decoded. In this
+   * case that means the decoder should start again from the beginning. This
+   * return value should be used in preference to that of the Insert call.
+   *
+   * @returns True if the decoder should decode another frame.
+   */
+  bool MarkComplete();
+
+  /**
+   * Advance the currently displayed frame of the frame buffer. If it reaches
+   * the end, it will loop back to the beginning. It should not be called unless
+   * a call to Get has returned a valid frame for the next frame index.
+   *
+   * As we advance, the number of frames we have buffered ahead of the current
+   * will shrink. Once that becomes too few, we will request a batch-sized set
+   * of frames to be decoded from the decoder.
+   *
+   * @param aExpectedFrame  The frame we expect to have advanced to. This is
+   *                        used for confirmation purposes (e.g. asserts).
+   *
+   * @returns True if the caller should restart the decoder.
+   */
+  bool AdvanceTo(size_t aExpectedFrame);
+
+  /**
+   * Resets the currently displayed frame of the frame buffer to the beginning.
+   * If the buffer is discarding old frames, it will actually discard all frames
+   * besides the first.
+   *
+   * @returns True if the caller should restart the decoder.
+   */
+  bool Reset();
+
+  /**
+   * @returns True if frames post-advance may be discarded and redecoded on
+   *          demand, else false.
+   */
+  bool MayDiscard() const { return mFrames.Length() > mThreshold; }
+
+  /**
+   * @returns True if the frame buffer was ever marked as complete. This implies
+   *          that the total number of frames is known and may be gotten from
+   *          Frames().Length().
+   */
+  bool SizeKnown() const { return mSizeKnown; }
+
+  /**
+   * @returns The current frame index we have advanced to.
+   */
+  size_t Displayed() const { return mGetIndex; }
+
+  /**
+   * @returns Outstanding frames desired from the decoder.
+   */
+  size_t PendingDecode() const { return mPending; }
+
+  /**
+   * @returns Outstanding frames to advance internally.
+   */
+  size_t PendingAdvance() const { return mAdvance; }
+
+  /**
+   * @returns Number of frames we request to be decoded each time it decides we
+   *          need more.
+   */
+  size_t Batch() const { return mBatch; }
+
+  /**
+   * @returns Maximum number of frames before we start discarding previous
+   *          frames post-advance.
+   */
+  size_t Threshold() const { return mThreshold; }
+
+  /**
+   * @returns The frames of this animation, in order. May contain empty indices.
+   */
+  const nsTArray<RawAccessFrameRef>& Frames() const { return mFrames; }
+
+private:
+  bool AdvanceInternal();
+
+  /// The frames of this animation, in order, but may have holes if discarding.
+  nsTArray<RawAccessFrameRef> mFrames;
+
+  // The maximum number of frames we can have before discarding.
+  size_t mThreshold;
+
+  // The minimum number of frames that we want buffered ahead of the display.
+  size_t mBatch;
+
+  // The number of frames to decode before we stop.
+  size_t mPending;
+
+  // The number of frames we need to auto-advance to synchronize with the caller.
+  size_t mAdvance;
+
+  // The mFrames index in which to insert the next decoded frame.
+  size_t mInsertIndex;
+
+  // The mFrames index that we have advanced to.
+  size_t mGetIndex;
+
+  // True if the total number of frames is known.
+  bool mSizeKnown;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_AnimationFrameBuffer_h
--- a/image/AnimationSurfaceProvider.cpp
+++ b/image/AnimationSurfaceProvider.cpp
@@ -3,37 +3,55 @@
  * 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 "AnimationSurfaceProvider.h"
 
 #include "gfxPrefs.h"
 #include "nsProxyRelease.h"
 
+#include "DecodePool.h"
 #include "Decoder.h"
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace image {
 
 AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
                                                    const SurfaceKey& aSurfaceKey,
-                                                   NotNull<Decoder*> aDecoder)
+                                                   NotNull<Decoder*> aDecoder,
+                                                   size_t aCurrentFrame)
   : ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey,
                      AvailabilityState::StartAsPlaceholder())
   , mImage(aImage.get())
   , mDecodingMutex("AnimationSurfaceProvider::mDecoder")
   , mDecoder(aDecoder.get())
   , mFramesMutex("AnimationSurfaceProvider::mFrames")
 {
   MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
              "Use MetadataDecodingTask for metadata decodes");
   MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(),
              "Use DecodedSurfaceProvider for single-frame image decodes");
+
+  // We still produce paletted surfaces for GIF which means the frames are
+  // smaller than one would expect for APNG. This may be removed if/when
+  // bug 1337111 lands and it is enabled by default.
+  size_t pixelSize = aDecoder->GetType() == DecoderType::GIF
+                     ? sizeof(uint8_t) : sizeof(uint32_t);
+
+  // Calculate how many frames we need to decode in this animation before we
+  // enter decode-on-demand mode.
+  IntSize frameSize = aSurfaceKey.Size();
+  size_t threshold =
+    (size_t(gfxPrefs::ImageAnimatedDecodeOnDemandThresholdKB()) * 1024) /
+    (pixelSize * frameSize.width * frameSize.height);
+  size_t batch = gfxPrefs::ImageAnimatedDecodeOnDemandBatchSize();
+
+  mFrames.Initialize(threshold, batch, aCurrentFrame);
 }
 
 AnimationSurfaceProvider::~AnimationSurfaceProvider()
 {
   DropImageReference();
 }
 
 void
@@ -43,58 +61,111 @@ AnimationSurfaceProvider::DropImageRefer
     return;  // Nothing to do.
   }
 
   // RasterImage objects need to be destroyed on the main thread.
   NS_ReleaseOnMainThreadSystemGroup("AnimationSurfaceProvider::mImage",
                                     mImage.forget());
 }
 
+void
+AnimationSurfaceProvider::Reset()
+{
+  // We want to go back to the beginning.
+  bool mayDiscard;
+  bool restartDecoder;
+
+  {
+    MutexAutoLock lock(mFramesMutex);
+
+    // If we have not crossed the threshold, we know we haven't discarded any
+    // frames, and thus we know it is safe move our display index back to the
+    // very beginning. It would be cleaner to let the frame buffer make this
+    // decision inside the AnimationFrameBuffer::Reset method, but if we have
+    // crossed the threshold, we need to hold onto the decoding mutex too. We
+    // should avoid blocking the main thread on the decoder threads.
+    mayDiscard = mFrames.MayDiscard();
+    if (!mayDiscard) {
+      restartDecoder = mFrames.Reset();
+    }
+  }
+
+  if (mayDiscard) {
+    // We are over the threshold and have started discarding old frames. In
+    // that case we need to seize the decoding mutex. Thankfully we know that
+    // we are in the process of decoding at most the batch size frames, so
+    // this should not take too long to acquire.
+    MutexAutoLock lock(mDecodingMutex);
+
+    // Recreate the decoder so we can regenerate the frames again.
+    mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
+    MOZ_ASSERT(mDecoder);
+
+    MutexAutoLock lock2(mFramesMutex);
+    restartDecoder = mFrames.Reset();
+  }
+
+  if (restartDecoder) {
+    DecodePool::Singleton()->AsyncRun(this);
+  }
+}
+
+void
+AnimationSurfaceProvider::Advance(size_t aFrame)
+{
+  bool restartDecoder;
+
+  {
+    // Typical advancement of a frame.
+    MutexAutoLock lock(mFramesMutex);
+    restartDecoder = mFrames.AdvanceTo(aFrame);
+  }
+
+  if (restartDecoder) {
+    DecodePool::Singleton()->AsyncRun(this);
+  }
+}
+
 DrawableFrameRef
 AnimationSurfaceProvider::DrawableRef(size_t aFrame)
 {
   MutexAutoLock lock(mFramesMutex);
 
   if (Availability().IsPlaceholder()) {
     MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder");
     return DrawableFrameRef();
   }
 
-  if (mFrames.IsEmpty()) {
-    MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no frames");
-    return DrawableFrameRef();
-  }
-
-  // If we don't have that frame, return an empty frame ref.
-  if (aFrame >= mFrames.Length()) {
-    return DrawableFrameRef();
-  }
-
-  // We've got the requested frame. Return it.
-  MOZ_ASSERT(mFrames[aFrame]);
-  return mFrames[aFrame]->DrawableRef();
+  return mFrames.Get(aFrame);
 }
 
 bool
 AnimationSurfaceProvider::IsFinished() const
 {
   MutexAutoLock lock(mFramesMutex);
 
   if (Availability().IsPlaceholder()) {
     MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder");
     return false;
   }
 
-  if (mFrames.IsEmpty()) {
+  if (mFrames.Frames().IsEmpty()) {
     MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames");
     return false;
   }
 
   // As long as we have at least one finished frame, we're finished.
-  return mFrames[0]->IsFinished();
+  return mFrames.Frames()[0]->IsFinished();
+}
+
+bool
+AnimationSurfaceProvider::IsFullyDecoded() const
+{
+  MutexAutoLock lock(mFramesMutex);
+  return mFrames.SizeKnown() && !mFrames.MayDiscard();
 }
 
 size_t
 AnimationSurfaceProvider::LogicalSizeInBytes() const
 {
   // When decoding animated images, we need at most three live surfaces: the
   // composited surface, the previous composited surface for
   // DisposalMethod::RESTORE_PREVIOUS, and the surface we're currently decoding
@@ -116,132 +187,154 @@ AnimationSurfaceProvider::AddSizeOfExclu
                                                  size_t& aNonHeapSizeOut,
                                                  size_t& aExtHandlesOut)
 {
   // Note that the surface cache lock is already held here, and then we acquire
   // mFramesMutex. For this method, this ordering is unavoidable, which means
   // that we must be careful to always use the same ordering elsewhere.
   MutexAutoLock lock(mFramesMutex);
 
-  for (const RawAccessFrameRef& frame : mFrames) {
-    frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut,
-                                  aNonHeapSizeOut, aExtHandlesOut);
+  for (const RawAccessFrameRef& frame : mFrames.Frames()) {
+    if (frame) {
+      frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut,
+                                    aNonHeapSizeOut, aExtHandlesOut);
+    }
   }
 }
 
 void
 AnimationSurfaceProvider::Run()
 {
   MutexAutoLock lock(mDecodingMutex);
 
-  if (!mDecoder || !mImage) {
+  if (!mDecoder) {
     MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
     return;
   }
 
   while (true) {
     // Run the decoder.
     LexerResult result = mDecoder->Decode(WrapNotNull(this));
 
     if (result.is<TerminalState>()) {
       // We may have a new frame now, but it's not guaranteed - a decoding
       // failure or truncated data may mean that no new frame got produced.
       // Since we're not sure, rather than call CheckForNewFrameAtYield() here
       // we call CheckForNewFrameAtTerminalState(), which handles both of these
       // possibilities.
-      CheckForNewFrameAtTerminalState();
+      bool continueDecoding = CheckForNewFrameAtTerminalState();
+      FinishDecoding();
 
-      // We're done!
-      FinishDecoding();
+      // Even if it is the last frame, we may not have enough frames buffered
+      // ahead of the current.
+      if (continueDecoding) {
+        MOZ_ASSERT(mDecoder);
+        continue;
+      }
+
       return;
     }
 
     // Notify for the progress we've made so far.
-    if (mDecoder->HasProgress()) {
+    if (mImage && mDecoder->HasProgress()) {
       NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
     }
 
     if (result == LexerResult(Yield::NEED_MORE_DATA)) {
       // We can't make any more progress right now. The decoder itself will ensure
       // that we get reenqueued when more data is available; just return for now.
       return;
     }
 
-    // There's new output available - a new frame! Grab it.
+    // There's new output available - a new frame! Grab it. If we don't need any
+    // more for the moment we can break out of the loop.
     MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE));
-    CheckForNewFrameAtYield();
+    if (!CheckForNewFrameAtYield()) {
+      return;
+    }
   }
 }
 
-void
+bool
 AnimationSurfaceProvider::CheckForNewFrameAtYield()
 {
   mDecodingMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mDecoder);
 
   bool justGotFirstFrame = false;
+  bool continueDecoding;
 
   {
     MutexAutoLock lock(mFramesMutex);
 
     // Try to get the new frame from the decoder.
     RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
     if (!frame) {
       MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?");
-      return;
+      return true;
     }
 
     // We should've gotten a different frame than last time.
-    MOZ_ASSERT_IF(!mFrames.IsEmpty(),
-                  mFrames.LastElement().get() != frame.get());
+    MOZ_ASSERT_IF(!mFrames.Frames().IsEmpty(),
+                  mFrames.Frames().LastElement().get() != frame.get());
 
     // Append the new frame to the list.
-    mFrames.AppendElement(Move(frame));
+    continueDecoding = mFrames.Insert(Move(frame));
 
-    if (mFrames.Length() == 1) {
+    // We only want to handle the first frame if it is the first pass for the
+    // animation decoder. The owning image will be cleared after that.
+    size_t frameCount = mFrames.Frames().Length();
+    if (frameCount == 1 && mImage) {
       justGotFirstFrame = true;
     }
   }
 
   if (justGotFirstFrame) {
     AnnounceSurfaceAvailable();
   }
+
+  return continueDecoding;
 }
 
-void
+bool
 AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
 {
   mDecodingMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mDecoder);
 
   bool justGotFirstFrame = false;
+  bool continueDecoding;
 
   {
     MutexAutoLock lock(mFramesMutex);
 
+    // The decoder may or may not have a new frame for us at this point. Avoid
+    // reinserting the same frame again.
     RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
-    if (!frame) {
-      return;
-    }
-
-    if (!mFrames.IsEmpty() && mFrames.LastElement().get() == frame.get()) {
-      return;  // We already have this one.
+    if (!frame || (!mFrames.Frames().IsEmpty() &&
+                   mFrames.Frames().LastElement().get() == frame.get())) {
+      return mFrames.MarkComplete();
     }
 
     // Append the new frame to the list.
-    mFrames.AppendElement(Move(frame));
+    mFrames.Insert(Move(frame));
+    continueDecoding = mFrames.MarkComplete();
 
-    if (mFrames.Length() == 1) {
+    // We only want to handle the first frame if it is the first pass for the
+    // animation decoder. The owning image will be cleared after that.
+    if (mFrames.Frames().Length() == 1 && mImage) {
       justGotFirstFrame = true;
     }
   }
 
   if (justGotFirstFrame) {
     AnnounceSurfaceAvailable();
   }
+
+  return continueDecoding;
 }
 
 void
 AnimationSurfaceProvider::AnnounceSurfaceAvailable()
 {
   mFramesMutex.AssertNotCurrentThreadOwns();
   MOZ_ASSERT(mImage);
 
@@ -252,24 +345,37 @@ AnimationSurfaceProvider::AnnounceSurfac
   // acquire the surface cache lock and then mFramesMutex.
   SurfaceCache::SurfaceAvailable(WrapNotNull(this));
 }
 
 void
 AnimationSurfaceProvider::FinishDecoding()
 {
   mDecodingMutex.AssertCurrentThreadOwns();
-  MOZ_ASSERT(mImage);
   MOZ_ASSERT(mDecoder);
 
-  // Send notifications.
-  NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
+  if (mImage) {
+    // Send notifications.
+    NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
+  }
 
   // Destroy our decoder; we don't need it anymore.
-  mDecoder = nullptr;
+  bool mayDiscard;
+  {
+    MutexAutoLock lock(mFramesMutex);
+    mayDiscard = mFrames.MayDiscard();
+  }
+
+  if (mayDiscard) {
+    // Recreate the decoder so we can regenerate the frames again.
+    mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
+    MOZ_ASSERT(mDecoder);
+  } else {
+    mDecoder = nullptr;
+  }
 
   // We don't need a reference to our image anymore, either, and we don't want
   // one. We may be stored in the surface cache for a long time after decoding
   // finishes. If we don't drop our reference to the image, we'll end up
   // keeping it alive as long as we remain in the surface cache, which could
   // greatly extend the image's lifetime - in fact, if the image isn't
   // discardable, it'd result in a leak!
   DropImageReference();
--- a/image/AnimationSurfaceProvider.h
+++ b/image/AnimationSurfaceProvider.h
@@ -8,16 +8,17 @@
  */
 
 #ifndef mozilla_image_AnimationSurfaceProvider_h
 #define mozilla_image_AnimationSurfaceProvider_h
 
 #include "FrameAnimator.h"
 #include "IDecodingTask.h"
 #include "ISurfaceProvider.h"
+#include "AnimationFrameBuffer.h"
 
 namespace mozilla {
 namespace image {
 
 /**
  * An ISurfaceProvider that manages the decoding of animated images and
  * dynamically generates surfaces for the current playback state of the
  * animation.
@@ -26,34 +27,38 @@ class AnimationSurfaceProvider final
   : public ISurfaceProvider
   , public IDecodingTask
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnimationSurfaceProvider, override)
 
   AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
                            const SurfaceKey& aSurfaceKey,
-                           NotNull<Decoder*> aDecoder);
+                           NotNull<Decoder*> aDecoder,
+                           size_t aCurrentFrame);
 
 
   //////////////////////////////////////////////////////////////////////////////
   // ISurfaceProvider implementation.
   //////////////////////////////////////////////////////////////////////////////
 
 public:
   // We use the ISurfaceProvider constructor of DrawableSurface to indicate that
   // our surfaces are computed lazily.
   DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); }
 
   bool IsFinished() const override;
+  bool IsFullyDecoded() const override;
   size_t LogicalSizeInBytes() const override;
   void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                               size_t& aHeapSizeOut,
                               size_t& aNonHeapSizeOut,
                               size_t& aExtHandlesOut) override;
+  void Reset() override;
+  void Advance(size_t aFrame) override;
 
 protected:
   DrawableFrameRef DrawableRef(size_t aFrame) override;
 
   // Animation frames are always locked. This is because we only want to release
   // their memory atomically (due to the surface cache discarding them). If they
   // were unlocked, the OS could end up releasing the memory of random frames
   // from the middle of the animation, which is not worth the complexity of
@@ -73,33 +78,37 @@ public:
   // Full decodes are low priority compared to metadata decodes because they
   // don't block layout or page load.
   TaskPriority Priority() const override { return TaskPriority::eLow; }
 
 private:
   virtual ~AnimationSurfaceProvider();
 
   void DropImageReference();
-  void CheckForNewFrameAtYield();
-  void CheckForNewFrameAtTerminalState();
   void AnnounceSurfaceAvailable();
   void FinishDecoding();
 
+  // @returns Whether or not we should continue decoding.
+  bool CheckForNewFrameAtYield();
+
+  // @returns Whether or not we should restart decoding.
+  bool CheckForNewFrameAtTerminalState();
+
   /// The image associated with our decoder.
   RefPtr<RasterImage> mImage;
 
   /// A mutex to protect mDecoder. Always taken before mFramesMutex.
   mutable Mutex mDecodingMutex;
 
   /// The decoder used to decode this animation.
   RefPtr<Decoder> mDecoder;
 
   /// A mutex to protect mFrames. Always taken after mDecodingMutex.
   mutable Mutex mFramesMutex;
 
   /// The frames of this animation, in order.
-  nsTArray<RawAccessFrameRef> mFrames;
+  AnimationFrameBuffer mFrames;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_AnimationSurfaceProvider_h
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -239,16 +239,21 @@ public:
    * to the various decoder constructors instead.
    */
   void SetIterator(SourceBufferIterator&& aIterator)
   {
     MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
     mIterator.emplace(Move(aIterator));
   }
 
+  SourceBuffer* GetSourceBuffer() const
+  {
+    return mIterator->Owner();
+  }
+
   /**
    * Should this decoder send partial invalidations?
    */
   bool ShouldSendPartialInvalidations() const
   {
     return !(mDecoderFlags & DecoderFlags::IS_REDECODE);
   }
 
@@ -293,16 +298,22 @@ public:
   bool InFrame() const { return mInFrame; }
 
   /// Is the image valid if embedded inside an ICO.
   virtual bool IsValidICOResource() const
   {
     return false;
   }
 
+  /// Type of decoder.
+  virtual DecoderType GetType() const
+  {
+    return DecoderType::UNKNOWN;
+  }
+
   enum DecodeStyle {
       PROGRESSIVE, // produce intermediate frames representing the partial
                    // state of the image
       SEQUENTIAL   // decode to final image immediately
   };
 
   /**
    * Get or set the DecoderFlags that influence the behavior of this decoder.
--- a/image/DecoderFactory.cpp
+++ b/image/DecoderFactory.cpp
@@ -170,16 +170,17 @@ DecoderFactory::CreateDecoder(DecoderTyp
 
 /* static */ nsresult
 DecoderFactory::CreateAnimationDecoder(DecoderType aType,
                                        NotNull<RasterImage*> aImage,
                                        NotNull<SourceBuffer*> aSourceBuffer,
                                        const IntSize& aIntrinsicSize,
                                        DecoderFlags aDecoderFlags,
                                        SurfaceFlags aSurfaceFlags,
+                                       size_t aCurrentFrame,
                                        IDecodingTask** aOutTask)
 {
   if (aType == DecoderType::UNKNOWN) {
     return NS_ERROR_INVALID_ARG;
   }
 
   MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG,
              "Calling CreateAnimationDecoder for non-animating DecoderType");
@@ -200,17 +201,17 @@ DecoderFactory::CreateAnimationDecoder(D
     return NS_ERROR_FAILURE;
   }
 
   // Create an AnimationSurfaceProvider which will manage the decoding process
   // and make this decoder's output available in the surface cache.
   SurfaceKey surfaceKey =
     RasterSurfaceKey(aIntrinsicSize, aSurfaceFlags, PlaybackType::eAnimated);
   auto provider = MakeNotNull<RefPtr<AnimationSurfaceProvider>>(
-    aImage, surfaceKey, WrapNotNull(decoder));
+    aImage, surfaceKey, WrapNotNull(decoder), aCurrentFrame);
 
   // Attempt to insert the surface provider into the surface cache right away so
   // we won't trigger any more decoders with the same parameters.
   switch (SurfaceCache::Insert(provider)) {
     case InsertOutcome::SUCCESS:
       break;
     case InsertOutcome::FAILURE_ALREADY_PRESENT:
       return NS_ERROR_ALREADY_INITIALIZED;
@@ -219,16 +220,39 @@ DecoderFactory::CreateAnimationDecoder(D
   }
 
   // Return the surface provider in its IDecodingTask guise.
   RefPtr<IDecodingTask> task = provider.get();
   task.forget(aOutTask);
   return NS_OK;
 }
 
+/* static */ already_AddRefed<Decoder>
+DecoderFactory::CloneAnimationDecoder(Decoder* aDecoder)
+{
+  MOZ_ASSERT(aDecoder);
+  MOZ_ASSERT(aDecoder->HasAnimation());
+
+  RefPtr<Decoder> decoder = GetDecoder(aDecoder->GetType(), nullptr,
+                                       /* aIsRedecode = */ true);
+  MOZ_ASSERT(decoder, "Should have a decoder now");
+
+  // Initialize the decoder.
+  decoder->SetMetadataDecode(false);
+  decoder->SetIterator(aDecoder->GetSourceBuffer()->Iterator());
+  decoder->SetDecoderFlags(aDecoder->GetDecoderFlags());
+  decoder->SetSurfaceFlags(aDecoder->GetSurfaceFlags());
+
+  if (NS_FAILED(decoder->Init())) {
+    return nullptr;
+  }
+
+  return decoder.forget();
+}
+
 /* static */ already_AddRefed<IDecodingTask>
 DecoderFactory::CreateMetadataDecoder(DecoderType aType,
                                       NotNull<RasterImage*> aImage,
                                       NotNull<SourceBuffer*> aSourceBuffer)
 {
   if (aType == DecoderType::UNKNOWN) {
     return nullptr;
   }
--- a/image/DecoderFactory.h
+++ b/image/DecoderFactory.h
@@ -89,32 +89,43 @@ public:
    *               notifications as decoding progresses.
    * @param aSourceBuffer The SourceBuffer which the decoder will read its data
    *                      from.
    * @param aIntrinsicSize The intrinsic size of the image, normally obtained
    *                       during the metadata decode.
    * @param aDecoderFlags Flags specifying the behavior of this decoder.
    * @param aSurfaceFlags Flags specifying the type of output this decoder
    *                      should produce.
+   * @param aCurrentFrame The current frame the decoder should auto advance to.
    * @param aOutTask Task representing the decoder.
    * @return NS_OK if the decoder has been created/initialized successfully;
    *         NS_ERROR_ALREADY_INITIALIZED if there is already an active decoder
    *           for this image;
    *         Else some other unrecoverable error occurred.
    */
   static nsresult
   CreateAnimationDecoder(DecoderType aType,
                          NotNull<RasterImage*> aImage,
                          NotNull<SourceBuffer*> aSourceBuffer,
                          const gfx::IntSize& aIntrinsicSize,
                          DecoderFlags aDecoderFlags,
                          SurfaceFlags aSurfaceFlags,
+                         size_t aCurrentFrame,
                          IDecodingTask** aOutTask);
 
   /**
+   * Creates and initializes a decoder for animated images, cloned from the
+   * given decoder.
+   *
+   * @param aDecoder Decoder to clone.
+   */
+  static already_AddRefed<Decoder>
+  CloneAnimationDecoder(Decoder* aDecoder);
+
+  /**
    * Creates and initializes a metadata decoder of type @aType. This decoder
    * will only decode the image's header, extracting metadata like the size of
    * the image. No actual image data will be decoded and no surfaces will be
    * allocated. The decoder will send notifications to @aImage.
    *
    * @param aType Which type of decoder to create - JPEG, PNG, etc.
    * @param aImage The image will own the decoder and which should receive
    *               notifications as decoding progresses.
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -66,22 +66,17 @@ AnimationState::UpdateStateInternal(Look
     // If mHasBeenDecoded is true then we know the true total frame count and
     // we can use it to determine if we have all the frames now so we know if
     // we are currently fully decoded.
     // If mHasBeenDecoded is false then we'll get another UpdateState call
     // when the decode finishes.
     if (mHasBeenDecoded) {
       Maybe<uint32_t> frameCount = FrameCount();
       MOZ_ASSERT(frameCount.isSome());
-      if (NS_SUCCEEDED(aResult.Surface().Seek(*frameCount - 1)) &&
-          aResult.Surface()->IsFinished()) {
-        mIsCurrentlyDecoded = true;
-      } else {
-        mIsCurrentlyDecoded = false;
-      }
+      mIsCurrentlyDecoded = aResult.Surface().IsFullyDecoded();
     }
   }
 
   gfx::IntRect ret;
 
   if (aAllowInvalidation) {
     // Update the value of mCompositedFrameInvalid.
     if (mIsCurrentlyDecoded || aAnimationFinished) {
@@ -298,18 +293,22 @@ FrameAnimator::AdvanceFrame(AnimationSta
   MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount());
   RawAccessFrameRef nextFrame = GetRawFrame(aFrames, nextFrameIndex);
 
   // We should always check to see if we have the next frame even if we have
   // previously finished decoding. If we needed to redecode (e.g. due to a draw
   // failure) we would have discarded all the old frames and may not yet have
   // the new ones.
   if (!nextFrame || !nextFrame->IsFinished()) {
-    // Uh oh, the frame we want to show is currently being decoded (partial)
-    // Wait until the next refresh driver tick and try again
+    // Uh oh, the frame we want to show is currently being decoded (partial).
+    // Similar to the above case, we could be blocked by network or decoding,
+    // and so we should advance our current time rather than risk jumping
+    // through the animation. We will wait until the next refresh driver tick
+    // and try again.
+    aState.mCurrentAnimationFrameTime = aTime;
     return ret;
   }
 
   Maybe<FrameTimeout> nextFrameTimeout = GetTimeoutForFrame(aState, aFrames, nextFrameIndex);
   // GetTimeoutForFrame can only return none if frame doesn't exist,
   // but we just got it above.
   MOZ_ASSERT(nextFrameTimeout.isSome());
   if (*nextFrameTimeout == FrameTimeout::Forever()) {
@@ -325,16 +324,17 @@ FrameAnimator::AdvanceFrame(AnimationSta
     if (!DoBlend(aFrames, &ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
       // something went wrong, move on to next
       NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
       nextFrame->SetCompositingFailed(true);
       Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState, aFrames);
       MOZ_ASSERT(currentFrameEndTime.isSome());
       aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
       aState.mCurrentAnimationFrameIndex = nextFrameIndex;
+      aFrames.Advance(nextFrameIndex);
 
       return ret;
     }
 
     nextFrame->SetCompositingFailed(false);
   }
 
   Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState, aFrames);
@@ -370,23 +370,43 @@ FrameAnimator::AdvanceFrame(AnimationSta
         MOZ_ASSERT(loops <= CheckedUint64(aState.mLoopRemainingCount).value());
         aState.mLoopRemainingCount -= CheckedInt32(loops).value();
       }
     }
   }
 
   // Set currentAnimationFrameIndex at the last possible moment
   aState.mCurrentAnimationFrameIndex = nextFrameIndex;
+  aFrames.Advance(nextFrameIndex);
 
   // If we're here, we successfully advanced the frame.
   ret.mFrameAdvanced = true;
 
   return ret;
 }
 
+void
+FrameAnimator::ResetAnimation(AnimationState& aState)
+{
+  aState.ResetAnimation();
+
+  // Our surface provider is synchronized to our state, so we need to reset its
+  // state as well, if we still have one.
+  LookupResult result =
+    SurfaceCache::Lookup(ImageKey(mImage),
+                         RasterSurfaceKey(mSize,
+                                          DefaultSurfaceFlags(),
+                                          PlaybackType::eAnimated));
+  if (!result) {
+    return;
+  }
+
+  result.Surface().Reset();
+}
+
 RefreshResult
 FrameAnimator::RequestRefresh(AnimationState& aState,
                               const TimeStamp& aTime,
                               bool aAnimationFinished)
 {
   // By default, an empty RefreshResult.
   RefreshResult ret;
 
--- a/image/FrameAnimator.h
+++ b/image/FrameAnimator.h
@@ -285,16 +285,22 @@ public:
   }
 
   ~FrameAnimator()
   {
     MOZ_COUNT_DTOR(FrameAnimator);
   }
 
   /**
+   * Call when you need to re-start animating. Ensures we start from the first
+   * frame.
+   */
+  void ResetAnimation(AnimationState& aState);
+
+  /**
    * Re-evaluate what frame we're supposed to be on, and do whatever blending
    * is necessary to get us to that frame.
    *
    * Returns the result of that blending, including whether the current frame
    * changed and what the resulting dirty rectangle is.
    */
   RefreshResult RequestRefresh(AnimationState& aState,
                                const TimeStamp& aTime,
--- a/image/ISurfaceProvider.h
+++ b/image/ISurfaceProvider.h
@@ -48,16 +48,22 @@ public:
   const SurfaceKey& GetSurfaceKey() const { return mSurfaceKey; }
 
   /// @return a (potentially lazily computed) drawable reference to a surface.
   virtual DrawableSurface Surface();
 
   /// @return true if DrawableRef() will return a completely decoded surface.
   virtual bool IsFinished() const = 0;
 
+  /// @return true if the underlying decoder is currently fully decoded. For
+  /// animated images, this means that at least every frame has been decoded
+  /// at least once. It does not guarantee that all of the frames are present,
+  /// as the surface provider has the option to discard as it deems necessary.
+  virtual bool IsFullyDecoded() const { return IsFinished(); }
+
   /// @return the number of bytes of memory this ISurfaceProvider is expected to
   /// require. Optimizations may result in lower real memory usage. Trivial
   /// overhead is ignored. Because this value is used in bookkeeping, it's
   /// important that it be constant over the lifetime of this object.
   virtual size_t LogicalSizeInBytes() const = 0;
 
   /// @return the actual number of bytes of memory this ISurfaceProvider is
   /// using. May vary over the lifetime of the ISurfaceProvider. The default
@@ -71,16 +77,19 @@ public:
     if (!ref) {
       return;
     }
 
     ref->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut,
                                 aNonHeapSizeOut, aExtHandlesOut);
   }
 
+  virtual void Reset() { }
+  virtual void Advance(size_t aFrame) { }
+
   /// @return the availability state of this ISurfaceProvider, which indicates
   /// whether DrawableRef() could successfully return a surface. Should only be
   /// called from SurfaceCache code as it relies on SurfaceCache for
   /// synchronization.
   AvailabilityState& Availability() { return mAvailability; }
   const AvailabilityState& Availability() const { return mAvailability; }
 
 protected:
@@ -185,16 +194,46 @@ public:
       return NS_ERROR_FAILURE;
     }
 
     mDrawableRef = mProvider->DrawableRef(aFrame);
 
     return mDrawableRef ? NS_OK : NS_ERROR_FAILURE;
   }
 
+  void Reset()
+  {
+    if (!mProvider) {
+      MOZ_ASSERT_UNREACHABLE("Trying to reset a static DrawableSurface?");
+      return;
+    }
+
+    mProvider->Reset();
+  }
+
+  void Advance(size_t aFrame)
+  {
+    if (!mProvider) {
+      MOZ_ASSERT_UNREACHABLE("Trying to advance a static DrawableSurface?");
+      return;
+    }
+
+    mProvider->Advance(aFrame);
+  }
+
+  bool IsFullyDecoded() const
+  {
+    if (!mProvider) {
+      MOZ_ASSERT_UNREACHABLE("Trying to check decoding state of a static DrawableSurface?");
+      return false;
+    }
+
+    return mProvider->IsFullyDecoded();
+  }
+
   explicit operator bool() const { return mHaveSurface; }
   imgFrame* operator->() { return DrawableRef().get(); }
 
 private:
   DrawableSurface(const DrawableSurface& aOther) = delete;
   DrawableSurface& operator=(const DrawableSurface& aOther) = delete;
 
   DrawableFrameRef& DrawableRef()
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -864,17 +864,18 @@ RasterImage::ResetAnimation()
 
   mAnimationFinished = false;
 
   if (mAnimating) {
     StopAnimation();
   }
 
   MOZ_ASSERT(mAnimationState, "Should have AnimationState");
-  mAnimationState->ResetAnimation();
+  MOZ_ASSERT(mFrameAnimator, "Should have FrameAnimator");
+  mFrameAnimator->ResetAnimation(*mAnimationState);
 
   NotifyProgress(NoProgress, mAnimationState->FirstFrameRefreshArea());
 
   // Start the animation again. It may not have been running before, if
   // mAnimationFinished was true before entering this function.
   EvaluateAnimation();
 
   return NS_OK;
@@ -1243,19 +1244,21 @@ RasterImage::Decode(const IntSize& aSize
     surfaceFlags &= ~SurfaceFlags::NO_PREMULTIPLY_ALPHA;
   }
 
   // Create a decoder.
   RefPtr<IDecodingTask> task;
   nsresult rv;
   bool animated = mAnimationState && aPlaybackType == PlaybackType::eAnimated;
   if (animated) {
+    size_t currentFrame = mAnimationState->GetCurrentAnimationFrameIndex();
     rv = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this),
                                                 mSourceBuffer, mSize,
                                                 decoderFlags, surfaceFlags,
+                                                currentFrame,
                                                 getter_AddRefs(task));
   } else {
     rv = DecoderFactory::CreateDecoder(mDecoderType, WrapNotNull(this),
                                        mSourceBuffer, mSize, aSize,
                                        decoderFlags, surfaceFlags,
                                        getter_AddRefs(task));
   }
 
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -120,16 +120,18 @@ class RasterImage;
 
 /// Decoder for BMP-Files, as used by Windows and OS/2.
 
 class nsBMPDecoder : public Decoder
 {
 public:
   ~nsBMPDecoder();
 
+  DecoderType GetType() const override { return DecoderType::BMP; }
+
   /// @return true if this BMP is a valid ICO resource.
   bool IsValidICOResource() const override { return true; }
 
   /// Obtains the internal output image buffer.
   uint32_t* GetImageData() { return reinterpret_cast<uint32_t*>(mImageData); }
 
   /// Obtains the length of the internal output image buffer.
   size_t GetImageDataLength() const { return mImageDataLength; }
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -19,16 +19,18 @@ class RasterImage;
 //////////////////////////////////////////////////////////////////////
 // nsGIFDecoder2 Definition
 
 class nsGIFDecoder2 : public Decoder
 {
 public:
   ~nsGIFDecoder2();
 
+  DecoderType GetType() const override { return DecoderType::GIF; }
+
 protected:
   LexerResult DoDecode(SourceBufferIterator& aIterator,
                        IResumable* aOnResume) override;
   nsresult FinishInternal() override;
 
   Maybe<Telemetry::HistogramID> SpeedHistogram() const override;
 
 private:
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -40,16 +40,17 @@ enum class ICOState
 class nsICODecoder : public Decoder
 {
 public:
   virtual ~nsICODecoder() { }
 
   /// @return The offset from the beginning of the ICO to the first resource.
   size_t FirstResourceOffset() const;
 
+  DecoderType GetType() const override { return DecoderType::ICO; }
   LexerResult DoDecode(SourceBufferIterator& aIterator,
                        IResumable* aOnResume) override;
   nsresult FinishInternal() override;
   nsresult FinishWithErrorInternal() override;
 
 private:
   friend class DecoderFactory;
 
--- a/image/decoders/nsIconDecoder.h
+++ b/image/decoders/nsIconDecoder.h
@@ -32,16 +32,18 @@ class RasterImage;
 //
 ////////////////////////////////////////////////////////////////////////////////
 
 class nsIconDecoder : public Decoder
 {
 public:
   virtual ~nsIconDecoder();
 
+  DecoderType GetType() const override { return DecoderType::ICON; }
+
   LexerResult DoDecode(SourceBufferIterator& aIterator,
                        IResumable* aOnResume) override;
 
 private:
   friend class DecoderFactory;
 
   // Decoders should only be instantiated via DecoderFactory.
   explicit nsIconDecoder(RasterImage* aImage);
--- a/image/decoders/nsJPEGDecoder.h
+++ b/image/decoders/nsJPEGDecoder.h
@@ -47,16 +47,18 @@ typedef enum {
 class RasterImage;
 struct Orientation;
 
 class nsJPEGDecoder : public Decoder
 {
 public:
   virtual ~nsJPEGDecoder();
 
+  DecoderType GetType() const override { return DecoderType::JPEG; }
+
   void NotifyDone();
 
 protected:
   nsresult InitInternal() override;
   LexerResult DoDecode(SourceBufferIterator& aIterator,
                        IResumable* aOnResume) override;
   nsresult FinishInternal() override;
 
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -20,16 +20,18 @@ class RasterImage;
 class nsPNGDecoder : public Decoder
 {
 public:
   virtual ~nsPNGDecoder();
 
   /// @return true if this PNG is a valid ICO resource.
   bool IsValidICOResource() const override;
 
+  DecoderType GetType() const override { return DecoderType::PNG; }
+
 protected:
   nsresult InitInternal() override;
   nsresult FinishInternal() override;
   LexerResult DoDecode(SourceBufferIterator& aIterator,
                        IResumable* aOnResume) override;
 
   Maybe<Telemetry::HistogramID> SpeedHistogram() const override;
 
--- a/image/imgRequestProxy.cpp
+++ b/image/imgRequestProxy.cpp
@@ -15,16 +15,17 @@
 #include "imgINotificationObserver.h"
 #include "mozilla/dom/TabGroup.h"       // for TabGroup
 #include "mozilla/dom/DocGroup.h"       // for DocGroup
 #include "mozilla/Move.h"
 #include "mozilla/Telemetry.h"          // for Telemetry
 
 using namespace mozilla;
 using namespace mozilla::image;
+using mozilla::Move;
 
 // The split of imgRequestProxy and imgRequestProxyStatic means that
 // certain overridden functions need to be usable in the destructor.
 // Since virtual functions can't be used in that way, this class
 // provides a behavioural trait for each class to use instead.
 class ProxyBehaviour
 {
  public:
@@ -347,17 +348,17 @@ imgRequestProxy::AddToOwner(nsIDocument*
   // we have nothing to signal. However if we were told what document this
   // is for, it is likely that future listeners will belong to the same
   // scheduler group.
   //
   // With a listener, we always need to update our scheduler group. A null
   // scheduler group is valid with or without a document, but that means
   // we will use the most generic event target possible on dispatch.
   if (aLoadingDocument) {
-    RefPtr<dom::DocGroup> docGroup = aLoadingDocument->GetDocGroup();
+    RefPtr<mozilla::dom::DocGroup> docGroup = aLoadingDocument->GetDocGroup();
     if (docGroup) {
       mTabGroup = docGroup->GetTabGroup();
       MOZ_ASSERT(mTabGroup);
 
       mEventTarget = docGroup->EventTargetFor(mozilla::TaskCategory::Other);
       MOZ_ASSERT(mEventTarget);
     }
   }
--- a/image/moz.build
+++ b/image/moz.build
@@ -49,16 +49,17 @@ EXPORTS += [
     'imgRequest.h',
     'imgRequestProxy.h',
     'IProgressObserver.h',
     'Orientation.h',
     'SurfaceCacheUtils.h',
 ]
 
 UNIFIED_SOURCES += [
+    'AnimationFrameBuffer.cpp',
     'AnimationSurfaceProvider.cpp',
     'ClippedImage.cpp',
     'DecodedSurfaceProvider.cpp',
     'DecodePool.cpp',
     'Decoder.cpp',
     'DecoderFactory.cpp',
     'DynamicImage.cpp',
     'FrameAnimator.cpp',
new file mode 100644
--- /dev/null
+++ b/image/test/gtest/TestAnimationFrameBuffer.cpp
@@ -0,0 +1,515 @@
+/* -*- 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/. */
+
+#include "gtest/gtest.h"
+
+#include "mozilla/Move.h"
+#include "AnimationFrameBuffer.h"
+
+using namespace mozilla;
+using namespace mozilla::image;
+
+static RawAccessFrameRef
+CreateEmptyFrame()
+{
+  RefPtr<imgFrame> frame = new imgFrame();
+  nsresult rv = frame->InitForAnimator(nsIntSize(1, 1), SurfaceFormat::B8G8R8A8);
+  EXPECT_TRUE(NS_SUCCEEDED(rv));
+  RawAccessFrameRef frameRef = frame->RawAccessRef();
+  frame->Finish();
+  return frameRef;
+}
+
+static bool
+Fill(AnimationFrameBuffer& buffer, size_t aLength)
+{
+  bool keepDecoding = false;
+  for (size_t i = 0; i < aLength; ++i) {
+    RawAccessFrameRef frame = CreateEmptyFrame();
+    keepDecoding = buffer.Insert(Move(frame->RawAccessRef()));
+  }
+  return keepDecoding;
+}
+
+static void
+CheckFrames(const AnimationFrameBuffer& buffer, size_t aStart, size_t aEnd, bool aExpected)
+{
+  for (size_t i = aStart; i < aEnd; ++i) {
+    EXPECT_EQ(aExpected, !!buffer.Frames()[i]);
+  }
+}
+
+static void
+CheckRemoved(const AnimationFrameBuffer& buffer, size_t aStart, size_t aEnd)
+{
+  CheckFrames(buffer, aStart, aEnd, false);
+}
+
+static void
+CheckRetained(const AnimationFrameBuffer& buffer, size_t aStart, size_t aEnd)
+{
+  CheckFrames(buffer, aStart, aEnd, true);
+}
+
+class ImageAnimationFrameBuffer : public ::testing::Test
+{
+public:
+  ImageAnimationFrameBuffer()
+  { }
+
+private:
+  AutoInitializeImageLib mInit;
+};
+
+TEST_F(ImageAnimationFrameBuffer, InitialState)
+{
+  const size_t kThreshold = 800;
+  const size_t kBatch = 100;
+  AnimationFrameBuffer buffer;
+  buffer.Initialize(kThreshold, kBatch, 0);
+
+  EXPECT_EQ(kThreshold, buffer.Threshold());
+  EXPECT_EQ(kBatch, buffer.Batch());
+  EXPECT_EQ(size_t(0), buffer.Displayed());
+  EXPECT_EQ(kBatch * 2, buffer.PendingDecode());
+  EXPECT_EQ(size_t(0), buffer.PendingAdvance());
+  EXPECT_FALSE(buffer.MayDiscard());
+  EXPECT_FALSE(buffer.SizeKnown());
+  EXPECT_TRUE(buffer.Frames().IsEmpty());
+}
+
+TEST_F(ImageAnimationFrameBuffer, ThresholdTooSmall)
+{
+  const size_t kThreshold = 0;
+  const size_t kBatch = 10;
+  AnimationFrameBuffer buffer;
+  buffer.Initialize(kThreshold, kBatch, 0);
+
+  EXPECT_EQ(kBatch * 2 + 1, buffer.Threshold());
+  EXPECT_EQ(kBatch, buffer.Batch());
+  EXPECT_EQ(kBatch * 2, buffer.PendingDecode());
+  EXPECT_EQ(size_t(0), buffer.PendingAdvance());
+}
+
+TEST_F(ImageAnimationFrameBuffer, BatchTooSmall)
+{
+  const size_t kThreshold = 10;
+  const size_t kBatch = 0;
+  AnimationFrameBuffer buffer;
+  buffer.Initialize(kThreshold, kBatch, 0);
+
+  EXPECT_EQ(kThreshold, buffer.Threshold());
+  EXPECT_EQ(size_t(1), buffer.Batch());
+  EXPECT_EQ(size_t(2), buffer.PendingDecode());
+  EXPECT_EQ(size_t(0), buffer.PendingAdvance());
+}
+
+TEST_F(ImageAnimationFrameBuffer, BatchTooBig)
+{
+  const size_t kThreshold = 50;
+  const size_t kBatch = SIZE_MAX;
+  AnimationFrameBuffer buffer;
+  buffer.Initialize(kThreshold, kBatch, 0);
+
+  // The rounding is important here (e.g. SIZE_MAX/4 * 2 != SIZE_MAX/2).
+  EXPECT_EQ(SIZE_MAX/4, buffer.Batch());
+  EXPECT_EQ(buffer.Batch() * 2 + 1, buffer.Threshold());
+  EXPECT_EQ(buffer.Batch() * 2, buffer.PendingDecode());
+  EXPECT_EQ(size_t(0), buffer.PendingAdvance());
+}
+
+TEST_F(ImageAnimationFrameBuffer, FinishUnderBatchAndThreshold)
+{
+  const size_t kThreshold = 30;
+  const size_t kBatch = 10;
+  AnimationFrameBuffer buffer;
+  buffer.Initialize(kThreshold, kBatch, 0);
+  const auto& frames = buffer.Frames();
+
+  EXPECT_EQ(kBatch * 2, buffer.PendingDecode());
+
+  RawAccessFrameRef firstFrame;
+  for (size_t i = 0; i < 5; ++i) {
+    RawAccessFrameRef frame = CreateEmptyFrame();
+    bool keepDecoding = buffer.Insert(Move(frame->RawAccessRef()));
+    EXPECT_TRUE(keepDecoding);
+    EXPECT_FALSE(buffer.SizeKnown());
+
+    if (i == 4) {
+      EXPECT_EQ(size_t(15), buffer.PendingDecode());
+      keepDecoding = buffer.MarkComplete();
+      EXPECT_FALSE(keepDecoding);
+      EXPECT_TRUE(buffer.SizeKnown());
+      EXPECT_EQ(size_t(0), buffer.PendingDecode());
+    }
+
+    EXPECT_FALSE(buffer.MayDiscard());
+
+    DrawableFrameRef gotFrame = buffer.Get(i);
+    EXPECT_EQ(frame.get(), gotFrame.get());
+    ASSERT_EQ(i + 1, frames.Length());
+    EXPECT_EQ(frame.get(), frames[i].get());
+
+    if (i == 0) {
+      firstFrame = Move(frame);
+      EXPECT_EQ(size_t(0), buffer.Displayed());
+    } else {
+      EXPECT_EQ(i - 1, buffer.Displayed());
+      bool restartDecoder = buffer.AdvanceTo(i);
+      EXPECT_FALSE(restartDecoder);
+      EXPECT_EQ(i, buffer.Displayed());
+    }
+
+    gotFrame = buffer.Get(0);
+    EXPECT_EQ(firstFrame.get(), gotFrame.get());
+  }
+
+  // Loop again over the animation and make sure it is still all there.
+  for (size_t i = 0; i < frames.Length(); ++i) {
+    DrawableFrameRef gotFrame = buffer.Get(i);
+    EXPECT_TRUE(gotFrame);
+
+    bool restartDecoder = buffer.AdvanceTo(i);
+    EXPECT_FALSE(restartDecoder);
+  }
+}
+
+TEST_F(ImageAnimationFrameBuffer, FinishMultipleBatchesUnderThreshold)
+{
+  const size_t kThreshold = 30;
+  const size_t kBatch = 2;
+  AnimationFrameBuffer buffer;
+  buffer.Initialize(kThreshold, kBatch, 0);
+  const auto& frames = buffer.Frames();
+
+  EXPECT_EQ(kBatch * 2, buffer.PendingDecode());
+
+  // Add frames until it tells us to stop.
+  bool keepDecoding;
+  do {
+    keepDecoding = buffer.Insert(CreateEmptyFrame());
+    EXPECT_FALSE(buffer.SizeKnown());
+    EXPECT_FALSE(buffer.MayDiscard());
+  } while (keepDecoding);
+
+  EXPECT_EQ(size_t(0), buffer.PendingDecode());
+  EXPECT_EQ(size_t(4), frames.Length());
+
+  // Progress through the animation until it lets us decode again.
+  bool restartDecoder = false;
+  size_t i = 0;
+  do {
+    DrawableFrameRef gotFrame = buffer.Get(i);
+    EXPECT_TRUE(gotFrame);
+    if (i > 0) {
+      restartDecoder = buffer.AdvanceTo(i);
+    }
+    ++i;
+  } while (!restartDecoder);
+
+  EXPECT_EQ(size_t(2), buffer.PendingDecode());
+  EXPECT_EQ(size_t(2), buffer.Displayed());
+
+  // Add the last frame.
+  keepDecoding = buffer.Insert(CreateEmptyFrame());
+  EXPECT_TRUE(keepDecoding);
+  keepDecoding = buffer.MarkComplete();
+  EXPECT_FALSE(keepDecoding);
+  EXPECT_TRUE(buffer.SizeKnown());
+  EXPECT_EQ(size_t(0), buffer.PendingDecode());
+  EXPECT_EQ(size_t(5), frames.Length());
+
+  // Finish progressing through the animation.
+  for ( ; i < frames.Length(); ++i) {
+    DrawableFrameRef gotFrame = buffer.Get(i);
+    EXPECT_TRUE(gotFrame);
+    restartDecoder = buffer.AdvanceTo(i);
+    EXPECT_FALSE(restartDecoder);
+  }
+
+  // Loop again over the animation and make sure it is still all there.
+  for (i = 0; i < frames.Length(); ++i) {
+    DrawableFrameRef gotFrame = buffer.Get(i);
+    EXPECT_TRUE(gotFrame);
+    restartDecoder = buffer.AdvanceTo(i);
+    EXPECT_FALSE(restartDecoder);
+  }
+
+  // Loop to the third frame and then reset the animation.
+  for (i = 0; i < 3; ++i) {
+    DrawableFrameRef gotFrame = buffer.Get(i);
+    EXPECT_TRUE(gotFrame);
+    restartDecoder = buffer.AdvanceTo(i);
+    EXPECT_FALSE(restartDecoder);
+  }
+
+  // Since we are below the threshold, we can reset the get index only.
+  // Nothing else should have changed.
+  restartDecoder = buffer.Reset();
+  EXPECT_FALSE(restartDecoder);
+  CheckRetained(buffer, 0, 5);
+  EXPECT_EQ(size_t(0), buffer.PendingDecode());
+  EXPECT_EQ(size_t(0), buffer.PendingAdvance());
+  EXPECT_EQ(size_t(0), buffer.Displayed());
+}
+
+TEST_F(ImageAnimationFrameBuffer, MayDiscard)
+{
+  const size_t kThreshold = 8;
+  const size_t kBatch = 3;
+  AnimationFrameBuffer buffer;
+  buffer.Initialize(kThreshold, kBatch, 0);
+  const auto& frames = buffer.Frames();
+
+  EXPECT_EQ(kBatch * 2, buffer.PendingDecode());
+
+  // Add frames until it tells us to stop.
+  bool keepDecoding;
+  do {
+    keepDecoding = buffer.Insert(CreateEmptyFrame());
+    EXPECT_FALSE(buffer.SizeKnown());
+    EXPECT_FALSE(buffer.MayDiscard());
+  } while (keepDecoding);
+
+  EXPECT_EQ(size_t(0), buffer.PendingDecode());
+  EXPECT_EQ(size_t(6), frames.Length());
+
+  // Progress through the animation until it lets us decode again.
+  bool restartDecoder = false;
+  size_t i = 0;
+  do {
+    DrawableFrameRef gotFrame = buffer.Get(i);
+    EXPECT_TRUE(gotFrame);
+    if (i > 0) {
+      restartDecoder = buffer.AdvanceTo(i);
+    }
+    ++i;
+  } while (!restartDecoder);
+
+  EXPECT_EQ(size_t(3), buffer.PendingDecode());
+  EXPECT_EQ(size_t(3), buffer.Displayed());
+
+  // Add more frames.
+  do {
+    keepDecoding = buffer.Insert(CreateEmptyFrame());
+    EXPECT_FALSE(buffer.SizeKnown());
+  } while (keepDecoding);
+
+  EXPECT_TRUE(buffer.MayDiscard());
+  EXPECT_EQ(size_t(0), buffer.PendingDecode());
+  EXPECT_EQ(size_t(9), frames.Length());
+
+  // It should have be able to remove two frames given we have advanced to the
+  // fourth frame.
+  CheckRetained(buffer, 0, 1);
+  CheckRemoved(buffer, 1, 3);
+  CheckRetained(buffer, 3, 9);
+
+  // Progress through the animation so more. Make sure it removes frames as we
+  // go along.
+  do {
+    DrawableFrameRef gotFrame = buffer.Get(i);
+    EXPECT_TRUE(gotFrame);
+    restartDecoder = buffer.AdvanceTo(i);
+    EXPECT_FALSE(frames[i - 1]);
+    EXPECT_TRUE(frames[i]);
+    i++;
+  } while (!restartDecoder);
+
+  EXPECT_EQ(size_t(3), buffer.PendingDecode());
+  EXPECT_EQ(size_t(6), buffer.Displayed());
+
+  // Add the last frame. It should still let us add more frames, but the next
+  // frame will restart at the beginning.
+  keepDecoding = buffer.Insert(CreateEmptyFrame());
+  EXPECT_TRUE(keepDecoding);
+  keepDecoding = buffer.MarkComplete();
+  EXPECT_TRUE(keepDecoding);
+  EXPECT_TRUE(buffer.SizeKnown());
+  EXPECT_EQ(size_t(2), buffer.PendingDecode());
+  EXPECT_EQ(size_t(10), frames.Length());
+
+  // Use remaining pending room. It shouldn't add new frames, only replace.
+  do {
+    keepDecoding = buffer.Insert(CreateEmptyFrame());
+  } while (keepDecoding);
+
+  EXPECT_EQ(size_t(0), buffer.PendingDecode());
+  EXPECT_EQ(size_t(10), frames.Length());
+
+  // Advance as far as we can. This should require us to loop the animation to
+  // reach a missing frame.
+  do {
+    if (i == frames.Length()) {
+      i = 0;
+    }
+
+    DrawableFrameRef gotFrame = buffer.Get(i);
+    if (!gotFrame) {
+      break;
+    }
+
+    restartDecoder = buffer.AdvanceTo(i);
+    ++i;
+  } while (true);
+
+  EXPECT_EQ(size_t(3), buffer.PendingDecode());
+  EXPECT_EQ(size_t(2), i);
+  EXPECT_EQ(size_t(1), buffer.Displayed());
+
+  // Decode some more.
+  keepDecoding = Fill(buffer, buffer.PendingDecode());
+  EXPECT_FALSE(keepDecoding);
+  EXPECT_EQ(size_t(0), buffer.PendingDecode());
+
+  // Can we retry advancing again?
+  DrawableFrameRef gotFrame = buffer.Get(i);
+  EXPECT_TRUE(gotFrame);
+  restartDecoder = buffer.AdvanceTo(i);
+  EXPECT_EQ(size_t(2), buffer.Displayed());
+  EXPECT_FALSE(frames[i - 1]);
+  EXPECT_TRUE(frames[i]);
+
+  // Since we are above the threshold, we must reset everything.
+  restartDecoder = buffer.Reset();
+  EXPECT_FALSE(restartDecoder);
+  CheckRetained(buffer, 0, 1);
+  CheckRemoved(buffer, 1, frames.Length());
+  EXPECT_EQ(kBatch * 2, buffer.PendingDecode());
+  EXPECT_EQ(size_t(0), buffer.PendingAdvance());
+  EXPECT_EQ(size_t(0), buffer.Displayed());
+}
+
+TEST_F(ImageAnimationFrameBuffer, ResetIncompleteAboveThreshold)
+{
+  const size_t kThreshold = 5;
+  const size_t kBatch = 2;
+  AnimationFrameBuffer buffer;
+  buffer.Initialize(kThreshold, kBatch, 0);
+  const auto& frames = buffer.Frames();
+
+  // Add frames until we exceed the threshold.
+  bool keepDecoding;
+  bool restartDecoder;
+  size_t i = 0;
+  do {
+    keepDecoding = buffer.Insert(CreateEmptyFrame());
+    EXPECT_TRUE(keepDecoding);
+    if (i > 0) {
+      restartDecoder = buffer.AdvanceTo(i);
+      EXPECT_FALSE(restartDecoder);
+    }
+    ++i;
+  } while (!buffer.MayDiscard());
+
+  // Should have threshold + 1 frames, and still not complete.
+  EXPECT_EQ(size_t(6), frames.Length());
+  EXPECT_FALSE(buffer.SizeKnown());
+
+  // Restart the animation, we still had pending frames to decode since we
+  // advanced in lockstep, so it should not ask us to restart the decoder.
+  restartDecoder = buffer.Reset();
+  EXPECT_FALSE(restartDecoder);
+  CheckRetained(buffer, 0, 1);
+  CheckRemoved(buffer, 1, frames.Length());
+  EXPECT_EQ(kBatch * 2, buffer.PendingDecode());
+  EXPECT_EQ(size_t(0), buffer.PendingAdvance());
+  EXPECT_EQ(size_t(0), buffer.Displayed());
+
+  // Adding new frames should not grow the insertion array, but instead
+  // should reuse the space already allocated. Given that we are able to
+  // discard frames once we cross the threshold, we should confirm that
+  // we only do so if we have advanced beyond them.
+  size_t oldFramesLength = frames.Length();
+  size_t advanceUpTo = frames.Length() - kBatch;
+  for (i = 0; i < oldFramesLength; ++i) {
+    keepDecoding = buffer.Insert(CreateEmptyFrame());
+    EXPECT_TRUE(keepDecoding);
+    EXPECT_TRUE(frames[i]);
+    EXPECT_EQ(oldFramesLength, frames.Length());
+    if (i > 0) {
+      // If we stop advancing, we should still retain the previous frames.
+      EXPECT_TRUE(frames[i-1]);
+      if (i <= advanceUpTo) {
+        restartDecoder = buffer.AdvanceTo(i);
+        EXPECT_FALSE(restartDecoder);
+      }
+    }
+  }
+
+  // Add one more frame. It should have grown the array this time.
+  keepDecoding = buffer.Insert(CreateEmptyFrame());
+  EXPECT_TRUE(keepDecoding);
+  ASSERT_EQ(i + 1, frames.Length());
+  EXPECT_TRUE(frames[i]);
+}
+
+TEST_F(ImageAnimationFrameBuffer, StartAfterBeginning)
+{
+  const size_t kThreshold = 30;
+  const size_t kBatch = 2;
+  const size_t kStartFrame = 7;
+  AnimationFrameBuffer buffer;
+  buffer.Initialize(kThreshold, kBatch, kStartFrame);
+
+  EXPECT_EQ(kStartFrame, buffer.PendingAdvance());
+
+  // Add frames until it tells us to stop. It should be later than before,
+  // because it auto-advances until its displayed frame is kStartFrame.
+  bool keepDecoding;
+  size_t i = 0;
+  do {
+    keepDecoding = buffer.Insert(CreateEmptyFrame());
+    EXPECT_FALSE(buffer.SizeKnown());
+    EXPECT_FALSE(buffer.MayDiscard());
+
+    if (i <= kStartFrame) {
+      EXPECT_EQ(i, buffer.Displayed());
+      EXPECT_EQ(kStartFrame - i, buffer.PendingAdvance());
+    } else {
+      EXPECT_EQ(kStartFrame, buffer.Displayed());
+      EXPECT_EQ(size_t(0), buffer.PendingAdvance());
+    }
+
+    i++;
+  } while (keepDecoding);
+
+  EXPECT_EQ(size_t(0), buffer.PendingDecode());
+  EXPECT_EQ(size_t(0), buffer.PendingAdvance());
+  EXPECT_EQ(size_t(10), buffer.Frames().Length());
+}
+
+TEST_F(ImageAnimationFrameBuffer, StartAfterBeginningAndReset)
+{
+  const size_t kThreshold = 30;
+  const size_t kBatch = 2;
+  const size_t kStartFrame = 7;
+  AnimationFrameBuffer buffer;
+  buffer.Initialize(kThreshold, kBatch, kStartFrame);
+
+  EXPECT_EQ(kStartFrame, buffer.PendingAdvance());
+
+  // Add frames until it tells us to stop. It should be later than before,
+  // because it auto-advances until its displayed frame is kStartFrame.
+  for (size_t i = 0; i < 5; ++i) {
+    bool keepDecoding = buffer.Insert(CreateEmptyFrame());
+    EXPECT_TRUE(keepDecoding);
+    EXPECT_FALSE(buffer.SizeKnown());
+    EXPECT_FALSE(buffer.MayDiscard());
+    EXPECT_EQ(i, buffer.Displayed());
+    EXPECT_EQ(kStartFrame - i, buffer.PendingAdvance());
+  }
+
+  // When we reset the animation, it goes back to the beginning. That means
+  // we can forget about what we were told to advance to at the start. While
+  // we have plenty of frames in our buffer, we still need one more because
+  // in the real scenario, the decoder thread is still running and it is easier
+  // to let it insert its last frame than to coordinate quitting earlier.
+  buffer.Reset();
+  EXPECT_EQ(size_t(0), buffer.Displayed());
+  EXPECT_EQ(size_t(1), buffer.PendingDecode());
+  EXPECT_EQ(size_t(0), buffer.PendingAdvance());
+}
+
--- a/image/test/gtest/moz.build
+++ b/image/test/gtest/moz.build
@@ -4,16 +4,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 Library('imagetest')
 
 UNIFIED_SOURCES = [
     'Common.cpp',
     'TestADAM7InterpolatingFilter.cpp',
+    'TestAnimationFrameBuffer.cpp',
     'TestContainers.cpp',
     'TestCopyOnWrite.cpp',
     'TestDecoders.cpp',
     'TestDecodeToSurface.cpp',
     'TestDeinterlacingFilter.cpp',
     'TestMetadata.cpp',
     'TestRemoveFrameRectFilter.cpp',
     'TestSourceBuffer.cpp',
--- a/image/test/mochitest/mochitest.ini
+++ b/image/test/mochitest/mochitest.ini
@@ -65,16 +65,17 @@ support-files =
   keep.gif
   keep.png
   lime100x100.svg
   lime-anim-100x100.svg
   lime-anim-100x100-2.svg
   lime-css-anim-100x100.svg
   opaque.bmp
   purple.gif
+  rainbow.gif
   red.gif
   red.png
   ref-iframe.html
   restore-previous.gif
   restore-previous.png
   rillybad.jpg
   schrep.png
   shaver.png
@@ -128,16 +129,17 @@ skip-if = os == 'android'
 [test_bug1325080.html]
 [test_bullet_animation.html]
 skip-if = os == 'android'
 [test_changeOfSource.html]
 skip-if = os == 'android'
 [test_changeOfSource2.html]
 skip-if = os == 'android'
 [test_discardAnimatedImage.html]
+[test_discardFramesAnimatedImage.html]
 [test_drawDiscardedImage.html]
 [test_error_events.html]
 [test_image_crossorigin_data_url.html]
 [test_ImageContentLoaded.html]
 [test_has_transparency.html]
 skip-if = os == 'android'
 [test_net_failedtoprocess.html]
 skip-if = os == 'android'
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a247a80df043d9e0e7ecb0ede468c8774522e72e
GIT binary patch
literal 1572
zc${<hbhEHbOkqf2_`tyMABg_{SNzGsAi}`Fp!lEL&ow02*)hP?NY8+o5hCQ7o0y*J
zo0y)NoXwk_n46nuYoKRhYGP{2paWFE05XYzsko(o<>|Nli|1^))xG)Np5Od!k36P5
z>s<Eg)V6or$3OX8|JM8b*SYWi_<6Y6k1YDw;iEPE%(Bm%y<W?&toqt@Hf#Hxb>F%9
zx{p8E^s~or_4PN~e)IO9e*R_G-@fy&zyI0ypI<`5!XqN1qGQI29Vc$Q_`#7G0dyR~
x-9$LDp#kjKA>q{j|A&lI8HSKkK`tE{PW?Z0oC<d75OFFfXAKdjHVm9otpRsT9_0W4
new file mode 100644
--- /dev/null
+++ b/image/test/mochitest/test_discardFramesAnimatedImage.html
@@ -0,0 +1,268 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=523950
+-->
+<head>
+  <title>Test that animated images can discard frames and redecode</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+  <script type="text/javascript" src="imgutils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=523950">Mozilla Bug 523950</a>
+<p id="display"></p>
+<div id="content">
+  <div id="container">
+    <canvas id="canvas" width="100" height="100"></canvas>
+    <img id="rainbow.gif"/>
+  </div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 523950. **/
+SimpleTest.waitForExplicitFinish();
+
+var gFinished = false;
+
+var gNumOnloads = 0;
+
+var gNumDiscards = 0;
+
+window.onload = function() {
+  // Enable minimal frame discard thresholds for the test.
+  SpecialPowers.pushPrefEnv({
+    'set':[['image.animated.decode-on-demand.threshold-kb',0],
+           ['image.animated.decode-on-demand.batch-size',1],
+           ['image.mem.discardable',true],
+           ['image.mem.animated.discardable',true]]
+  }, runTest);
+}
+
+var gImgs = ['rainbow.gif'];
+// If we are currently counting frame updates.
+var gCountingFrameUpdates = false;
+// The number of frame update notifications for the images in gImgs that happen
+// after discarding. (The last two images are finite looping so we don't expect
+// them to get incremented but it's possible if they don't finish their
+// animation before we discard them.)
+var gNumFrameUpdates = [0];
+// The last snapshot of the image. Used to check that the image actually changes.
+var gLastSnapShot = [null];
+// Number of observed changes in the snapshot.
+var gNumSnapShotChanges = [0];
+// If we've removed the observer.
+var gRemovedObserver = [false];
+
+// rainbow.gif has 9 frames, so we choose arbitrarily 22 to include two loops.
+var kNumFrameUpdatesToExpect = 22;
+
+function runTest() {
+  var thresholdKb =
+    SpecialPowers.getIntPref('image.animated.decode-on-demand.threshold-kb');
+  var batchSize =
+    SpecialPowers.getIntPref('image.animated.decode-on-demand.batch-size');
+  var discardable =
+    SpecialPowers.getBoolPref('image.mem.discardable');
+  var animDiscardable =
+    SpecialPowers.getBoolPref('image.mem.animated.discardable');
+  if (thresholdKb > 0 || batchSize > 1 || !discardable || !animDiscardable) {
+    ok(true, "discarding frames of animated images is disabled, nothing to test");
+    SimpleTest.finish();
+    return;
+  }
+
+  setTimeout(step2, 0);
+}
+
+function step2() {
+  // Only set the src after setting the pref.
+  for (var i = 0; i < gImgs.length; i++) {
+    var elm = document.getElementById(gImgs[i]);
+    elm.src = gImgs[i];
+    elm.onload = checkIfLoaded;
+  }
+}
+
+function step3() {
+  // Draw the images to canvas to force them to be decoded.
+  for (var i = 0; i < gImgs.length; i++) {
+    drawCanvas(document.getElementById(gImgs[i]));
+  }
+
+  for (var i = 0; i < gImgs.length; i++) {
+    addCallbacks(document.getElementById(gImgs[i]), i);
+  }
+
+  setTimeout(step4, 0);
+}
+
+function step4() {
+  ok(true, "now accepting frame updates");
+  gCountingFrameUpdates = true;
+}
+
+function step5() {
+  ok(true, "discarding images");
+
+  document.getElementById("container").style.display = "none";
+  document.documentElement.offsetLeft; // force that style to take effect
+
+  // Reset our state to let us do it all again after discarding.
+  resetState();
+
+  // Draw the images to canvas to force them to be decoded.
+  for (var i = 0; i < gImgs.length; i++) {
+    requestDiscard(document.getElementById(gImgs[i]));
+  }
+
+  // the discard observers will call step6
+}
+
+function step6() {
+  // Repeat the cycle now that we discarded everything.
+  ok(gNumDiscards >= gImgs.length, "discard complete, restarting animations");
+  document.getElementById("container").style.display = "";
+
+  // Draw the images to canvas to force them to be decoded.
+  for (var i = 0; i < gImgs.length; i++) {
+    drawCanvas(document.getElementById(gImgs[i]));
+  }
+
+  setTimeout(step4, 0);
+}
+
+function checkIfLoaded() {
+  ++gNumOnloads;
+  if (gNumOnloads != gImgs.length) {
+    return;
+  }
+
+  ok(true, "got onloads");
+  setTimeout(step3, 0);
+}
+
+function resetState() {
+  gFinished = false;
+  gCountingFrameUpdates = false;
+  for (var i = 0; i < gNumFrameUpdates.length; ++i) {
+    gNumFrameUpdates[i] = 0;
+  }
+  for (var i = 0; i < gNumSnapShotChanges.length; ++i) {
+    gNumSnapShotChanges[i] = 0;
+  }
+  for (var i = 0; i < gLastSnapShot.length; ++i) {
+    gLastSnapShot[i] = null;
+  }
+}
+
+function checkIfFinished() {
+  if (gFinished) {
+    return;
+  }
+
+  for (var i = 0; i < gNumFrameUpdates.length; ++i) {
+    if (gNumFrameUpdates[i] < kNumFrameUpdatesToExpect ||
+        gNumSnapShotChanges[i] < kNumFrameUpdatesToExpect) {
+      return;
+    }
+  }
+
+  ok(true, "got expected frame updates");
+  gFinished = true;
+
+  if (gNumDiscards == 0) {
+    // If we haven't discarded any complete images, we should do so, and
+    // verify the animation again.
+    setTimeout(step5, 0);
+    return;
+  }
+
+  SimpleTest.finish();
+}
+
+// arrayIndex is the index into the arrays gNumFrameUpdates and gNumDecodes
+// to increment when a frame update notification is received.
+function addCallbacks(anImage, arrayIndex) {
+  var observer = new ImageDecoderObserverStub();
+  observer.discard = function () {
+    gNumDiscards++;
+    ok(true, "got image discard");
+    if (gNumDiscards == gImgs.length) {
+      step6();
+    }
+  };
+  observer.frameUpdate = function () {
+    if (!gCountingFrameUpdates) {
+      return;
+    }
+
+    // Do this off a setTimeout since nsImageLoadingContent uses a scriptblocker
+    // when it notifies us and calling drawWindow can call will paint observers
+    // which can dispatch a scrollport event, and events assert if dispatched
+    // when there is a scriptblocker.
+    setTimeout(function () {
+      gNumFrameUpdates[arrayIndex]++;
+
+      var r = document.getElementById(gImgs[arrayIndex]).getBoundingClientRect();
+      var snapshot = snapshotRect(window, r, "rgba(0,0,0,0)");
+      if (gLastSnapShot[arrayIndex] != null) {
+        if (snapshot.toDataURL() != gLastSnapShot[arrayIndex].toDataURL()) {
+          gNumSnapShotChanges[arrayIndex]++;
+        }
+      }
+      gLastSnapShot[arrayIndex] = snapshot;
+
+      if (gNumFrameUpdates[arrayIndex] >= kNumFrameUpdatesToExpect &&
+          gNumSnapShotChanges[arrayIndex] >= kNumFrameUpdatesToExpect &&
+	  gNumDiscards >= gImgs.length) {
+        if (!gRemovedObserver[arrayIndex]) {
+	  ok(true, "removing observer for " + arrayIndex);
+          gRemovedObserver[arrayIndex] = true;
+          imgLoadingContent.removeObserver(scriptedObserver);
+        }
+      }
+      if (!gFinished) {
+        // because we do this in a setTimeout we can have several in flight
+        // so don't call ok if we've already finished.
+        ok(true, "got frame update");
+      }
+      checkIfFinished();
+    }, 0);
+  };
+  observer = SpecialPowers.wrapCallbackObject(observer);
+
+  var scriptedObserver = SpecialPowers.Cc["@mozilla.org/image/tools;1"]
+                           .getService(SpecialPowers.Ci.imgITools)
+                           .createScriptedObserver(observer);
+
+  var imgLoadingContent = SpecialPowers.wrap(anImage);
+  imgLoadingContent.addObserver(scriptedObserver);
+}
+
+function requestDiscard(anImage) {
+  var request = SpecialPowers.wrap(anImage)
+      .getRequest(SpecialPowers.Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+  setTimeout(() => request.requestDiscard(), 0);
+}
+
+function drawCanvas(anImage) {
+  var canvas = document.getElementById('canvas');
+  var context = canvas.getContext('2d');
+
+  context.clearRect(0,0,100,100);
+  var cleared = canvas.toDataURL();
+
+  context.drawImage(anImage, 0, 0);
+  ok(true, "we got through the drawImage call without an exception being thrown");
+
+  ok(cleared != canvas.toDataURL(), "drawImage drew something");
+}
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/js/src/builtin/Profilers.cpp
+++ b/js/src/builtin/Profilers.cpp
@@ -175,17 +175,17 @@ JS_ResumeProfilers(const char* profileNa
     return ControlProfilers(true);
 }
 
 JS_PUBLIC_API(bool)
 JS_DumpProfile(const char* outfile, const char* profileName)
 {
     bool ok = true;
 #ifdef MOZ_CALLGRIND
-    js_DumpCallgrind(outfile);
+    ok = js_DumpCallgrind(outfile);
 #endif
     return ok;
 }
 
 #ifdef MOZ_PROFILING
 
 struct RequiredStringArg {
     JSContext* mCx;
--- a/js/src/builtin/SelfHostingDefines.h
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -18,16 +18,19 @@
 
 // Unforgeable version of Function.prototype.apply.
 #define FUN_APPLY(FUN, RECEIVER, ARGS) \
   callFunction(std_Function_apply, FUN, RECEIVER, ARGS)
 
 // NB: keep this in sync with the copy in vm/ArgumentsObject.h.
 #define MAX_ARGS_LENGTH (500 * 1000)
 
+// NB: keep this in sync with the copy in vm/String.h.
+#define MAX_STRING_LENGTH ((1 << 28) - 1)
+
 // Spread non-empty argument list of up to 15 elements.
 #define SPREAD(v, n) SPREAD_##n(v)
 #define SPREAD_1(v) v[0]
 #define SPREAD_2(v) SPREAD_1(v), v[1]
 #define SPREAD_3(v) SPREAD_2(v), v[2]
 #define SPREAD_4(v) SPREAD_3(v), v[3]
 #define SPREAD_5(v) SPREAD_4(v), v[4]
 #define SPREAD_6(v) SPREAD_5(v), v[5]
--- a/js/src/builtin/String.js
+++ b/js/src/builtin/String.js
@@ -63,18 +63,17 @@ function String_generic_match(thisValue,
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "String.match");
     return callFunction(String_match, thisValue, regexp);
 }
 
 /**
  * A helper function implementing the logic for both String.prototype.padStart
  * and String.prototype.padEnd as described in ES7 Draft March 29, 2016
  */
-function String_pad(maxLength, fillString, padEnd = false) {
-
+function String_pad(maxLength, fillString, padEnd) {
     // Steps 1-2.
     RequireObjectCoercible(this);
     let str = ToString(this);
 
     // Steps 3-4.
     let intMaxLength = ToLength(maxLength);
     let strLen = str.length;
 
@@ -84,25 +83,31 @@ function String_pad(maxLength, fillStrin
 
     // Steps 6-7.
     let filler = fillString === undefined ? " " : ToString(fillString);
 
     // Step 8.
     if (filler === "")
         return str;
 
+    // Throw an error if the final string length exceeds the maximum string
+    // length. Perform this check early so we can use int32 operations below.
+    if (intMaxLength > MAX_STRING_LENGTH)
+        ThrowRangeError(JSMSG_RESULTING_STRING_TOO_LARGE);
+
     // Step 9.
     let fillLen = intMaxLength - strLen;
 
     // Step 10.
+    // Perform an int32 division to ensure String_repeat is not called with a
+    // double to avoid repeated bailouts in ToInteger.
     let truncatedStringFiller = callFunction(String_repeat, filler,
-                                             fillLen / filler.length);
+                                             (fillLen / filler.length) | 0);
 
-    truncatedStringFiller += callFunction(String_substr, filler, 0,
-                                          fillLen % filler.length);
+    truncatedStringFiller += Substring(filler, 0, fillLen % filler.length);
 
     // Step 11.
     if (padEnd === true)
         return str + truncatedStringFiller;
     return truncatedStringFiller + str;
 }
 
 function String_pad_start(maxLength, fillString = " ") {
@@ -498,21 +503,24 @@ function String_repeat(count) {
 
     // Steps 4-5.
     var n = ToInteger(count);
 
     // Steps 6-7.
     if (n < 0)
         ThrowRangeError(JSMSG_NEGATIVE_REPETITION_COUNT);
 
-    if (!(n * S.length < (1 << 28)))
+    // Inverted condition to handle |Infinity * 0 = NaN| correctly.
+    if (!(n * S.length <= MAX_STRING_LENGTH))
         ThrowRangeError(JSMSG_RESULTING_STRING_TOO_LARGE);
 
     // Communicate |n|'s possible range to the compiler.
-    n = n & ((1 << 28) - 1);
+    assert((MAX_STRING_LENGTH & (MAX_STRING_LENGTH + 1)) === 0,
+           "MAX_STRING_LENGTH can be used as a bitmask");
+    n = n & MAX_STRING_LENGTH;
 
     // Steps 8-9.
     var T = "";
     for (;;) {
         if (n & 1)
             T += S;
         n >>= 1;
         if (n)
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -455,27 +455,16 @@ GCParameter(JSContext* cx, unsigned argc
     }
 
     uint32_t value = floor(d);
     if (param == JSGC_MARK_STACK_LIMIT && JS::IsIncrementalGCInProgress(cx)) {
         JS_ReportErrorASCII(cx, "attempt to set markStackLimit while a GC is in progress");
         return false;
     }
 
-    if (param == JSGC_MAX_BYTES) {
-        uint32_t gcBytes = JS_GetGCParameter(cx, JSGC_BYTES);
-        if (value < gcBytes) {
-            JS_ReportErrorASCII(cx,
-                                "attempt to set maxBytes to the value less than the current "
-                                "gcBytes (%u)",
-                                gcBytes);
-            return false;
-        }
-    }
-
     bool ok;
     {
         JSRuntime* rt = cx->runtime();
         AutoLockGC lock(rt);
         ok = rt->gc.setParameter(param, value, lock);
     }
 
     if (!ok) {
--- a/js/src/jit-test/tests/auto-regress/bug716512.js
+++ b/js/src/jit-test/tests/auto-regress/bug716512.js
@@ -1,6 +1,6 @@
 // |jit-test| error:Error
 
 // Binary: cache/js-dbg-64-9a230265bad5-linux
 // Flags:
 //
-gcparam("maxBytes", 22000);
+gcparam("maxBytes", -1);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -4483,30 +4483,36 @@ CodeGenerator::visitCallGeneric(LCallGen
     Label invoke, thunk, makeCall, end;
 
     // Known-target case is handled by LCallKnown.
     MOZ_ASSERT(!call->hasSingleTarget());
 
     masm.checkStackAlignment();
 
     // Guard that calleereg is actually a function object.
-    masm.branchTestObjClass(Assembler::NotEqual, calleereg, nargsreg, &JSFunction::class_, &invoke);
+    if (call->mir()->needsClassCheck()) {
+        masm.branchTestObjClass(Assembler::NotEqual, calleereg, nargsreg, &JSFunction::class_,
+                                &invoke);
+    }
 
     // Guard that calleereg is an interpreted function with a JSScript or a
     // wasm function.
     // If we are constructing, also ensure the callee is a constructor.
     if (call->mir()->isConstructing()) {
         masm.branchIfNotInterpretedConstructor(calleereg, nargsreg, &invoke);
     } else {
         masm.branchIfFunctionHasNoJitEntry(calleereg, /* isConstructing */ false, &invoke);
         masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, calleereg, objreg,
                                 &invoke);
     }
 
-    masm.loadJitCodeRaw(calleereg, objreg);
+    if (call->mir()->needsArgCheck())
+        masm.loadJitCodeRaw(calleereg, objreg);
+    else
+        masm.loadJitCodeNoArgCheck(calleereg, objreg);
 
     // Nestle the StackPointer up to the argument vector.
     masm.freeStack(unusedStack);
 
     // Construct the IonFramePrefix.
     uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), JitFrame_IonJS,
                                               JitFrameLayout::Size());
     masm.Push(Imm32(call->numActualArgs()));
@@ -11365,31 +11371,31 @@ class OutOfLineSwitch : public OutOfLine
             base = ::js::jit::pc;
 #else
             MOZ_CRASH("NYI: SwitchTableType::Inline");
 #endif
         } else {
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
             MOZ_CRASH("NYI: SwitchTableType::OutOfLine");
 #else
-            masm.mov(start_.patchAt(), temp);
+            masm.mov(start(), temp);
             base = temp;
 #endif
         }
         BaseIndex jumpTarget(base, index, ScalePointer);
         masm.branchToComputedAddress(jumpTarget);
     }
 
     // Register an entry in the switch table.
     void addTableEntry(MacroAssembler& masm) {
         if ((!isOutOfLine_ && tableType == SwitchTableType::Inline) ||
             (isOutOfLine_ && tableType == SwitchTableType::OutOfLine))
         {
             CodeLabel cl;
-            masm.writeCodePointer(cl.patchAt());
+            masm.writeCodePointer(&cl);
             masm.propagateOOM(codeLabels_.append(mozilla::Move(cl)));
         }
     }
     // Register the code, to which the table will jump to.
     void addCodeEntry(MacroAssembler& masm) {
         Label entry;
         masm.bind(&entry);
         masm.propagateOOM(labels_.append(mozilla::Move(entry)));
@@ -11407,17 +11413,17 @@ CodeGenerator::visitOutOfLineSwitch(OutO
     jumpTable->setOutOfLine();
     if (tableType == SwitchTableType::OutOfLine) {
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
         MOZ_CRASH("NYI: SwitchTableType::OutOfLine");
 #elif defined(JS_CODEGEN_NONE)
         MOZ_CRASH();
 #else
         masm.haltingAlign(sizeof(void*));
-        masm.use(jumpTable->start()->target());
+        masm.bind(jumpTable->start());
         masm.addCodeLabel(*jumpTable->start());
 #endif
     }
 
     // Add table entries if the table is inlined.
     auto& labels = jumpTable->labels();
     for (size_t i = 0, e = labels.length(); i < e; i++)
         jumpTable->addTableEntry(masm);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -36,16 +36,17 @@
 #include "vm/UnboxedObject-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::AssertedCast;
 using mozilla::DebugOnly;
 using mozilla::Maybe;
+using mozilla::Nothing;
 
 using JS::TrackedStrategy;
 using JS::TrackedOutcome;
 using JS::TrackedTypeSite;
 
 class jit::BaselineFrameInspector
 {
   public:
@@ -4393,41 +4394,43 @@ IonBuilder::inlineCallsite(const Inlinin
 
     // Perform a polymorphic dispatch.
     MOZ_TRY(inlineCalls(callInfo, targets, choiceSet, propCache.get()));
 
     return InliningStatus_Inlined;
 }
 
 AbortReasonOr<Ok>
-IonBuilder::inlineGenericFallback(JSFunction* target, CallInfo& callInfo, MBasicBlock* dispatchBlock)
+IonBuilder::inlineGenericFallback(const Maybe<CallTargets>& targets, CallInfo& callInfo,
+                                  MBasicBlock* dispatchBlock)
 {
     // Generate a new block with all arguments on-stack.
     MBasicBlock* fallbackBlock;
     MOZ_TRY_VAR(fallbackBlock, newBlock(dispatchBlock, pc));
     graph().addBlock(fallbackBlock);
 
     // Create a new CallInfo to track modified state within this block.
     CallInfo fallbackInfo(alloc(), pc, callInfo.constructing(), callInfo.ignoresReturnValue());
     if (!fallbackInfo.init(callInfo))
         return abort(AbortReason::Alloc);
     fallbackInfo.popCallStack(fallbackBlock);
 
     // Generate an MCall, which uses stateful |current|.
     MOZ_TRY(setCurrentAndSpecializePhis(fallbackBlock));
-    MOZ_TRY(makeCall(target, fallbackInfo));
+    MOZ_TRY(makeCall(targets, fallbackInfo));
 
     // Pass return block to caller as |current|.
     return Ok();
 }
 
 AbortReasonOr<Ok>
-IonBuilder::inlineObjectGroupFallback(CallInfo& callInfo, MBasicBlock* dispatchBlock,
-                                     MObjectGroupDispatch* dispatch, MGetPropertyCache* cache,
-                                     MBasicBlock** fallbackTarget)
+IonBuilder::inlineObjectGroupFallback(const Maybe<CallTargets>& targets,
+                                      CallInfo& callInfo, MBasicBlock* dispatchBlock,
+                                      MObjectGroupDispatch* dispatch, MGetPropertyCache* cache,
+                                      MBasicBlock** fallbackTarget)
 {
     // Getting here implies the following:
     // 1. The call function is an MGetPropertyCache, or an MGetPropertyCache
     //    followed by an MTypeBarrier.
     MOZ_ASSERT(callInfo.fun()->isGetPropertyCache() || callInfo.fun()->isTypeBarrier());
 
     // 2. The MGetPropertyCache has inlineable cases by guarding on the ObjectGroup.
     MOZ_ASSERT(dispatch->numCases() > 0);
@@ -4503,17 +4506,17 @@ IonBuilder::inlineObjectGroupFallback(Ca
 
     // Construct an end block with the correct resume point.
     MBasicBlock* preCallBlock;
     MOZ_TRY_VAR(preCallBlock, newBlock(getPropBlock, pc, preCallResumePoint));
     graph().addBlock(preCallBlock);
     getPropBlock->end(MGoto::New(alloc(), preCallBlock));
 
     // Now inline the MCallGeneric, using preCallBlock as the dispatch point.
-    MOZ_TRY(inlineGenericFallback(nullptr, fallbackInfo, preCallBlock));
+    MOZ_TRY(inlineGenericFallback(targets, fallbackInfo, preCallBlock));
 
     // inlineGenericFallback() set the return block as |current|.
     preCallBlock->end(MGoto::New(alloc(), current));
     *fallbackTarget = prepBlock;
     return Ok();
 }
 
 AbortReasonOr<Ok>
@@ -4722,42 +4725,41 @@ IonBuilder::inlineCalls(CallInfo& callIn
             }
         }
     } else {
         useFallback = dispatch->numCases() < targets.length();
     }
 
     // If necessary, generate a fallback path.
     if (useFallback) {
+        // Annotate the fallback call with the target information.
+        Maybe<CallTargets> remainingTargets;
+        remainingTargets.emplace(alloc());
+        for (uint32_t i = 0; i < targets.length(); i++) {
+            if (!maybeCache && choiceSet[i])
+                continue;
+
+            JSObject* target = targets[i].target;
+            if (!target->is<JSFunction>()) {
+                remainingTargets = Nothing();
+                break;
+            }
+            if (!remainingTargets->append(&target->as<JSFunction>()))
+                return abort(AbortReason::Alloc);
+        }
+
         // Generate fallback blocks, and set |current| to the fallback return block.
         if (maybeCache) {
             MBasicBlock* fallbackTarget;
-            MOZ_TRY(inlineObjectGroupFallback(callInfo, dispatchBlock,
+            MOZ_TRY(inlineObjectGroupFallback(remainingTargets, callInfo, dispatchBlock,
                                               dispatch->toObjectGroupDispatch(),
                                               maybeCache, &fallbackTarget));
             dispatch->addFallback(fallbackTarget);
         } else {
-            JSFunction* remaining = nullptr;
-
-            // If there is only 1 remaining case, we can annotate the fallback call
-            // with the target information.
-            if (dispatch->numCases() + 1 == targets.length()) {
-                for (uint32_t i = 0; i < targets.length(); i++) {
-                    if (choiceSet[i])
-                        continue;
-
-                    MOZ_ASSERT(!remaining);
-                    JSObject* target = targets[i].target;
-                    if (target->is<JSFunction>() && target->isSingleton())
-                        remaining = &target->as<JSFunction>();
-                    break;
-                }
-            }
-
-            MOZ_TRY(inlineGenericFallback(remaining, callInfo, dispatchBlock));
+            MOZ_TRY(inlineGenericFallback(remainingTargets, callInfo, dispatchBlock));
             dispatch->addFallback(current);
         }
 
         MBasicBlock* fallbackReturnBlock = current;
 
         // Connect fallback case to return infrastructure.
         MDefinition* retVal = fallbackReturnBlock->peek(-1);
         retPhi->addInput(retVal);
@@ -5412,29 +5414,42 @@ IonBuilder::jsop_call(uint32_t argc, boo
     MOZ_TRY_VAR(status, inlineCallsite(targets, callInfo));
     if (status == InliningStatus_Inlined)
         return Ok();
 
     // Discard unreferenced & pre-allocated resume points.
     replaceMaybeFallbackFunctionGetter(nullptr);
 
     // No inline, just make the call.
-    JSFunction* target = nullptr;
-    if (targets.length() == 1 && targets[0].target->is<JSFunction>())
-        target = &targets[0].target->as<JSFunction>();
-
-    if (target && status == InliningStatus_WarmUpCountTooLow) {
+    Maybe<CallTargets> callTargets;
+    if (!targets.empty()) {
+        callTargets.emplace(alloc());
+        for (const InliningTarget& target : targets) {
+            if (!target.target->is<JSFunction>()) {
+                callTargets = Nothing();
+                break;
+            }
+            if (!callTargets->append(&target.target->as<JSFunction>()))
+                return abort(AbortReason::Alloc);
+        }
+    }
+
+    if (status == InliningStatus_WarmUpCountTooLow &&
+        callTargets &&
+        callTargets->length() == 1)
+    {
+        JSFunction* target = callTargets.ref()[0];
         MRecompileCheck* check =
             MRecompileCheck::New(alloc(), target->nonLazyScript(),
                                  optimizationInfo().inliningRecompileThreshold(),
                                  MRecompileCheck::RecompileCheck_Inlining);
         current->add(check);
     }
 
-    return makeCall(target, callInfo);
+    return makeCall(callTargets, callInfo);
 }
 
 AbortReasonOr<bool>
 IonBuilder::testShouldDOMCall(TypeSet* inTypes, JSFunction* func, JSJitInfo::OpType opType)
 {
     if (!func->isNative() || !func->hasJitInfo())
         return false;
 
@@ -5487,16 +5502,20 @@ ArgumentTypesMatch(MDefinition* def, Sta
 }
 
 bool
 IonBuilder::testNeedsArgumentCheck(JSFunction* target, CallInfo& callInfo)
 {
     // If we have a known target, check if the caller arg types are a subset of callee.
     // Since typeset accumulates and can't decrease that means we don't need to check
     // the arguments anymore.
+
+    if (target->isNative())
+        return false;
+
     if (!target->hasScript())
         return true;
 
     JSScript* targetScript = target->nonLazyScript();
 
     if (!ArgumentTypesMatch(callInfo.thisArg(), TypeScript::ThisTypes(targetScript)))
         return true;
     uint32_t expected_args = Min<uint32_t>(callInfo.argc(), target->nargs());
@@ -5508,21 +5527,27 @@ IonBuilder::testNeedsArgumentCheck(JSFun
         if (!TypeScript::ArgTypes(targetScript, i)->mightBeMIRType(MIRType::Undefined))
             return true;
     }
 
     return false;
 }
 
 AbortReasonOr<MCall*>
-IonBuilder::makeCallHelper(JSFunction* target, CallInfo& callInfo)
+IonBuilder::makeCallHelper(const Maybe<CallTargets>& targets, CallInfo& callInfo)
 {
     // This function may be called with mutated stack.
     // Querying TI for popped types is invalid.
 
+    MOZ_ASSERT_IF(targets, !targets->empty());
+
+    JSFunction* target = nullptr;
+    if (targets && targets->length() == 1)
+        target = targets.ref()[0];
+
     uint32_t targetArgs = callInfo.argc();
 
     // Collect number of missing arguments provided that the target is
     // scripted. Native functions are passed an explicit 'argc' parameter.
     if (target && !target->isNativeWithCppEntry())
         targetArgs = Max<uint32_t>(target->nargs(), callInfo.argc());
 
     bool isDOMCall = false;
@@ -5576,18 +5601,32 @@ IonBuilder::makeCallHelper(JSFunction* t
         callInfo.thisArg()->setImplicitlyUsedUnchecked();
         callInfo.setThis(create);
     }
 
     // Pass |this| and function.
     MDefinition* thisArg = callInfo.thisArg();
     call->addArg(0, thisArg);
 
-    if (target && !testNeedsArgumentCheck(target, callInfo))
-        call->disableArgCheck();
+    if (targets) {
+        // The callee must be one of the target JSFunctions, so we don't need a
+        // Class check.
+        call->disableClassCheck();
+
+        // Determine whether we can skip the callee's prologue type checks.
+        bool needArgCheck = false;
+        for (JSFunction* target : targets.ref()) {
+            if (testNeedsArgumentCheck(target, callInfo)) {
+                needArgCheck = true;
+                break;
+            }
+        }
+        if (!needArgCheck)
+            call->disableArgCheck();
+    }
 
     call->initFunction(callInfo.fun());
 
     current->add(call);
     return call;
 }
 
 static bool
@@ -5605,38 +5644,55 @@ DOMCallNeedsBarrier(const JSJitInfo* jit
     if (jitinfo->returnType() == JSVAL_TYPE_OBJECT)
         return true;
 
     // No need for a barrier if we're already expecting the type we'll produce.
     return MIRTypeFromValueType(jitinfo->returnType()) != types->getKnownMIRType();
 }
 
 AbortReasonOr<Ok>
-IonBuilder::makeCall(JSFunction* target, CallInfo& callInfo)
-{
+IonBuilder::makeCall(const Maybe<CallTargets>& targets, CallInfo& callInfo)
+{
+#ifdef DEBUG
     // Constructor calls to non-constructors should throw. We don't want to use
     // CallKnown in this case.
-    MOZ_ASSERT_IF(callInfo.constructing() && target, target->isConstructor());
+    if (callInfo.constructing() && targets) {
+        for (JSFunction* target : targets.ref())
+            MOZ_ASSERT(target->isConstructor());
+    }
+#endif
 
     MCall* call;
-    MOZ_TRY_VAR(call, makeCallHelper(target, callInfo));
+    MOZ_TRY_VAR(call, makeCallHelper(targets, callInfo));
 
     current->push(call);
     if (call->isEffectful())
         MOZ_TRY(resumeAfter(call));
 
     TemporaryTypeSet* types = bytecodeTypes(pc);
 
     if (call->isCallDOMNative())
         return pushDOMTypeBarrier(call, types, call->getSingleTarget()->rawJSFunction());
 
     return pushTypeBarrier(call, types, BarrierKind::TypeSet);
 }
 
 AbortReasonOr<Ok>
+IonBuilder::makeCall(JSFunction* target, CallInfo& callInfo)
+{
+    Maybe<CallTargets> targets;
+    if (target) {
+        targets.emplace(alloc());
+        if (!targets->append(target))
+            return abort(AbortReason::Alloc);
+    }
+    return makeCall(targets, callInfo);
+}
+
+AbortReasonOr<Ok>
 IonBuilder::jsop_eval(uint32_t argc)
 {
     int calleeDepth = -((int)argc + 2);
     TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet();
 
     // Emit a normal call if the eval has never executed. This keeps us from
     // disabling compilation for the script when testing with --ion-eager.
     if (calleeTypes && calleeTypes->empty())
@@ -8588,22 +8644,16 @@ IonBuilder::computeHeapType(const Tempor
 }
 
 AbortReasonOr<Ok>
 IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
 {
     TemporaryTypeSet* types = bytecodeTypes(pc);
 
     MOZ_ASSERT(index->type() == MIRType::Int32 || index->type() == MIRType::Double);
-    if (JSOp(*pc) == JSOP_CALLELEM) {
-        // Indexed call on an element of an array. Populate the observed types
-        // with any objects that could be in the array, to avoid extraneous
-        // type barriers.
-        AddObjectsForPropertyRead(alloc(), obj, nullptr, types);
-    }
 
     BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, alloc(), constraints(),
                                                        obj, nullptr, types);
     bool needsHoleCheck = !ElementAccessIsPacked(constraints(), obj);
 
     // Reads which are on holes in the object do not have to bail out if
     // undefined values have been observed at this access site and the access
     // cannot hit another indexed property on the object or its prototypes.
@@ -11735,18 +11785,22 @@ IonBuilder::setPropTryCommonSetter(bool*
             if (status == InliningStatus_Inlined) {
                 *emitted = true;
                 return Ok();
             }
           }
         }
     }
 
+    Maybe<CallTargets> targets;
+    targets.emplace(alloc());
+    if (!targets->append(commonSetter))
+        return abort(AbortReason::Alloc);
     MCall* call;
-    MOZ_TRY_VAR(call, makeCallHelper(commonSetter, callInfo));
+    MOZ_TRY_VAR(call, makeCallHelper(targets, callInfo));
 
     current->push(value);
     MOZ_TRY(resumeAfter(call));
 
     // If the setter could have been inlined, don't track success. The call to
     // makeInliningDecision above would have tracked a specific reason why we
     // couldn't inline.
     if (!commonSetter->isInterpreted())
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -31,16 +31,18 @@ class BaselineFrameInspector;
 
 enum class InlinableNative : uint16_t;
 
 // Records information about a baseline frame for compilation that is stable
 // when later used off thread.
 BaselineFrameInspector*
 NewBaselineFrameInspector(TempAllocator* temp, BaselineFrame* frame, CompileInfo* info);
 
+using CallTargets = Vector<JSFunction*, 6, JitAllocPolicy>;
+
 class IonBuilder
   : public MIRGenerator,
     public mozilla::LinkedListElement<IonBuilder>
 {
 
   public:
     IonBuilder(JSContext* analysisContext, CompileCompartment* comp,
                const JitCompileOptions& options, TempAllocator* temp,
@@ -817,19 +819,21 @@ class IonBuilder
     InliningResult inlineSingleCall(CallInfo& callInfo, JSObject* target);
 
     // Call functions
     InliningResult inlineCallsite(const InliningTargets& targets, CallInfo& callInfo);
     AbortReasonOr<Ok> inlineCalls(CallInfo& callInfo, const InliningTargets& targets,
                                   BoolVector& choiceSet, MGetPropertyCache* maybeCache);
 
     // Inlining helpers.
-    AbortReasonOr<Ok> inlineGenericFallback(JSFunction* target, CallInfo& callInfo,
+    AbortReasonOr<Ok> inlineGenericFallback(const Maybe<CallTargets>& targets,
+                                            CallInfo& callInfo,
                                             MBasicBlock* dispatchBlock);
-    AbortReasonOr<Ok> inlineObjectGroupFallback(CallInfo& callInfo, MBasicBlock* dispatchBlock,
+    AbortReasonOr<Ok> inlineObjectGroupFallback(const Maybe<CallTargets>& targets,
+                                                CallInfo& callInfo, MBasicBlock* dispatchBlock,
                                                 MObjectGroupDispatch* dispatch,
                                                 MGetPropertyCache* cache,
                                                 MBasicBlock** fallbackTarget);
 
     enum AtomicCheckResult {
         DontCheckAtomicResult,
         DoCheckAtomicResult
     };
@@ -837,17 +841,18 @@ class IonBuilder
     bool atomicsMeetsPreconditions(CallInfo& callInfo, Scalar::Type* arrayElementType,
                                    bool* requiresDynamicCheck,
                                    AtomicCheckResult checkResult=DoCheckAtomicResult);
     void atomicsCheckBounds(CallInfo& callInfo, MInstruction** elements, MDefinition** index,
                             BoundsCheckKind kind);
 
     bool testNeedsArgumentCheck(JSFunction* target, CallInfo& callInfo);
 
-    AbortReasonOr<MCall*> makeCallHelper(JSFunction* target, CallInfo& callInfo);
+    AbortReasonOr<MCall*> makeCallHelper(const Maybe<CallTargets>& targets, CallInfo& callInfo);
+    AbortReasonOr<Ok> makeCall(const Maybe<CallTargets>& targets, CallInfo& callInfo);
     AbortReasonOr<Ok> makeCall(JSFunction* target, CallInfo& callInfo);
 
     MDefinition* patchInlinedReturn(CallInfo& callInfo, MBasicBlock* exit, MBasicBlock* bottom);
     MDefinition* patchInlinedReturns(CallInfo& callInfo, MIRGraphReturns& returns,
                                      MBasicBlock* bottom);
     MDefinition* specializeInlinedReturn(MDefinition* rdef, MBasicBlock* exit);
 
     NativeObject* commonPrototypeWithGetterSetter(TemporaryTypeSet* types, PropertyName* name,
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -6453,59 +6453,16 @@ jit::PropertyReadIsIdempotent(CompilerCo
             if (property.nonData(constraints))
                 return false;
         }
     }
 
     return true;
 }
 
-void
-jit::AddObjectsForPropertyRead(TempAllocator& tempAlloc, MDefinition* obj, PropertyName* name,
-                               TemporaryTypeSet* observed)
-{
-    // Add objects to observed which *could* be observed by reading name from obj,
-    // to hopefully avoid unnecessary type barriers and code invalidations.
-
-    LifoAlloc* alloc = tempAlloc.lifoAlloc();
-
-    TemporaryTypeSet* types = obj->resultTypeSet();
-    if (!types || types->unknownObject()) {
-        observed->addType(TypeSet::AnyObjectType(), alloc);
-        return;
-    }
-
-    for (size_t i = 0; i < types->getObjectCount(); i++) {
-        TypeSet::ObjectKey* key = types->getObject(i);
-        if (!key)
-            continue;
-
-        if (key->unknownProperties()) {
-            observed->addType(TypeSet::AnyObjectType(), alloc);
-            return;
-        }
-
-        jsid id = name ? NameToId(name) : JSID_VOID;
-        HeapTypeSetKey property = key->property(id);
-        HeapTypeSet* types = property.maybeTypes();
-        if (!types)
-            continue;
-
-        if (types->unknownObject()) {
-            observed->addType(TypeSet::AnyObjectType(), alloc);
-            return;
-        }
-
-        for (size_t i = 0; i < types->getObjectCount(); i++) {
-            if (TypeSet::ObjectKey* key = types->getObject(i))
-                observed->addType(TypeSet::ObjectType(key), alloc);
-        }
-    }
-}
-
 AbortReasonOr<bool>
 PrototypeHasIndexedProperty(IonBuilder* builder, JSObject* obj)
 {
     do {
         TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(builder->checkNurseryObject(obj));
         if (ClassCanHaveExtraProperties(key->clasp()))
             return true;
         if (key->unknownProperties())
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -4207,24 +4207,26 @@ class MCall
 
     // True if the call is for JSOP_NEW.
     bool construct_:1;
 
     // True if the caller does not use the return value.
     bool ignoresReturnValue_:1;
 
     bool needsArgCheck_:1;
+    bool needsClassCheck_:1;
 
     MCall(WrappedFunction* target, uint32_t numActualArgs, bool construct, bool ignoresReturnValue)
       : MVariadicInstruction(classOpcode),
         target_(target),
         numActualArgs_(numActualArgs),
         construct_(construct),
         ignoresReturnValue_(ignoresReturnValue),
-        needsArgCheck_(true)
+        needsArgCheck_(true),
+        needsClassCheck_(true)
     {
         setResultType(MIRType::Value);
     }
 
   public:
     INSTRUCTION_HEADER(Call)
     static MCall* New(TempAllocator& alloc, JSFunction* target, size_t maxArgc, size_t numActualArgs,
                       bool construct, bool ignoresReturnValue, bool isDOMCall,
@@ -4232,20 +4234,27 @@ class MCall
 
     void initFunction(MDefinition* func) {
         initOperand(FunctionOperandIndex, func);
     }
 
     bool needsArgCheck() const {
         return needsArgCheck_;
     }
-
     void disableArgCheck() {
         needsArgCheck_ = false;
     }
+
+    bool needsClassCheck() const {
+        return needsClassCheck_;
+    }
+    void disableClassCheck() {
+        needsClassCheck_ = false;
+    }
+
     MDefinition* getFunction() const {
         return getOperand(FunctionOperandIndex);
     }
     void replaceFunction(MInstruction* newfunc) {
         replaceOperand(FunctionOperandIndex, newfunc);
     }
 
     void addArg(size_t argnum, MDefinition* arg);
@@ -15154,18 +15163,16 @@ BarrierKind PropertyReadNeedsTypeBarrier
                                          MDefinition* obj, PropertyName* name,
                                          TemporaryTypeSet* observed);
 AbortReasonOr<BarrierKind>
 PropertyReadOnPrototypeNeedsTypeBarrier(IonBuilder* builder,
                                         MDefinition* obj, PropertyName* name,
                                         TemporaryTypeSet* observed);
 bool PropertyReadIsIdempotent(CompilerConstraintList* constraints,
                               MDefinition* obj, PropertyName* name);
-void AddObjectsForPropertyRead(TempAllocator& tempAlloc, MDefinition* obj, PropertyName* name,
-                               TemporaryTypeSet* observed);
 bool CanWriteProperty(TempAllocator& alloc, CompilerConstraintList* constraints,
                       HeapTypeSetKey property, MDefinition* value,
                       MIRType implicitType = MIRType::None);
 bool PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList* constraints,
                                    MBasicBlock* current, MDefinition** pobj,
                                    PropertyName* name, MDefinition** pvalue,
                                    bool canModify, MIRType implicitType = MIRType::None);
 AbortReasonOr<bool>
--- a/js/src/jit/arm/Assembler-arm.cpp
+++ b/js/src/jit/arm/Assembler-arm.cpp
@@ -957,33 +957,34 @@ Assembler::trace(JSTracer* trc)
         CompactBufferReader reader(dataRelocations_);
         ::TraceDataRelocations(trc, &m_buffer, reader);
     }
 }
 
 void
 Assembler::processCodeLabels(uint8_t* rawCode)
 {
-    for (size_t i = 0; i < codeLabels_.length(); i++) {
-        CodeLabel label = codeLabels_[i];
-        Bind(rawCode, *label.patchAt(), *label.target());
+    for (const CodeLabel& label : codeLabels_) {
+        Bind(rawCode, label);
     }
 }
 
 void
-Assembler::writeCodePointer(CodeOffset* label)
+Assembler::writeCodePointer(CodeLabel* label)
 {
     BufferOffset off = writeInst(-1);
-    label->bind(off.getOffset());
+    label->patchAt()->bind(off.getOffset());
 }
 
 void
-Assembler::Bind(uint8_t* rawCode, CodeOffset label, CodeOffset target)
+Assembler::Bind(uint8_t* rawCode, const CodeLabel& label)
 {
-    *reinterpret_cast<const void**>(rawCode + label.offset()) = rawCode + target.offset();
+    size_t offset = label.patchAt().offset();
+    size_t target = label.target().offset();
+    *reinterpret_cast<const void**>(rawCode + offset) = rawCode + target;
 }
 
 Assembler::Condition
 Assembler::InvertCondition(Condition cond)
 {
     const uint32_t ConditionInversionBit = 0x10000000;
     return Condition(ConditionInversionBit ^ cond);
 }
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -1451,17 +1451,17 @@ class Assembler : public AssemblerShared
     // be overwritten subsequently.
     BufferOffset allocBranchInst();
 
     // A static variant for the cases where we don't want to have an assembler
     // object.
     static void WriteInstStatic(uint32_t x, uint32_t* dest);
 
   public:
-    void writeCodePointer(CodeOffset* label);
+    void writeCodePointer(CodeLabel* label);
 
     void haltingAlign(int alignment);
     void nopAlign(int alignment);
     BufferOffset as_nop();
     BufferOffset as_alu(Register dest, Register src1, Operand2 op2,
                         ALUOp op, SBit s = LeaveCC, Condition c = Always);
     BufferOffset as_mov(Register dest,
                         Operand2 op2, SBit s = LeaveCC, Condition c = Always);
@@ -1727,17 +1727,17 @@ class Assembler : public AssemblerShared
     void bindLater(Label* label, wasm::OldTrapDesc target);
     uint32_t currentOffset() {
         return nextOffset().getOffset();
     }
     void retarget(Label* label, Label* target);
     // I'm going to pretend this doesn't exist for now.
     void retarget(Label* label, void* target, Relocation::Kind reloc);
 
-    static void Bind(uint8_t* rawCode, CodeOffset label, CodeOffset target);
+    static void Bind(uint8_t* rawCode, const CodeLabel& label);
 
     void as_bkpt();
     BufferOffset as_illegal_trap();
 
   public:
     static void TraceJumpRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader);
     static void TraceDataRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader);
 
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -1197,17 +1197,17 @@ CodeGeneratorARM::emitTableSwitchDispatc
     masm.ma_b(defaultcase);
 
     // To fill in the CodeLabels for the case entries, we need to first generate
     // the case entries (we don't yet know their offsets in the instruction
     // stream).
     OutOfLineTableSwitch* ool = new(alloc()) OutOfLineTableSwitch(alloc(), mir);
     for (int32_t i = 0; i < cases; i++) {
         CodeLabel cl;
-        masm.writeCodePointer(cl.patchAt());
+        masm.writeCodePointer(&cl);
         masm.propagateOOM(ool->addCodeLabel(cl));
     }
     addOutOfLineCode(ool, mir);
 }
 
 void
 CodeGeneratorARM::visitMathD(LMathD* math)
 {
--- a/js/src/jit/arm64/Assembler-arm64.h
+++ b/js/src/jit/arm64/Assembler-arm64.h
@@ -240,24 +240,25 @@ class Assembler : public vixl::Assembler
     }
     size_t bytesNeeded() const {
         return SizeOfCodeGenerated() +
             jumpRelocationTableBytes() +
             dataRelocationTableBytes();
     }
 
     void processCodeLabels(uint8_t* rawCode) {
-        for (size_t i = 0; i < codeLabels_.length(); i++) {
-            CodeLabel label = codeLabels_[i];
-            Bind(rawCode, *label.patchAt(), *label.target());
+        for (const CodeLabel& label : codeLabels_) {
+            Bind(rawCode, label);
         }
     }
 
-    static void Bind(uint8_t* rawCode, CodeOffset label, CodeOffset address) {
-        *reinterpret_cast<const void**>(rawCode + label.offset()) = rawCode + address.offset();
+    static void Bind(uint8_t* rawCode, const CodeLabel& label) {
+        size_t patchAtOffset = label.patchAt().offset();
+        size_t targetOffset = label.target().offset();
+        *reinterpret_cast<const void**>(rawCode + patchAtOffset) = rawCode + targetOffset;
     }
 
     void retarget(Label* cur, Label* next);
 
     // The buffer is about to be linked. Ensure any constant pools or
     // excess bookkeeping has been flushed to the instruction stream.
     void flush() {
         armbuffer_.flushPool();
@@ -365,20 +366,20 @@ class Assembler : public vixl::Assembler
             return reinterpret_cast<Instruction*>(&ldr);
         }
     };
 
     // Offset of the patchable target for the given entry.
     static const size_t OffsetOfJumpTableEntryPointer = 8;
 
   public:
-    void writeCodePointer(CodeOffset* label) {
+    void writeCodePointer(CodeLabel* label) {
         uintptr_t x = uintptr_t(-1);
         BufferOffset off = EmitData(&x, sizeof(uintptr_t));
-        label->bind(off.getOffset());
+        label->patchAt()->bind(off.getOffset());
     }
 
 
     void verifyHeapAccessDisassembly(uint32_t begin, uint32_t end,
                                      const Disassembler::HeapAccess& heapAccess)
     {
         MOZ_CRASH("verifyHeapAccessDisassembly");
     }
--- a/js/src/jit/mips-shared/Assembler-mips-shared.h
+++ b/js/src/jit/mips-shared/Assembler-mips-shared.h
@@ -1251,21 +1251,18 @@ class AssemblerMIPSShared : public Assem
     BufferOffset as_tltu(Register rs, Register rt, uint32_t code = 0);
     BufferOffset as_teq(Register rs, Register rt, uint32_t code = 0);
     BufferOffset as_tne(Register rs, Register rt, uint32_t code = 0);
 
     // label operations
     void bind(Label* label, BufferOffset boff = BufferOffset());
     void bindLater(Label* label, wasm::OldTrapDesc target);
     virtual void bind(InstImm* inst, uintptr_t branch, uintptr_t target) = 0;
-    void bind(CodeOffset* label) {
-        label->bind(currentOffset());
-    }
-    void use(CodeOffset* label) {
-        label->bind(currentOffset());
+    void bind(CodeLabel* label) {
+        label->target()->bind(currentOffset());
     }
     uint32_t currentOffset() {
         return nextOffset().getOffset();
     }
     void retarget(Label* label, Label* target);
 
     void call(Label* label);
     void call(void* target);
@@ -1297,19 +1294,21 @@ class AssemblerMIPSShared : public Assem
     InstImm invertBranch(InstImm branch, BOffImm16 skipOffset);
     void addPendingJump(BufferOffset src, ImmPtr target, Relocation::Kind kind) {
         enoughMemory_ &= jumps_.append(RelativePatch(src, target.value, kind));
         if (kind == Relocation::JITCODE)
             writeRelocation(src);
     }
 
     void addLongJump(BufferOffset src, BufferOffset dst) {
-        CodeOffset patchAt(src.getOffset());
-        CodeOffset target(dst.getOffset());
-        addCodeLabel(CodeLabel(patchAt, target));
+        CodeLabel cl;
+        cl.patchAt()->bind(src.getOffset());
+        cl.target()->bind(dst.getOffset());
+        cl.setLinkMode(CodeLabel::JumpImmediate);
+        addCodeLabel(mozilla::Move(cl));
     }
 
   public:
     void flushBuffer() {
     }
 
     void comment(const char* msg) {
         spew("; %s", msg);
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
@@ -1843,16 +1843,91 @@ CodeGeneratorMIPSShared::visitLoadTypedA
 }
 
 void
 CodeGeneratorMIPSShared::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins)
 {
     MOZ_CRASH("NYI");
 }
 
+class js::jit::OutOfLineTableSwitch : public OutOfLineCodeBase<CodeGeneratorMIPSShared>
+{
+    MTableSwitch* mir_;
+    CodeLabel jumpLabel_;
+
+    void accept(CodeGeneratorMIPSShared* codegen) {
+        codegen->visitOutOfLineTableSwitch(this);
+    }
+
+  public:
+    OutOfLineTableSwitch(MTableSwitch* mir)
+      : mir_(mir)
+    {}
+
+    MTableSwitch* mir() const {
+        return mir_;
+    }
+
+    CodeLabel* jumpLabel() {
+        return &jumpLabel_;
+    }
+};
+
+void
+CodeGeneratorMIPSShared::visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool)
+{
+    MTableSwitch* mir = ool->mir();
+
+    masm.haltingAlign(sizeof(void*));
+    masm.bind(ool->jumpLabel());
+    masm.addCodeLabel(*ool->jumpLabel());
+
+    for (size_t i = 0; i < mir->numCases(); i++) {
+        LBlock* caseblock = skipTrivialBlocks(mir->getCase(i))->lir();
+        Label* caseheader = caseblock->label();
+        uint32_t caseoffset = caseheader->offset();
+
+        // The entries of the jump table need to be absolute addresses and thus
+        // must be patched after codegen is finished.
+        CodeLabel cl;
+        masm.writeCodePointer(&cl);
+        cl.target()->bind(caseoffset);
+        masm.addCodeLabel(cl);
+    }
+}
+
+void
+CodeGeneratorMIPSShared::emitTableSwitchDispatch(MTableSwitch* mir, Register index,
+                                           Register base)
+{
+    Label* defaultcase = skipTrivialBlocks(mir->getDefault())->lir()->label();
+
+    // Lower value with low value
+    if (mir->low() != 0)
+        masm.subPtr(Imm32(mir->low()), index);
+
+    // Jump to default case if input is out of range
+    int32_t cases = mir->numCases();
+    masm.branchPtr(Assembler::AboveOrEqual, index, ImmWord(cases), defaultcase);
+
+    // To fill in the CodeLabels for the case entries, we need to first
+    // generate the case entries (we don't yet know their offsets in the
+    // instruction stream).
+    OutOfLineTableSwitch* ool = new(alloc()) OutOfLineTableSwitch(mir);
+    addOutOfLineCode(ool, mir);
+
+    // Compute the position where a pointer to the right case stands.
+    masm.ma_li(base, ool->jumpLabel());
+
+    BaseIndex pointer(base, index, ScalePointer);
+
+    // Jump to the right case
+    masm.branchToComputedAddress(pointer);
+}
+
 template <typename T>
 void
 CodeGeneratorMIPSShared::emitWasmLoad(T* lir)
 {
     const MWasmLoad* mir = lir->mir();
 
     Register ptrScratch = InvalidReg;
     if(!lir->ptrCopy()->isBogusTemp()){
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.h
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.h
@@ -117,16 +117,18 @@ class CodeGeneratorMIPSShared : public C
         }
     }
     void testZeroEmitBranch(Assembler::Condition cond, Register reg,
                             MBasicBlock* ifTrue, MBasicBlock* ifFalse)
     {
         emitBranch(reg, Imm32(0), cond, ifTrue, ifFalse);
     }
 
+    void emitTableSwitchDispatch(MTableSwitch* mir, Register index, Register base);
+
     template <typename T>
     void emitWasmLoad(T* ins);
     template <typename T>
     void emitWasmStore(T* ins);
 
   public:
     // Instruction visitors.
     void visitMinMaxD(LMinMaxD* ins);
@@ -188,16 +190,17 @@ class CodeGeneratorMIPSShared : public C
     void visitRoundF(LRoundF* lir);
     void visitTruncateDToInt32(LTruncateDToInt32* ins);
     void visitTruncateFToInt32(LTruncateFToInt32* ins);
 
     void visitWasmTruncateToInt32(LWasmTruncateToInt32* lir);
 
     // Out of line visitors.
     void visitOutOfLineBailout(OutOfLineBailout* ool);
+    void visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool);
     void visitOutOfLineWasmTruncateCheck(OutOfLineWasmTruncateCheck* ool);
     void visitCopySignD(LCopySignD* ins);
     void visitCopySignF(LCopySignF* ins);
 
   public:
     CodeGeneratorMIPSShared(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm);
 
     void visitValue(LValue* value);
--- a/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
+++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
@@ -965,16 +965,23 @@ void
 MacroAssembler::branchTestMagic(Condition cond, const BaseIndex& address, Label* label)
 {
     SecondScratchRegisterScope scratch2(*this);
     extractTag(address, scratch2);
     branchTestMagic(cond, scratch2, label);
 }
 
 void
+MacroAssembler::branchToComputedAddress(const BaseIndex& addr)
+{
+    loadPtr(addr, ScratchRegister);
+    branch(ScratchRegister);
+}
+
+void
 MacroAssembler::cmp32Move32(Condition cond, Register lhs, Register rhs, Register src,
                             Register dest)
 {
     MOZ_CRASH();
 }
 
 void
 MacroAssembler::cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src,
--- a/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp
+++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp
@@ -1650,19 +1650,19 @@ MacroAssembler::popReturnAddress()
 // ===============================================================
 // Jit Frames.
 
 uint32_t
 MacroAssembler::pushFakeReturnAddress(Register scratch)
 {
     CodeLabel cl;
 
-    ma_li(scratch, cl.patchAt());
+    ma_li(scratch, &cl);
     Push(scratch);
-    bind(cl.target());
+    bind(&cl);
     uint32_t retAddr = currentOffset();
 
     addCodeLabel(cl);
     return retAddr;
 }
 
 void
 MacroAssembler::loadStoreBuffer(Register ptr, Register buffer)
--- a/js/src/jit/mips32/Assembler-mips32.cpp
+++ b/js/src/jit/mips32/Assembler-mips32.cpp
@@ -289,22 +289,32 @@ Assembler::trace(JSTracer* trc)
     }
     if (dataRelocations_.length()) {
         CompactBufferReader reader(dataRelocations_);
         ::TraceDataRelocations(trc, &m_buffer, reader);
     }
 }
 
 void
-Assembler::Bind(uint8_t* rawCode, CodeOffset label, CodeOffset target)
+Assembler::Bind(uint8_t* rawCode, const CodeLabel& label)
 {
-    if (label.bound()) {
-        intptr_t offset = label.offset();
-        Instruction* inst = (Instruction*) (rawCode + offset);
-        AssemblerMIPSShared::UpdateLuiOriValue(inst, inst->next(), (uint32_t)(rawCode + target.offset()));
+    if (label.patchAt().bound()) {
+
+        auto mode = label.linkMode();
+        intptr_t offset = label.patchAt().offset();
+        intptr_t target = label.target().offset();
+
+        if (mode == CodeLabel::RawPointer) {
+            *reinterpret_cast<const void**>(rawCode + offset) = rawCode + target;
+        } else {
+            MOZ_ASSERT(mode == CodeLabel::MoveImmediate || mode == CodeLabel::JumpImmediate);
+            Instruction* inst = (Instruction*) (rawCode + offset);
+            AssemblerMIPSShared::UpdateLuiOriValue(inst, inst->next(),
+                                                  (uint32_t)(rawCode + target));
+        }
     }
 }
 
 void
 Assembler::bind(InstImm* inst, uintptr_t branch, uintptr_t target)
 {
     int32_t offset = target - branch;
     InstImm inst_bgezal = InstImm(op_regimm, zero, rt_bgezal, BOffImm16(0));
@@ -426,19 +436,18 @@ Assembler::bind(RepatchLabel* label)
         }
     }
     label->bind(dest.getOffset());
 }
 
 void
 Assembler::processCodeLabels(uint8_t* rawCode)
 {
-    for (size_t i = 0; i < codeLabels_.length(); i++) {
-        CodeLabel label = codeLabels_[i];
-        Bind(rawCode, *label.patchAt(), *label.target());
+    for (const CodeLabel& label : codeLabels_) {
+        Bind(rawCode, label);
     }
 }
 
 uint32_t
 Assembler::PatchWrite_NearCallSize()
 {
     return 4 * sizeof(uint32_t);
 }
--- a/js/src/jit/mips32/Assembler-mips32.h
+++ b/js/src/jit/mips32/Assembler-mips32.h
@@ -170,17 +170,17 @@ class Assembler : public AssemblerMIPSSh
         FloatRegister odd(reg.id() | 1, FloatRegister::Single);
         return odd;
     }
 
   public:
     using AssemblerMIPSShared::bind;
 
     void bind(RepatchLabel* label);
-    static void Bind(uint8_t* rawCode, CodeOffset label, CodeOffset target);
+    static void Bind(uint8_t* rawCode, const CodeLabel& label);
 
     void processCodeLabels(uint8_t* rawCode);
 
     static void TraceJumpRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader);
     static void TraceDataRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader);
 
     void bind(InstImm* inst, uintptr_t branch, uintptr_t target);
 
--- a/js/src/jit/mips32/CodeGenerator-mips32.cpp
+++ b/js/src/jit/mips32/CodeGenerator-mips32.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "jit/mips32/CodeGenerator-mips32.h"
 
-#include "mozilla/ArrayUtils.h"
 #include "mozilla/MathAlgorithms.h"
 
 #include "jit/CodeGenerator.h"
 #include "jit/JitCompartment.h"
 #include "jit/JitFrames.h"
 #include "jit/MIR.h"
 #include "jit/MIRGraph.h"
 #include "js/Conversions.h"
@@ -19,92 +18,16 @@
 #include "vm/TraceLogging.h"
 
 #include "jit/MacroAssembler-inl.h"
 #include "jit/shared/CodeGenerator-shared-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
-class js::jit::OutOfLineTableSwitch : public OutOfLineCodeBase<CodeGeneratorMIPS>
-{
-    MTableSwitch* mir_;
-    CodeLabel jumpLabel_;
-
-    void accept(CodeGeneratorMIPS* codegen) {
-        codegen->visitOutOfLineTableSwitch(this);
-    }
-
-  public:
-    OutOfLineTableSwitch(MTableSwitch* mir)
-      : mir_(mir)
-    {}
-
-    MTableSwitch* mir() const {
-        return mir_;
-    }
-
-    CodeLabel* jumpLabel() {
-        return &jumpLabel_;
-    }
-};
-
-void
-CodeGeneratorMIPS::visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool)
-{
-    MTableSwitch* mir = ool->mir();
-
-    masm.haltingAlign(sizeof(void*));
-    masm.bind(ool->jumpLabel()->target());
-    masm.addCodeLabel(*ool->jumpLabel());
-
-    for (size_t i = 0; i < mir->numCases(); i++) {
-        LBlock* caseblock = skipTrivialBlocks(mir->getCase(i))->lir();
-        Label* caseheader = caseblock->label();
-        uint32_t caseoffset = caseheader->offset();
-
-        // The entries of the jump table need to be absolute addresses and thus
-        // must be patched after codegen is finished.
-        CodeLabel cl;
-        masm.ma_li(ScratchRegister, cl.patchAt());
-        masm.branch(ScratchRegister);
-        cl.target()->bind(caseoffset);
-        masm.addCodeLabel(cl);
-    }
-}
-
-void
-CodeGeneratorMIPS::emitTableSwitchDispatch(MTableSwitch* mir, Register index,
-                                           Register address)
-{
-    Label* defaultcase = skipTrivialBlocks(mir->getDefault())->lir()->label();
-
-    // Lower value with low value
-    if (mir->low() != 0)
-        masm.subPtr(Imm32(mir->low()), index);
-
-    // Jump to default case if input is out of range
-    int32_t cases = mir->numCases();
-    masm.branchPtr(Assembler::AboveOrEqual, index, ImmWord(cases), defaultcase);
-
-    // To fill in the CodeLabels for the case entries, we need to first
-    // generate the case entries (we don't yet know their offsets in the
-    // instruction stream).
-    OutOfLineTableSwitch* ool = new(alloc()) OutOfLineTableSwitch(mir);
-    addOutOfLineCode(ool, mir);
-
-    // Compute the position where a pointer to the right case stands.
-    masm.ma_li(address, ool->jumpLabel()->patchAt());
-    masm.lshiftPtr(Imm32(4), index);
-    masm.addPtr(index, address);
-
-    masm.branch(address);
-}
-
-
 ValueOperand
 CodeGeneratorMIPS::ToValue(LInstruction* ins, size_t pos)
 {
     Register typeReg = ToRegister(ins->getOperand(pos + TYPE_INDEX));
     Register payloadReg = ToRegister(ins->getOperand(pos + PAYLOAD_INDEX));
     return ValueOperand(typeReg, payloadReg);
 }
 
--- a/js/src/jit/mips32/CodeGenerator-mips32.h
+++ b/js/src/jit/mips32/CodeGenerator-mips32.h
@@ -26,18 +26,16 @@ class CodeGeneratorMIPS : public CodeGen
         emitBranch(value.typeReg(), (Imm32)ImmType(JSVAL_TYPE_UNDEFINED), cond, ifTrue, ifFalse);
     }
     void testObjectEmitBranch(Assembler::Condition cond, const ValueOperand& value,
                               MBasicBlock* ifTrue, MBasicBlock* ifFalse)
     {
         emitBranch(value.typeReg(), (Imm32)ImmType(JSVAL_TYPE_OBJECT), cond, ifTrue, ifFalse);
     }
 
-    void emitTableSwitchDispatch(MTableSwitch* mir, Register index, Register base);
-
     template <typename T>
     void emitWasmLoadI64(T* ins);
     template <typename T>
     void emitWasmStoreI64(T* ins);
 
   public:
     void visitCompareB(LCompareB* lir);
     void visitCompareBAndBranch(LCompareBAndBranch* lir);
@@ -61,17 +59,16 @@ class CodeGeneratorMIPS : public CodeGen
     void visitCtzI64(LCtzI64* ins);
     void visitNotI64(LNotI64* ins);
     void visitWasmTruncateToInt64(LWasmTruncateToInt64* ins);
     void visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir);
     void visitTestI64AndBranch(LTestI64AndBranch* lir);
 
     // Out of line visitors.
     void visitOutOfLineBailout(OutOfLineBailout* ool);
-    void visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool);
   protected:
     ValueOperand ToValue(LInstruction* ins, size_t pos);
     ValueOperand ToTempValue(LInstruction* ins, size_t pos);
 
     // Functions for LTestVAndBranch.
     void splitTagForTest(const ValueOperand& value, ScratchTagScope& tag);
 
   public:
--- a/js/src/jit/mips32/MacroAssembler-mips32-inl.h
+++ b/js/src/jit/mips32/MacroAssembler-mips32-inl.h
@@ -974,34 +974,16 @@ MacroAssembler::branchTestMagic(Conditio
     else
         branchTestMagic(Assembler::NotEqual, valaddr, label);
 
     branch32(cond, ToPayload(valaddr), Imm32(why), label);
     bind(&notMagic);
 }
 
 void
-MacroAssembler::branchToComputedAddress(const BaseIndex& addr)
-{
-    int32_t shift = Imm32::ShiftOf(addr.scale).value;
-    if (shift) {
-        // 4 instructions : lui ori jr nop
-        as_sll(ScratchRegister, addr.index, 4);
-        as_addu(ScratchRegister, addr.base, ScratchRegister);
-    } else {
-        as_addu(ScratchRegister, addr.base, addr.index);
-    }
-
-    if (addr.offset)
-        asMasm().addPtr(Imm32(addr.offset), ScratchRegister);
-    as_jr(ScratchRegister);
-    as_nop();
-}
-
-void
 MacroAssembler::branchTruncateDoubleMaybeModUint32(FloatRegister src, Register dest, Label* fail)
 {
     as_truncwd(ScratchFloat32Reg, src);
     as_cfc1(ScratchRegister, Assembler::FCSR);
     moveFromFloat32(ScratchFloat32Reg, dest);
     ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
     ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
 }
--- a/js/src/jit/mips32/MacroAssembler-mips32.cpp
+++ b/js/src/jit/mips32/MacroAssembler-mips32.cpp
@@ -175,21 +175,22 @@ MacroAssemblerMIPSCompat::convertInt32To
 void
 MacroAssemblerMIPSCompat::convertInt32ToFloat32(const Address& src, FloatRegister dest)
 {
     ma_ls(dest, src);
     as_cvtsw(dest, dest);
 }
 
 void
-MacroAssemblerMIPS::ma_li(Register dest, CodeOffset* label)
+MacroAssemblerMIPS::ma_li(Register dest, CodeLabel* label)
 {
     BufferOffset bo = m_buffer.nextOffset();
     ma_liPatchable(dest, ImmWord(/* placeholder */ 0));
-    label->bind(bo.getOffset());
+    label->patchAt()->bind(bo.getOffset());
+    label->setLinkMode(CodeLabel::MoveImmediate);
 }
 
 void
 MacroAssemblerMIPS::ma_li(Register dest, ImmWord imm)
 {
     ma_li(dest, Imm32(uint32_t(imm.value)));
 }
 
--- a/js/src/jit/mips32/MacroAssembler-mips32.h
+++ b/js/src/jit/mips32/MacroAssembler-mips32.h
@@ -75,17 +75,17 @@ class MacroAssemblerMIPS : public MacroA
     using MacroAssemblerMIPSShared::ma_ls;
     using MacroAssemblerMIPSShared::ma_ld;
     using MacroAssemblerMIPSShared::ma_load;
     using MacroAssemblerMIPSShared::ma_store;
     using MacroAssemblerMIPSShared::ma_cmp_set;
     using MacroAssemblerMIPSShared::ma_subTestOverflow;
     using MacroAssemblerMIPSShared::ma_liPatchable;
 
-    void ma_li(Register dest, CodeOffset* label);
+    void ma_li(Register dest, CodeLabel* label);
 
     void ma_li(Register dest, ImmWord imm);
     void ma_liPatchable(Register dest, ImmPtr imm);
     void ma_liPatchable(Register dest, ImmWord imm);
 
     // load
     void ma_load(Register dest, Address address, LoadStoreSize size = SizeWord,
                  LoadStoreExtension extension = SignExtend);
@@ -219,17 +219,17 @@ class MacroAssemblerMIPSCompat : public 
         as_ori(dest, src, 0);
     }
     void mov(ImmWord imm, Register dest) {
         ma_li(dest, imm);
     }
     void mov(ImmPtr imm, Register dest) {
         mov(ImmWord(uintptr_t(imm.value)), dest);
     }
-    void mov(CodeOffset* label, Register dest) {
+    void mov(CodeLabel* label, Register dest) {
         ma_li(dest, label);
     }
     void mov(Register src, Address dest) {
         MOZ_CRASH("NYI-IC");
     }
     void mov(Address src, Register dest) {
         MOZ_CRASH("NYI-IC");
     }
@@ -307,21 +307,20 @@ class MacroAssemblerMIPSCompat : public 
         CodeOffset label = CodeOffset(currentOffset());
         ma_liPatchable(dest, imm);
         return label;
     }
     CodeOffset movWithPatch(ImmPtr imm, Register dest) {
         return movWithPatch(ImmWord(uintptr_t(imm.value)), dest);
     }
 
-    void writeCodePointer(CodeOffset* label) {
-        label->bind(currentOffset());
-        ma_liPatchable(ScratchRegister, ImmWord(0));
-        as_jr(ScratchRegister);
-        as_nop();
+    void writeCodePointer(CodeLabel* label) {
+        BufferOffset off = writeInst(-1);
+        label->patchAt()->bind(off.getOffset());
+        label->setLinkMode(CodeLabel::RawPointer);
     }
 
     void jump(Label* label) {
         ma_b(label);
     }
     void jump(Register reg) {
         as_jr(reg);
         as_nop();
--- a/js/src/jit/mips32/Trampoline-mips32.cpp
+++ b/js/src/jit/mips32/Trampoline-mips32.cpp
@@ -223,17 +223,17 @@ JitRuntime::generateEnterJIT(JSContext* 
 
         Register scratch = regs.takeAny();
 
         Register numStackValues = regs.takeAny();
         masm.load32(slotNumStackValues, numStackValues);
 
         // Push return address.
         masm.subPtr(Imm32(sizeof(uintptr_t)), StackPointer);
-        masm.ma_li(scratch, returnLabel.patchAt());
+        masm.ma_li(scratch, &returnLabel);
         masm.storePtr(scratch, Address(StackPointer, 0));
 
         // Push previous frame pointer.
         masm.subPtr(Imm32(sizeof(uintptr_t)), StackPointer);
         masm.storePtr(BaselineFrameReg, Address(StackPointer, 0));
 
         // Reserve frame.
         Register framePtr = BaselineFrameReg;
@@ -296,17 +296,17 @@ JitRuntime::generateEnterJIT(JSContext* 
         masm.jump(jitcode);
 
         // OOM: load error value, discard return address and previous frame
         // pointer and return.
         masm.bind(&error);
         masm.movePtr(framePtr, StackPointer);
         masm.addPtr(Imm32(2 * sizeof(uintptr_t)), StackPointer);
         masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
-        masm.ma_li(scratch, oomReturnLabel.patchAt());
+        masm.ma_li(scratch, &oomReturnLabel);
         masm.jump(scratch);
 
         masm.bind(&notOsr);
         // Load the scope chain in R1.
         MOZ_ASSERT(R1.scratchReg() != reg_code);
         masm.loadPtr(slotScopeChain, R1.scratchReg());
     }
 
@@ -314,19 +314,19 @@ JitRuntime::generateEnterJIT(JSContext* 
     // the stack would be aligned once the call is complete.
     masm.assertStackAlignment(JitStackAlignment, sizeof(uintptr_t));
 
     // Call the function with pushing return address to stack.
     masm.callJitNoProfiler(reg_code);
 
     {
         // Interpreter -> Baseline OSR will return here.
-        masm.bind(returnLabel.target());
+        masm.bind(&returnLabel);
         masm.addCodeLabel(returnLabel);
-        masm.bind(oomReturnLabel.target());
+        masm.bind(&oomReturnLabel);
         masm.addCodeLabel(oomReturnLabel);
     }
 
     // Pop arguments off the stack.
     // s0 <- 8*argc (size of all arguments we pushed on the stack)
     masm.pop(s0);
     masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), s0);
     masm.addPtr(s0, StackPointer);
--- a/js/src/jit/mips64/Assembler-mips64.cpp
+++ b/js/src/jit/mips64/Assembler-mips64.cpp
@@ -222,22 +222,31 @@ Assembler::trace(JSTracer* trc)
     }
     if (dataRelocations_.length()) {
         CompactBufferReader reader(dataRelocations_);
         ::TraceDataRelocations(trc, &m_buffer, reader);
     }
 }
 
 void
-Assembler::Bind(uint8_t* rawCode, CodeOffset label, CodeOffset target)
+Assembler::Bind(uint8_t* rawCode, const CodeLabel& label)
 {
-    if (label.bound()) {
-        intptr_t offset = label.offset();
-        Instruction* inst = (Instruction*) (rawCode + offset);
-        Assembler::UpdateLoad64Value(inst, (uint64_t)(rawCode + target.offset()));
+    if (label.patchAt().bound()) {
+
+        auto mode = label.linkMode();
+        intptr_t offset = label.patchAt().offset();
+        intptr_t target = label.target().offset();
+
+        if (mode == CodeLabel::RawPointer) {
+            *reinterpret_cast<const void**>(rawCode + offset) = rawCode + target;
+        } else {
+            MOZ_ASSERT(mode == CodeLabel::MoveImmediate || mode == CodeLabel::JumpImmediate);
+            Instruction* inst = (Instruction*) (rawCode + offset);
+            Assembler::UpdateLoad64Value(inst, (uint64_t)(rawCode + target));
+        }
     }
 }
 
 void
 Assembler::bind(InstImm* inst, uintptr_t branch, uintptr_t target)
 {
     int64_t offset = target - branch;
     InstImm inst_bgezal = InstImm(op_regimm, zero, rt_bgezal, BOffImm16(0));
@@ -359,19 +368,18 @@ Assembler::bind(RepatchLabel* label)
         }
     }
     label->bind(dest.getOffset());
 }
 
 void
 Assembler::processCodeLabels(uint8_t* rawCode)
 {
-    for (size_t i = 0; i < codeLabels_.length(); i++) {
-        CodeLabel label = codeLabels_[i];
-        Bind(rawCode, *label.patchAt(), *label.target());
+    for (const CodeLabel& label : codeLabels_) {
+        Bind(rawCode, label);
     }
 }
 
 uint32_t
 Assembler::PatchWrite_NearCallSize()
 {
     // Load an address needs 4 instructions, and a jump with a delay slot.
     return (4 + 2) * sizeof(uint32_t);
--- a/js/src/jit/mips64/Assembler-mips64.h
+++ b/js/src/jit/mips64/Assembler-mips64.h
@@ -167,17 +167,17 @@ class Assembler : public AssemblerMIPSSh
     // MacroAssemblers hold onto gcthings, so they are traced by the GC.
     void trace(JSTracer* trc);
 
     static uintptr_t GetPointer(uint8_t*);
 
     using AssemblerMIPSShared::bind;
 
     void bind(RepatchLabel* label);
-    static void Bind(uint8_t* rawCode, CodeOffset label, CodeOffset target);
+    static void Bind(uint8_t* rawCode, const CodeLabel& label);
 
     void processCodeLabels(uint8_t* rawCode);
 
     static void TraceJumpRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader);
     static void TraceDataRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader);
 
     void bind(InstImm* inst, uintptr_t branch, uintptr_t target);
 
--- a/js/src/jit/mips64/CodeGenerator-mips64.cpp
+++ b/js/src/jit/mips64/CodeGenerator-mips64.cpp
@@ -18,96 +18,16 @@
 #include "vm/TraceLogging.h"
 
 #include "jit/MacroAssembler-inl.h"
 #include "jit/shared/CodeGenerator-shared-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
-class js::jit::OutOfLineTableSwitch : public OutOfLineCodeBase<CodeGeneratorMIPS64>
-{
-    MTableSwitch* mir_;
-    CodeLabel jumpLabel_;
-
-    void accept(CodeGeneratorMIPS64* codegen) {
-        codegen->visitOutOfLineTableSwitch(this);
-    }
-
-  public:
-    OutOfLineTableSwitch(MTableSwitch* mir)
-      : mir_(mir)
-    {}
-
-    MTableSwitch* mir() const {
-        return mir_;
-    }
-
-    CodeLabel* jumpLabel() {
-        return &jumpLabel_;
-    }
-};
-
-void
-CodeGeneratorMIPS64::visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool)
-{
-    MTableSwitch* mir = ool->mir();
-
-    masm.haltingAlign(sizeof(void*));
-    masm.bind(ool->jumpLabel()->target());
-    masm.addCodeLabel(*ool->jumpLabel());
-
-    for (size_t i = 0; i < mir->numCases(); i++) {
-        LBlock* caseblock = skipTrivialBlocks(mir->getCase(i))->lir();
-        Label* caseheader = caseblock->label();
-        uint32_t caseoffset = caseheader->offset();
-
-        // The entries of the jump table need to be absolute addresses and thus
-        // must be patched after codegen is finished. Each table entry uses 8
-        // instructions (4 for load address, 2 for branch, and 2 padding).
-        CodeLabel cl;
-        masm.ma_li(ScratchRegister, cl.patchAt());
-        masm.branch(ScratchRegister);
-        masm.as_nop();
-        masm.as_nop();
-        cl.target()->bind(caseoffset);
-        masm.addCodeLabel(cl);
-    }
-}
-
-void
-CodeGeneratorMIPS64::emitTableSwitchDispatch(MTableSwitch* mir, Register index,
-                                             Register address)
-{
-    Label* defaultcase = skipTrivialBlocks(mir->getDefault())->lir()->label();
-
-    // Lower value with low value
-    if (mir->low() != 0)
-        masm.subPtr(Imm32(mir->low()), index);
-
-    // Jump to default case if input is out of range
-    int32_t cases = mir->numCases();
-    masm.branch32(Assembler::AboveOrEqual, index, Imm32(cases), defaultcase);
-
-    // To fill in the CodeLabels for the case entries, we need to first
-    // generate the case entries (we don't yet know their offsets in the
-    // instruction stream).
-    OutOfLineTableSwitch* ool = new(alloc()) OutOfLineTableSwitch(mir);
-    addOutOfLineCode(ool, mir);
-
-    // Compute the position where a pointer to the right case stands.
-    masm.ma_li(address, ool->jumpLabel()->patchAt());
-    // index = size of table entry * index.
-    // See CodeGeneratorMIPS64::visitOutOfLineTableSwitch
-    masm.lshiftPtr(Imm32(5), index);
-    masm.addPtr(index, address);
-
-    masm.branch(address);
-}
-
 ValueOperand
 CodeGeneratorMIPS64::ToValue(LInstruction* ins, size_t pos)
 {
     return ValueOperand(ToRegister(ins->getOperand(pos)));
 }
 
 ValueOperand
 CodeGeneratorMIPS64::ToTempValue(LInstruction* ins, size_t pos)
--- a/js/src/jit/mips64/CodeGenerator-mips64.h
+++ b/js/src/jit/mips64/CodeGenerator-mips64.h
@@ -32,17 +32,16 @@ class CodeGeneratorMIPS64 : public CodeG
     void testObjectEmitBranch(Assembler::Condition cond, const ValueOperand& value,
                               MBasicBlock* ifTrue, MBasicBlock* ifFalse)
     {
         MOZ_ASSERT(value.valueReg() != SecondScratchReg);
         masm.splitTag(value.valueReg(), SecondScratchReg);
         emitBranch(SecondScratchReg, ImmTag(JSVAL_TAG_OBJECT), cond, ifTrue, ifFalse);
     }
 
-    void emitTableSwitchDispatch(MTableSwitch* mir, Register index, Register base);
 
     template <typename T>
     void emitWasmLoadI64(T* ins);
     template <typename T>
     void emitWasmStoreI64(T* ins);
 
   public:
     void visitCompareB(LCompareB* lir);
@@ -67,17 +66,16 @@ class CodeGeneratorMIPS64 : public CodeG
     void visitCtzI64(LCtzI64* lir);
     void visitNotI64(LNotI64* lir);
     void visitWasmTruncateToInt64(LWasmTruncateToInt64* lir);
     void visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir);
     void visitTestI64AndBranch(LTestI64AndBranch* lir);
 
     // Out of line visitors.
     void visitOutOfLineBailout(OutOfLineBailout* ool);
-    void visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool);
   protected:
     ValueOperand ToValue(LInstruction* ins, size_t pos);
     ValueOperand ToTempValue(LInstruction* ins, size_t pos);
 
     // Functions for LTestVAndBranch.
     void splitTagForTest(const ValueOperand& value, ScratchTagScope& tag);
 
   public:
--- a/js/src/jit/mips64/MacroAssembler-mips64-inl.h
+++ b/js/src/jit/mips64/MacroAssembler-mips64-inl.h
@@ -724,34 +724,16 @@ MacroAssembler::branchTestMagic(Conditio
 {
     uint64_t magic = MagicValue(why).asRawBits();
     SecondScratchRegisterScope scratch(*this);
     loadPtr(valaddr, scratch);
     ma_b(scratch, ImmWord(magic), label, cond);
 }
 
 void
-MacroAssembler::branchToComputedAddress(const BaseIndex& addr)
-{
-    int32_t shift = Imm32::ShiftOf(addr.scale).value;
-    if (shift) {
-        // 6 instructions : lui ori dror32 ori jr nop
-        ma_mul(ScratchRegister, addr.index, Imm32(6 * 4));
-        as_daddu(ScratchRegister, addr.base, ScratchRegister);
-    } else {
-        as_daddu(ScratchRegister, addr.base, addr.index);
-    }
-
-    if (addr.offset)
-        asMasm().addPtr(Imm32(addr.offset), ScratchRegister);
-    as_jr(ScratchRegister);
-    as_nop();
-}
-
-void
 MacroAssembler::branchTruncateDoubleMaybeModUint32(FloatRegister src, Register dest, Label* fail)
 {
     as_truncld(ScratchDoubleReg, src);
     as_cfc1(ScratchRegister, Assembler::FCSR);
     moveFromDouble(ScratchDoubleReg, dest);
     ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseV, 1);
     ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual);
 
--- a/js/src/jit/mips64/MacroAssembler-mips64.cpp
+++ b/js/src/jit/mips64/MacroAssembler-mips64.cpp
@@ -161,21 +161,22 @@ MacroAssemblerMIPS64Compat::convertInt32
 
 void
 MacroAssemblerMIPS64Compat::movq(Register rs, Register rd)
 {
     ma_move(rd, rs);
 }
 
 void
-MacroAssemblerMIPS64::ma_li(Register dest, CodeOffset* label)
+MacroAssemblerMIPS64::ma_li(Register dest, CodeLabel* label)
 {
     BufferOffset bo = m_buffer.nextOffset();
     ma_liPatchable(dest, ImmWord(/* placeholder */ 0));
-    label->bind(bo.getOffset());
+    label->patchAt()->bind(bo.getOffset());
+    label->setLinkMode(CodeLabel::MoveImmediate);
 }
 
 void
 MacroAssemblerMIPS64::ma_li(Register dest, ImmWord imm)
 {
     int64_t value = imm.value;
 
     if (-1 == (value >> 15) || 0 == (value >> 15)) {
--- a/js/src/jit/mips64/MacroAssembler-mips64.h
+++ b/js/src/jit/mips64/MacroAssembler-mips64.h
@@ -76,17 +76,17 @@ class MacroAssemblerMIPS64 : public Macr
     using MacroAssemblerMIPSShared::ma_sd;
     using MacroAssemblerMIPSShared::ma_ls;
     using MacroAssemblerMIPSShared::ma_ld;
     using MacroAssemblerMIPSShared::ma_load;
     using MacroAssemblerMIPSShared::ma_store;
     using MacroAssemblerMIPSShared::ma_cmp_set;
     using MacroAssemblerMIPSShared::ma_subTestOverflow;
 
-    void ma_li(Register dest, CodeOffset* label);
+    void ma_li(Register dest, CodeLabel* label);
     void ma_li(Register dest, ImmWord imm);
     void ma_liPatchable(Register dest, ImmPtr imm);
     void ma_liPatchable(Register dest, ImmWord imm, LiFlags flags = Li48);
 
     // Negate
     void ma_dnegu(Register rd, Register rs);
 
     // Shift operations
@@ -231,17 +231,17 @@ class MacroAssemblerMIPS64Compat : publi
         as_ori(dest, src, 0);
     }
     void mov(ImmWord imm, Register dest) {
         ma_li(dest, imm);
     }
     void mov(ImmPtr imm, Register dest) {
         mov(ImmWord(uintptr_t(imm.value)), dest);
     }
-    void mov(CodeOffset* label, Register dest) {
+    void mov(CodeLabel* label, Register dest) {
         ma_li(dest, label);
     }
     void mov(Register src, Address dest) {
         MOZ_CRASH("NYI-IC");
     }
     void mov(Address src, Register dest) {
         MOZ_CRASH("NYI-IC");
     }
@@ -330,21 +330,22 @@ class MacroAssemblerMIPS64Compat : publi
         return offset;
     }
     CodeOffset movWithPatch(ImmPtr imm, Register dest) {
         CodeOffset offset = CodeOffset(currentOffset());
         ma_liPatchable(dest, imm);
         return offset;
     }
 
-    void writeCodePointer(CodeOffset* label) {
-        label->bind(currentOffset());
-        ma_liPatchable(ScratchRegister, ImmWord(0));
-        as_jr(ScratchRegister);
-        as_nop();
+    void writeCodePointer(CodeLabel* label) {
+        label->patchAt()->bind(currentOffset());
+        label->setLinkMode(CodeLabel::RawPointer);
+        m_buffer.ensureSpace(sizeof(void*));
+        writeInst(-1);
+        writeInst(-1);
     }
 
     void jump(Label* label) {
         ma_b(label);
     }
     void jump(Register reg) {
         as_jr(reg);
         as_nop();
--- a/js/src/jit/mips64/Trampoline-mips64.cpp
+++ b/js/src/jit/mips64/Trampoline-mips64.cpp
@@ -247,17 +247,17 @@ JitRuntime::generateEnterJIT(JSContext* 
         masm.ma_b(OsrFrameReg, OsrFrameReg, &notOsr, Assembler::Zero, ShortJump);
 
         Register numStackValues = reg_values;
         regs.take(numStackValues);
         Register scratch = regs.takeAny();
 
         // Push return address.
         masm.subPtr(Imm32(sizeof(uintptr_t)), StackPointer);
-        masm.ma_li(scratch, returnLabel.patchAt());
+        masm.ma_li(scratch, &returnLabel);
         masm.storePtr(scratch, Address(StackPointer, 0));
 
         // Push previous frame pointer.
         masm.subPtr(Imm32(sizeof(uintptr_t)), StackPointer);
         masm.storePtr(BaselineFrameReg, Address(StackPointer, 0));
 
         // Reserve frame.
         Register framePtr = BaselineFrameReg;
@@ -319,17 +319,17 @@ JitRuntime::generateEnterJIT(JSContext* 
         masm.jump(jitcode);
 
         // OOM: load error value, discard return address and previous frame
         // pointer and return.
         masm.bind(&error);
         masm.movePtr(framePtr, StackPointer);
         masm.addPtr(Imm32(2 * sizeof(uintptr_t)), StackPointer);
         masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
-        masm.ma_li(scratch, oomReturnLabel.patchAt());
+        masm.ma_li(scratch, &oomReturnLabel);
         masm.jump(scratch);
 
         masm.bind(&notOsr);
         // Load the scope chain in R1.
         MOZ_ASSERT(R1.scratchReg() != reg_code);
         masm.ma_move(R1.scratchReg(), reg_chain);
     }
 
@@ -337,19 +337,19 @@ JitRuntime::generateEnterJIT(JSContext* 
     // the stack would be aligned once the call is complete.
     masm.assertStackAlignment(JitStackAlignment, sizeof(uintptr_t));
 
     // Call the function with pushing return address to stack.
     masm.callJitNoProfiler(reg_code);
 
     {
         // Interpreter -> Baseline OSR will return here.
-        masm.bind(returnLabel.target());
+        masm.bind(&returnLabel);
         masm.addCodeLabel(returnLabel);
-        masm.bind(oomReturnLabel.target());
+        masm.bind(&oomReturnLabel);
         masm.addCodeLabel(oomReturnLabel);
     }
 
     // Pop arguments off the stack.
     // s0 <- 8*argc (size of all arguments we pushed on the stack)
     masm.pop(s0);
     masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), s0);
     masm.addPtr(s0, StackPointer);
--- a/js/src/jit/none/MacroAssembler-none.h
+++ b/js/src/jit/none/MacroAssembler-none.h
@@ -148,17 +148,17 @@ class Assembler : public AssemblerShared
 
     static void PatchWrite_NearCall(CodeLocationLabel, CodeLocationLabel) { MOZ_CRASH(); }
     static uint32_t PatchWrite_NearCallSize() { MOZ_CRASH(); }
 
     static void ToggleToJmp(CodeLocationLabel) { MOZ_CRASH(); }
     static void ToggleToCmp(CodeLocationLabel) { MOZ_CRASH(); }
     static void ToggleCall(CodeLocationLabel, bool) { MOZ_CRASH(); }
 
-    static void Bind(uint8_t*, CodeOffset, CodeOffset) { MOZ_CRASH(); }
+    static void Bind(uint8_t*, const CodeLabel&) { MOZ_CRASH(); }
 
     static uintptr_t GetPointer(uint8_t*) { MOZ_CRASH(); }
 
     static bool HasRoundInstruction(RoundingMode) { return false; }
 
     void verifyHeapAccessDisassembly(uint32_t begin, uint32_t end,
                                      const Disassembler::HeapAccess& heapAccess)
     {
@@ -226,17 +226,17 @@ class MacroAssemblerNone : public Assemb
     void processCodeLabels(uint8_t*) { MOZ_CRASH(); }
 
     void flushBuffer() { MOZ_CRASH(); }
 
     template <typename T> void bind(T) { MOZ_CRASH(); }
     void bindLater(Label*, wasm::OldTrapDesc) { MOZ_CRASH(); }
     template <typename T> void j(Condition, T) { MOZ_CRASH(); }
     template <typename T> void jump(T) { MOZ_CRASH(); }
-    void writeCodePointer(CodeOffset* label) { MOZ_CRASH(); }
+    void writeCodePointer(CodeLabel* label) { MOZ_CRASH(); }
     void haltingAlign(size_t) { MOZ_CRASH(); }
     void nopAlign(size_t) { MOZ_CRASH(); }
     void checkStackAlignment() { MOZ_CRASH(); }
     uint32_t currentOffset() { MOZ_CRASH(); }
     CodeOffset labelForPatch() { MOZ_CRASH(); }
 
     void nop() { MOZ_CRASH(); }
     void breakpoint() { MOZ_CRASH(); }
--- a/js/src/jit/shared/Assembler-shared.h
+++ b/js/src/jit/shared/Assembler-shared.h
@@ -27,16 +27,20 @@
 #endif
 
 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
 // JS_SMALL_BRANCH means the range on a branch instruction
 // is smaller than the whole address space
 # define JS_SMALL_BRANCH
 #endif
 
+#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
+# define JS_CODELABEL_LINKMODE
+#endif
+
 using mozilla::CheckedInt;
 
 namespace js {
 namespace jit {
 
 namespace Disassembler {
 class HeapAccess;
 } // namespace Disassembler
@@ -524,26 +528,41 @@ class CodeOffset
         offset_ += delta;
     }
 };
 
 // A code label contains an absolute reference to a point in the code. Thus, it
 // cannot be patched until after linking.
 // When the source label is resolved into a memory address, this address is
 // patched into the destination address.
+// Some need to distinguish between multiple ways of patching that address.
+// See JS_CODELABEL_LINKMODE.
 class CodeLabel
 {
     // The destination position, where the absolute reference should get
     // patched into.
     CodeOffset patchAt_;
 
     // The source label (relative) in the code to where the destination should
     // get patched to.
     CodeOffset target_;
 
+#ifdef JS_CODELABEL_LINKMODE
+public:
+    enum LinkMode
+    {
+        Uninitialized = 0,
+        RawPointer,
+        MoveImmediate,
+        JumpImmediate
+    };
+private:
+    LinkMode linkMode_ = Uninitialized;
+#endif
+
   public:
     CodeLabel()
     { }
     explicit CodeLabel(const CodeOffset& patchAt)
       : patchAt_(patchAt)
     { }
     CodeLabel(const CodeOffset& patchAt, const CodeOffset& target)
       : patchAt_(patchAt),
@@ -556,16 +575,24 @@ class CodeLabel
         return &target_;
     }
     CodeOffset patchAt() const {
         return patchAt_;
     }
     CodeOffset target() const {
         return target_;
     }
+#ifdef JS_CODELABEL_LINKMODE
+    LinkMode linkMode() const {
+        return linkMode_;
+    }
+    void setLinkMode(LinkMode value) {
+        linkMode_ = value;
+    }
+#endif
 };
 
 typedef Vector<CodeLabel, 0, SystemAllocPolicy> CodeLabelVector;
 
 // Location of a jump or label in a generated JitCode block, relative to the
 // start of the block.
 
 class CodeOffsetJump
--- a/js/src/jit/x64/Assembler-x64.h
+++ b/js/src/jit/x64/Assembler-x64.h
@@ -932,19 +932,19 @@ class Assembler : public AssemblerX86Sha
         movq(src, dest);
     }
     void mov(Imm32 imm32, const Operand& dest) {
         movq(imm32, dest);
     }
     void mov(Register src, Register dest) {
         movq(src, dest);
     }
-    void mov(CodeOffset* label, Register dest) {
+    void mov(CodeLabel* label, Register dest) {
         masm.movq_i64r(/* placeholder */ 0, dest.encoding());
-        label->bind(masm.size());
+        label->patchAt()->bind(masm.size());
     }
     void xchg(Register src, Register dest) {
         xchgq(src, dest);
     }
 
     void lea(const Operand& src, Register dest) {
         switch (src.kind()) {
           case Operand::MEM_REG_DISP:
--- a/js/src/jit/x64/Trampoline-x64.cpp
+++ b/js/src/jit/x64/Trampoline-x64.cpp
@@ -183,17 +183,17 @@ JitRuntime::generateEnterJIT(JSContext* 
 
         Label notOsr;
         masm.branchTestPtr(Assembler::Zero, OsrFrameReg, OsrFrameReg, &notOsr);
 
         Register numStackValues = regs.takeAny();
         masm.movq(numStackValuesAddr, numStackValues);
 
         // Push return address
-        masm.mov(returnLabel.patchAt(), scratch);
+        masm.mov(&returnLabel, scratch);
         masm.push(scratch);
 
         // Push previous frame pointer.
         masm.push(rbp);
 
         // Reserve frame.
         Register framePtr = rbp;
         masm.subPtr(Imm32(BaselineFrame::Size()), rsp);
@@ -273,35 +273,35 @@ JitRuntime::generateEnterJIT(JSContext* 
         masm.jump(reg_code);
 
         // OOM: load error value, discard return address and previous frame
         // pointer and return.
         masm.bind(&error);
         masm.mov(framePtr, rsp);
         masm.addPtr(Imm32(2 * sizeof(uintptr_t)), rsp);
         masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
-        masm.mov(oomReturnLabel.patchAt(), scratch);
+        masm.mov(&oomReturnLabel, scratch);
         masm.jump(scratch);
 
         masm.bind(&notOsr);
         masm.movq(scopeChain, R1.scratchReg());
     }
 
     // The call will push the return address on the stack, thus we check that
     // the stack would be aligned once the call is complete.
     masm.assertStackAlignment(JitStackAlignment, sizeof(uintptr_t));
 
     // Call function.
     masm.callJitNoProfiler(reg_code);
 
     {
         // Interpreter -> Baseline OSR will return here.
-        masm.use(returnLabel.target());
+        masm.bind(&returnLabel);
         masm.addCodeLabel(returnLabel);
-        masm.use(oomReturnLabel.target());
+        masm.bind(&oomReturnLabel);
         masm.addCodeLabel(oomReturnLabel);
     }
 
     // Pop arguments and padding from stack.
     masm.pop(r14);              // Pop and decode descriptor.
     masm.shrq(Imm32(FRAMESIZE_SHIFT), r14);
     masm.addq(r14, rsp);        // Remove arguments.
 
--- a/js/src/jit/x86-shared/Assembler-x86-shared.cpp
+++ b/js/src/jit/x86-shared/Assembler-x86-shared.cpp
@@ -133,19 +133,18 @@ AssemblerX86Shared::executableCopy(void*
         blackbox[4] = uintptr_t(0xFFFF8888);
         MOZ_CRASH("Corrupt code buffer");
     }
 }
 
 void
 AssemblerX86Shared::processCodeLabels(uint8_t* rawCode)
 {
-    for (size_t i = 0; i < codeLabels_.length(); i++) {
-        CodeLabel label = codeLabels_[i];
-        Bind(rawCode, *label.patchAt(), *label.target());
+    for (const CodeLabel& label : codeLabels_) {
+        Bind(rawCode, label);
     }
 }
 
 AssemblerX86Shared::Condition
 AssemblerX86Shared::InvertCondition(Condition cond)
 {
     switch (cond) {
       case Zero:
--- a/js/src/jit/x86-shared/Assembler-x86-shared.h
+++ b/js/src/jit/x86-shared/Assembler-x86-shared.h
@@ -444,20 +444,20 @@ class AssemblerX86Shared : public Assemb
 
   public:
     void haltingAlign(int alignment) {
         masm.haltingAlign(alignment);
     }
     void nopAlign(int alignment) {
         masm.nopAlign(alignment);
     }
-    void writeCodePointer(CodeOffset* label) {
+    void writeCodePointer(CodeLabel* label) {
         // Use -1 as dummy value. This will be patched after codegen.
         masm.jumpTablePointer(-1);
-        label->bind(masm.size());
+        label->patchAt()->bind(masm.size());
     }
     void cmovCCl(Condition cond, const Operand& src, Register dest) {
         X86Encoding::Condition cc = static_cast<X86Encoding::Condition>(cond);
         switch (src.kind()) {
           case Operand::REG:
             masm.cmovCCl_rr(cc, src.reg(), dest.encoding());
             break;
           case Operand::MEM_REG_DISP:
@@ -987,18 +987,18 @@ class AssemblerX86Shared : public Assemb
     void bind(RepatchLabel* label) {
         JmpDst dst(masm.label());
         if (label->used()) {
             JmpSrc jmp(label->offset());
             masm.linkJump(jmp, dst);
         }
         label->bind(dst.offset());
     }
-    void use(CodeOffset* label) {
-        label->bind(currentOffset());
+    void bind(CodeLabel* label) {
+        label->target()->bind(currentOffset());
     }
     uint32_t currentOffset() {
         return masm.label().offset();
     }
 
     // Re-routes pending jumps to a new label.
     void retarget(Label* label, Label* target) {
         if (!label->used())
@@ -1019,20 +1019,21 @@ class AssemblerX86Shared : public Assemb
                 target->use(jmp.offset());
                 masm.setNextJump(jmp, prev);
             }
             jmp = JmpSrc(next.offset());
         } while (more);
         label->reset();
     }
 
-    static void Bind(uint8_t* raw, CodeOffset label, CodeOffset target) {
-        if (label.bound()) {
-            intptr_t offset = label.offset();
-            X86Encoding::SetPointer(raw + offset, raw + target.offset());
+    static void Bind(uint8_t* raw, const CodeLabel& label) {
+        if (label.patchAt().bound()) {
+            intptr_t offset = label.patchAt().offset();
+            intptr_t target = label.target().offset();
+            X86Encoding::SetPointer(raw + offset, raw + target);
         }
     }
 
     void ret() {
         masm.ret();
     }
     void retn(Imm32 n) {
         // Remove the size of the return address which is included in the frame.
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -1842,28 +1842,28 @@ class OutOfLineTableSwitch : public OutO
 };
 
 void
 CodeGeneratorX86Shared::visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool)
 {
     MTableSwitch* mir = ool->mir();
 
     masm.haltingAlign(sizeof(void*));
-    masm.use(ool->jumpLabel()->target());
+    masm.bind(ool->jumpLabel());
     masm.addCodeLabel(*ool->jumpLabel());
 
     for (size_t i = 0; i < mir->numCases(); i++) {
         LBlock* caseblock = skipTrivialBlocks(mir->getCase(i))->lir();
         Label* caseheader = caseblock->label();
         uint32_t caseoffset = caseheader->offset();
 
         // The entries of the jump table need to be absolute addresses and thus
         // must be patched after codegen is finished.
         CodeLabel cl;
-        masm.writeCodePointer(cl.patchAt());
+        masm.writeCodePointer(&cl);
         cl.target()->bind(caseoffset);
         masm.addCodeLabel(cl);
     }
 }
 
 void
 CodeGeneratorX86Shared::emitTableSwitchDispatch(MTableSwitch* mir, Register index, Register base)
 {
@@ -1880,17 +1880,17 @@ CodeGeneratorX86Shared::emitTableSwitchD
 
     // To fill in the CodeLabels for the case entries, we need to first
     // generate the case entries (we don't yet know their offsets in the
     // instruction stream).
     OutOfLineTableSwitch* ool = new(alloc()) OutOfLineTableSwitch(mir);
     addOutOfLineCode(ool, mir);
 
     // Compute the position where a pointer to the right case stands.
-    masm.mov(ool->jumpLabel()->patchAt(), base);
+    masm.mov(ool->jumpLabel(), base);
     BaseIndex pointer(base, index, ScalePointer);
 
     // Jump to the right case
     masm.branchToComputedAddress(pointer);
 }
 
 void
 CodeGeneratorX86Shared::visitMathD(LMathD* math)
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp
+++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp
@@ -654,19 +654,19 @@ MacroAssembler::patchCallToNop(uint8_t* 
 // ===============================================================
 // Jit Frames.
 
 uint32_t
 MacroAssembler::pushFakeReturnAddress(Register scratch)
 {
     CodeLabel cl;
 
-    mov(cl.patchAt(), scratch);
+    mov(&cl, scratch);
     Push(scratch);
-    use(cl.target());
+    bind(&cl);
     uint32_t retAddr = currentOffset();
 
     addCodeLabel(cl);
     return retAddr;
 }
 
 // ===============================================================
 // WebAssembly
--- a/js/src/jit/x86/Assembler-x86.h
+++ b/js/src/jit/x86/Assembler-x86.h
@@ -345,20 +345,20 @@ class Assembler : public AssemblerX86Sha
         movl(src, dest);
     }
     void mov(Register src, const Operand& dest) {
         movl(src, dest);
     }
     void mov(Imm32 imm, const Operand& dest) {
         movl(imm, dest);
     }
-    void mov(CodeOffset* label, Register dest) {
+    void mov(CodeLabel* label, Register dest) {
         // Put a placeholder value in the instruction stream.
         masm.movl_i32r(0, dest.encoding());
-        label->bind(masm.size());
+        label->patchAt()->bind(masm.size());
     }
     void mov(Register src, Register dest) {
         movl(src, dest);
     }
     void xchg(Register src, Register dest) {
         xchgl(src, dest);
     }
     void lea(const Operand& src, Register dest) {
--- a/js/src/jit/x86/Trampoline-x86.cpp
+++ b/js/src/jit/x86/Trampoline-x86.cpp
@@ -177,17 +177,17 @@ JitRuntime::generateEnterJIT(JSContext* 
 
         Register numStackValues = regs.takeAny();
         masm.loadPtr(Address(ebp, ARG_STACKVALUES), numStackValues);
 
         Register jitcode = regs.takeAny();
         masm.loadPtr(Address(ebp, ARG_JITCODE), jitcode);
 
         // Push return address.
-        masm.mov(returnLabel.patchAt(), scratch);
+        masm.mov(&returnLabel, scratch);
         masm.push(scratch);
 
         // Push previous frame pointer.
         masm.push(ebp);
 
         // Reserve frame.
         Register framePtr = ebp;
         masm.subPtr(Imm32(BaselineFrame::Size()), esp);
@@ -264,17 +264,17 @@ JitRuntime::generateEnterJIT(JSContext* 
         masm.jump(jitcode);
 
         // OOM: load error value, discard return address and previous frame
         // pointer and return.
         masm.bind(&error);
         masm.mov(framePtr, esp);
         masm.addPtr(Imm32(2 * sizeof(uintptr_t)), esp);
         masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
-        masm.mov(oomReturnLabel.patchAt(), scratch);
+        masm.mov(&oomReturnLabel, scratch);
         masm.jump(scratch);
 
         masm.bind(&notOsr);
         masm.loadPtr(Address(ebp, ARG_SCOPECHAIN), R1.scratchReg());
     }
 
     // The call will push the return address on the stack, thus we check that
     // the stack would be aligned once the call is complete.
@@ -283,19 +283,19 @@ JitRuntime::generateEnterJIT(JSContext* 
     /***************************************************************
         Call passed-in code, get return value and fill in the
         passed in return value pointer
     ***************************************************************/
     masm.call(Address(ebp, ARG_JITCODE));
 
     {
         // Interpreter -> Baseline OSR will return here.
-        masm.use(returnLabel.target());
+        masm.bind(&returnLabel);
         masm.addCodeLabel(returnLabel);
-        masm.use(oomReturnLabel.target());
+        masm.bind(&oomReturnLabel);
         masm.addCodeLabel(oomReturnLabel);
     }
 
     // Pop arguments off the stack.
     // eax <- 8*argc (size of all arguments we pushed on the stack)
     masm.pop(eax);
     masm.shrl(Imm32(FRAMESIZE_SHIFT), eax); // Unmark EntryFrame.
     masm.addl(eax, esp);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1678,21 +1678,21 @@ class ReservedRooted : public RootedBase
     DECLARE_POINTER_ASSIGN_OPS(ReservedRooted, T)
 };
 
 void
 js::ReportInNotObjectError(JSContext* cx, HandleValue lref, int lindex,
                            HandleValue rref, int rindex)
 {
     auto uniqueCharsFromString = [](JSContext* cx, HandleValue ref) -> UniqueChars {
-        static const size_t MAX_STRING_LENGTH = 16;
+        static const size_t MaxStringLength = 16;
         RootedString str(cx, ref.toString());
-        if (str->length() > MAX_STRING_LENGTH) {
+        if (str->length() > MaxStringLength) {
             StringBuffer buf(cx);
-            if (!buf.appendSubstring(str, 0, MAX_STRING_LENGTH))
+            if (!buf.appendSubstring(str, 0, MaxStringLength))
                 return nullptr;
             if (!buf.append("..."))
                 return nullptr;
             str = buf.finishString();
             if (!str)
                 return nullptr;
         }
         return UniqueChars(JS_EncodeString(cx, str));
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -37,16 +37,17 @@
 #include "gc/HashUtil.h"
 #include "gc/Marking.h"
 #include "gc/Policy.h"
 #include "jit/AtomicOperations.h"
 #include "jit/InlinableNatives.h"
 #include "js/CharacterEncoding.h"
 #include "js/Date.h"
 #include "js/Wrapper.h"
+#include "vm/ArgumentsObject.h"
 #include "vm/Compression.h"
 #include "vm/GeneratorObject.h"
 #include "vm/Interpreter.h"
 #include "vm/Iteration.h"
 #include "vm/JSCompartment.h"
 #include "vm/JSContext.h"
 #include "vm/JSFunction.h"
 #include "vm/Printer.h"
@@ -3189,8 +3190,14 @@ js::GetSelfHostedFunctionName(JSFunction
     if (!name.isString())
         return nullptr;
     return &name.toString()->asAtom();
 }
 
 static_assert(JSString::MAX_LENGTH <= INT32_MAX,
               "StringIteratorNext in builtin/String.js assumes the stored index "
               "into the string is an Int32Value");
+
+static_assert(JSString::MAX_LENGTH == MAX_STRING_LENGTH,
+              "JSString::MAX_LENGTH matches self-hosted constant for maximum string length");
+
+static_assert(ARGS_LENGTH_MAX == MAX_ARGS_LENGTH,
+              "ARGS_LENGTH_MAX matches self-hosted constant for maximum arguments length");
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -3381,30 +3381,30 @@ class BaseCompiler final : public BaseCo
         // Flush constant pools to ensure that the table is never interrupted by
         // constant pool entries.
         masm.flush();
 
         masm.bind(theTable);
 
         for (uint32_t i = 0; i < labels.length(); i++) {
             CodeLabel cl;
-            masm.writeCodePointer(cl.patchAt());
+            masm.writeCodePointer(&cl);
             cl.target()->bind(labels[i].offset());
             masm.addCodeLabel(cl);
         }
     }
 
     void tableSwitch(Label* theTable, RegI32 switchValue, Label* dispatchCode) {
         masm.bind(dispatchCode);
 
 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
         ScratchI32 scratch(*this);
         CodeLabel tableCl;
 
-        masm.mov(tableCl.patchAt(), scratch);
+        masm.mov(&tableCl, scratch);
 
         tableCl.target()->bind(theTable->offset());
         masm.addCodeLabel(tableCl);
 
         masm.jmp(Operand(scratch, switchValue, ScalePointer));
 #elif defined(JS_CODEGEN_ARM)
         // Flush constant pools: offset must reflect the distance from the MOV
         // to the start of the table; as the address of the MOV is given by the
@@ -3430,28 +3430,22 @@ class BaseCompiler final : public BaseCo
 
         // Jump indirect via table element.
         masm.ma_ldr(DTRAddr(scratch, DtrRegImmShift(switchValue, LSL, 2)), pc, Offset,
                     Assembler::Always);
 #elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
         ScratchI32 scratch(*this);
         CodeLabel tableCl;
 
-        masm.ma_li(scratch, tableCl.patchAt());
-# ifdef JS_CODEGEN_MIPS32
-        masm.lshiftPtr(Imm32(4), switchValue);
-# else
-        masm.ma_mul(switchValue, switchValue, Imm32(6 * 4));
-# endif
-        masm.addPtr(switchValue, scratch);
+        masm.ma_li(scratch, &tableCl);
 
         tableCl.target()->bind(theTable->offset());
         masm.addCodeLabel(tableCl);
 
-        masm.branch(scratch);
+        masm.branchToComputedAddress(BaseIndex(scratch, switchValue, ScalePointer));
 #else
         MOZ_CRASH("BaseCompiler platform hook: tableSwitch");
 #endif
     }
 
     RegI32 captureReturnedI32() {
         RegI32 r = RegI32(ReturnReg);
         MOZ_ASSERT(isAvailableI32(r));
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -107,19 +107,23 @@ FreeCode::operator()(uint8_t* bytes)
 #endif
     DeallocateExecutableMemory(bytes, codeLength);
 }
 
 static bool
 StaticallyLink(const ModuleSegment& ms, const LinkDataTier& linkData)
 {
     for (LinkDataTier::InternalLink link : linkData.internalLinks) {
-        CodeOffset patchAt(link.patchAtOffset);
-        CodeOffset target(link.targetOffset);
-        Assembler::Bind(ms.base(), patchAt, target);
+        CodeLabel label;
+        label.patchAt()->bind(link.patchAtOffset);
+        label.target()->bind(link.targetOffset);
+#ifdef JS_CODELABEL_LINKMODE
+        label.setLinkMode(static_cast<CodeLabel::LinkMode>(link.mode));
+#endif
+        Assembler::Bind(ms.base(), label);
     }
 
     if (!EnsureBuiltinThunksInitialized())
         return false;
 
     for (auto imm : MakeEnumeratedRange(SymbolicAddress::Limit)) {
         const Uint32Vector& offsets = linkData.symbolicLinks[imm];
         if (offsets.empty())
@@ -136,19 +140,23 @@ StaticallyLink(const ModuleSegment& ms, 
 
     return true;
 }
 
 static void
 StaticallyUnlink(uint8_t* base, const LinkDataTier& linkData)
 {
     for (LinkDataTier::InternalLink link : linkData.internalLinks) {
-        CodeOffset patchAt(link.patchAtOffset);
-        CodeOffset target(-size_t(base));  // to reset immediate to null
-        Assembler::Bind(base, patchAt, target);
+        CodeLabel label;
+        label.patchAt()->bind(link.patchAtOffset);
+        label.target()->bind(-size_t(base)); // to reset immediate to null
+#ifdef JS_CODELABEL_LINKMODE
+        label.setLinkMode(static_cast<CodeLabel::LinkMode>(link.mode));
+#endif
+        Assembler::Bind(base, label);
     }
 
     for (auto imm : MakeEnumeratedRange(SymbolicAddress::Limit)) {
         const Uint32Vector& offsets = linkData.symbolicLinks[imm];
         if (offsets.empty())
             continue;
 
         void* target = SymbolicAddressTarget(imm);
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -622,16 +622,19 @@ ModuleGenerator::linkCompiledCode(const 
         if (!linkDataTier_->symbolicLinks[access.target].append(patchAt))
             return false;
     }
 
     for (const CodeLabel& codeLabel : code.codeLabels) {
         LinkDataTier::InternalLink link;
         link.patchAtOffset = offsetInModule + codeLabel.patchAt().offset();
         link.targetOffset = offsetInModule + codeLabel.target().offset();
+#ifdef JS_CODELABEL_LINKMODE
+        link.mode = codeLabel.linkMode();
+#endif
         if (!linkDataTier_->internalLinks.append(link))
             return false;
     }
 
     return true;
 }
 
 static bool
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -57,16 +57,19 @@ struct LinkDataTier : LinkDataTierCachea
     explicit LinkDataTier(Tier tier) : tier(tier) {}
 
     LinkDataTierCacheablePod& pod() { return *this; }
     const LinkDataTierCacheablePod& pod() const { return *this; }
 
     struct InternalLink {
         uint32_t patchAtOffset;
         uint32_t targetOffset;
+#ifdef JS_CODELABEL_LINKMODE
+        uint32_t mode;
+#endif
     };
     typedef Vector<InternalLink, 0, SystemAllocPolicy> InternalLinkVector;
 
     struct SymbolicLinkArray : EnumeratedArray<SymbolicAddress, SymbolicAddress::Limit, Uint32Vector> {
         WASM_DECLARE_SERIALIZABLE(SymbolicLinkArray)
     };
 
     InternalLinkVector  internalLinks;
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -2661,19 +2661,31 @@ already_AddRefed<LayerManager> nsDisplay
         rootPresContext->ComputePluginGeometryUpdates(aBuilder->RootReferenceFrame(), aBuilder, this);
       }
       // This must be called even if PluginGeometryUpdates were not computed.
       rootPresContext->CollectPluginGeometryUpdates(layerManager);
     }
 
     WebRenderLayerManager* wrManager = static_cast<WebRenderLayerManager*>(layerManager.get());
 
+    nsIDocShell* docShell = presContext->GetDocShell();
+    nsTArray<wr::WrFilterOp> wrFilters;
+    gfx::Matrix5x4* colorMatrix = nsDocShell::Cast(docShell)->GetColorMatrix();
+    if (colorMatrix) {
+      wr::WrFilterOp gs = {
+        wr::WrFilterOpType::ColorMatrix
+      };
+      MOZ_ASSERT(sizeof(gs.matrix) == sizeof(colorMatrix->components));
+      memcpy(&(gs.matrix), colorMatrix->components, sizeof(gs.matrix));
+      wrFilters.AppendElement(gs);
+    }
+
     MaybeSetupTransactionIdAllocator(layerManager, presContext);
     bool temp = aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap());
-    wrManager->EndTransactionWithoutLayer(this, aBuilder);
+    wrManager->EndTransactionWithoutLayer(this, aBuilder, wrFilters);
 
     // For layers-free mode, we check the invalidation state bits in the EndTransaction.
     // So we clear the invalidation state bits after EndTransaction.
     if (widgetTransaction ||
         // SVG-as-an-image docs don't paint as part of the retained layer tree,
         // but they still need the invalidation state bits cleared in order for
         // invalidation for CSS/SMIL animation to work properly.
         (document && document->IsBeingUsedAsImage())) {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4688,16 +4688,25 @@ pref("browser.zoom.full", false);
 pref("zoom.minPercent", 30);
 pref("zoom.maxPercent", 300);
 pref("toolkit.zoomManager.zoomValues", ".3,.5,.67,.8,.9,1,1.1,1.2,1.33,1.5,1.7,2,2.4,3");
 
 //
 // Image-related prefs
 //
 
+// The maximum size (in kB) that the aggregate frames of an animation can use
+// before it starts to discard already displayed frames and redecode them as
+// necessary.
+pref("image.animated.decode-on-demand.threshold-kb", 20480);
+
+// The minimum number of frames we want to have buffered ahead of an
+// animation's currently displayed frame.
+pref("image.animated.decode-on-demand.batch-size", 6);
+
 // Maximum number of surfaces for an image before entering "factor of 2" mode.
 // This in addition to the number of "native" sizes of an image. A native size
 // is a size for which we can decode a frame without up or downscaling. Most
 // images only have 1, but some (i.e. ICOs) may have multiple frames for the
 // same data at different sizes.
 pref("image.cache.factor2.threshold-surfaces", 4);
 
 // The maximum size, in bytes, of the decoded images we cache
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -2888,16 +2888,22 @@ SetCacheFlags(uint32_t& aLoadFlags, uint
 
 NS_IMETHODIMP
 HttpBaseChannel::SetFetchCacheMode(uint32_t aFetchCacheMode)
 {
   ENSURE_CALLED_BEFORE_CONNECT();
 
   // Now, set the load flags that implement each cache mode.
   switch (aFetchCacheMode) {
+  case nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT:
+    // The "default" mode means to use the http cache normally and
+    // respect any http cache-control headers.  We effectively want
+    // to clear our cache related load flags.
+    SetCacheFlags(mLoadFlags, 0);
+    break;
   case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE:
     // no-store means don't consult the cache on the way to the network, and
     // don't store the response in the cache even if it's cacheable.
     SetCacheFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE);
     break;
   case nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD:
     // reload means don't consult the cache on the way to the network, but
     // do store the response in the cache if possible.
--- a/security/manager/ssl/ContentSignatureVerifier.cpp
+++ b/security/manager/ssl/ContentSignatureVerifier.cpp
@@ -53,16 +53,18 @@ ContentSignatureVerifier::VerifyContentS
     *_retval = false;
     CSVerifier_LOG(("CSVerifier: Signature verification failed\n"));
     if (rv == NS_ERROR_INVALID_SIGNATURE) {
       return NS_OK;
     }
     // This failure can have many different reasons but we don't treat it as
     // invalid signature.
     Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 3);
+    Telemetry::AccumulateCategoricalKeyed(mFingerprint,
+      Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err3);
     return rv;
   }
 
   return End(_retval);
 }
 
 bool
 IsNewLine(char16_t c)
@@ -158,16 +160,28 @@ ContentSignatureVerifier::CreateContextI
   Input certDER;
   mozilla::pkix::Result result =
     certDER.Init(BitwiseCast<uint8_t*, unsigned char*>(certSecItem->data),
                  certSecItem->len);
   if (result != Success) {
     return NS_ERROR_FAILURE;
   }
 
+  // Get EE certificate fingerprint for telemetry.
+  unsigned char fingerprint[SHA256_LENGTH] = {0};
+  SECStatus srv =
+    PK11_HashBuf(SEC_OID_SHA256, fingerprint, certSecItem->data,
+                 AssertedCast<int32_t>(certSecItem->len));
+  if (srv != SECSuccess) {
+    return NS_ERROR_FAILURE;
+  }
+  SECItem fingerprintItem = {siBuffer, fingerprint, SHA256_LENGTH};
+  mFingerprint.Truncate();
+  UniquePORTString tmpFingerprintString(CERT_Hexify(&fingerprintItem, 0));
+  mFingerprint.Append(tmpFingerprintString.get());
 
   // Check the signerCert chain is good
   CSTrustDomain trustDomain(certCertList);
   result = BuildCertChain(trustDomain, certDER, Now(),
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::noParticularKeyUsageRequired,
                           KeyPurposeId::id_kp_codeSigning,
                           CertPolicyId::anyPolicy,
@@ -175,22 +189,28 @@ ContentSignatureVerifier::CreateContextI
   if (result != Success) {
     // if there was a library error, return an appropriate error
     if (IsFatalError(result)) {
       return NS_ERROR_FAILURE;
     }
     // otherwise, assume the signature was invalid
     if (result == mozilla::pkix::Result::ERROR_EXPIRED_CERTIFICATE) {
       Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 4);
+      Telemetry::AccumulateCategoricalKeyed(mFingerprint,
+        Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err4);
     } else if (result ==
                mozilla::pkix::Result::ERROR_NOT_YET_VALID_CERTIFICATE) {
       Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 5);
+      Telemetry::AccumulateCategoricalKeyed(mFingerprint,
+        Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err5);
     } else {
       // Building cert chain failed for some other reason.
       Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 6);
+      Telemetry::AccumulateCategoricalKeyed(mFingerprint,
+        Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err6);
     }
     CSVerifier_LOG(("CSVerifier: The supplied chain is bad (%s)\n",
                     MapResultToName(result)));
     return NS_ERROR_INVALID_SIGNATURE;
   }
 
   // Check the SAN
   Input hostnameInput;
@@ -202,24 +222,28 @@ ContentSignatureVerifier::CreateContextI
     return NS_ERROR_FAILURE;
   }
 
   BRNameMatchingPolicy nameMatchingPolicy(BRNameMatchingPolicy::Mode::Enforce);
   result = CheckCertHostname(certDER, hostnameInput, nameMatchingPolicy);
   if (result != Success) {
     // EE cert isnot valid for the given host name.
     Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 7);
+    Telemetry::AccumulateCategoricalKeyed(mFingerprint,
+      Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err7);
     return NS_ERROR_INVALID_SIGNATURE;
   }
 
   mKey.reset(CERT_ExtractPublicKey(node->cert));
 
   // in case we were not able to extract a key
   if (!mKey) {
     Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 8);
+    Telemetry::AccumulateCategoricalKeyed(mFingerprint,
+      Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err8);
     CSVerifier_LOG(("CSVerifier: unable to extract a key\n"));
     return NS_ERROR_INVALID_SIGNATURE;
   }
 
   // Base 64 decode the signature
   nsAutoCString rawSignature;
   rv = Base64Decode(mSignature, rawSignature);
   if (NS_FAILED(rv)) {
@@ -250,22 +274,26 @@ ContentSignatureVerifier::CreateContextI
   // this is the only OID we support for now
   SECOidTag oid = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE;
 
   mCx = UniqueVFYContext(
     VFY_CreateContext(mKey.get(), &signatureItem, oid, nullptr));
   if (!mCx) {
     // Creating context failed.
     Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 9);
+    Telemetry::AccumulateCategoricalKeyed(mFingerprint,
+      Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err9);
     return NS_ERROR_INVALID_SIGNATURE;
   }
 
   if (VFY_Begin(mCx.get()) != SECSuccess) {
     // Creating context failed.
     Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 9);
+    Telemetry::AccumulateCategoricalKeyed(mFingerprint,
+      Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err9);
     return NS_ERROR_INVALID_SIGNATURE;
   }
 
   rv = UpdateInternal(kPREFIX);
   if (NS_FAILED(rv)) {
     return rv;
   }
   // add data if we got any
@@ -423,16 +451,18 @@ ContentSignatureVerifier::End(bool* _ret
     return NS_ERROR_FAILURE;
   }
 
   bool result = (VFY_End(mCx.get()) == SECSuccess);
   if (result) {
     Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 0);
   } else {
     Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 1);
+    Telemetry::AccumulateCategoricalKeyed(mFingerprint,
+      Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err1);
   }
   *_retval = result;
 
   return NS_OK;
 }
 
 nsresult
 ContentSignatureVerifier::ParseContentSignatureHeader(
--- a/security/manager/ssl/ContentSignatureVerifier.h
+++ b/security/manager/ssl/ContentSignatureVerifier.h
@@ -68,11 +68,13 @@ private:
   // verification key
   mozilla::UniqueSECKEYPublicKey mKey;
   // name of the verifying context
   nsCString mName;
   // callback to notify when finished
   nsCOMPtr<nsIContentSignatureReceiverCallback> mCallback;
   // channel to download the cert chain
   nsCOMPtr<nsIChannel> mChannel;
+  // EE certificate fingerprint
+  nsCString mFingerprint;
 };
 
 #endif // ContentSignatureVerifier_h
--- a/security/manager/ssl/tests/unit/test_content_signing.js
+++ b/security/manager/ssl/tests/unit/test_content_signing.js
@@ -10,16 +10,18 @@
 const PREF_SIGNATURE_ROOT = "security.content.signature.root_hash";
 
 const TEST_DATA_DIR = "test_content_signing/";
 
 const ONECRL_NAME = "oneCRL-signer.mozilla.org";
 const ABOUT_NEWTAB_NAME = "remotenewtab.content-signature.mozilla.org";
 var VERIFICATION_HISTOGRAM = Services.telemetry
                                      .getHistogramById("CONTENT_SIGNATURE_VERIFICATION_STATUS");
+var ERROR_HISTOGRAM = Services.telemetry
+                              .getKeyedHistogramById("CONTENT_SIGNATURE_VERIFICATION_ERRORS");
 
 function getSignatureVerifier() {
   return Cc["@mozilla.org/security/contentsignatureverifier;1"]
            .createInstance(Ci.nsIContentSignatureVerifier);
 }
 
 function setRoot(filename) {
   let cert = constructCertFromFile(filename);
@@ -30,27 +32,35 @@ function loadChain(prefix, names) {
   let chain = [];
   for (let name of names) {
     let filename = `${prefix}_${name}.pem`;
     chain.push(readFile(do_get_file(filename)));
   }
   return chain;
 }
 
-function check_telemetry(expected_index, expected) {
+function check_telemetry(expected_index, expected, expectedId = "") {
   for (let i = 0; i < 10; i++) {
     let expected_value = 0;
     if (i == expected_index) {
       expected_value = expected;
     }
+    let errorSnapshot = ERROR_HISTOGRAM.snapshot();
+    for (let k in errorSnapshot) {
+      // We clear the histogram every time so there should be only this one
+      // category.
+      equal(k, expectedId);
+      equal(errorSnapshot[k].counts[i], expected_value);
+    }
     equal(VERIFICATION_HISTOGRAM.snapshot().counts[i], expected_value,
       "count " + i + ": " + VERIFICATION_HISTOGRAM.snapshot().counts[i] +
       " expected " + expected_value);
   }
   VERIFICATION_HISTOGRAM.clear();
+  ERROR_HISTOGRAM.clear();
 }
 
 function run_test() {
   // set up some data
   const DATA = readFile(do_get_file(TEST_DATA_DIR + "test.txt"));
   const GOOD_SIGNATURE = "p384ecdsa=" +
       readFile(do_get_file(TEST_DATA_DIR + "test.txt.signature"))
       .trim();
@@ -80,117 +90,117 @@ function run_test() {
 
   // Check signature verification works without error before the root is set
   VERIFICATION_HISTOGRAM.clear();
   let chain1 = oneCRLChain.join("\n");
   let verifier = getSignatureVerifier();
   ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1, ONECRL_NAME),
      "Before the root is set, signatures should fail to verify but not throw.");
   // Check for generic chain building error.
-  check_telemetry(6, 1);
+  check_telemetry(6, 1, "DA7EBEF3F52224744D6C67D85162E2F6B234A1B15A8EEFAE81DB7BD6C8DB7531");
 
   setRoot(TEST_DATA_DIR + "content_signing_root.pem");
 
   // Check good signatures from good certificates with the correct SAN
   verifier = getSignatureVerifier();
   ok(verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1, ONECRL_NAME),
      "A OneCRL signature should verify with the OneCRL chain");
   let chain2 = remoteNewTabChain.join("\n");
   verifier = getSignatureVerifier();
   ok(verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain2,
                                      ABOUT_NEWTAB_NAME),
      "A newtab signature should verify with the newtab chain");
   // Check for valid signature
-  check_telemetry(0, 2);
+  check_telemetry(0, 2, "EEE207A9F4D1DC1FB71222B42C3DA4D2DC41DDDF75F4B7137D290B3B1317CDB3");
 
   // Check a bad signature when a good chain is provided
   chain1 = oneCRLChain.join("\n");
   verifier = getSignatureVerifier();
   ok(!verifier.verifyContentSignature(DATA, BAD_SIGNATURE, chain1, ONECRL_NAME),
      "A bad signature should not verify");
   // Check for invalid signature
-  check_telemetry(1, 1);
+  check_telemetry(1, 1, "DA7EBEF3F52224744D6C67D85162E2F6B234A1B15A8EEFAE81DB7BD6C8DB7531");
 
   // Check a good signature from cert with good SAN but a different key than the
   // one used to create the signature
   let badKeyChain = oneCRLBadKeyChain.join("\n");
   verifier = getSignatureVerifier();
   ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, badKeyChain,
                                       ONECRL_NAME),
      "A signature should not verify if the signing key is wrong");
   // Check for wrong key in cert.
-  check_telemetry(9, 1);
+  check_telemetry(9, 1, "64012AA308FF36A629FAF47EE3F4F6541E5FC88387B2B2D70B9497016F00A9E5");
 
   // Check a good signature from cert with good SAN but a different key than the
   // one used to create the signature (this time, an RSA key)
   let rsaKeyChain = oneCRLBadKeyChain.join("\n");
   verifier = getSignatureVerifier();
   ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, rsaKeyChain,
                                       ONECRL_NAME),
      "A signature should not verify if the signing key is wrong (RSA)");
   // Check for wrong key in cert.
-  check_telemetry(9, 1);
+  check_telemetry(9, 1, "64012AA308FF36A629FAF47EE3F4F6541E5FC88387B2B2D70B9497016F00A9E5");
 
   // Check a good signature from cert with good SAN but with chain missing root
   let missingRoot = [oneCRLChain[0], oneCRLChain[1]].join("\n");
   verifier = getSignatureVerifier();
   ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, missingRoot,
                                       ONECRL_NAME),
      "A signature should not verify if the chain is incomplete (missing root)");
   // Check for generic chain building error.
-  check_telemetry(6, 1);
+  check_telemetry(6, 1, "DA7EBEF3F52224744D6C67D85162E2F6B234A1B15A8EEFAE81DB7BD6C8DB7531");
 
   // Check a good signature from cert with good SAN but with no path to root
   let missingInt = [oneCRLChain[0], oneCRLChain[2]].join("\n");
   verifier = getSignatureVerifier();
   ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, missingInt,
                                       ONECRL_NAME),
      "A signature should not verify if the chain is incomplete (missing int)");
   // Check for generic chain building error.
-  check_telemetry(6, 1);
+  check_telemetry(6, 1, "DA7EBEF3F52224744D6C67D85162E2F6B234A1B15A8EEFAE81DB7BD6C8DB7531");
 
   // Check good signatures from good certificates with the wrong SANs
   chain1 = oneCRLChain.join("\n");
   verifier = getSignatureVerifier();
   ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
                                       ABOUT_NEWTAB_NAME),
      "A OneCRL signature should not verify if we require the newtab SAN");
   // Check for invalid EE cert.
-  check_telemetry(7, 1);
+  check_telemetry(7, 1, "DA7EBEF3F52224744D6C67D85162E2F6B234A1B15A8EEFAE81DB7BD6C8DB7531");
 
   chain2 = remoteNewTabChain.join("\n");
   verifier = getSignatureVerifier();
   ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain2,
                                       ONECRL_NAME),
      "A newtab signature should not verify if we require the OneCRL SAN");
   // Check for invalid EE cert.
-  check_telemetry(7, 1);
+  check_telemetry(7, 1, "EEE207A9F4D1DC1FB71222B42C3DA4D2DC41DDDF75F4B7137D290B3B1317CDB3");
 
   // Check good signatures with good chains with some other invalid names
   verifier = getSignatureVerifier();
   ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1, ""),
      "A signature should not verify if the SANs do not match an empty name");
   // Check for invalid EE cert.
-  check_telemetry(7, 1);
+  check_telemetry(7, 1, "DA7EBEF3F52224744D6C67D85162E2F6B234A1B15A8EEFAE81DB7BD6C8DB7531");
 
   // Test expired certificate.
   let chainExpired = expiredOneCRLChain.join("\n");
   verifier = getSignatureVerifier();
   ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chainExpired, ""),
      "A signature should not verify if the signing certificate is expired");
   // Check for expired cert.
-  check_telemetry(4, 1);
+  check_telemetry(4, 1, "EB32151498D7F8E60D9342700B994F152E2429568ED3B128538CBB167FAD02EE");
 
   // Test not valid yet certificate.
   let chainNotValidYet = notValidYetOneCRLChain.join("\n");
   verifier = getSignatureVerifier();
   ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chainNotValidYet, ""),
      "A signature should not verify if the signing certificate is not valid yet");
   // Check for not yet valid cert.
-  check_telemetry(5, 1);
+  check_telemetry(5, 1, "8CC04E15EB0C44AFA4C5DE6C24C468EED8F7F44CB4451A80496826EFA88E8F87");
 
   let relatedName = "subdomain." + ONECRL_NAME;
   verifier = getSignatureVerifier();
   ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
                                       relatedName),
      "A signature should not verify if the SANs do not match a related name");
 
   let randomName = "\xb1\x9bU\x1c\xae\xaa3\x19H\xdb\xed\xa1\xa1\xe0\x81\xfb" +
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-1b20549e1075
+NSS_3_36_BETA3
--- a/security/nss/automation/release/nspr-version.txt
+++ b/security/nss/automation/release/nspr-version.txt
@@ -1,9 +1,9 @@
-4.18
+4.19
 
 # The first line of this file must contain the human readable NSPR
 # version number, which is the minimum required version of NSPR
 # that is supported by this version of NSS.
 #
 # This information is used by release automation,
 # when creating an NSS source archive.
 #
--- a/security/nss/automation/taskcluster/docker-hacl/Dockerfile
+++ b/security/nss/automation/taskcluster/docker-hacl/Dockerfile
@@ -4,17 +4,17 @@ MAINTAINER Franziskus Kiefer <franziskus
 # Based on the HACL* image from Benjamin Beurdouche and
 # the original F* formula with Daniel Fabian
 
 # Pinned versions of HACL* (F* and KreMLin are pinned as submodules)
 ENV haclrepo https://github.com/mitls/hacl-star.git
 
 # Define versions of dependencies
 ENV opamv 4.04.2
-ENV haclversion dcd48329d535727dbde93877b124c5ec4a7a2b20
+ENV haclversion 104de0fbc83939a5e76012d64e3db2b3c0524bd1
 
 # Install required packages and set versions
 ADD setup.sh /tmp/setup.sh
 RUN bash /tmp/setup.sh
 
 # Create user, add scripts.
 RUN useradd -ms /bin/bash worker
 WORKDIR /home/worker
--- a/security/nss/automation/taskcluster/graph/src/extend.js
+++ b/security/nss/automation/taskcluster/graph/src/extend.js
@@ -72,33 +72,34 @@ queue.filter(task => {
     }
 
     // No mac
     if (task.platform == "mac") {
       return false;
     }
   }
 
-  if (task.tests == "fips" && task.platform == "mac") {
+  if (task.tests == "fips" &&
+     (task.platform == "mac" || task.platform == "aarch64")) {
     return false;
   }
 
   // Only old make builds have -Ddisable_libpkix=0 and can run chain tests.
   if (task.tests == "chains" && task.collection != "make") {
     return false;
   }
 
   if (task.group == "Test") {
     // Don't run test builds on old make platforms, and not for fips gyp.
     if (task.collection == "make" || task.collection == "fips") {
       return false;
     }
   }
 
-  // Don't run additional hardware tests on ARM (we don't have anything there).
+  // Don't run all additional hardware tests on ARM.
   if (task.group == "Cipher" && task.platform == "aarch64" && task.env &&
       (task.env.NSS_DISABLE_PCLMUL == "1" || task.env.NSS_DISABLE_HW_AES == "1"
        || task.env.NSS_DISABLE_AVX == "1")) {
     return false;
   }
 
   return true;
 });
@@ -266,16 +267,28 @@ export default async function main() {
         "/bin/bash",
         "-c",
         "bin/checkout.sh && nss/automation/taskcluster/scripts/build_gyp.sh --opt"
       ],
       collection: "opt",
     }, aarch64_base)
   );
 
+  await scheduleLinux("Linux AArch64 (debug, make)",
+    merge({
+      env: {USE_64: "1"},
+      command: [
+         "/bin/bash",
+         "-c",
+         "bin/checkout.sh && nss/automation/taskcluster/scripts/build.sh"
+      ],
+      collection: "make",
+    }, aarch64_base)
+  );
+
   await scheduleMac("Mac (opt)", {collection: "opt"}, "--opt");
   await scheduleMac("Mac (debug)", {collection: "debug"});
 }
 
 
 async function scheduleMac(name, base, args = "") {
   let mac_base = merge(base, {
     env: {
@@ -895,16 +908,23 @@ function scheduleTests(task_build, task_
     name: "Cipher tests", symbol: "NoPCLMUL", tests: "cipher",
     env: {NSS_DISABLE_PCLMUL: "1"}, group: "Cipher"
   }));
   queue.scheduleTask(merge(no_cert_base, {
     name: "Cipher tests", symbol: "NoAVX", tests: "cipher",
     env: {NSS_DISABLE_AVX: "1"}, group: "Cipher"
   }));
   queue.scheduleTask(merge(no_cert_base, {
+    name: "Cipher tests", symbol: "NoSSSE3|NEON", tests: "cipher",
+    env: {
+      NSS_DISABLE_ARM_NEON: "1",
+      NSS_DISABLE_SSSE3: "1"
+    }, group: "Cipher"
+  }));
+  queue.scheduleTask(merge(no_cert_base, {
     name: "EC tests", symbol: "EC", tests: "ec"
   }));
   queue.scheduleTask(merge(no_cert_base, {
     name: "Lowhash tests", symbol: "Lowhash", tests: "lowhash"
   }));
   queue.scheduleTask(merge(no_cert_base, {
     name: "SDR tests", symbol: "SDR", tests: "sdr"
   }));
--- a/security/nss/automation/taskcluster/graph/src/queue.js
+++ b/security/nss/automation/taskcluster/graph/src/queue.js
@@ -26,20 +26,21 @@ function fromNow(hours) {
 
 function parseRoutes(routes) {
   let rv = [
     `tc-treeherder.v2.${process.env.TC_PROJECT}.${process.env.NSS_HEAD_REVISION}.${process.env.NSS_PUSHLOG_ID}`,
     ...routes
   ];
 
   // Notify about failures (except on try).
-  if (process.env.TC_PROJECT != "nss-try") {
+  // Turned off, too noisy.
+  /*if (process.env.TC_PROJECT != "nss-try") {
     rv.push(`notify.email.${process.env.TC_OWNER}.on-failed`,
             `notify.email.${process.env.TC_OWNER}.on-exception`);
-  }
+  }*/
 
   return rv;
 }
 
 function parseFeatures(list) {
   return list.reduce((map, feature) => {
     map[feature] = true;
     return map;
deleted file mode 100644
--- a/security/nss/cmd/certcgi/HOWTO.txt
+++ /dev/null
@@ -1,137 +0,0 @@
-        How to setup your very own Cert-O-Matic Root CA server
-
- 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/.
-
-        How to setup your very own Cert-O-Matic Root CA server
-
-The program certcgi is part of a small test CA that is used inside 
-Netscape by the NSS development team.  That CA is affectionately known
-as "Cert-O-Matic" or "Cert-O-Matic II".  It presently runs on a server
-named interzone.mcom.com inside Netscape's firewall.
-
-If you wish to setup your own Cert-O-Matic, here are directions.
-
-Disclaimer:  This program does not follow good practices for root CAs.
-It should be used only for playing/testing and never for production use.
-Remember, you've been warned!
-
-Cert-O-Matic consists of some html files, shell scripts, one executable
-program that uses NSS and NSPR, the usual set of NSS .db files, and a file
-in which to remember the serial number of the last cert issued.  The 
-html files and the source to the executable program are in this directory.
-Sample shell scripts are shown below.  
-
-The shell scripts and executable program run as CGI "scripts".  The
-entire thing runs on an ordinary http web server.  It would also run on
-an https web server.  The shell scripts and html files must be
-customized for the server on which they run.
-
-The package assumes you have a "document root" directory $DOCROOT, and a
-"cgi-bin" directory $CGIBIN.  In this example, the document root is
-assumed to be located in /var/www/htdocs, and the cgi-bin directory in
-/var/www/cgi-bin.
-
-The server is assumed to run all cgi scripts as the user "nobody".
-The names of the cgi scripts run directly by the server all end in .cgi
-because some servers like it that way.
-
-Instructions:
-
-- Create directory $DOCROOT/certomatic
-- Copy the following files from nss/cmd/certcgi to $DOCROOT/certomatic
-        ca.html index.html main.html nscp_ext_form.html stnd_ext_form.html
-- Edit the html files, substituting the name of your own server for the
-  server named in those files.
-- In some web page (e.g. your server's home page), provide an html link to
-  $DOCROOT/certomatic/index.html. This is where users start to get their
-  own certs from certomatic.
-- give these files and directories appropriate permissions.
-
-- Create directories $CGIBIN/certomatic and $CGIBIN/certomatic/bin
-  make sure that $CGIBIN/certomatic is writable by "nobody"
-
-- Create a new set of NSS db files there with the following command:
-
-        certutil -N -d $CGIBIN/certomatic
-
-- when certutil prompts you for the password, enter the word foo
-  because that is compiled into the certcgi program.
-
-- Create the new Root CA cert with this command
-
-        certutil -S -x -d $CGIBIN/certomatic -n "Cert-O-Matic II" \
-        -s "CN=Cert-O-Matic II, O=Cert-O-Matic II" -t TCu,cu,cu -k rsa \
-        -g 1024 -m 10001 -v 60
-
-  (adjust the -g, -m and -v parameters to taste.  -s and -x must be as
-shown.)
-
-- dump out the new root CA cert in base64 encoding:
-
-        certutil -d $CGIBIN/certomatic -L -n "Cert-O-Matic II" -a > \
-          $CGIBIN/certomatic/root.cacert
-
-- In $CGIBIN/certomatic/bin add two shell scripts - one to download the
-  root CA cert on demand, and one to run the certcgi program.
-
-download.cgi, the script to install the root CA cert into a browser on
-demand, is this:
-
-#!/bin/sh
-echo "Content-type: application/x-x509-ca-cert"
-echo
-cat $CGIBIN/certomatic/root.cacert
-
-You'll have to put the real path into that cat command because CGIBIN
-won't be defined when this script is run by the server.
-
-certcgi.cgi, the script to run the certcgi program is similar to this:
-
-#!/bin/sh
-cd $CGIBIN/certomatic/bin
-LD_LIBRARY_PATH=$PLATFORM/lib
-export LD_LIBRARY_PATH
-$PLATFORM/bin/certcgi $* 2>&1
-
-Where $PLATFORM/lib is where the NSPR nad NSS DSOs are located, and
-$PLATFORM/bin is where certcgi is located.  PLATFORM is not defined when 
-the server runs this script, so you'll have to substitute the right value 
-in your script.  certcgi requires that the working directory be one level 
-below the NSS DBs, that is, the DBs are accessed in the directory "..".
-
-You'll want to provide an html link somewhere to the script that downloads
-the root.cacert file.  You'll probably want to put that next to the link
-that loads the index.html page.  On interzone, this is done with the 
-following html:
-
-<a href="/certomatic/index.html">Cert-O-Matic II Root CA server</a>
-<p>
-<a href="/cgi-bin/certomatic/bin/download.cgi">Download and trust Root CA
-certificate</a>
-
-The index.html file in this directory invokes the certcgi.cgi script with 
-the form post method, so if you change the name of the certcgi.cgi script, 
-you'll also have to change the index.html file in $DOCROOT/certomatic
-
-The 4 files used by the certcgi program (the 3 NSS DBs, and the serial
-number file) are not required to live in $CGIBIN/certomatic, but they are
-required to live in $CWD/.. when certcgi starts.
-
-Known bugs:
-
-1. Because multiple of these CAs exist simultaneously, it would be best if 
-they didn't all have to be called "Cert-O-Matic II", but that string is 
-presently hard coded into certcgi.c.
-
-2. the html files in this directory contain numerous extraneous <FORM> tags
-which appear to use the post method and have action URLS that are never
-actually used.  burp.cgi and echoform.cgi are never actually used.  This
-should be cleaned up.
-
-3. The html files use <layer> tags which are supported only in Netscape 
-Navigator and Netscape Communication 4.x browsers.  The html files do 
-not work as intended with Netscape 6.x, Mozilla or Microsoft IE browsers.
-The html files should be fixed to work with all those named browsers.
-
deleted file mode 100644
--- a/security/nss/cmd/certcgi/Makefile
+++ /dev/null
@@ -1,48 +0,0 @@
-#! gmake
-# 
-# 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/.
-
-#######################################################################
-# (1) Include initial platform-independent assignments (MANDATORY).   #
-#######################################################################
-
-include manifest.mn
-
-#######################################################################
-# (2) Include "global" configuration information. (OPTIONAL)          #
-#######################################################################
-
-include $(CORE_DEPTH)/coreconf/config.mk
-
-#######################################################################
-# (3) Include "component" configuration information. (OPTIONAL)       #
-#######################################################################
-
-#######################################################################
-# (4) Include "local" platform-dependent assignments (OPTIONAL).      #
-#######################################################################
-
-include ../platlibs.mk
-
-#######################################################################
-# (5) Execute "global" rules. (OPTIONAL)                              #
-#######################################################################
-
-include $(CORE_DEPTH)/coreconf/rules.mk
-
-#######################################################################
-# (6) Execute "component" rules. (OPTIONAL)                           #
-#######################################################################
-
-
-
-#######################################################################
-# (7) Execute "local" rules. (OPTIONAL).                              #
-#######################################################################
-
-
-
-include ../platrules.mk
-
deleted file mode 100644
--- a/security/nss/cmd/certcgi/ca.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<form method="post" name="ca_form" action="mailto:jerdonek@netscape.com">
-<input type="radio" name="caChoiceradio" value="SignWithDefaultkey" 
-    onClick="{parent.choice_change(this.form)}"> 
-    Use the Cert-O-matic certificate to issue the cert</p>
-<input type="radio" name="caChoiceradio" value="SignWithRandomChain" 
-    onClick="{parent.choice_change(this.form)}"> Use a 
-    <input type="text" size="2" maxsize="2" name="autoCAs"> CA long 
-    automatically generated chain ending with the Cert-O-Matic Cert 
-    (18 maximum)</p>
-<input type="radio" name="caChoiceradio" value="SignWithSpecifiedChain" 
-    onClick="{parent.choice_change(this.form)}"> Use a 
-    <input type="text" size="1" maxlength="1" name="manCAs" 
-    onChange="{parent.ca_num_change(this.value,this.form)}"> CA long 
-    user input chain ending in the Cert-O-Matic Cert.</p>
-</form>
deleted file mode 100644
--- a/security/nss/cmd/certcgi/ca_form.html
+++ /dev/null
@@ -1,357 +0,0 @@
-<html>
-<!-- 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/. -->
-    <form method="post" name="primary_form" action="http://interzone.mcom.com/burp.cgi">
-    <table border=0 cellspacing=10 cellpadding=0>
-    <tr>
-    <td>
-    Common Name:</td><td> <input type="text" name="name" onChange="{window.top.reset_subject('CN=', value, form)}"></p>
-    </td>
-    <td></td><td></td><td>
-    Mail: </td><td><input type="text" name="email" onChange="var temp;{if (email_type[0].checked) {temp = 'MAIL='} else {temp = 'E='}} ;{window.top.reset_subject(temp, value, form)}"></p>
-    RFC 1274<input type="radio" name="email_type" value="1" onClick="window.top.switch_mail(form)">
-    e-mail<input type="radio" name="email_type" value="2" checked onClick="window.top.switch_mail(form)"></td>
-    <tr>
-    <td>
-    Organization: </td><td>  <input type="text" name="org" onChange="{window.top.reset_subject('O=', value, form)}"></p></td>
-    <td></td><td></td><td>
-    Organizational Unit: </td><td><input type="text" name="org_unit" onChange="{window.top.reset_subject('OU=', value, form)}"></p></td>
-    <tr>
-    <td>
-    RFC 1274 UID: </td><td><input type="text" name="uid" onChange="{window.top.reset_subject('UID=', value, form)}"></p></td>
-    <td></td><td></td><td>
-    Locality: </td><td><input type="text" name="loc" onChange="{window.top.reset_subject('L=', value, form)}"></p></td>
-    <tr>
-    <td>
-    State or Province: </td><td><input type="text" name="state" onChange="{window.top.reset_subject('ST=', value, form)}"></p></td>
-    <td></td><td></td><td>
-    Country: </td><td><input type="text" size="2" maxsize="2" name="country" onChange="{window.top.reset_subject('C=', value, form)}"></p></td>
-    </table>
-    <table border=0 cellspacing=10 cellpadding=0>
-    <tr>
-    <td>
-    Serial Number:</p>
-    <DD>
-    <input type="radio" name="serial" value="auto" checked> Auto Generate</P>
-    <DD>
-    <input type="radio" name="serial" value="input">
-    Use this value: <input type="text" name="serial_value" size="8" maxlength="8"></p>
-    </td>
-    <td></td><td></td><td></td><td></td>
-    <td>
-    X.509 version:</p>
-    <DD>
-    <input type="radio" name="ver" value="1" checked> Version 1</p>
-    <DD>
-    <input type="radio" name="ver" value="3"> Version 3</P></td>
-    <td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td>
-    <td>
-    Key Type:</p>
-    <DD>
-    <input type="radio" name="keyType" value="rsa" checked> RSA</p>
-    <DD>
-    <input type="radio" name="keyType" value="dsa"> DSA</P></td>
-    </table>
-    DN: <input type="text" name="subject" size="70" onChange="{window.top.reset_subjectFields(form)}"></P>
-    <Select name="keysize">
-        <option>1024 (High Grade)
-        <option>768 (Medium Grade)
-        <option>512 (Low Grade)
-    </select> 
-    </p>
-  <hr>
-    </p>
-    <table  border=1 cellspacing=5 cellpadding=5>
-    <tr>
-    <td>
-    <b>Netscape Certificate Type: </b></p>
-    Activate extension: <input type="checkbox" name="netscape-cert-type"></P>
-    Critical: <input type="checkbox" name="netscape-cert-type-crit">
-    <td>
-    <input type="checkbox" name="netscape-cert-type-ssl-client"> SSL Client</P>
-    <input type="checkbox" name="netscape-cert-type-ssl-server"> SSL Server</P>
-    <input type="checkbox" name="netscape-cert-type-smime"> S/MIME</P>
-    <input type="checkbox" name="netscape-cert-type-object-signing"> Object Signing</P>    
-    <input type="checkbox" name="netscape-cert-type-reserved"> Reserved for future use (bit 4)</P>
-    <input type="checkbox" name="netscape-cert-type-ssl-ca"> SSL CA</P>
-    <input type="checkbox" name="netscape-cert-type-smime-ca"> S/MIME CA</P>
-    <input type="checkbox" name="netscape-cert-type-object-signing-ca"> Object Signing CA</P>
-    </tr>
-    <tr>
-    <td>
-    <b>Netscape Base URL:</b></p>
-    Activate extension: <input type="checkbox" name="netscape-base-url"></P>
-    Critical: <input type="checkbox" name="netscape-base-url-crit">
-    <td>
-    <input type="text" name="netscape-base-url-text" size="50">
-    </tr>
-    <tr>
-    <td>
-    <b>Netscape Revocation URL:</b></p>
-    Activate extension: <input type="checkbox" name="netscape-revocation-url"></P>
-    Critical: <input type="checkbox" name="netscape-revocation-url-crit">
-    <td>
-    <input type="text" name="netscape-revocation-url-text" size="50">
-    </tr>
-    <tr>
-    <td>    
-    <b>Netscape CA Revocation URL:</b></p>
-    Activate extension: <input type="checkbox" name="netscape-ca-revocation-url"></P>
-    Critical: <input type="checkbox" name="netscape-ca-revocation-url-crit">
-    <td>
-    <input type="text" name="netscape-ca-revocation-url-text" size="50">
-    </tr>
-    <tr>
-    <td>    
-    <b>Netscape Certificate Renewal URL:</b></p>
-    Activate extension: <input type="checkbox" name="netscape-cert-renewal-url"></P>
-    Critical: <input type="checkbox" name="netscape-cert-renewal-url-crit">
-    <td>
-    <input type="text" name="netscape-cert-renewal-url-text" size="50">
-    </tr>
-    <tr>
-    <td>
-    <b>Netscape CA Policy URL:</b></p>
-    Activate extension: <input type="checkbox" name="netscape-ca-policy-url"></P>
-    Critical: <input type="checkbox" name="netscape-ca-policy-url-crit">
-    <td>
-    <input type="text" name="netscape-ca-policy-url-text" size="50">
-    </tr>
-    <tr>
-    <td>
-    <b>Netscape SSL Server Name:</b></p>
-    Activate extension: <input type="checkbox" name="netscape-ssl-server-name"></P>
-    Critical: <input type="checkbox" name="netscape-ssl-server-name-crit">
-    <td>
-    <input type="text" name="netscape-ssl-server-name-text" size="50">
-    </tr>
-    <tr>
-    <td>
-    <b>Netscape Comment:</b></p>
-    Activate extension: <input type="checkbox" name="netscape-comment"></P>
-    Critical: <input type="checkbox" name="netscape-comment-crit">
-    <td>
-    <textarea name="netscape-comment-text" rows="5" cols="50"></textarea>
-    </tr>
-    </table>
-    </p>
-  <hr>
-    </p>
-    <table  border=1 cellspacing=5 cellpadding=5>
-    <form method="post" name="primary_form" action="http://interzone.mcom.com/burp.cgi">
-    <tr>
-    <td>
-    <b>Key Usage: </b></p>
-    Activate extension: <input type="checkbox" name="keyUsage"></P>
-    Critical: <input type="checkbox" name="keyUsage-crit">
-    <td>
-    <input type="checkbox" name="keyUsage-digitalSignature"> Digital Signature</P>
-    <input type="checkbox" name="keyUsage-nonRepudiation"> Non Repudiation</P>    
-    <input type="checkbox" name="keyUsage-keyEncipherment"> Key Encipherment</P>    
-    <input type="checkbox" name="keyUsage-dataEncipherment"> Data Encipherment</P>
-    <input type="checkbox" name="keyUsage-keyAgreement"> Key Agreement</P>
-    <input type="checkbox" name="keyUsage-keyCertSign"> Key Certificate Signing</P>
-    <input type="checkbox" name="keyUsage-cRLSign"> CRL Signing</P>
-    </tr>
-    <tr>
-    <td>
-    <b>Extended Key Usage: </b></p>
-    Activate extension: <input type="checkbox" name="extKeyUsage"></P>
-    Critical: <input type="checkbox" name="extKeyUsage-crit">
-    <td>
-    <input type="checkbox" name="extKeyUsage-serverAuth"> Server Auth</P>
-    <input type="checkbox" name="extKeyUsage-clientAuth"> Client Auth</P>
-    <input type="checkbox" name="extKeyUsage-codeSign"> Code Signing</P>
-    <input type="checkbox" name="extKeyUsage-emailProtect"> Email Protection</P>
-    <input type="checkbox" name="extKeyUsage-timeStamp"> Timestamp</P>
-    <input type="checkbox" name="extKeyUsage-ocspResponder"> OCSP Responder</P>
-    <input type="checkbox" name="extKeyUsage-NS-govtApproved"> Step-up</P>
-    <input type="checkbox" name="extKeyUsage-msTrustListSign"> Microsoft Trust List Signing</P>
-    </tr>
-    <tr>
-    <td>
-    <b>Basic Constraints:</b></p>
-    Activate extension: <input type="checkbox" name="basicConstraints"></P>
-    Critical: <input type="checkbox" name="basicConstraints-crit">
-    <td>
-    CA:</p>
-    <dd><input type=radio name="basicConstraints-cA-radio" value="CA"> True</p>
-    <dd><input type=radio name="basicConstraints-cA-radio" value="NotCA"> False</p>
-    <input type="checkbox" name="basicConstraints-pathLengthConstraint">
-     Include Path length:  <input type="text" name="basicConstraints-pathLengthConstraint-text" size="2"></p>
-    </tr>
-    <tr>
-    <td>
-    <b>Authority Key Identifier:</b></p>
-    Activate extension: <input type="checkbox" name="authorityKeyIdentifier">
-    <td>
-    <input type="radio" name="authorityKeyIdentifier-radio" value="keyIdentifier"> Key Identider</p>
-    <input type="radio" name="authorityKeyIdentifier-radio" value="authorityCertIssuer"> Issuer Name and Serial number</p>
-    </tr>
-    <tr>
-    <td>    
-    <b>Subject Key Identifier:</b></p>
-    Activate extension: <input type="checkbox" name="subjectKeyIdentifier">
-    <td>
-    Key Identifier: 
-    <input type="text" name="subjectKeyIdentifier-text"></p>
-    This is an:<p>
-    <dd><dd><input type="radio" name="subjectKeyIdentifier-radio" value="ascii"> ascii text value<p>
-    <dd><dd><input type="radio" name="subjectKeyIdentifier-radio" value="hex"> hex value<p>
-    </tr>
-    <tr>
-    <td>    
-    <b>Private Key Usage Period:</b></p>
-    Activate extension: <input type="checkbox" name="privKeyUsagePeriod"></p>
-    Critical: <input type="checkbox" name="privKeyUsagePeriod-crit">
-    <td>
-    Use:</p>
-    <dd><input type="radio" name="privKeyUsagePeriod-radio" value="notBefore"> Not Before</p>
-    <dd><input type="radio" name="privKeyUsagePeriod-radio" value="notAfter"> Not After</p>
-    <dd><input type="radio" name="privKeyUsagePeriod-radio" value="both" > Both</p>
-    <b>Not to be used to sign before:</b></p>
-    <dd><input type="radio" name="privKeyUsagePeriod-notBefore-radio" value="auto"> Set to time of certificate issue</p>
-    <dd><input type="radio" name="privKeyUsagePeriod-notBefore-radio" value="manual"> Use This value</p>
-    <dd><dd>(YYYY/MM/DD HH:MM:SS): 
-    <input type="text" name="privKeyUsagePeriod-notBefore-year" size="4" maxlength="4">/
-    <input type="text" name="privKeyUsagePeriod-notBefore-month" size="2" maxlength="2">/
-    <input type="text" name="privKeyUsagePeriod-notBefore-day" size="2" maxlength="2"> 
-    <input type="text" name="privKeyUsagePeriod-notBefore-hour" size="2" maxlength="2">:
-    <input type="text" name="privKeyUsagePeriod-notBefore-minute" size="2" maxlength="2">:
-    <input type="text" name="privKeyUsagePeriod-notBefore-second" size="2" maxlength="2"></p>
-    <b>Not to be used to sign after:</b></p>
-    <dd>(YYYY/MM/DD HH:MM:SS): 
-    <input type="text" name="privKeyUsagePeriod-notAfter-year" size="4" maxlength="4">/
-    <input type="text" name="privKeyUsagePeriod-notAfter-month" size="2" maxlength="2">/
-    <input type="text" name="privKeyUsagePeriod-notAfter-day" size="2" maxlength="2"> 
-    <input type="text" name="privKeyUsagePeriod-notAfter-hour" size="2" maxlength="2">:
-    <input type="text" name="privKeyUsagePeriod-notAfter-minute" size="2" maxlength="2">:
-    <input type="text" name="privKeyUsagePeriod-notAfter-second" size="2" maxlength="2"></p>
-    </tr>
-    <tr>
-    <td>
-    <b>Subject Alternative Name:</b></p>
-    Activate extension: <input type="checkbox" name="SubAltName"></P>
-    Critical: <input type="checkbox" name="SubAltName-crit">
-    <td>
-      <table>
-      <tr>
-      <td>
-      General Names:</p>
-      <select name="SubAltNameSelect" multiple size="10">
-      </select></p></p>
-      <input type="button" name="SubAltName-add" value="Add" onClick="{parent.addSubAltName(this.form)}">
-      <input type="button" name="SubAltName-delete" value="Delete" onClick="parent.deleteSubAltName(this.form)">
-      </td><td>
-        <table><tr><td>
-        Name Type: </td></tr><tr><td>
-        <input type="radio" name="SubAltNameRadio" value="otherName" onClick="parent.setSubAltNameType(form)"> Other Name, 
-        OID: <input type="text" name="SubAltNameOtherNameOID" size="6"> </td><td>
-        <input type="radio" name="SubAltNameRadio" value="rfc822Name" onClick="parent.setSubAltNameType(form)"> RFC 822 Name</td></tr><td>
-        <input type="radio" name="SubAltNameRadio" value="dnsName" onClick="parent.setSubAltNameType(form)"> DNS Name </td><td>
-        <input type="radio" name="SubAltNameRadio" value="x400" onClick="parent.setSubAltNameType(form)"> X400 Address</td></tr><td>
-        <input type="radio" name="SubAltNameRadio" value="directoryName" onClick="parent.setSubAltNameType(form)"> Directory Name</td><td>
-        <input type="radio" name="SubAltNameRadio" value="ediPartyName" onClick="parent.setSubAltNameType(form)"> EDI Party Name</td></tr><td>
-        <input type="radio" name="SubAltNameRadio" value="URL" onClick="parent.setSubAltNameType(form)"> Uniform Resource Locator</td><td>
-        <input type="radio" name="SubAltNameRadio" value="ipAddress" onClick="parent.setSubAltNameType(form)"> IP Address</td></tr><td>
-        <input type="radio" name="SubAltNameRadio" value="regID"onClick="parent.setSubAltNameType(form)"> Registered ID</td><td>
-	<input type="radio" name="SubAltNameRadio" value="nscpNickname" onClick="parent.setSubAltNameType(form)"> Netscape Certificate Nickname</td><td></tr>
-        </table>
-      Name: <input type="text" name="SubAltNameText">
-        Binary Encoded: <input type="checkbox" name="SubAltNameDataType" value="binary" onClick="parent.setSubAltNameType(form)"></p>
-      </tr>
-      </table>
-    </tr>
-
-
-    <tr>
-    <td>
-    <b>Issuer Alternative Name:</b></p>
-    Activate extension: <input type="checkbox" name="IssuerAltName"></P>
-    Critical: <input type="checkbox" name="IssuerAltName-crit">
-    <td>
-      <input type="radio" name="IssuerAltNameSourceRadio" value="auto"> Use the Subject Alternative Name from the Issuers Certificate</p>
-      <input type="radio" name="IssuerAltNameSourceRadio" value="man"> Use this Name:
-      <table>
-      <tr>
-      <td>
-      General Names:</p>
-      <select name="IssuerAltNameSelect" multiple size="10">
-      </select></p></p>
-      <input type="button" name="IssuerAltName-add" value="Add" onClick="{parent.addIssuerAltName(this.form)}">
-      <input type="button" name="IssuerAltName-delete" value="Delete" onClick="parent.deleteIssuerAltName(this.form)">
-      </td><td>
-        <table><tr><td>
-        Name Type: </td></tr><tr><td>
-        <input type="radio" name="IssuerAltNameRadio" value="otherName" onClick="parent.setIssuerAltNameType(form)"> Other Name, 
-        OID: <input type="text" name="IssuerAltNameOtherNameOID" size="6"> </td><td>
-        <input type="radio" name="IssuerAltNameRadio" value="rfc822Name" onClick="parent.setIssuerAltNameType(form)"> RFC 822 Name</td></tr><td>
-        <input type="radio" name="IssuerAltNameRadio" value="dnsName" onClick="parent.setIssuerAltNameType(form)"> DNS Name </td><td>
-        <input type="radio" name="IssuerAltNameRadio" value="x400" onClick="parent.setIssuerAltNameType(form)"> X400 Address</td></tr><td>
-        <input type="radio" name="IssuerAltNameRadio" value="directoryName" onClick="parent.setIssuerAltNameType(form)"> Directory Name</td><td>
-        <input type="radio" name="IssuerAltNameRadio" value="ediPartyName" onClick="parent.setIssuerAltNameType(form)"> EDI Party Name</td></tr><td>
-        <input type="radio" name="IssuerAltNameRadio" value="URL" onClick="parent.setIssuerAltNameType(form)"> Uniform Resource Locator</td><td>
-        <input type="radio" name="IssuerAltNameRadio" value="ipAddress" onClick="parent.setIssuerAltNameType(form)"> IP Address</td></tr><td>
-        <input type="radio" name="IssuerAltNameRadio" value="regID" onClick="parent.setIssuerAltNameType(form)"> Registered ID</td><td></tr>
-        </table>
-      Name: <input type="text" name="IssuerAltNameText"> 
-        Binary Encoded: <input type="checkbox" name="IssuerAltNameDataType" value="binary" onClick="parent.setIssuerAltNameType(form)"></p>
-      </tr>
-      </table>
-    </tr>
-
-    <tr>
-    <td>
-    <b>Name Constraints:</b></p>
-    Activate extension: <input type="checkbox" name="NameConstraints"></P>
-    <td>
-      <table>
-      <tr>
-      <td>
-      Name Constraints:</p>
-
-
-      <select name="NameConstraintSelect" multiple size="10">
-      </select></p></p>
-      <input type="button" name="NameConstraint-add" value="Add" onClick="{parent.addNameConstraint(this.form)}">
-      <input type="button" name="NameConstraint-delete" value="Delete" onClick="parent.deleteNameConstraint(this.form)">
-      </td><td>
-        <table><tr><td>
-        Name Type: </td></tr><tr><td>
-        <input type="radio" name="NameConstraintRadio" value="otherName" onClick="parent.setNameConstraintNameType(form)"> Other Name,
-        OID: <input type="text" name="NameConstraintOtherNameOID" size="6">  </td><td>
-        <input type="radio" name="NameConstraintRadio" value="rfc822Name" onClick="parent.setNameConstraintNameType(form)"> RFC 822 Name</td></tr><td>
-        <input type="radio" name="NameConstraintRadio" value="dnsName" onClick="parent.setNameConstraintNameType(form)"> DNS Name </td><td>
-        <input type="radio" name="NameConstraintRadio" value="x400" onClick="parent.setNameConstraintNameType(form)"> X400 Address</td></tr><td>
-        <input type="radio" name="NameConstraintRadio" value="directoryName" onClick="parent.setNameConstraintNameType(form)"> Directory Name</td><td>
-        <input type="radio" name="NameConstraintRadio" value="ediPartyName" onClick="parent.setNameConstraintNameType(form)"> EDI Party Name</td></tr><td>
-        <input type="radio" name="NameConstraintRadio" value="URL" onClick="parent.setNameConstraintNameType(form)"> Uniform Resource Locator</td><td>
-        <input type="radio" name="NameConstraintRadio" value="ipAddress" onClick="parent.setNameConstraintNameType(form)"> IP Address</td></tr><td>
-        <input type="radio" name="NameConstraintRadio" value="regID" onClick="parent.setNameConstraintNameType(form)"> Registered ID</td><td></tr>
-        </table>
-      Name: <input type="text" name="NameConstraintText">
-        Binary Encoded: <input type="checkbox" name="NameConstraintNameDataType" value="binary" onClick="parent.setNameConstraintNameType(form)"></p>
-      Constraint type:<p>
-      <dd><input type="radio" name="NameConstraintTypeRadio" value="permited"> permited<p>
-      <dd><input type="radio" name="NameConstraintTypeRadio" value="excluded"> excluded<p>
-      Minimum: <input type="text" name="NameConstraintMin" size="8" maxlength="8"></p>
-      Maximum: <input type="text" name="NameConstraintMax" size="8" maxlength="8"></p>
-
-
-
-      </tr>
-      </table>
-    </tr>
-    </table>
-    </form>
-
-
-
-
-
-
-
-
-
-
deleted file mode 100644
--- a/security/nss/cmd/certcgi/certcgi.c
+++ /dev/null
@@ -1,2245 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* Cert-O-Matic CGI */
-
-#include "nspr.h"
-#include "prtypes.h"
-#include "prtime.h"
-#include "prlong.h"
-
-#include "pk11func.h"
-#include "cert.h"
-#include "cryptohi.h"
-#include "secoid.h"
-#include "secder.h"
-#include "genname.h"
-#include "xconst.h"
-#include "secutil.h"
-#include "pk11pqg.h"
-#include "certxutl.h"
-#include "nss.h"
-
-/* #define TEST           1 */
-/* #define FILEOUT        1 */
-/* #define OFFLINE        1 */
-#define START_FIELDS 100
-#define PREFIX_LEN 6
-#define SERIAL_FILE "../serial"
-#define DB_DIRECTORY ".."
-
-static char *progName;
-
-typedef struct PairStr Pair;
-
-struct PairStr {
-    char *name;
-    char *data;
-};
-
-char prefix[PREFIX_LEN];
-
-const SEC_ASN1Template CERTIA5TypeTemplate[] = {
-    { SEC_ASN1_IA5_STRING }
-};
-
-SECKEYPrivateKey *privkeys[9] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                                  NULL, NULL };
-
-#ifdef notdef
-const SEC_ASN1Template CERT_GeneralNameTemplate[] = {
-    { SEC_ASN1_SEQUENCE_OF, 0, SEC_AnyTemplate }
-};
-#endif
-
-static void
-error_out(char *error_string)
-{
-    printf("Content-type: text/plain\n\n");
-    printf("%s", error_string);
-    fflush(stderr);
-    fflush(stdout);
-    exit(1);
-}
-
-static void
-error_allocate(void)
-{
-    error_out("ERROR: Unable to allocate memory");
-}
-
-static char *
-make_copy_string(char *read_pos,
-                 int length,
-                 char sentinal_value)
-/* copys string from to a new string it creates and
-       returns a pointer to the new string */
-{
-    int remaining = length;
-    char *write_pos;
-    char *new;
-
-    new = write_pos = (char *)PORT_Alloc(length);
-    if (new == NULL) {
-        error_allocate();
-    }
-    while (*read_pos != sentinal_value) {
-        if (remaining == 1) {
-            remaining += length;
-            length = length * 2;
-            new = PORT_Realloc(new, length);
-            if (new == NULL) {
-                error_allocate();
-            }
-            write_pos = new + length - remaining;
-        }
-        *write_pos = *read_pos;
-        ++write_pos;
-        ++read_pos;
-        remaining = remaining - 1;
-    }
-    *write_pos = '\0';
-    return new;
-}
-
-static SECStatus
-clean_input(Pair *data)
-/* converts the non-alphanumeric characters in a form post
-       from hex codes back to characters */
-{
-    int length;
-    int hi_digit;
-    int low_digit;
-    char character;
-    char *begin_pos;
-    char *read_pos;
-    char *write_pos;
-    PRBool name = PR_TRUE;
-
-    begin_pos = data->name;
-    while (begin_pos != NULL) {
-        length = strlen(begin_pos);
-        read_pos = write_pos = begin_pos;
-        while ((read_pos - begin_pos) < length) {
-            if (*read_pos == '+') {
-                *read_pos = ' ';
-            }
-            if (*read_pos == '%') {
-                hi_digit = *(read_pos + 1);
-                low_digit = *(read_pos + 2);
-                read_pos += 3;
-                if (isdigit(hi_digit)) {
-                    hi_digit = hi_digit - '0';
-                } else {
-                    hi_digit = toupper(hi_digit);
-                    if (isxdigit(hi_digit)) {
-                        hi_digit = (hi_digit - 'A') + 10;
-                    } else {
-                        error_out("ERROR: Form data incorrectly formated");
-                    }
-                }
-                if (isdigit(low_digit)) {
-                    low_digit = low_digit - '0';
-                } else {
-                    low_digit = toupper(low_digit);
-                    if ((low_digit >= 'A') && (low_digit <= 'F')) {
-                        low_digit = (low_digit - 'A') + 10;
-                    } else {
-                        error_out("ERROR: Form data incorrectly formated");
-                    }
-                }
-                character = (hi_digit << 4) | low_digit;
-                if (character != 10) {
-                    *write_pos = character;
-                    ++write_pos;
-                }
-            } else {
-                *write_pos = *read_pos;
-                ++write_pos;
-                ++read_pos;
-            }
-        }
-        *write_pos = '\0';
-        if (name == PR_TRUE) {
-            begin_pos = data->data;
-            name = PR_FALSE;
-        } else {
-            data++;
-            begin_pos = data->name;
-            name = PR_TRUE;
-        }
-    }
-    return SECSuccess;
-}
-
-static char *
-make_name(char *new_data)
-/* gets the next field name in the input string and returns
-       a pointer to a string containing a copy of it */
-{
-    int length = 20;
-    char *name;
-
-    name = make_copy_string(new_data, length, '=');
-    return name;
-}
-
-static char *
-make_data(char *new_data)
-/* gets the data for the next field in the input string
-       and returns a pointer to a string containing it */
-{
-    int length = 100;
-    char *data;
-    char *read_pos;
-
-    read_pos = new_data;
-    while (*(read_pos - 1) != '=') {
-        ++read_pos;
-    }
-    data = make_copy_string(read_pos, length, '&');
-    return data;
-}
-
-static Pair
-make_pair(char *new_data)
-/* makes a pair name/data pair from the input string */
-{
-    Pair temp;
-
-    temp.name = make_name(new_data);
-    temp.data = make_data(new_data);
-    return temp;
-}
-
-static Pair *
-make_datastruct(char *data, int len)
-/* parses the input from the form post into a data
-       structure of field name/data pairs */
-{
-    Pair *datastruct;
-    Pair *current;
-    char *curr_pos;
-    int fields = START_FIELDS;
-    int remaining = START_FIELDS;
-
-    curr_pos = data;
-    datastruct = current = (Pair *)PORT_Alloc(fields * sizeof(Pair));
-    if (datastruct == NULL) {
-        error_allocate();
-    }
-    while (curr_pos - data < len) {
-        if (remaining == 1) {
-            remaining += fields;
-            fields = fields * 2;
-            datastruct = (Pair *)PORT_Realloc(datastruct, fields * sizeof(Pair));
-            if (datastruct == NULL) {
-                error_allocate();
-            }
-            current = datastruct + (fields - remaining);
-        }
-        *current = make_pair(curr_pos);
-        while (*curr_pos != '&') {
-            ++curr_pos;
-        }
-        ++curr_pos;
-        ++current;
-        remaining = remaining - 1;
-    }
-    current->name = NULL;
-    return datastruct;
-}
-
-static char *
-return_name(Pair *data_struct,
-            int n)
-/* returns a pointer to the name of the nth
-       (starting from 0) item in the data structure */
-{
-    char *name;
-
-    if ((data_struct + n)->name != NULL) {
-        name = (data_struct + n)->name;
-        return name;
-    } else {
-        return NULL;
-    }
-}
-
-static char *
-return_data(Pair *data_struct, int n)
-/* returns a pointer to the data of the nth (starting from 0)
-       itme in the data structure */
-{
-    char *data;
-
-    data = (data_struct + n)->data;
-    return data;
-}
-
-static char *
-add_prefix(char *field_name)
-{
-    extern char prefix[PREFIX_LEN];
-    int i = 0;
-    char *rv;
-    char *write;
-
-    rv = write = PORT_Alloc(PORT_Strlen(prefix) + PORT_Strlen(field_name) + 1);
-    for (i = 0; i < PORT_Strlen(prefix); i++) {
-        *write = prefix[i];
-        write++;
-    }
-    *write = '\0';
-    rv = PORT_Strcat(rv, field_name);
-    return rv;
-}
-
-static char *
-find_field(Pair *data,
-           char *field_name,
-           PRBool add_pre)
-/* returns a pointer to the data of the first pair
-       thats name matches the string it is passed */
-{
-    int i = 0;
-    char *retrieved;
-    int found = 0;
-
-    if (add_pre) {
-        field_name = add_prefix(field_name);
-    }
-    while (return_name(data, i) != NULL) {
-        if (PORT_Strcmp(return_name(data, i), field_name) == 0) {
-            retrieved = return_data(data, i);
-            found = 1;
-            break;
-        }
-        i++;
-    }
-    if (!found) {
-        retrieved = NULL;
-    }
-    return retrieved;
-}
-
-static PRBool
-find_field_bool(Pair *data,
-                char *fieldname,
-                PRBool add_pre)
-{
-    char *rv;
-
-    rv = find_field(data, fieldname, add_pre);
-
-    if ((rv != NULL) && (PORT_Strcmp(rv, "true")) == 0) {
-        return PR_TRUE;
-    } else {
-        return PR_FALSE;
-    }
-}
-
-static CERTCertificateRequest *
-makeCertReq(Pair *form_data,
-            int which_priv_key)
-/* makes and encodes a certrequest */
-{
-
-    PK11SlotInfo *slot;
-    CERTCertificateRequest *certReq = NULL;
-    CERTSubjectPublicKeyInfo *spki;
-    SECKEYPrivateKey *privkey = NULL;
-    SECKEYPublicKey *pubkey = NULL;
-    CERTName *name;
-    char *key;
-    extern SECKEYPrivateKey *privkeys[9];
-    int keySizeInBits;
-    char *challenge = "foo";
-    SECStatus rv = SECSuccess;
-    PQGParams *pqgParams = NULL;
-    PQGVerify *pqgVfy = NULL;
-
-    name = CERT_AsciiToName(find_field(form_data, "subject", PR_TRUE));
-    if (name == NULL) {
-        error_out("ERROR: Unable to create Subject Name");
-    }
-    key = find_field(form_data, "key", PR_TRUE);
-    if (key == NULL) {
-        switch (*find_field(form_data, "keysize", PR_TRUE)) {
-            case '0':
-                keySizeInBits = 2048;
-                break;
-            case '1':
-                keySizeInBits = 1024;
-                break;
-            case '2':
-                keySizeInBits = 512;
-                break;
-            default:
-                error_out("ERROR: Unsupported Key length selected");
-        }
-        if (find_field_bool(form_data, "keyType-dsa", PR_TRUE)) {
-            rv = PK11_PQG_ParamGen(keySizeInBits, &pqgParams, &pqgVfy);
-            if (rv != SECSuccess) {
-                error_out("ERROR: Unable to generate PQG parameters");
-            }
-            slot = PK11_GetBestSlot(CKM_DSA_KEY_PAIR_GEN, NULL);
-            privkey = PK11_GenerateKeyPair(slot, CKM_DSA_KEY_PAIR_GEN,
-                                           pqgParams, &pubkey, PR_FALSE,
-                                           PR_TRUE, NULL);
-        } else {
-            privkey = SECKEY_CreateRSAPrivateKey(keySizeInBits, &pubkey, NULL);
-        }
-        privkeys[which_priv_key] = privkey;
-        spki = SECKEY_CreateSubjectPublicKeyInfo(pubkey);
-    } else {
-        spki = SECKEY_ConvertAndDecodePublicKeyAndChallenge(key, challenge,
-                                                            NULL);
-        if (spki == NULL) {
-            error_out("ERROR: Unable to decode Public Key and Challenge String");
-        }
-    }
-    certReq = CERT_CreateCertificateRequest(name, spki, NULL);
-    if (certReq == NULL) {
-        error_out("ERROR: Unable to create Certificate Request");
-    }
-    if (pubkey != NULL) {
-        SECKEY_DestroyPublicKey(pubkey);
-    }
-    if (spki != NULL) {
-        SECKEY_DestroySubjectPublicKeyInfo(spki);
-    }
-    if (pqgParams != NULL) {
-        PK11_PQG_DestroyParams(pqgParams);
-    }
-    if (pqgVfy != NULL) {
-        PK11_PQG_DestroyVerify(pqgVfy);
-    }
-    return certReq;
-}
-
-static CERTCertificate *
-MakeV1Cert(CERTCertDBHandle *handle,
-           CERTCertificateRequest *req,
-           char *issuerNameStr,
-           PRBool selfsign,
-           int serialNumber,
-           int warpmonths,
-           Pair *data)
-{
-    CERTCertificate *issuerCert = NULL;
-    CERTValidity *validity;
-    CERTCertificate *cert = NULL;
-    PRExplodedTime printableTime;
-    PRTime now,
-        after;
-    if (!selfsign) {
-        issuerCert = CERT_FindCertByNameString(handle, issuerNameStr);
-        if (!issuerCert) {
-            error_out("ERROR: Could not find issuer's certificate");
-            return NULL;
-        }
-    }
-    if (find_field_bool(data, "manValidity", PR_TRUE)) {
-        (void)DER_AsciiToTime(&now, find_field(data, "notBefore", PR_TRUE));
-    } else {
-        now = PR_Now();
-    }
-    PR_ExplodeTime(now, PR_GMTParameters, &printableTime);
-    if (warpmonths) {
-        printableTime.tm_month += warpmonths;
-        now = PR_ImplodeTime(&printableTime);
-        PR_ExplodeTime(now, PR_GMTParameters, &printableTime);
-    }
-    if (find_field_bool(data, "manValidity", PR_TRUE)) {
-        (void)DER_AsciiToTime(&after, find_field(data, "notAfter", PR_TRUE));
-        PR_ExplodeTime(after, PR_GMTParameters, &printableTime);
-    } else {
-        printableTime.tm_month += 3;
-        after = PR_ImplodeTime(&printableTime);
-    }
-    /* note that the time is now in micro-second unit */
-    validity = CERT_CreateValidity(now, after);
-
-    if (selfsign) {
-        cert = CERT_CreateCertificate(serialNumber, &(req->subject), validity, req);
-    } else {
-        cert = CERT_CreateCertificate(serialNumber, &(issuerCert->subject), validity, req);
-    }
-
-    CERT_DestroyValidity(validity);
-    if (issuerCert) {
-        CERT_DestroyCertificate(issuerCert);
-    }
-    return (cert);
-}
-
-static int
-get_serial_number(Pair *data)
-{
-    int serial = 0;
-    int error;
-    char *filename = SERIAL_FILE;
-    char *SN;
-    FILE *serialFile;
-
-    if (find_field_bool(data, "serial-auto", PR_TRUE)) {
-        serialFile = fopen(filename, "r");
-        if (serialFile != NULL) {
-            size_t nread = fread(&serial, sizeof(int), 1, serialFile);
-            if (ferror(serialFile) != 0 || nread != 1) {
-                error_out("Error: Unable to read serial number file");
-            }
-            if (serial == -1) {
-                serial = 21;
-            }
-            fclose(serialFile);
-            ++serial;
-            serialFile = fopen(filename, "w");
-            if (serialFile == NULL) {
-                error_out("ERROR: Unable to open serial number file for writing");
-            }
-            fwrite(&serial, sizeof(int), 1, serialFile);
-            if (ferror(serialFile) != 0) {
-                error_out("Error: Unable to write to serial number file");
-            }
-        } else {
-            fclose(serialFile);
-            serialFile = fopen(filename, "w");
-            if (serialFile == NULL) {
-                error_out("ERROR: Unable to open serial number file");
-            }
-            serial = 21;
-            fwrite(&serial, sizeof(int), 1, serialFile);
-            if (ferror(serialFile) != 0) {
-                error_out("Error: Unable to write to serial number file");
-            }
-            error = ferror(serialFile);
-            if (error != 0) {
-                error_out("ERROR: Unable to write to serial file");
-            }
-        }
-        fclose(serialFile);
-    } else {
-        SN = find_field(data, "serial_value", PR_TRUE);
-        while (*SN != '\0') {
-            serial = serial * 16;
-            if ((*SN >= 'A') && (*SN <= 'F')) {
-                serial += *SN - 'A' + 10;
-            } else {
-                if ((*SN >= 'a') && (*SN <= 'f')) {
-                    serial += *SN - 'a' + 10;
-                } else {
-                    serial += *SN - '0';
-                }
-            }
-            ++SN;
-        }
-    }
-    return serial;
-}
-
-typedef SECStatus (*EXTEN_VALUE_ENCODER)(PLArenaPool *extHandle, void *value, SECItem *encodedValue);
-
-static SECStatus
-EncodeAndAddExtensionValue(
-    PLArenaPool *arena,
-    void *extHandle,
-    void *value,
-    PRBool criticality,
-    int extenType,
-    EXTEN_VALUE_ENCODER EncodeValueFn)
-{
-    SECItem encodedValue;
-    SECStatus rv;
-
-    encodedValue.data = NULL;
-    encodedValue.len = 0;
-    rv = (*EncodeValueFn)(arena, value, &encodedValue);
-    if (rv != SECSuccess) {
-        error_out("ERROR: Unable to encode extension value");
-    }
-    rv = CERT_AddExtension(extHandle, extenType, &encodedValue, criticality, PR_TRUE);
-    return (rv);
-}
-
-static SECStatus
-AddKeyUsage(void *extHandle,
-            Pair *data)
-{
-    SECItem bitStringValue;
-    unsigned char keyUsage = 0x0;
-
-    if (find_field_bool(data, "keyUsage-digitalSignature", PR_TRUE)) {
-        keyUsage |= (0x80 >> 0);
-    }
-    if (find_field_bool(data, "keyUsage-nonRepudiation", PR_TRUE)) {
-        keyUsage |= (0x80 >> 1);
-    }
-    if (find_field_bool(data, "keyUsage-keyEncipherment", PR_TRUE)) {
-        keyUsage |= (0x80 >> 2);
-    }
-    if (find_field_bool(data, "keyUsage-dataEncipherment", PR_TRUE)) {
-        keyUsage |= (0x80 >> 3);
-    }
-    if (find_field_bool(data, "keyUsage-keyAgreement", PR_TRUE)) {
-        keyUsage |= (0x80 >> 4);
-    }
-    if (find_field_bool(data, "keyUsage-keyCertSign", PR_TRUE)) {
-        keyUsage |= (0x80 >> 5);
-    }
-    if (find_field_bool(data, "keyUsage-cRLSign", PR_TRUE)) {
-        keyUsage |= (0x80 >> 6);
-    }
-
-    bitStringValue.data = &keyUsage;
-    bitStringValue.len = 1;
-
-    return (CERT_EncodeAndAddBitStrExtension(extHandle, SEC_OID_X509_KEY_USAGE, &bitStringValue,
-                                             (find_field_bool(data, "keyUsage-crit", PR_TRUE))));
-}
-
-static CERTOidSequence *
-CreateOidSequence(void)
-{
-    CERTOidSequence *rv = (CERTOidSequence *)NULL;
-    PLArenaPool *arena = (PLArenaPool *)NULL;
-
-    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
-    if ((PLArenaPool *)NULL == arena) {
-        goto loser;
-    }
-
-    rv = (CERTOidSequence *)PORT_ArenaZAlloc(arena, sizeof(CERTOidSequence));
-    if ((CERTOidSequence *)NULL == rv) {
-        goto loser;
-    }
-
-    rv->oids = (SECItem **)PORT_ArenaZAlloc(arena, sizeof(SECItem *));
-    if ((SECItem **)NULL == rv->oids) {
-        goto loser;
-    }
-
-    rv->arena = arena;
-    return rv;
-
-loser:
-    if ((PLArenaPool *)NULL != arena) {
-        PORT_FreeArena(arena, PR_FALSE);
-    }
-
-    return (CERTOidSequence *)NULL;
-}
-
-static SECStatus
-AddOidToSequence(CERTOidSequence *os, SECOidTag oidTag)
-{
-    SECItem **oids;
-    PRUint32 count = 0;
-    SECOidData *od;
-
-    od = SECOID_FindOIDByTag(oidTag);
-    if ((SECOidData *)NULL == od) {
-        return SECFailure;
-    }
-
-    for (oids = os->oids; (SECItem *)NULL != *oids; oids++) {
-        count++;
-    }
-
-    /* ArenaZRealloc */
-
-    {
-        PRUint32 i;
-
-        oids = (SECItem **)PORT_ArenaZAlloc(os->arena, sizeof(SECItem *) * (count + 2));
-        if ((SECItem **)NULL == oids) {
-            return SECFailure;
-        }
-
-        for (i = 0; i < count; i++) {
-            oids[i] = os->oids[i];
-        }
-
-        /* ArenaZFree(os->oids); */
-    }
-
-    os->oids = oids;
-    os->oids[count] = &od->oid;
-
-    return SECSuccess;
-}
-
-static SECItem *
-EncodeOidSequence(CERTOidSequence *os)
-{
-    SECItem *rv;
-    extern const SEC_ASN1Template CERT_OidSeqTemplate[];
-
-    rv = (SECItem *)PORT_ArenaZAlloc(os->arena, sizeof(SECItem));
-    if ((SECItem *)NULL == rv) {
-        goto loser;
-    }
-
-    if (!SEC_ASN1EncodeItem(os->arena, rv, os, CERT_OidSeqTemplate)) {
-        goto loser;
-    }
-
-    return rv;
-
-loser:
-    return (SECItem *)NULL;
-}
-
-static SECStatus
-AddExtKeyUsage(void *extHandle, Pair *data)
-{
-    SECStatus rv;
-    CERTOidSequence *os;
-    SECItem *value;
-    PRBool crit;
-
-    os = CreateOidSequence();
-    if ((CERTOidSequence *)NULL == os) {
-        return SECFailure;
-    }
-
-    if (find_field_bool(data, "extKeyUsage-serverAuth", PR_TRUE)) {
-        rv = AddOidToSequence(os, SEC_OID_EXT_KEY_USAGE_SERVER_AUTH);
-        if (SECSuccess != rv)
-            goto loser;
-    }
-
-    if (find_field_bool(data, "extKeyUsage-msTrustListSign", PR_TRUE)) {
-        rv = AddOidToSequence(os, SEC_OID_MS_EXT_KEY_USAGE_CTL_SIGNING);
-        if (SECSuccess != rv)
-            goto loser;
-    }
-
-    if (find_field_bool(data, "extKeyUsage-clientAuth", PR_TRUE)) {
-        rv = AddOidToSequence(os, SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH);
-        if (SECSuccess != rv)
-            goto loser;
-    }
-
-    if (find_field_bool(data, "extKeyUsage-codeSign", PR_TRUE)) {
-        rv = AddOidToSequence(os, SEC_OID_EXT_KEY_USAGE_CODE_SIGN);
-        if (SECSuccess != rv)
-            goto loser;
-    }
-
-    if (find_field_bool(data, "extKeyUsage-emailProtect", PR_TRUE)) {
-        rv = AddOidToSequence(os, SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT);
-        if (SECSuccess != rv)
-            goto loser;
-    }
-
-    if (find_field_bool(data, "extKeyUsage-timeStamp", PR_TRUE)) {
-        rv = AddOidToSequence(os, SEC_OID_EXT_KEY_USAGE_TIME_STAMP);
-        if (SECSuccess != rv)
-            goto loser;
-    }
-
-    if (find_field_bool(data, "extKeyUsage-ocspResponder", PR_TRUE)) {
-        rv = AddOidToSequence(os, SEC_OID_OCSP_RESPONDER);
-        if (SECSuccess != rv)
-            goto loser;
-    }
-
-    if (find_field_bool(data, "extKeyUsage-NS-govtApproved", PR_TRUE)) {
-        rv = AddOidToSequence(os, SEC_OID_NS_KEY_USAGE_GOVT_APPROVED);
-        if (SECSuccess != rv)
-            goto loser;
-    }
-
-    value = EncodeOidSequence(os);
-
-    crit = find_field_bool(data, "extKeyUsage-crit", PR_TRUE);
-
-    rv = CERT_AddExtension(extHandle, SEC_OID_X509_EXT_KEY_USAGE, value,
-                           crit, PR_TRUE);
-/*FALLTHROUGH*/
-loser:
-    CERT_DestroyOidSequence(os);
-    return rv;
-}
-
-static SECStatus
-AddSubKeyID(void *extHandle,
-            Pair *data,
-            CERTCertificate *subjectCert)
-{
-    SECItem encodedValue;
-    SECStatus rv;
-    char *read;
-    char *write;
-    char *first;
-    char character;
-    int high_digit = 0,
-        low_digit = 0;
-    int len;
-    PRBool odd = PR_FALSE;
-
-    encodedValue.data = NULL;
-    encodedValue.len = 0;
-    first = read = write = find_field(data, "subjectKeyIdentifier-text",
-                                      PR_TRUE);
-    len = PORT_Strlen(first);
-    odd = ((len % 2) != 0) ? PR_TRUE : PR_FALSE;
-    if (find_field_bool(data, "subjectKeyIdentifier-radio-hex", PR_TRUE)) {
-        if (odd) {
-            error_out("ERROR: Improperly formated subject key identifier, hex values must be expressed as an octet string");
-        }
-        while (*read != '\0') {
-            if (!isxdigit(*read)) {
-                error_out("ERROR: Improperly formated subject key identifier");
-            }
-            *read = toupper(*read);
-            if ((*read >= 'A') && (*read <= 'F')) {
-                high_digit = *read - 'A' + 10;
-            } else {
-                high_digit = *read - '0';
-            }
-            ++read;
-            if (!isxdigit(*read)) {
-                error_out("ERROR: Improperly formated subject key identifier");
-            }
-            *read = toupper(*read);
-            if ((*read >= 'A') && (*read <= 'F')) {
-                low_digit = *(read) - 'A' + 10;
-            } else {
-                low_digit = *(read) - '0';
-            }
-            character = (high_digit << 4) | low_digit;
-            *write = character;
-            ++write;
-            ++read;
-        }
-        *write = '\0';
-        len = write - first;
-    }
-    subjectCert->subjectKeyID.data = (unsigned char *)find_field(data, "subjectKeyIdentifier-text", PR_TRUE);
-    subjectCert->subjectKeyID.len = len;
-    rv = CERT_EncodeSubjectKeyID(NULL, &subjectCert->subjectKeyID, &encodedValue);
-    if (rv) {
-        return (rv);
-    }
-    return (CERT_AddExtension(extHandle, SEC_OID_X509_SUBJECT_KEY_ID,
-                              &encodedValue, PR_FALSE, PR_TRUE));
-}
-
-static SECStatus
-AddAuthKeyID(void *extHandle,
-             Pair *data,
-             char *issuerNameStr,
-             CERTCertDBHandle *handle)
-{
-    CERTAuthKeyID *authKeyID = NULL;
-    PLArenaPool *arena = NULL;
-    SECStatus rv = SECSuccess;
-    CERTCertificate *issuerCert = NULL;
-    CERTGeneralName *genNames;
-    CERTName *directoryName = NULL;
-
-    issuerCert = CERT_FindCertByNameString(handle, issuerNameStr);
-    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
-    if (!arena) {
-        error_allocate();
-    }
-
-    authKeyID = PORT_ArenaZAlloc(arena, sizeof(CERTAuthKeyID));
-    if (authKeyID == NULL) {
-        error_allocate();
-    }
-    if (find_field_bool(data, "authorityKeyIdentifier-radio-keyIdentifier",
-                        PR_TRUE)) {
-        authKeyID->keyID.data = PORT_ArenaAlloc(arena, PORT_Strlen((char *)issuerCert->subjectKeyID.data));
-        if (authKeyID->keyID.data == NULL) {
-            error_allocate();
-        }
-        PORT_Memcpy(authKeyID->keyID.data, issuerCert->subjectKeyID.data,
-                    authKeyID->keyID.len =
-                        PORT_Strlen((char *)issuerCert->subjectKeyID.data));
-    } else {
-
-        PORT_Assert(arena);
-        genNames = (CERTGeneralName *)PORT_ArenaZAlloc(arena, (sizeof(CERTGeneralName)));
-        if (genNames == NULL) {
-            error_allocate();
-        }
-        genNames->l.next = genNames->l.prev = &(genNames->l);
-        genNames->type = certDirectoryName;
-
-        directoryName = CERT_AsciiToName(issuerCert->subjectName);
-        if (!directoryName) {
-            error_out("ERROR: Unable to create Directory Name");
-        }
-        rv = CERT_CopyName(arena, &genNames->name.directoryName,
-                           directoryName);
-        CERT_DestroyName(directoryName);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to copy Directory Name");
-        }
-        authKeyID->authCertIssuer = genNames;
-        if (authKeyID->authCertIssuer == NULL && SECFailure == PORT_GetError()) {
-            error_out("ERROR: Unable to get Issuer General Name for Authority Key ID Extension");
-        }
-        authKeyID->authCertSerialNumber = issuerCert->serialNumber;
-    }
-    rv = EncodeAndAddExtensionValue(arena, extHandle, authKeyID, PR_FALSE,
-                                    SEC_OID_X509_AUTH_KEY_ID,
-                                    (EXTEN_VALUE_ENCODER)
-                                        CERT_EncodeAuthKeyID);
-    if (arena) {
-        PORT_FreeArena(arena, PR_FALSE);
-    }
-    return (rv);
-}
-
-static SECStatus
-AddPrivKeyUsagePeriod(void *extHandle,
-                      Pair *data,
-                      CERTCertificate *cert)
-{
-    char *notBeforeStr;
-    char *notAfterStr;
-    PLArenaPool *arena = NULL;
-    SECStatus rv = SECSuccess;
-    CERTPrivKeyUsagePeriod *pkup;
-
-    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
-    if (!arena) {
-        error_allocate();
-    }
-    pkup = PORT_ArenaZNew(arena, CERTPrivKeyUsagePeriod);
-    if (pkup == NULL) {
-        error_allocate();
-    }
-    notBeforeStr = (char *)PORT_Alloc(16);
-    if (notBeforeStr == NULL) {
-        error_allocate();
-    }
-    notAfterStr = (char *)PORT_Alloc(16);
-    if (notAfterStr == NULL) {
-        error_allocate();
-    }
-    *notBeforeStr = '\0';
-    *notAfterStr = '\0';
-    pkup->arena = arena;
-    pkup->notBefore.len = 0;
-    pkup->notBefore.data = NULL;
-    pkup->notAfter.len = 0;
-    pkup->notAfter.data = NULL;
-    if (find_field_bool(data, "privKeyUsagePeriod-radio-notBefore", PR_TRUE) ||
-        find_field_bool(data, "privKeyUsagePeriod-radio-both", PR_TRUE)) {
-        pkup->notBefore.len = 15;
-        pkup->notBefore.data = (unsigned char *)notBeforeStr;
-        if (find_field_bool(data, "privKeyUsagePeriod-notBefore-radio-manual",
-                            PR_TRUE)) {
-            PORT_Strcat(notBeforeStr, find_field(data,
-                                                 "privKeyUsagePeriod-notBefore-year",
-                                                 PR_TRUE));
-            PORT_Strcat(notBeforeStr, find_field(data,
-                                                 "privKeyUsagePeriod-notBefore-month",
-                                                 PR_TRUE));
-            PORT_Strcat(notBeforeStr, find_field(data,
-                                                 "privKeyUsagePeriod-notBefore-day",
-                                                 PR_TRUE));
-            PORT_Strcat(notBeforeStr, find_field(data,
-                                                 "privKeyUsagePeriod-notBefore-hour",
-                                                 PR_TRUE));
-            PORT_Strcat(notBeforeStr, find_field(data,
-                                                 "privKeyUsagePeriod-notBefore-minute",
-                                                 PR_TRUE));
-            PORT_Strcat(notBeforeStr, find_field(data,
-                                                 "privKeyUsagePeriod-notBefore-second",
-                                                 PR_TRUE));
-            if ((*(notBeforeStr + 14) != '\0') ||
-                (!isdigit(*(notBeforeStr + 13))) ||
-                (*(notBeforeStr + 12) >= '5' && *(notBeforeStr + 12) <= '0') ||
-                (!isdigit(*(notBeforeStr + 11))) ||
-                (*(notBeforeStr + 10) >= '5' && *(notBeforeStr + 10) <= '0') ||
-                (!isdigit(*(notBeforeStr + 9))) ||
-                (*(notBeforeStr + 8) >= '2' && *(notBeforeStr + 8) <= '0') ||
-                (!isdigit(*(notBeforeStr + 7))) ||
-                (*(notBeforeStr + 6) >= '3' && *(notBeforeStr + 6) <= '0') ||
-                (!isdigit(*(notBeforeStr + 5))) ||
-                (*(notBeforeStr + 4) >= '1' && *(notBeforeStr + 4) <= '0') ||
-                (!isdigit(*(notBeforeStr + 3))) ||
-                (!isdigit(*(notBeforeStr + 2))) ||
-                (!isdigit(*(notBeforeStr + 1))) ||
-                (!isdigit(*(notBeforeStr + 0))) ||
-                (*(notBeforeStr + 8) == '2' && *(notBeforeStr + 9) >= '4') ||
-                (*(notBeforeStr + 6) == '3' && *(notBeforeStr + 7) >= '1') ||
-                (*(notBeforeStr + 4) == '1' && *(notBeforeStr + 5) >= '2')) {
-                error_out("ERROR: Improperly formated private key usage period");
-            }
-            *(notBeforeStr + 14) = 'Z';
-            *(notBeforeStr + 15) = '\0';
-        } else {
-            if ((*(cert->validity.notBefore.data) > '5') ||
-                ((*(cert->validity.notBefore.data) == '5') &&
-                 (*(cert->validity.notBefore.data + 1) != '0'))) {
-                PORT_Strcat(notBeforeStr, "19");
-            } else {
-                PORT_Strcat(notBeforeStr, "20");
-            }
-            PORT_Strcat(notBeforeStr, (char *)cert->validity.notBefore.data);
-        }
-    }
-    if (find_field_bool(data, "privKeyUsagePeriod-radio-notAfter", PR_TRUE) ||
-        find_field_bool(data, "privKeyUsagePeriod-radio-both", PR_TRUE)) {
-        pkup->notAfter.len = 15;
-        pkup->notAfter.data = (unsigned char *)notAfterStr;
-        PORT_Strcat(notAfterStr, find_field(data, "privKeyUsagePeriod-notAfter-year",
-                                            PR_TRUE));
-        PORT_Strcat(notAfterStr, find_field(data, "privKeyUsagePeriod-notAfter-month",
-                                            PR_TRUE));
-        PORT_Strcat(notAfterStr, find_field(data, "privKeyUsagePeriod-notAfter-day",
-                                            PR_TRUE));
-        PORT_Strcat(notAfterStr, find_field(data, "privKeyUsagePeriod-notAfter-hour",
-                                            PR_TRUE));
-        PORT_Strcat(notAfterStr, find_field(data, "privKeyUsagePeriod-notAfter-minute",
-                                            PR_TRUE));
-        PORT_Strcat(notAfterStr, find_field(data, "privKeyUsagePeriod-notAfter-second",
-                                            PR_TRUE));
-        if ((*(notAfterStr + 14) != '\0') ||
-            (!isdigit(*(notAfterStr + 13))) ||
-            (*(notAfterStr + 12) >= '5' && *(notAfterStr + 12) <= '0') ||
-            (!isdigit(*(notAfterStr + 11))) ||
-            (*(notAfterStr + 10) >= '5' && *(notAfterStr + 10) <= '0') ||
-            (!isdigit(*(notAfterStr + 9))) ||
-            (*(notAfterStr + 8) >= '2' && *(notAfterStr + 8) <= '0') ||
-            (!isdigit(*(notAfterStr + 7))) ||
-            (*(notAfterStr + 6) >= '3' && *(notAfterStr + 6) <= '0') ||
-            (!isdigit(*(notAfterStr + 5))) ||
-            (*(notAfterStr + 4) >= '1' && *(notAfterStr + 4) <= '0') ||
-            (!isdigit(*(notAfterStr + 3))) ||
-            (!isdigit(*(notAfterStr + 2))) ||
-            (!isdigit(*(notAfterStr + 1))) ||
-            (!isdigit(*(notAfterStr + 0))) ||
-            (*(notAfterStr + 8) == '2' && *(notAfterStr + 9) >= '4') ||
-            (*(notAfterStr + 6) == '3' && *(notAfterStr + 7) >= '1') ||
-            (*(notAfterStr + 4) == '1' && *(notAfterStr + 5) >= '2')) {
-            error_out("ERROR: Improperly formated private key usage period");
-        }
-        *(notAfterStr + 14) = 'Z';
-        *(notAfterStr + 15) = '\0';
-    }
-
-    PORT_Assert(arena);
-
-    rv = EncodeAndAddExtensionValue(arena, extHandle, pkup,
-                                    find_field_bool(data,
-                                                    "privKeyUsagePeriod-crit",
-                                                    PR_TRUE),
-                                    SEC_OID_X509_PRIVATE_KEY_USAGE_PERIOD,
-                                    (EXTEN_VALUE_ENCODER)
-                                        CERT_EncodePrivateKeyUsagePeriod);
-    PORT_FreeArena(arena, PR_FALSE);
-    PORT_Free(notBeforeStr);
-    PORT_Free(notAfterStr);
-    return (rv);
-}
-
-static SECStatus
-AddBasicConstraint(void *extHandle,
-                   Pair *data)
-{
-    CERTBasicConstraints basicConstraint;
-    SECItem encodedValue;
-    SECStatus rv;
-
-    encodedValue.data = NULL;
-    encodedValue.len = 0;
-    basicConstraint.pathLenConstraint = CERT_UNLIMITED_PATH_CONSTRAINT;
-    basicConstraint.isCA = (find_field_bool(data, "basicConstraints-cA-radio-CA",
-                                            PR_TRUE));
-    if (find_field_bool(data, "basicConstraints-pathLengthConstraint", PR_TRUE)) {
-        basicConstraint.pathLenConstraint = atoi(find_field(data, "basicConstraints-pathLengthConstraint-text",
-                                                            PR_TRUE));
-    }
-
-    rv = CERT_EncodeBasicConstraintValue(NULL, &basicConstraint,
-                                         &encodedValue);
-    if (rv)
-        return (rv);
-    rv = CERT_AddExtension(extHandle, SEC_OID_X509_BASIC_CONSTRAINTS,
-                           &encodedValue,
-                           (find_field_bool(data, "basicConstraints-crit",
-                                            PR_TRUE)),
-                           PR_TRUE);
-
-    PORT_Free(encodedValue.data);
-    return (rv);
-}
-
-static SECStatus
-AddNscpCertType(void *extHandle,
-                Pair *data)
-{
-    SECItem bitStringValue;
-    unsigned char CertType = 0x0;
-
-    if (find_field_bool(data, "netscape-cert-type-ssl-client", PR_TRUE)) {
-        CertType |= (0x80 >> 0);
-    }
-    if (find_field_bool(data, "netscape-cert-type-ssl-server", PR_TRUE)) {
-        CertType |= (0x80 >> 1);
-    }
-    if (find_field_bool(data, "netscape-cert-type-smime", PR_TRUE)) {
-        CertType |= (0x80 >> 2);
-    }
-    if (find_field_bool(data, "netscape-cert-type-object-signing", PR_TRUE)) {
-        CertType |= (0x80 >> 3);
-    }
-    if (find_field_bool(data, "netscape-cert-type-reserved", PR_TRUE)) {
-        CertType |= (0x80 >> 4);
-    }
-    if (find_field_bool(data, "netscape-cert-type-ssl-ca", PR_TRUE)) {
-        CertType |= (0x80 >> 5);
-    }
-    if (find_field_bool(data, "netscape-cert-type-smime-ca", PR_TRUE)) {
-        CertType |= (0x80 >> 6);
-    }
-    if (find_field_bool(data, "netscape-cert-type-object-signing-ca", PR_TRUE)) {
-        CertType |= (0x80 >> 7);
-    }
-
-    bitStringValue.data = &CertType;
-    bitStringValue.len = 1;
-
-    return (CERT_EncodeAndAddBitStrExtension(extHandle, SEC_OID_NS_CERT_EXT_CERT_TYPE, &bitStringValue,
-                                             (find_field_bool(data, "netscape-cert-type-crit", PR_TRUE))));
-}
-
-static SECStatus
-add_IA5StringExtension(void *extHandle,
-                       char *string,
-                       PRBool crit,
-                       int idtag)
-{
-    SECItem encodedValue;
-    SECStatus rv;
-
-    encodedValue.data = NULL;
-    encodedValue.len = 0;
-
-    rv = CERT_EncodeIA5TypeExtension(NULL, string, &encodedValue);
-    if (rv) {
-        return (rv);
-    }
-    return (CERT_AddExtension(extHandle, idtag, &encodedValue, crit, PR_TRUE));
-}
-
-static SECItem *
-string_to_oid(char *string)
-{
-    int i;
-    int length = 20;
-    int remaining;
-    int first_value;
-    int second_value;
-    int value;
-    int oidLength;
-    unsigned char *oidString;
-    unsigned char *write;
-    unsigned char *read;
-    unsigned char *temp;
-    SECItem *oid;
-
-    remaining = length;
-    i = 0;
-    while (*string == ' ') {
-        string++;
-    }
-    while (isdigit(*(string + i))) {
-        i++;
-    }
-    if (*(string + i) == '.') {
-        *(string + i) = '\0';
-    } else {
-        error_out("ERROR: Improperly formated OID");
-    }
-    first_value = atoi(string);
-    if (first_value < 0 || first_value > 2) {
-        error_out("ERROR: Improperly formated OID");
-    }
-    string += i + 1;
-    i = 0;
-    while (isdigit(*(string + i))) {
-        i++;
-    }
-    if (*(string + i) == '.') {
-        *(string + i) = '\0';
-    } else {
-        error_out("ERROR: Improperly formated OID");
-    }
-    second_value = atoi(string);
-    if (second_value < 0 || second_value > 39) {
-        error_out("ERROR: Improperly formated OID");
-    }
-    oidString = PORT_ZAlloc(2);
-    *oidString = (first_value * 40) + second_value;
-    *(oidString + 1) = '\0';
-    oidLength = 1;
-    string += i + 1;
-    i = 0;
-    temp = write = PORT_ZAlloc(length);
-    while (*string != '\0') {
-        value = 0;
-        while (isdigit(*(string + i))) {
-            i++;
-        }
-        if (*(string + i) == '\0') {
-            value = atoi(string);
-            string += i;
-        } else {
-            if (*(string + i) == '.') {
-                *(string + i) = '\0';
-                value = atoi(string);
-                string += i + 1;
-            } else {
-                *(string + i) = '\0';
-                i++;
-                value = atoi(string);
-                while (*(string + i) == ' ')
-                    i++;
-                if (*(string + i) != '\0') {
-                    error_out("ERROR: Improperly formated OID");
-                }
-            }
-        }
-        i = 0;
-        while (value != 0) {
-            if (remaining < 1) {
-                remaining += length;
-                length = length * 2;
-                temp = PORT_Realloc(temp, length);
-                write = temp + length - remaining;
-            }
-            *write = (value & 0x7f) | (0x80);
-            write++;
-            remaining--;
-            value = value >> 7;
-        }
-        *temp = *temp & (0x7f);
-        oidLength += write - temp;
-        oidString = PORT_Realloc(oidString, (oidLength + 1));
-        read = write - 1;
-        write = oidLength + oidString - 1;
-        for (i = 0; i < (length - remaining); i++) {
-            *write = *read;
-            write--;
-            read++;
-        }
-        write = temp;
-        remaining = length;
-    }
-    *(oidString + oidLength) = '\0';
-    oid = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
-    oid->data = oidString;
-    oid->len = oidLength;
-    PORT_Free(temp);
-    return oid;
-}
-
-static SECItem *
-string_to_ipaddress(char *string)
-{
-    int i = 0;
-    int value;
-    int j = 0;
-    SECItem *ipaddress;
-
-    while (*string == ' ') {
-        string++;
-    }
-    ipaddress = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
-    ipaddress->data = PORT_ZAlloc(9);
-    while (*string != '\0' && j < 8) {
-        while (isdigit(*(string + i))) {
-            i++;
-        }
-        if (*(string + i) == '.') {
-            *(string + i) = '\0';
-            value = atoi(string);
-            string = string + i + 1;
-            i = 0;
-        } else {
-            if (*(string + i) == '\0') {
-                value = atoi(string);
-                string = string + i;
-                i = 0;
-            } else {
-                *(string + i) = '\0';
-                while (*(string + i) == ' ') {
-                    i++;
-                }
-                if (*(string + i) == '\0') {
-                    value = atoi(string);
-                    string = string + i;
-                    i = 0;
-                } else {
-                    error_out("ERROR: Improperly formated IP Address");
-                }
-            }
-        }
-        if (value >= 0 && value < 256) {
-            *(ipaddress->data + j) = value;
-        } else {
-            error_out("ERROR: Improperly formated IP Address");
-        }
-        j++;
-    }
-    *(ipaddress->data + j) = '\0';
-    if (j != 4 && j != 8) {
-        error_out("ERROR: Improperly formated IP Address");
-    }
-    ipaddress->len = j;
-    return ipaddress;
-}
-
-static int
-chr_to_hex(char c)
-{
-    if (isdigit(c)) {
-        return c - '0';
-    }
-    if (isxdigit(c)) {
-        return toupper(c) - 'A' + 10;
-    }
-    return -1;
-}
-
-static SECItem *
-string_to_binary(char *string)
-{
-    SECItem *rv;
-
-    rv = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
-    if (rv == NULL) {
-        error_allocate();
-    }
-    rv->data = (unsigned char *)PORT_ZAlloc((PORT_Strlen(string)) / 3 + 2);
-    rv->len = 0;
-    while (*string && !isxdigit(*string)) {
-        string++;
-    }
-    while (*string) {
-        int high, low;
-        high = chr_to_hex(*string++);
-        low = chr_to_hex(*string++);
-        if (high < 0 || low < 0) {
-            error_out("ERROR: Improperly formated binary encoding");
-        }
-        rv->data[(rv->len)++] = high << 4 | low;
-        if (*string != ':') {
-            break;
-        }
-        ++string;
-    }
-    while (*string == ' ') {
-        ++string;
-    }
-    if (*string) {
-        error_out("ERROR: Junk after binary encoding");
-    }
-
-    return rv;
-}
-
-static SECStatus
-MakeGeneralName(char *name,
-                CERTGeneralName *genName,
-                PLArenaPool *arena)
-{
-    SECItem *oid;
-    SECOidData *oidData;
-    SECItem *ipaddress;
-    SECItem *temp = NULL;
-    int i;
-    int nameType;
-    PRBool binary = PR_FALSE;
-    SECStatus rv = SECSuccess;
-    PRBool nickname = PR_FALSE;
-
-    PORT_Assert(genName);
-    PORT_Assert(arena);
-    nameType = *(name + PORT_Strlen(name) - 1) - '0';
-    if (nameType == 0 && *(name + PORT_Strlen(name) - 2) == '1') {
-        nickname = PR_TRUE;
-        nameType = certOtherName;
-    }
-    if (nameType < 1 || nameType > 9) {
-        error_out("ERROR: Unknown General Name Type");
-    }
-    *(name + PORT_Strlen(name) - 4) = '\0';
-    genName->type = nameType;
-
-    switch (genName->type) {
-        case certURI:
-        case certRFC822Name:
-        case certDNSName: {
-            genName->name.other.data = (unsigned char *)name;
-            genName->name.other.len = PORT_Strlen(name);
-            break;
-        }
-
-        case certIPAddress: {
-            ipaddress = string_to_ipaddress(name);
-            genName->name.other.data = ipaddress->data;
-            genName->name.other.len = ipaddress->len;
-            break;
-        }
-
-        case certRegisterID: {
-            oid = string_to_oid(name);
-            genName->name.other.data = oid->data;
-            genName->name.other.len = oid->len;
-            break;
-        }
-
-        case certEDIPartyName:
-        case certX400Address: {
-
-            genName->name.other.data = PORT_ArenaAlloc(arena,
-                                                       PORT_Strlen(name) + 2);
-            if (genName->name.other.data == NULL) {
-                error_allocate();
-            }
-
-            PORT_Memcpy(genName->name.other.data + 2, name, PORT_Strlen(name));
-            /* This may not be accurate for all cases.
-             For now, use this tag type */
-            genName->name.other.data[0] = (char)(((genName->type - 1) &
-                                                  0x1f) |
-                                                 0x80);
-            genName->name.other.data[1] = (char)PORT_Strlen(name);
-            genName->name.other.len = PORT_Strlen(name) + 2;
-            break;
-        }
-
-        case certOtherName: {
-            i = 0;
-            if (!nickname) {
-                while (!isdigit(*(name + PORT_Strlen(name) - i))) {
-                    i++;
-                }
-                if (*(name + PORT_Strlen(name) - i) == '1') {
-                    binary = PR_TRUE;
-                } else {
-                    binary = PR_FALSE;
-                }
-                while (*(name + PORT_Strlen(name) - i) != '-') {
-                    i++;
-                }
-                *(name + PORT_Strlen(name) - i - 1) = '\0';
-                i = 0;
-                while (*(name + i) != '-') {
-                    i++;
-                }
-                *(name + i - 1) = '\0';
-                oid = string_to_oid(name + i + 2);
-            } else {
-                oidData = SECOID_FindOIDByTag(SEC_OID_NETSCAPE_NICKNAME);
-                oid = &oidData->oid;
-                while (*(name + PORT_Strlen(name) - i) != '-') {
-                    i++;
-                }
-                *(name + PORT_Strlen(name) - i) = '\0';
-            }
-            genName->name.OthName.oid.data = oid->data;
-            genName->name.OthName.oid.len = oid->len;
-            if (binary) {
-                temp = string_to_binary(name);
-                genName->name.OthName.name.data = temp->data;
-                genName->name.OthName.name.len = temp->len;
-            } else {
-                temp = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
-                if (temp == NULL) {
-                    error_allocate();
-                }
-                temp->data = (unsigned char *)name;
-                temp->len = PORT_Strlen(name);
-                SEC_ASN1EncodeItem(arena, &(genName->name.OthName.name), temp,
-                                   CERTIA5TypeTemplate);
-            }
-            PORT_Free(temp);
-            break;
-        }
-
-        case certDirectoryName: {
-            CERTName *directoryName = NULL;
-
-            directoryName = CERT_AsciiToName(name);
-            if (!directoryName) {
-                error_out("ERROR: Improperly formated alternative name");
-                break;
-            }
-            rv = CERT_CopyName(arena, &genName->name.directoryName,
-                               directoryName);
-            CERT_DestroyName(directoryName);
-
-            break;
-        }
-    }
-    genName->l.next = &(genName->l);
-    genName->l.prev = &(genName->l);
-    return rv;
-}
-
-static CERTGeneralName *
-MakeAltName(Pair *data,
-            char *which,
-            PLArenaPool *arena)
-{
-    CERTGeneralName *SubAltName;
-    CERTGeneralName *current;
-    CERTGeneralName *newname;
-    char *name = NULL;
-    SECStatus rv = SECSuccess;
-    int len;
-
-    len = PORT_Strlen(which);
-    name = find_field(data, which, PR_TRUE);
-    SubAltName = current = (CERTGeneralName *)PORT_ZAlloc(sizeof(CERTGeneralName));
-    if (current == NULL) {
-        error_allocate();
-    }
-    while (name != NULL) {
-
-        rv = MakeGeneralName(name, current, arena);
-
-        if (rv != SECSuccess) {
-            break;
-        }
-        if (*(which + len - 1) < '9') {
-            *(which + len - 1) = *(which + len - 1) + 1;
-        } else {
-            if (isdigit(*(which + len - 2))) {
-                *(which + len - 2) = *(which + len - 2) + 1;
-                *(which + len - 1) = '0';
-            } else {
-                *(which + len - 1) = '1';
-                *(which + len) = '0';
-                *(which + len + 1) = '\0';
-                len++;
-            }
-        }
-        len = PORT_Strlen(which);
-        name = find_field(data, which, PR_TRUE);
-        if (name != NULL) {
-            newname = (CERTGeneralName *)PORT_ZAlloc(sizeof(CERTGeneralName));
-            if (newname == NULL) {
-                error_allocate();
-            }
-            current->l.next = &(newname->l);
-            newname->l.prev = &(current->l);
-            current = newname;
-            newname = NULL;
-        } else {
-            current->l.next = &(SubAltName->l);
-            SubAltName->l.prev = &(current->l);
-        }
-    }
-    if (rv == SECFailure) {
-        return NULL;
-    }
-    return SubAltName;
-}
-
-static CERTNameConstraints *
-MakeNameConstraints(Pair *data,
-                    PLArenaPool *arena)
-{
-    CERTNameConstraints *NameConstraints;
-    CERTNameConstraint *current = NULL;
-    CERTNameConstraint *last_permited = NULL;
-    CERTNameConstraint *last_excluded = NULL;
-    char *constraint = NULL;
-    char *which;
-    SECStatus rv = SECSuccess;
-    int len;
-    int i;
-    long max;
-    long min;
-    PRBool permited;
-
-    NameConstraints = (CERTNameConstraints *)PORT_ZAlloc(sizeof(CERTNameConstraints));
-    which = make_copy_string("NameConstraintSelect0", 25, '\0');
-    len = PORT_Strlen(which);
-    constraint = find_field(data, which, PR_TRUE);
-    NameConstraints->permited = NameConstraints->excluded = NULL;
-    while (constraint != NULL) {
-        current = (CERTNameConstraint *)PORT_ZAlloc(sizeof(CERTNameConstraint));
-        if (current == NULL) {
-            error_allocate();
-        }
-        i = 0;
-        while (*(constraint + PORT_Strlen(constraint) - i) != '-') {
-            i++;
-        }
-        *(constraint + PORT_Strlen(constraint) - i - 1) = '\0';
-        max = (long)atoi(constraint + PORT_Strlen(constraint) + 3);
-        if (max > 0) {
-            (void)SEC_ASN1EncodeInteger(arena, &current->max, max);
-        }
-        i = 0;
-        while (*(constraint + PORT_Strlen(constraint) - i) != '-') {
-            i++;
-        }
-        *(constraint + PORT_Strlen(constraint) - i - 1) = '\0';
-        min = (long)atoi(constraint + PORT_Strlen(constraint) + 3);
-        (void)SEC_ASN1EncodeInteger(arena, &current->min, min);
-        while (*(constraint + PORT_Strlen(constraint) - i) != '-') {
-            i++;
-        }
-        *(constraint + PORT_Strlen(constraint) - i - 1) = '\0';
-        if (*(constraint + PORT_Strlen(constraint) + 3) == 'p') {
-            permited = PR_TRUE;
-        } else {
-            permited = PR_FALSE;
-        }
-        rv = MakeGeneralName(constraint, &(current->name), arena);
-
-        if (rv != SECSuccess) {
-            break;
-        }
-        if (*(which + len - 1) < '9') {
-            *(which + len - 1) = *(which + len - 1) + 1;
-        } else {
-            if (isdigit(*(which + len - 2))) {
-                *(which + len - 2) = *(which + len - 2) + 1;
-                *(which + len - 1) = '0';
-            } else {
-                *(which + len - 1) = '1';
-                *(which + len) = '0';
-                *(which + len + 1) = '\0';
-                len++;
-            }
-        }
-        len = PORT_Strlen(which);
-        if (permited) {
-            if (NameConstraints->permited == NULL) {
-                NameConstraints->permited = last_permited = current;
-            }
-            last_permited->l.next = &(current->l);
-            current->l.prev = &(last_permited->l);
-            last_permited = current;
-        } else {
-            if (NameConstraints->excluded == NULL) {
-                NameConstraints->excluded = last_excluded = current;
-            }
-            last_excluded->l.next = &(current->l);
-            current->l.prev = &(last_excluded->l);
-            last_excluded = current;
-        }
-        constraint = find_field(data, which, PR_TRUE);
-        if (constraint != NULL) {
-            current = (CERTNameConstraint *)PORT_ZAlloc(sizeof(CERTNameConstraint));
-            if (current == NULL) {
-                error_allocate();
-            }
-        }
-    }
-    if (NameConstraints->permited != NULL) {
-        last_permited->l.next = &(NameConstraints->permited->l);
-        NameConstraints->permited->l.prev = &(last_permited->l);
-    }
-    if (NameConstraints->excluded != NULL) {
-        last_excluded->l.next = &(NameConstraints->excluded->l);
-        NameConstraints->excluded->l.prev = &(last_excluded->l);
-    }
-    if (which != NULL) {
-        PORT_Free(which);
-    }
-    if (rv == SECFailure) {
-        return NULL;
-    }
-    return NameConstraints;
-}
-
-static SECStatus
-AddAltName(void *extHandle,
-           Pair *data,
-           char *issuerNameStr,
-           CERTCertDBHandle *handle,
-           int type)
-{
-    PRBool autoIssuer = PR_FALSE;
-    PLArenaPool *arena = NULL;
-    CERTGeneralName *genName = NULL;
-    char *which = NULL;
-    char *name = NULL;
-    SECStatus rv = SECSuccess;
-    SECItem *issuersAltName = NULL;
-    CERTCertificate *issuerCert = NULL;
-
-    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
-    if (arena == NULL) {
-        error_allocate();
-    }
-    if (type == 0) {
-        which = make_copy_string("SubAltNameSelect0", 20, '\0');
-        genName = MakeAltName(data, which, arena);
-    } else {
-        if (autoIssuer) {
-            autoIssuer = find_field_bool(data, "IssuerAltNameSourceRadio-auto",
-                                         PR_TRUE);
-            issuerCert = CERT_FindCertByNameString(handle, issuerNameStr);
-            rv = cert_FindExtension((*issuerCert).extensions,
-                                    SEC_OID_X509_SUBJECT_ALT_NAME,
-                                    issuersAltName);
-            if (issuersAltName == NULL) {
-                name = PORT_Alloc(PORT_Strlen((*issuerCert).subjectName) + 4);
-                PORT_Strcpy(name, (*issuerCert).subjectName);
-                PORT_Strcat(name, " - 5");
-            }
-        } else {
-            which = make_copy_string("IssuerAltNameSelect0", 20, '\0');
-            genName = MakeAltName(data, which, arena);
-        }
-    }
-    if (type == 0) {
-        EncodeAndAddExtensionValue(arena, extHandle, genName,
-                                   find_field_bool(data, "SubAltName-crit",
-                                                   PR_TRUE),
-                                   SEC_OID_X509_SUBJECT_ALT_NAME,
-                                   (EXTEN_VALUE_ENCODER)
-                                       CERT_EncodeAltNameExtension);
-
-    } else {
-        if (autoIssuer && (name == NULL)) {
-            rv = CERT_AddExtension(extHandle, SEC_OID_X509_ISSUER_ALT_NAME, issuersAltName,
-                                   find_field_bool(data, "IssuerAltName-crit", PR_TRUE), PR_TRUE);
-        } else {
-            EncodeAndAddExtensionValue(arena, extHandle, genName,
-                                       find_field_bool(data,
-                                                       "IssuerAltName-crit",
-                                                       PR_TRUE),
-                                       SEC_OID_X509_ISSUER_ALT_NAME,
-                                       (EXTEN_VALUE_ENCODER)
-                                           CERT_EncodeAltNameExtension);
-        }
-    }
-    if (which != NULL) {
-        PORT_Free(which);
-    }
-    if (issuerCert != NULL) {
-        CERT_DestroyCertificate(issuerCert);
-    }
-    return rv;
-}
-
-static SECStatus
-AddNameConstraints(void *extHandle,
-                   Pair *data)
-{
-    PLArenaPool *arena = NULL;
-    CERTNameConstraints *constraints = NULL;
-    SECStatus rv = SECSuccess;
-
-    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
-    if (arena == NULL) {
-        error_allocate();
-    }
-    constraints = MakeNameConstraints(data, arena);
-    if (constraints != NULL) {
-        EncodeAndAddExtensionValue(arena, extHandle, constraints, PR_TRUE,
-                                   SEC_OID_X509_NAME_CONSTRAINTS,
-                                   (EXTEN_VALUE_ENCODER)
-                                       CERT_EncodeNameConstraintsExtension);
-    }
-    if (arena != NULL) {
-        PORT_ArenaRelease(arena, NULL);
-    }
-    return rv;
-}
-
-static SECStatus
-add_extensions(CERTCertificate *subjectCert,
-               Pair *data,
-               char *issuerNameStr,
-               CERTCertDBHandle *handle)
-{
-    void *extHandle;
-    SECStatus rv = SECSuccess;
-
-    extHandle = CERT_StartCertExtensions(subjectCert);
-    if (extHandle == NULL) {
-        error_out("ERROR: Unable to get certificates extension handle");
-    }
-    if (find_field_bool(data, "keyUsage", PR_TRUE)) {
-        rv = AddKeyUsage(extHandle, data);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Key Usage extension");
-        }
-    }
-
-    if (find_field_bool(data, "extKeyUsage", PR_TRUE)) {
-        rv = AddExtKeyUsage(extHandle, data);
-        if (SECSuccess != rv) {
-            error_out("ERROR: Unable to add Extended Key Usage extension");
-        }
-    }
-
-    if (find_field_bool(data, "basicConstraints", PR_TRUE)) {
-        rv = AddBasicConstraint(extHandle, data);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Basic Constraint extension");
-        }
-    }
-    if (find_field_bool(data, "subjectKeyIdentifier", PR_TRUE)) {
-        rv = AddSubKeyID(extHandle, data, subjectCert);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Subject Key Identifier Extension");
-        }
-    }
-    if (find_field_bool(data, "authorityKeyIdentifier", PR_TRUE)) {
-        rv = AddAuthKeyID(extHandle, data, issuerNameStr, handle);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Authority Key Identifier extension");
-        }
-    }
-    if (find_field_bool(data, "privKeyUsagePeriod", PR_TRUE)) {
-        rv = AddPrivKeyUsagePeriod(extHandle, data, subjectCert);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Private Key Usage Period extension");
-        }
-    }
-    if (find_field_bool(data, "SubAltName", PR_TRUE)) {
-        rv = AddAltName(extHandle, data, NULL, NULL, 0);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Subject Alternative Name extension");
-        }
-    }
-    if (find_field_bool(data, "IssuerAltName", PR_TRUE)) {
-        rv = AddAltName(extHandle, data, issuerNameStr, handle, 1);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Issuer Alternative Name Extension");
-        }
-    }
-    if (find_field_bool(data, "NameConstraints", PR_TRUE)) {
-        rv = AddNameConstraints(extHandle, data);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Name Constraints Extension");
-        }
-    }
-    if (find_field_bool(data, "netscape-cert-type", PR_TRUE)) {
-        rv = AddNscpCertType(extHandle, data);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Netscape Certificate Type Extension");
-        }
-    }
-    if (find_field_bool(data, "netscape-base-url", PR_TRUE)) {
-        rv = add_IA5StringExtension(extHandle,
-                                    find_field(data, "netscape-base-url-text",
-                                               PR_TRUE),
-                                    find_field_bool(data,
-                                                    "netscape-base-url-crit",
-                                                    PR_TRUE),
-                                    SEC_OID_NS_CERT_EXT_BASE_URL);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Netscape Base URL Extension");
-        }
-    }
-    if (find_field_bool(data, "netscape-revocation-url", PR_TRUE)) {
-        rv = add_IA5StringExtension(extHandle,
-                                    find_field(data,
-                                               "netscape-revocation-url-text",
-                                               PR_TRUE),
-                                    find_field_bool(data, "netscape-revocation-url-crit",
-                                                    PR_TRUE),
-                                    SEC_OID_NS_CERT_EXT_REVOCATION_URL);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Netscape Revocation URL Extension");
-        }
-    }
-    if (find_field_bool(data, "netscape-ca-revocation-url", PR_TRUE)) {
-        rv = add_IA5StringExtension(extHandle,
-                                    find_field(data,
-                                               "netscape-ca-revocation-url-text",
-                                               PR_TRUE),
-                                    find_field_bool(data, "netscape-ca-revocation-url-crit", PR_TRUE),
-                                    SEC_OID_NS_CERT_EXT_CA_REVOCATION_URL);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Netscape CA Revocation URL Extension");
-        }
-    }
-    if (find_field_bool(data, "netscape-cert-renewal-url", PR_TRUE)) {
-        rv = add_IA5StringExtension(extHandle,
-                                    find_field(data,
-                                               "netscape-cert-renewal-url-text",
-                                               PR_TRUE),
-                                    find_field_bool(data, "netscape-cert-renewal-url-crit",
-                                                    PR_TRUE),
-                                    SEC_OID_NS_CERT_EXT_CERT_RENEWAL_URL);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Netscape Certificate Renewal URL Extension");
-        }
-    }
-    if (find_field_bool(data, "netscape-ca-policy-url", PR_TRUE)) {
-        rv = add_IA5StringExtension(extHandle,
-                                    find_field(data,
-                                               "netscape-ca-policy-url-text",
-                                               PR_TRUE),
-                                    find_field_bool(data, "netscape-ca-policy-url-crit",
-                                                    PR_TRUE),
-                                    SEC_OID_NS_CERT_EXT_CA_POLICY_URL);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Netscape CA Policy URL Extension");
-        }
-    }
-    if (find_field_bool(data, "netscape-ssl-server-name", PR_TRUE)) {
-        rv = add_IA5StringExtension(extHandle,
-                                    find_field(data,
-                                               "netscape-ssl-server-name-text",
-                                               PR_TRUE),
-                                    find_field_bool(data, "netscape-ssl-server-name-crit",
-                                                    PR_TRUE),
-                                    SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Netscape SSL Server Name Extension");
-        }
-    }
-    if (find_field_bool(data, "netscape-comment", PR_TRUE)) {
-        rv = add_IA5StringExtension(extHandle,
-                                    find_field(data, "netscape-comment-text",
-                                               PR_TRUE),
-                                    find_field_bool(data,
-                                                    "netscape-comment-crit",
-                                                    PR_TRUE),
-                                    SEC_OID_NS_CERT_EXT_COMMENT);
-        if (rv != SECSuccess) {
-            error_out("ERROR: Unable to add Netscape Comment Extension");
-        }
-    }
-    CERT_FinishExtensions(extHandle);
-    return (rv);
-}
-
-char *
-return_dbpasswd(PK11SlotInfo *slot, PRBool retry, void *data)
-{
-    char *rv;
-
-    /* don't clobber our poor smart card */
-    if (retry == PR_TRUE) {
-        return NULL;
-    }
-    rv = PORT_Alloc(4);
-    PORT_Strcpy(rv, "foo");
-    return rv;
-}
-
-SECKEYPrivateKey *
-FindPrivateKeyFromNameStr(char *name,
-                          CERTCertDBHandle *certHandle)
-{
-    SECKEYPrivateKey *key;
-    CERTCertificate *cert;
-    CERTCertificate *p11Cert;
-
-    /* We don't presently have a PK11 function to find a cert by
-    ** subject name.
-    ** We do have a function to find a cert in the internal slot's
-    ** cert db by subject name, but it doesn't setup the slot info.
-    ** So, this HACK works, but should be replaced as soon as we
-    ** have a function to search for certs accross slots by subject name.
-    */
-    cert = CERT_FindCertByNameString(certHandle, name);
-    if (cert == NULL || cert->nickname == NULL) {
-        error_out("ERROR: Unable to retrieve issuers certificate");
-    }
-    p11Cert = PK11_FindCertFromNickname(cert->nickname, NULL);
-    if (p11Cert == NULL) {
-        error_out("ERROR: Unable to retrieve issuers certificate");
-    }
-    key = PK11_FindKeyByAnyCert(p11Cert, NULL);
-    return key;
-}
-
-static SECItem *
-SignCert(CERTCertificate *cert,
-         char *issuerNameStr,
-         Pair *data,
-         CERTCertDBHandle *handle,
-         int which_key)
-{
-    SECItem der;
-    SECKEYPrivateKey *caPrivateKey = NULL;
-    SECStatus rv;
-    PLArenaPool *arena;
-    SECOidTag algID;
-
-    if (which_key == 0) {
-        caPrivateKey = FindPrivateKeyFromNameStr(issuerNameStr, handle);
-    } else {
-        caPrivateKey = privkeys[which_key - 1];
-    }
-    if (caPrivateKey == NULL) {
-        error_out("ERROR: unable to retrieve issuers key");
-    }
-
-    arena = cert->arena;
-
-    algID = SEC_GetSignatureAlgorithmOidTag(caPrivateKey->keyType,
-                                            SEC_OID_UNKNOWN);
-    if (algID == SEC_OID_UNKNOWN) {
-        error_out("ERROR: Unknown key type for issuer.");
-        goto done;
-    }
-
-    rv = SECOID_SetAlgorithmID(arena, &cert->signature, algID, 0);
-    if (rv != SECSuccess) {
-        error_out("ERROR: Could not set signature algorithm id.");
-    }
-
-    if (find_field_bool(data, "ver-1", PR_TRUE)) {
-        *(cert->version.data) = 0;
-        cert->version.len = 1;
-    } else {
-        *(cert->version.data) = 2;
-        cert->version.len = 1;
-    }
-    der.data = NULL;
-    der.len = 0;
-    (void)SEC_ASN1EncodeItem(arena, &der, cert, CERT_CertificateTemplate);
-    if (der.data == NULL) {
-        error_out("ERROR: Could not encode certificate.\n");
-    }
-    rv = SEC_DerSignData(arena, &(cert->derCert), der.data, der.len, caPrivateKey,
-                         algID);
-    if (rv != SECSuccess) {
-        error_out("ERROR: Could not sign encoded certificate data.\n");
-    }
-done:
-    SECKEY_DestroyPrivateKey(caPrivateKey);
-    return &(cert->derCert);
-}
-
-int
-main(int argc, char **argv)
-{
-    int length = 500;
-    int remaining = 500;
-    int n;
-    int i;
-    int serial;
-    int chainLen;
-    int which_key;
-    char *pos;
-#ifdef OFFLINE
-    char *form_output = "key=MIIBPTCBpzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA7"
-                        "SLqjWBL9Wl11Vlg%0AaMqZCvcQOL%2FnvSqYPPRP0XZy9SoAeyWzQnBOiCm2t8H5mK7r2"
-                        "jnKdAQOmfhjaJil%0A3hNVu3SekHOXF6Ze7bkWa6%2FSGVcY%2FojkydxFSgY43nd1iyd"
-                        "zPQDp8WWLL%2BpVpt%2B%2B%0ATRhFtVXbF0fQI03j9h3BoTgP2lkCAwEAARYDZm9vMA0"
-                        "GCSqGSIb3DQEBBAUAA4GB%0AAJ8UfRKJ0GtG%2B%2BufCC6tAfTzKrq3CTBHnom55EyXc"
-                        "sAsv6WbDqI%2F0rLAPkn2Xo1r%0AnNhtMxIuj441blMt%2Fa3AGLOy5zmC7Qawt8IytvQ"
-                        "ikQ1XTpTBCXevytrmLjCmlURr%0ANJryTM48WaMQHiMiJpbXCqVJC1d%2FpEWBtqvALzZ"
-                        "aOOIy&subject=CN%3D%22test%22%26serial-auto%3Dtrue%26serial_value%3D%"
-                        "26ver-1%3Dtrue%26ver-3%3Dfalse%26caChoiceradio-SignWithDefaultkey%3Dt"
-                        "rue%26caChoiceradio-SignWithRandomChain%3Dfalse%26autoCAs%3D%26caChoi"
-                        "ceradio-SignWithSpecifiedChain%3Dfalse%26manCAs%3D%26%24";
-#else
-    char *form_output;
-#endif
-    char *issuerNameStr;
-    char *certName;
-    char *DBdir = DB_DIRECTORY;
-    char *prefixs[10] = { "CA#1-", "CA#2-", "CA#3-",
-                          "CA#4-", "CA#5-", "CA#6-",
-                          "CA#7-", "CA#8-", "CA#9-", "" };
-    Pair *form_data;
-    CERTCertificate *cert;
-    CERTCertDBHandle *handle;
-    CERTCertificateRequest *certReq = NULL;
-    int warpmonths = 0;
-    SECItem *certDER;
-#ifdef FILEOUT
-    FILE *outfile;
-#endif
-    SECStatus status = SECSuccess;
-    extern char prefix[PREFIX_LEN];
-    SEC_PKCS7ContentInfo *certChain;
-    SECItem *encodedCertChain;
-    PRBool UChain = PR_FALSE;
-
-    progName = strrchr(argv[0], '/');
-    progName = progName ? progName + 1 : argv[0];
-
-#ifdef TEST
-    sleep(20);
-#endif
-    SECU_ConfigDirectory(DBdir);
-
-    PK11_SetPasswordFunc(return_dbpasswd);
-    status = NSS_InitReadWrite(DBdir);
-    if (status != SECSuccess) {
-        SECU_PrintPRandOSError(progName);
-        return -1;
-    }
-    handle = CERT_GetDefaultCertDB();
-
-    prefix[0] = '\0';
-#if !defined(OFFLINE)
-    form_output = (char *)PORT_Alloc(length);
-    if (form_output == NULL) {
-        error_allocate();
-    }
-    pos = form_output;
-    while (feof(stdin) == 0) {
-        if (remaining <= 1) {
-            remaining += length;
-            length = length * 2;
-            form_output = PORT_Realloc(form_output, (length));
-            if (form_output == NULL) {
-                error_allocate();
-            }
-            pos = form_output + length - remaining;
-        }
-        n = fread(pos, 1, (size_t)(remaining - 1), stdin);
-        pos += n;
-        remaining -= n;
-    }
-    *pos = '&';
-    pos++;
-    length = pos - form_output;
-#else
-    length = PORT_Strlen(form_output);
-#endif
-#ifdef FILEOUT
-    printf("Content-type: text/plain\n\n");
-    fwrite(form_output, 1, (size_t)length, stdout);
-    printf("\n");
-#endif
-#ifdef FILEOUT
-    fwrite(form_output, 1, (size_t)length, stdout);
-    printf("\n");
-    fflush(stdout);
-#endif
-    form_data = make_datastruct(form_output, length);
-    status = clean_input(form_data);
-#if !defined(OFFLINE)
-    PORT_Free(form_output);
-#endif
-#ifdef FILEOUT
-    i = 0;
-    while (return_name(form_data, i) != NULL) {
-        printf("%s", return_name(form_data, i));
-        printf("=\n");
-        printf("%s", return_data(form_data, i));
-        printf("\n");
-        i++;
-    }
-    printf("I got that done, woo hoo\n");
-    fflush(stdout);
-#endif
-    issuerNameStr = PORT_Alloc(200);
-    if (find_field_bool(form_data, "caChoiceradio-SignWithSpecifiedChain",
-                        PR_FALSE)) {
-        UChain = PR_TRUE;
-        chainLen = atoi(find_field(form_data, "manCAs", PR_FALSE));
-        PORT_Strcpy(prefix, prefixs[0]);
-        issuerNameStr = PORT_Strcpy(issuerNameStr,
-                                    "CN=Cert-O-Matic II, O=Cert-O-Matic II");
-        if (chainLen == 0) {
-            UChain = PR_FALSE;
-        }
-    } else {
-        if (find_field_bool(form_data, "caChoiceradio-SignWithRandomChain",
-                            PR_FALSE)) {
-            PORT_Strcpy(prefix, prefixs[9]);
-            chainLen = atoi(find_field(form_data, "autoCAs", PR_FALSE));
-            if (chainLen < 1 || chainLen > 18) {
-                issuerNameStr = PORT_Strcpy(issuerNameStr,
-                                            "CN=CA18, O=Cert-O-Matic II");
-            }
-            issuerNameStr = PORT_Strcpy(issuerNameStr, "CN=CA");
-            issuerNameStr = PORT_Strcat(issuerNameStr,
-                                        find_field(form_data, "autoCAs", PR_FALSE));
-            issuerNameStr = PORT_Strcat(issuerNameStr, ", O=Cert-O-Matic II");
-        } else {
-            issuerNameStr = PORT_Strcpy(issuerNameStr,
-                                        "CN=Cert-O-Matic II, O=Cert-O-Matic II");
-        }
-        chainLen = 0;
-    }
-
-    i = -1;
-    which_key = 0;
-    do {
-        extern SECStatus cert_GetKeyID(CERTCertificate * cert);
-        i++;
-        if (i != 0 && UChain) {
-            PORT_Strcpy(prefix, prefixs[i]);
-        }
-        /*        find_field(form_data,"subject", PR_TRUE); */
-        certReq = makeCertReq(form_data, which_key);
-#ifdef OFFLINE
-        serial = 900;
-#else
-        serial = get_serial_number(form_data);
-#endif
-        cert = MakeV1Cert(handle, certReq, issuerNameStr, PR_FALSE,
-                          serial, warpmonths, form_data);
-        if (certReq != NULL) {
-            CERT_DestroyCertificateRequest(certReq);
-        }
-        if (find_field_bool(form_data, "ver-3", PR_TRUE)) {
-            status = add_extensions(cert, form_data, issuerNameStr, handle);
-            if (status != SECSuccess) {
-                error_out("ERROR: Unable to add extensions");
-            }
-        }
-        status = cert_GetKeyID(cert);
-        if (status == SECFailure) {
-            error_out("ERROR: Unable to get Key ID.");
-        }
-        certDER = SignCert(cert, issuerNameStr, form_data, handle, which_key);
-        CERT_NewTempCertificate(handle, certDER, NULL, PR_FALSE, PR_TRUE);
-        issuerNameStr = find_field(form_data, "subject", PR_TRUE);
-        /*        SECITEM_FreeItem(certDER, PR_TRUE); */
-        CERT_DestroyCertificate(cert);
-        if (i == (chainLen - 1)) {
-            i = 8;
-        }
-        ++which_key;
-    } while (i < 9 && UChain);
-
-#ifdef FILEOUT
-    outfile = fopen("../certout", "wb");
-#endif
-    certName = find_field(form_data, "subject", PR_FALSE);
-    cert = CERT_FindCertByNameString(handle, certName);
-    certChain = SEC_PKCS7CreateCertsOnly(cert, PR_TRUE, handle);
-    if (certChain == NULL) {
-        error_out("ERROR: No certificates in cert chain");
-    }
-    encodedCertChain = SEC_PKCS7EncodeItem(NULL, NULL, certChain, NULL, NULL,
-                                           NULL);
-    if (encodedCertChain) {
-#if !defined(FILEOUT)
-        printf("Content-type: application/x-x509-user-cert\r\n");
-        printf("Content-length: %d\r\n\r\n", encodedCertChain->len);
-        fwrite(encodedCertChain->data, 1, encodedCertChain->len, stdout);
-#else
-        fwrite(encodedCertChain->data, 1, encodedCertChain->len, outfile);
-#endif
-
-    } else {
-        error_out("Error: Unable to DER encode certificate");
-    }
-#ifdef FILEOUT
-    printf("\nI got here!\n");
-    fflush(outfile);
-    fclose(outfile);
-#endif
-    fflush(stdout);
-    if (NSS_Shutdown() != SECSuccess) {
-        exit(1);
-    }
-    return 0;
-}
deleted file mode 100644
--- a/security/nss/cmd/certcgi/certcgi.gyp
+++ /dev/null
@@ -1,33 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-{
-  'includes': [
-    '../../coreconf/config.gypi',
-    '../../cmd/platlibs.gypi'
-  ],
-  'targets': [
-    {
-      'target_name': 'certcgi',
-      'type': 'executable',
-      'sources': [
-        'certcgi.c'
-      ],
-      'dependencies': [
-        '<(DEPTH)/exports.gyp:dbm_exports',
-        '<(DEPTH)/exports.gyp:nss_exports',
-        '<(DEPTH)/lib/sqlite/sqlite.gyp:sqlite3'
-      ]
-    }
-  ],
-  'target_defaults': {
-    'defines': [
-      'NSPR20',
-      'NSS_USE_STATIC_LIBS'
-    ]
-  },
-  'variables': {
-    'module': 'nss',
-    'use_static_libs': 1
-  }
-}
\ No newline at end of file
deleted file mode 100644
--- a/security/nss/cmd/certcgi/index.html
+++ /dev/null
@@ -1,789 +0,0 @@
-<HTML>	<!-- -*- Mode: Java; tab-width: 8 -*- -->
-<!-- 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/. -->
-<HEAD>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 
-<SCRIPT LANGUAGE="JavaScript1.2">
-
-script_url = 'http://interzone.mcom.com/cgi-bin/certomatic/bin/certcgi.cgi'
-
-ext_page_ver1 =
-  make_page_intro('Version 1 extensions', "#FFFFFF") +
-  '<IFRAME WIDTH="100%" HEIGHT="100%" FRAMEBORDER=0 ID="ext1">' +
-  'Version 1 X.509 certs do not support extensions' +
-  '</IFRAME>' +
-  '</body></html>';
-
-num_ca = 0;
-
-your_certificate_index_label       = 'Your Certificate';
-netscape_extensions_index_label    = 'Netscape X.509 Extensions';
-standard_extensions_index_label    = 'Standard X.509 Extensions';
-certifying_authorities_index_label = 'Certifying Authorities';
-add_sub_alt_name_index_label       = 'Add Subject Alternative Name';
-
-index_list = 
-  '0, your_certificate_index_label,' +
-  '0, netscape_extensions_index_label,' +
-  '0, standard_extensions_index_label,' +
-  '0, certifying_authorities_index_label';
-
-add_index_list = '';
-
-ver = 3
-
-max_pages = 13;
-cur_page = 1;
-
-ext_page_array = new Array(max_pages);
-
-index_label = 'Options';
-
-var main_page = 
-  make_page_intro('Your Key', "#FFFFFF") +
-  '<IFRAME WIDTH="100%" HEIGHT="100%" FRAMEBORDER=0 ID="main" SRC="main.html">' +
-  '</IFRAME>' +
-  '</body></html>' ;
-
-function setSubAltNameType(form)
-{
-  with(form) {
-    if (SubAltNameRadio[0].checked) {
-      return true;
-    }
-    if (SubAltNameRadio[3].checked || SubAltNameRadio[5].checked) {
-      SubAltNameDataType.checked = true;
-      return true;
-    }
-    if (SubAltNameRadio[1].checked || SubAltNameRadio[2].checked ||
-	SubAltNameRadio[4].checked || SubAltNameRadio[6].checked ||
-	SubAltNameRadio[7].checked || SubAltNameRadio[8].checked) {
-      SubAltNameDataType.checked = false;
-      return true;
-    }
-  }
-  return true;
-}
-
-function setIssuerAltNameType(form)
-{
-  with(form) {
-    if (IssuerAltNameRadio[0].checked) {
-      return true;
-    }
-    if (IssuerAltNameRadio[3].checked || IssuerAltNameRadio[5].checked) {
-      IssuerAltNameDataType.checked = true;
-      return true;
-    }
-    if (IssuerAltNameRadio[1].checked || IssuerAltNameRadio[2].checked ||
-	IssuerAltNameRadio[4].checked || IssuerAltNameRadio[6].checked ||
-	IssuerAltNameRadio[7].checked || IssuerAltNameRadio[8].checked) {
-      IssuerAltNameDataType.checked = false;
-      return true;
-    }
-  }
-  return true;
-}
-
-
-function setNameConstraintNameType(form)
-{
-  with(form) {
-    if (NameConstraintRadio[0].checked) {
-      return true;
-    }
-    if (NameConstraintRadio[3].checked || NameConstraintRadio[5].checked) {
-      NameConstraintNameDataType.checked = true;
-      return true;
-    }
-    if (NameConstraintRadio[1].checked || NameConstraintRadio[2].checked ||
-	NameConstraintRadio[4].checked || NameConstraintRadio[6].checked ||
-	NameConstraintRadio[7].checked || NameConstraintRadio[8].checked) {
-      NameConstraintNameDataType.checked = false;
-      return true;
-    }
-  }
-  return true;
-}
-
-
-function addSubAltName(form)
-{
-  with(form) {
-    var len = SubAltNameSelect.length;
-    var value;
-    var i = 0;
-    while(!(i == (SubAltNameRadio.length - 1)) & 
-          !(SubAltNameRadio[i].checked == true)) {
-      i++;
-    }
-    if (i != 0) {
-      value = SubAltNameText.value + " - " + (i + 1);
-    } else {
-      value = SubAltNameText.value + " - " + 
-              SubAltNameOtherNameOID.value + " - ";
-      if (SubAltNameDataType.checked) {
-	value += "1 - ";
-      } else {
-	value += "0 - ";
-      }
-      value += (i + 1);
-      if (SubAltNameOtherNameOID.value == "") {
-	alert("Other names must include an OID");
-	return false;
-      }
-    }
-
-    if ((SubAltNameText.value == "") | (SubAltNameRadio[i].checked != true)) {
-      alert("Alternative Names must include values for name and name type.");
-    } else {
-      SubAltNameSelect.options[len] = new Option(value, value);
-    }
-  }
-  return true;
-}
-
-function deleteSubAltName(form)
-{
-  with(form) {
-    while (SubAltNameSelect.selectedIndex >= 0) {
-      SubAltNameSelect[SubAltNameSelect.selectedIndex] = null;
-    }
-  }
-}
-  
-function addIssuerAltName(form)
-{
-  with(form)
-  {
-    var len = IssuerAltNameSelect.length;
-    var value;
-    var i = 0;
-
-    while(!(i == (IssuerAltNameRadio.length -1)) & 
-          !(IssuerAltNameRadio[i].checked == true)) {
-      i++;
-    }
-    if (i != 0) {
-      value = IssuerAltNameText.value + " - " + (i + 1);
-    } else {
-      value = IssuerAltNameText.value + " - " + 
-              IssuerAltNameOtherNameOID.value + " - ";
-      if (IssuerAltNameDataType.checked) {
-	value += "1 - ";
-      } else {
-	value += "0 - ";
-      }
-      value += (i + 1);
-      if (IssuerAltNameOtherNameOID.value == "") {
-	  alert("Other names must include an OID");
-	  return false;
-      }
-    }
-    if ((IssuerAltNameText.value == "") | 
-        (IssuerAltNameRadio[i].checked != true)) {
-      alert("Alternative Names must include values for name and name type.")
-    } else {
-      IssuerAltNameSelect.options[len] = new Option(value, value);
-    }
</