Merge inbound to mozilla-central. a=merge
authorCosmin Sabou <csabou@mozilla.com>
Wed, 28 Feb 2018 23:48:29 +0200
changeset 405778 17e47288c2249ac21bd2d0585b19c835984ed32b
parent 405737 b3c95e78fd757c06527180822b4f500511485331 (current diff)
parent 405777 f558cf2238e47aa1b4f2b92e7bd2171fd5c48720 (diff)
child 405779 b996cabc7ef54bbe050d647494bf00d668ec52e6
child 405798 7ba8cf3ae689f1291c4b21d281179f0c65501d87
child 405965 3fa481af8f5b761082671b482079f0a6c6929d51
push id33531
push usercsabou@mozilla.com
push dateWed, 28 Feb 2018 21:49:08 +0000
treeherdermozilla-central@17e47288c224 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
17e47288c224 / 60.0a1 / 20180228220115 / files
nightly linux64
17e47288c224 / 60.0a1 / 20180228220115 / files
nightly mac
17e47288c224 / 60.0a1 / 20180228220115 / files
nightly win32
17e47288c224 / 60.0a1 / 20180228220115 / files
nightly win64
17e47288c224 / 60.0a1 / 20180228220115 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge 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)
-{