Bug 1410346 - Merge U2F.cpp and U2FManager.cpp r=jcj
authorTim Taubert <ttaubert@mozilla.com>
Tue, 28 Nov 2017 10:21:07 +0100
changeset 448346 da0b490b4aeadbce21c759fe14a1ee6d78072f50
parent 448345 de118e31bada2ce43ce86de7a098664cd6e3dfe1
child 448347 45030c5aa987416ffb6a23be325e65d484c12229
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjcj
bugs1410346
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1410346 - Merge U2F.cpp and U2FManager.cpp r=jcj Reviewers: jcj Reviewed By: jcj Bug #: 1410346 Differential Revision: https://phabricator.services.mozilla.com/D288
dom/u2f/U2F.cpp
dom/u2f/U2F.h
dom/u2f/U2FManager.cpp
dom/u2f/U2FManager.h
dom/u2f/U2FTransactionChild.cpp
dom/u2f/U2FTransactionChild.h
dom/u2f/moz.build
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -1,48 +1,75 @@
 /* -*- 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 "mozilla/dom/U2F.h"
+#include "mozilla/dom/Event.h"
 #include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/BackgroundChild.h"
 #include "nsContentUtils.h"
+#include "nsICryptoHash.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsURLParsers.h"
-#include "U2FManager.h"
+#include "U2FTransactionChild.h"
+#include "U2FUtil.h"
+#include "hasht.h"
+
+using namespace mozilla::ipc;
 
 // Forward decl because of nsHTMLDocument.h's complex dependency on /layout/style
 class nsHTMLDocument {
 public:
   bool IsRegistrableDomainSuffixOfOrEqualTo(const nsAString& aHostSuffixString,
                                             const nsACString& aOrigHost);
 };
 
 namespace mozilla {
 namespace dom {
 
 static mozilla::LazyLogModule gU2FLog("u2fmanager");
 
+NS_NAMED_LITERAL_STRING(kVisibilityChange, "visibilitychange");
 NS_NAMED_LITERAL_STRING(kFinishEnrollment, "navigator.id.finishEnrollment");
 NS_NAMED_LITERAL_STRING(kGetAssertion, "navigator.id.getAssertion");
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(U2F)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(U2F)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2F, mParent)
 
+/***********************************************************************
+ * Utility Functions
+ **********************************************************************/
+
+static ErrorCode
+ConvertNSResultToErrorCode(const nsresult& aError)
+{
+  if (aError == NS_ERROR_DOM_TIMEOUT_ERR) {
+    return ErrorCode::TIMEOUT;
+  }
+  /* Emitted by U2F{Soft,HID}TokenManager when we really mean ineligible */
+  if (aError == NS_ERROR_DOM_NOT_ALLOWED_ERR) {
+    return ErrorCode::DEVICE_INELIGIBLE;
+  }
+  return ErrorCode::OTHER_ERROR;
+}
+
 static uint32_t
 AdjustedTimeoutMillis(const Optional<Nullable<int32_t>>& opt_aSeconds)
 {
   uint32_t adjustedTimeoutMillis = 30000u;
   if (opt_aSeconds.WasPassed() && !opt_aSeconds.Value().IsNull()) {
     adjustedTimeoutMillis = opt_aSeconds.Value().Value() * 1000u;
     adjustedTimeoutMillis = std::max(15000u, adjustedTimeoutMillis);
     adjustedTimeoutMillis = std::min(120000u, adjustedTimeoutMillis);
@@ -168,16 +195,69 @@ EvaluateAppID(nsPIDOMWindowInner* aParen
   if (html->IsRegistrableDomainSuffixOfOrEqualTo(NS_ConvertUTF8toUTF16(lowestFacetHost),
                                                  appIdHost)) {
     return ErrorCode::OK;
   }
 
   return ErrorCode::BAD_REQUEST;
 }
 
+static nsresult
+BuildTransactionHashes(const nsCString& aRpId,
+                       const nsCString& aClientDataJSON,
+                       /* out */ CryptoBuffer& aRpIdHash,
+                       /* out */ CryptoBuffer& aClientDataHash)
+{
+  nsresult srv;
+  nsCOMPtr<nsICryptoHash> hashService =
+    do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
+  if (NS_FAILED(srv)) {
+    return srv;
+  }
+
+  if (!aRpIdHash.SetLength(SHA256_LENGTH, fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  srv = HashCString(hashService, aRpId, aRpIdHash);
+  if (NS_WARN_IF(NS_FAILED(srv))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (!aClientDataHash.SetLength(SHA256_LENGTH, fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  srv = HashCString(hashService, aClientDataJSON, aClientDataHash);
+  if (NS_WARN_IF(NS_FAILED(srv))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (MOZ_LOG_TEST(gU2FLog, LogLevel::Debug)) {
+    nsString base64;
+    Unused << NS_WARN_IF(NS_FAILED(aRpIdHash.ToJwkBase64(base64)));
+
+    MOZ_LOG(gU2FLog, LogLevel::Debug,
+            ("dom::U2FManager::RpID: %s", aRpId.get()));
+
+    MOZ_LOG(gU2FLog, LogLevel::Debug,
+            ("dom::U2FManager::Rp ID Hash (base64): %s",
+              NS_ConvertUTF16toUTF8(base64).get()));
+
+    Unused << NS_WARN_IF(NS_FAILED(aClientDataHash.ToJwkBase64(base64)));
+
+    MOZ_LOG(gU2FLog, LogLevel::Debug,
+            ("dom::U2FManager::Client Data JSON: %s", aClientDataJSON.get()));
+
+    MOZ_LOG(gU2FLog, LogLevel::Debug,
+            ("dom::U2FManager::Client Data Hash (base64): %s",
+              NS_ConvertUTF16toUTF8(base64).get()));
+  }
+
+  return NS_OK;
+}
+
 template<typename T, typename C>
 static void
 ExecuteCallback(T& aResp, Maybe<nsMainThreadPtrHandle<C>>& aCb)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (aCb.isNothing()) {
     return;
   }
@@ -189,33 +269,48 @@ ExecuteCallback(T& aResp, Maybe<nsMainTh
   MOZ_ASSERT(!!callback);
 
   ErrorResult error;
   callback->Call(aResp, error);
   NS_WARNING_ASSERTION(!error.Failed(), "dom::U2F::Promise callback failed");
   error.SuppressException(); // Useful exceptions already emitted
 }
 
+/***********************************************************************
+ * U2F JavaScript API Implementation
+ **********************************************************************/
+
 U2F::U2F(nsPIDOMWindowInner* aParent)
   : mParent(aParent)
 {
+  MOZ_ASSERT(NS_IsMainThread());
 }
 
 U2F::~U2F()
 {
-  mPromiseHolder.DisconnectIfExists();
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mTransaction.isSome()) {
+    RejectTransaction(NS_ERROR_ABORT);
+  }
+
+  if (mChild) {
+    RefPtr<U2FTransactionChild> c;
+    mChild.swap(c);
+    c->Send__delete__(c);
+  }
+
   mRegisterCallback.reset();
   mSignCallback.reset();
 }
 
 void
 U2F::Init(ErrorResult& aRv)
 {
   MOZ_ASSERT(mParent);
-  MOZ_ASSERT(!mEventTarget);
 
   nsCOMPtr<nsIDocument> doc = mParent->GetDoc();
   MOZ_ASSERT(doc);
   if (!doc) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
@@ -224,19 +319,16 @@ U2F::Init(ErrorResult& aRv)
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   if (NS_WARN_IF(mOrigin.IsEmpty())) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
-
-  mEventTarget = doc->EventTargetFor(TaskCategory::Other);
-  MOZ_ASSERT(mEventTarget);
 }
 
 /* virtual */ JSObject*
 U2F::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return U2FBinding::Wrap(aCx, this, aGivenProto);
 }
 
@@ -245,17 +337,19 @@ U2F::Register(const nsAString& aAppId,
               const Sequence<RegisterRequest>& aRegisterRequests,
               const Sequence<RegisteredKey>& aRegisteredKeys,
               U2FRegisterCallback& aCallback,
               const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
               ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  Cancel();
+  if (mTransaction.isSome()) {
+    CancelTransaction(NS_ERROR_ABORT);
+  }
 
   MOZ_ASSERT(mRegisterCallback.isNothing());
   mRegisterCallback = Some(nsMainThreadPtrHandle<U2FRegisterCallback>(
                         new nsMainThreadPtrHolder<U2FRegisterCallback>(
                             "U2F::Register::callback", &aCallback)));
 
   uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
 
@@ -297,61 +391,108 @@ U2F::Register(const nsAString& aAppId,
     return;
   }
 
   // Build the exclusion list, if any
   nsTArray<WebAuthnScopedCredentialDescriptor> excludeList;
   RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
                                        excludeList);
 
-  auto& localReqHolder = mPromiseHolder;
-  auto& localCb = mRegisterCallback;
-  RefPtr<U2FManager> mgr = U2FManager::GetOrCreate();
-  RefPtr<U2FPromise> p = mgr->Register(mParent, cAppId,
-                                       NS_ConvertUTF16toUTF8(clientDataJSON),
-                                       adjustedTimeoutMillis, excludeList);
-  p->Then(mEventTarget, "dom::U2F::Register::Promise::Resolve",
-          [&localCb, &localReqHolder](nsString aResponse) {
-              MOZ_LOG(gU2FLog, LogLevel::Debug,
-                      ("dom::U2F::Register::Promise::Resolve, response was %s",
-                        NS_ConvertUTF16toUTF8(aResponse).get()));
-              RegisterResponse response;
-              response.Init(aResponse);
+  auto clientData = NS_ConvertUTF16toUTF8(clientDataJSON);
+
+  CryptoBuffer rpIdHash, clientDataHash;
+  if (NS_FAILED(BuildTransactionHashes(cAppId, clientData,
+                                       rpIdHash, clientDataHash))) {
+    RegisterResponse response;
+    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
+    ExecuteCallback(response, mRegisterCallback);
+    return;
+  }
+
+  if (!MaybeCreateBackgroundActor()) {
+    RegisterResponse response;
+    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
+    ExecuteCallback(response, mRegisterCallback);
+    return;
+  }
+
+  ListenForVisibilityEvents();
+
+  // Always blank for U2F
+  nsTArray<WebAuthnExtension> extensions;
+
+  WebAuthnTransactionInfo info(rpIdHash,
+                               clientDataHash,
+                               adjustedTimeoutMillis,
+                               excludeList,
+                               extensions);
+
+  MOZ_ASSERT(mTransaction.isNothing());
+  mTransaction = Some(U2FTransaction(clientData));
+  mChild->SendRequestRegister(mTransaction.ref().mId, info);
+}
 
-              // U2F could be reentered from microtask-checkpoint while calling
-              // ExecuteCallback(), so we should mark Complete() earlier.
-              localReqHolder.Complete();
-              ExecuteCallback(response, localCb);
-          },
-          [&localCb, &localReqHolder](ErrorCode aErrorCode) {
-              MOZ_LOG(gU2FLog, LogLevel::Debug,
-                      ("dom::U2F::Register::Promise::Reject, response was %d",
-                        static_cast<uint32_t>(aErrorCode)));
-              RegisterResponse response;
-              response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
+void
+U2F::FinishRegister(const uint64_t& aTransactionId,
+                    nsTArray<uint8_t>& aRegBuffer)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Check for a valid transaction.
+  if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
+    return;
+  }
+
+  CryptoBuffer clientDataBuf;
+  if (NS_WARN_IF(!clientDataBuf.Assign(mTransaction.ref().mClientData))) {
+    RejectTransaction(NS_ERROR_ABORT);
+    return;
+  }
 
-              // U2F could be reentered from microtask-checkpoint while calling
-              // ExecuteCallback(), so we should mark Complete() earlier.
-              localReqHolder.Complete();
-              ExecuteCallback(response, localCb);
-          })
-  ->Track(mPromiseHolder);
+  CryptoBuffer regBuf;
+  if (NS_WARN_IF(!regBuf.Assign(aRegBuffer))) {
+    RejectTransaction(NS_ERROR_ABORT);
+    return;
+  }
+
+  nsString clientDataBase64;
+  nsString registrationDataBase64;
+  nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
+  nsresult rvRegistrationData = regBuf.ToJwkBase64(registrationDataBase64);
+
+  if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
+      NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
+    RejectTransaction(NS_ERROR_ABORT);
+    return;
+  }
+
+  // Assemble a response object to return
+  RegisterResponse response;
+  response.mVersion.Construct(kRequiredU2FVersion);
+  response.mClientData.Construct(clientDataBase64);
+  response.mRegistrationData.Construct(registrationDataBase64);
+  response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
+
+  ExecuteCallback(response, mRegisterCallback);
+  ClearTransaction();
 }
 
 void
 U2F::Sign(const nsAString& aAppId,
           const nsAString& aChallenge,
           const Sequence<RegisteredKey>& aRegisteredKeys,
           U2FSignCallback& aCallback,
           const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
           ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  Cancel();
+  if (mTransaction.isSome()) {
+    CancelTransaction(NS_ERROR_ABORT);
+  }
 
   MOZ_ASSERT(mSignCallback.isNothing());
   mSignCallback = Some(nsMainThreadPtrHandle<U2FSignCallback>(
                     new nsMainThreadPtrHolder<U2FSignCallback>(
                         "U2F::Sign::callback", &aCallback)));
 
   uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
 
@@ -378,71 +519,250 @@ U2F::Sign(const nsAString& aAppId,
     ExecuteCallback(response, mSignCallback);
     return;
   }
 
   // Build the key list, if any
   nsTArray<WebAuthnScopedCredentialDescriptor> permittedList;
   RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
                                        permittedList);
-  auto& localReqHolder = mPromiseHolder;
-  auto& localCb = mSignCallback;
-  RefPtr<U2FManager> mgr = U2FManager::GetOrCreate();
-  RefPtr<U2FPromise> p = mgr->Sign(mParent, cAppId,
-                                   NS_ConvertUTF16toUTF8(clientDataJSON),
-                                   adjustedTimeoutMillis, permittedList);
-  p->Then(mEventTarget, "dom::U2F::Sign::Promise::Resolve",
-          [&localCb, &localReqHolder](nsString aResponse) {
-              MOZ_LOG(gU2FLog, LogLevel::Debug,
-                      ("dom::U2F::Sign::Promise::Resolve, response was %s",
-                        NS_ConvertUTF16toUTF8(aResponse).get()));
-              SignResponse response;
-              response.Init(aResponse);
+
+  auto clientData = NS_ConvertUTF16toUTF8(clientDataJSON);
+
+  CryptoBuffer rpIdHash, clientDataHash;
+  if (NS_FAILED(BuildTransactionHashes(cAppId, clientData,
+                                       rpIdHash, clientDataHash))) {
+    SignResponse response;
+    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
+    ExecuteCallback(response, mSignCallback);
+    return;
+  }
+
+  if (!MaybeCreateBackgroundActor()) {
+    SignResponse response;
+    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
+    ExecuteCallback(response, mSignCallback);
+    return;
+  }
+
+  ListenForVisibilityEvents();
+
+  // Always blank for U2F
+  nsTArray<WebAuthnExtension> extensions;
+
+  WebAuthnTransactionInfo info(rpIdHash,
+                               clientDataHash,
+                               adjustedTimeoutMillis,
+                               permittedList,
+                               extensions);
+
+  MOZ_ASSERT(mTransaction.isNothing());
+  mTransaction = Some(U2FTransaction(clientData));
+  mChild->SendRequestSign(mTransaction.ref().mId, info);
+}
+
+void
+U2F::FinishSign(const uint64_t& aTransactionId,
+                nsTArray<uint8_t>& aCredentialId,
+                nsTArray<uint8_t>& aSigBuffer)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Check for a valid transaction.
+  if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
+    return;
+  }
 
-              // U2F could be reentered from microtask-checkpoint while calling
-              // ExecuteCallback(), so we should mark Complete() earlier.
-              localReqHolder.Complete();
-              ExecuteCallback(response, localCb);
-          },
-          [&localCb, &localReqHolder](ErrorCode aErrorCode) {
-              MOZ_LOG(gU2FLog, LogLevel::Debug,
-                      ("dom::U2F::Sign::Promise::Reject, response was %d",
-                        static_cast<uint32_t>(aErrorCode)));
-              SignResponse response;
-              response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
+  CryptoBuffer clientDataBuf;
+  if (NS_WARN_IF(!clientDataBuf.Assign(mTransaction.ref().mClientData))) {
+    RejectTransaction(NS_ERROR_ABORT);
+    return;
+  }
+
+  CryptoBuffer credBuf;
+  if (NS_WARN_IF(!credBuf.Assign(aCredentialId))) {
+    RejectTransaction(NS_ERROR_ABORT);
+    return;
+  }
+
+  CryptoBuffer sigBuf;
+  if (NS_WARN_IF(!sigBuf.Assign(aSigBuffer))) {
+    RejectTransaction(NS_ERROR_ABORT);
+    return;
+  }
 
-              // U2F could be reentered from microtask-checkpoint while calling
-              // ExecuteCallback(), so we should mark Complete() earlier.
-              localReqHolder.Complete();
-              ExecuteCallback(response, localCb);
-          })
-  ->Track(mPromiseHolder);
+  // Assemble a response object to return
+  nsString clientDataBase64;
+  nsString signatureDataBase64;
+  nsString keyHandleBase64;
+  nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
+  nsresult rvSignatureData = sigBuf.ToJwkBase64(signatureDataBase64);
+  nsresult rvKeyHandle = credBuf.ToJwkBase64(keyHandleBase64);
+  if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
+      NS_WARN_IF(NS_FAILED(rvSignatureData) ||
+      NS_WARN_IF(NS_FAILED(rvKeyHandle)))) {
+    RejectTransaction(NS_ERROR_ABORT);
+    return;
+  }
+
+  SignResponse response;
+  response.mKeyHandle.Construct(keyHandleBase64);
+  response.mClientData.Construct(clientDataBase64);
+  response.mSignatureData.Construct(signatureDataBase64);
+  response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
+
+  ExecuteCallback(response, mSignCallback);
+  ClearTransaction();
+}
+
+void
+U2F::ClearTransaction()
+{
+  if (!NS_WARN_IF(mTransaction.isNothing())) {
+    StopListeningForVisibilityEvents();
+  }
+
+  mTransaction.reset();
 }
 
 void
-U2F::Cancel()
+U2F::RejectTransaction(const nsresult& aError)
+{
+  if (!NS_WARN_IF(mTransaction.isNothing())) {
+    ErrorCode code = ConvertNSResultToErrorCode(aError);
+
+    if (mRegisterCallback.isSome()) {
+      RegisterResponse response;
+      response.mErrorCode.Construct(static_cast<uint32_t>(code));
+      ExecuteCallback(response, mRegisterCallback);
+    }
+
+    if (mSignCallback.isSome()) {
+      SignResponse response;
+      response.mErrorCode.Construct(static_cast<uint32_t>(code));
+      ExecuteCallback(response, mSignCallback);
+    }
+  }
+
+  ClearTransaction();
+}
+
+void
+U2F::CancelTransaction(const nsresult& aError)
+{
+  if (!NS_WARN_IF(!mChild || mTransaction.isNothing())) {
+    mChild->SendRequestCancel(mTransaction.ref().mId);
+  }
+
+  RejectTransaction(aError);
+}
+
+void
+U2F::RequestAborted(const uint64_t& aTransactionId, const nsresult& aError)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  const ErrorCode errorCode = ErrorCode::OTHER_ERROR;
+  if (mTransaction.isSome() && mTransaction.ref().mId == aTransactionId) {
+    RejectTransaction(aError);
+  }
+}
+
+/***********************************************************************
+ * Event Handling
+ **********************************************************************/
+
+void
+U2F::ListenForVisibilityEvents()
+{
+  nsCOMPtr<nsIDocument> doc = mParent->GetExtantDoc();
+  if (NS_WARN_IF(!doc)) {
+    return;
+  }
+
+  nsresult rv = doc->AddSystemEventListener(kVisibilityChange, this,
+                                            /* use capture */ true,
+                                            /* wants untrusted */ false);
+  Unused << NS_WARN_IF(NS_FAILED(rv));
+}
 
-  if (mRegisterCallback.isSome()) {
-    RegisterResponse response;
-    response.mErrorCode.Construct(static_cast<uint32_t>(errorCode));
-    ExecuteCallback(response, mRegisterCallback);
+void
+U2F::StopListeningForVisibilityEvents()
+{
+  nsCOMPtr<nsIDocument> doc = mParent->GetExtantDoc();
+  if (NS_WARN_IF(!doc)) {
+    return;
+  }
+
+  nsresult rv = doc->RemoveSystemEventListener(kVisibilityChange, this,
+                                               /* use capture */ true);
+  Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+
+NS_IMETHODIMP
+U2F::HandleEvent(nsIDOMEvent* aEvent)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aEvent);
+
+  nsAutoString type;
+  aEvent->GetType(type);
+  if (!type.Equals(kVisibilityChange)) {
+    return NS_ERROR_FAILURE;
   }
 
-  if (mSignCallback.isSome()) {
-    SignResponse response;
-    response.mErrorCode.Construct(static_cast<uint32_t>(errorCode));
-    ExecuteCallback(response, mSignCallback);
+  nsCOMPtr<nsIDocument> doc =
+    do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
+  if (NS_WARN_IF(!doc)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (doc->Hidden()) {
+    MOZ_LOG(gU2FLog, LogLevel::Debug,
+            ("Visibility change: U2F window is hidden, cancelling job."));
+
+    CancelTransaction(NS_ERROR_ABORT);
   }
 
-  RefPtr<U2FManager> mgr = U2FManager::Get();
-  if (mgr) {
-    mgr->MaybeCancelTransaction(NS_ERROR_DOM_OPERATION_ERR);
+  return NS_OK;
+}
+
+/***********************************************************************
+ * IPC Protocol Implementation
+ **********************************************************************/
+
+bool
+U2F::MaybeCreateBackgroundActor()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mChild) {
+    return true;
   }
 
-  mPromiseHolder.DisconnectIfExists();
+  PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
+  if (NS_WARN_IF(!actorChild)) {
+    return false;
+  }
+
+  RefPtr<U2FTransactionChild> mgr(new U2FTransactionChild(this));
+  PWebAuthnTransactionChild* constructedMgr =
+    actorChild->SendPWebAuthnTransactionConstructor(mgr);
+
+  if (NS_WARN_IF(!constructedMgr)) {
+    return false;
+  }
+
+  MOZ_ASSERT(constructedMgr == mgr);
+  mChild = mgr.forget();
+
+  return true;
+}
+
+void
+U2F::ActorDestroyed()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mChild = nullptr;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/u2f/U2F.h
+++ b/dom/u2f/U2F.h
@@ -12,34 +12,63 @@
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/U2FBinding.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/MozPromise.h"
 #include "nsProxyRelease.h"
 #include "nsWrapperCache.h"
 #include "U2FAuthenticator.h"
+#include "nsIDOMEventListener.h"
 
 class nsISerialEventTarget;
 
 namespace mozilla {
 namespace dom {
 
+class U2FTransactionChild;
 class U2FRegisterCallback;
 class U2FSignCallback;
 
 // Defined in U2FBinding.h by the U2F.webidl; their use requires a JSContext.
 struct RegisterRequest;
 struct RegisteredKey;
 
-// The U2F Class is used by the JS engine to initiate U2F operations.
-class U2F final : public nsISupports
+class U2FTransaction
+{
+public:
+  explicit U2FTransaction(const nsCString& aClientData)
+    : mClientData(aClientData)
+    , mId(NextId())
+  {
+    MOZ_ASSERT(mId > 0);
+  }
+
+  // Client data used to assemble reply objects.
+  nsCString mClientData;
+
+  // Unique transaction id.
+  uint64_t mId;
+
+private:
+  // Generates a unique id for new transactions. This doesn't have to be unique
+  // forever, it's sufficient to differentiate between temporally close
+  // transactions, where messages can intersect. Can overflow.
+  static uint64_t NextId() {
+    static uint64_t id = 0;
+    return ++id;
+  }
+};
+
+class U2F final : public nsIDOMEventListener
                 , public nsWrapperCache
 {
 public:
+  NS_DECL_NSIDOMEVENTLISTENER
+
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(U2F)
 
   explicit U2F(nsPIDOMWindowInner* aParent);
 
   nsPIDOMWindowInner*
   GetParentObject() const
   {
@@ -63,26 +92,56 @@ public:
   void
   Sign(const nsAString& aAppId,
        const nsAString& aChallenge,
        const Sequence<RegisteredKey>& aRegisteredKeys,
        U2FSignCallback& aCallback,
        const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
        ErrorResult& aRv);
 
-private:
+  void
+  FinishRegister(const uint64_t& aTransactionId, nsTArray<uint8_t>& aRegBuffer);
+
+  void
+  FinishSign(const uint64_t& aTransactionId,
+             nsTArray<uint8_t>& aCredentialId,
+             nsTArray<uint8_t>& aSigBuffer);
+
   void
-  Cancel();
+  RequestAborted(const uint64_t& aTransactionId, const nsresult& aError);
+
+  void ActorDestroyed();
+
+private:
+  ~U2F();
+
+  // Visibility event handling.
+  void ListenForVisibilityEvents();
+  void StopListeningForVisibilityEvents();
+
+  // Clears all information we have about the current transaction.
+  void ClearTransaction();
+  // Rejects the current transaction and calls ClearTransaction().
+  void RejectTransaction(const nsresult& aError);
+  // Cancels the current transaction (by sending a Cancel message to the
+  // parent) and rejects it by calling RejectTransaction().
+  void CancelTransaction(const nsresult& aError);
+
+  bool MaybeCreateBackgroundActor();
 
   nsString mOrigin;
   nsCOMPtr<nsPIDOMWindowInner> mParent;
-  nsCOMPtr<nsISerialEventTarget> mEventTarget;
+
+  // U2F API callbacks.
   Maybe<nsMainThreadPtrHandle<U2FRegisterCallback>> mRegisterCallback;
   Maybe<nsMainThreadPtrHandle<U2FSignCallback>> mSignCallback;
-  MozPromiseRequestHolder<U2FPromise> mPromiseHolder;
 
-  ~U2F();
+  // IPC Channel to the parent process.
+  RefPtr<U2FTransactionChild> mChild;
+
+  // The current transaction, if any.
+  Maybe<U2FTransaction> mTransaction;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_U2F_h
deleted file mode 100644
--- a/dom/u2f/U2FManager.cpp
+++ /dev/null
@@ -1,490 +0,0 @@
-/* -*- 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 "hasht.h"
-#include "nsICryptoHash.h"
-#include "nsNetCID.h"
-#include "U2FManager.h"
-#include "U2FTransactionChild.h"
-#include "U2FUtil.h"
-#include "mozilla/ClearOnShutdown.h"
-#include "mozilla/dom/Promise.h"
-#include "mozilla/dom/PWebAuthnTransaction.h"
-#include "mozilla/dom/WebCryptoCommon.h"
-#include "mozilla/ipc/PBackgroundChild.h"
-#include "mozilla/ipc/BackgroundChild.h"
-
-using namespace mozilla::ipc;
-
-namespace mozilla {
-namespace dom {
-
-/***********************************************************************
- * Statics
- **********************************************************************/
-
-namespace {
-StaticRefPtr<U2FManager> gU2FManager;
-static mozilla::LazyLogModule gU2FManagerLog("u2fmanager");
-}
-
-NS_NAMED_LITERAL_STRING(kVisibilityChange, "visibilitychange");
-
-NS_IMPL_ISUPPORTS(U2FManager, nsIDOMEventListener);
-
-/***********************************************************************
- * Utility Functions
- **********************************************************************/
-
-static void
-ListenForVisibilityEvents(nsPIDOMWindowInner* aParent,
-                          U2FManager* aListener)
-{
-  MOZ_ASSERT(aParent);
-  MOZ_ASSERT(aListener);
-
-  nsCOMPtr<nsIDocument> doc = aParent->GetExtantDoc();
-  if (NS_WARN_IF(!doc)) {
-    return;
-  }
-
-  nsresult rv = doc->AddSystemEventListener(kVisibilityChange, aListener,
-                                            /* use capture */ true,
-                                            /* wants untrusted */ false);
-  Unused << NS_WARN_IF(NS_FAILED(rv));
-}
-
-static void
-StopListeningForVisibilityEvents(nsPIDOMWindowInner* aParent,
-                                 U2FManager* aListener)
-{
-  MOZ_ASSERT(aParent);
-  MOZ_ASSERT(aListener);
-
-  nsCOMPtr<nsIDocument> doc = aParent->GetExtantDoc();
-  if (NS_WARN_IF(!doc)) {
-    return;
-  }
-
-  nsresult rv = doc->RemoveSystemEventListener(kVisibilityChange, aListener,
-                                               /* use capture */ true);
-  Unused << NS_WARN_IF(NS_FAILED(rv));
-}
-
-static ErrorCode
-ConvertNSResultToErrorCode(const nsresult& aError)
-{
-  if (aError == NS_ERROR_DOM_TIMEOUT_ERR) {
-    return ErrorCode::TIMEOUT;
-  }
-  /* Emitted by U2F{Soft,HID}TokenManager when we really mean ineligible */
-  if (aError == NS_ERROR_DOM_NOT_ALLOWED_ERR) {
-    return ErrorCode::DEVICE_INELIGIBLE;
-  }
-  return ErrorCode::OTHER_ERROR;
-}
-
-/***********************************************************************
- * U2FManager Implementation
- **********************************************************************/
-
-U2FManager::U2FManager()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-}
-
-void
-U2FManager::ClearTransaction()
-{
-  if (!NS_WARN_IF(mTransaction.isNothing())) {
-    StopListeningForVisibilityEvents(mTransaction.ref().mParent, this);
-  }
-
-  mTransaction.reset();
-}
-
-void
-U2FManager::RejectTransaction(const nsresult& aError)
-{
-  if (!NS_WARN_IF(mTransaction.isNothing())) {
-    ErrorCode code = ConvertNSResultToErrorCode(aError);
-    mTransaction.ref().mPromise.Reject(code, __func__);
-  }
-
-  ClearTransaction();
-}
-
-void
-U2FManager::CancelTransaction(const nsresult& aError)
-{
-  if (!NS_WARN_IF(!mChild || mTransaction.isNothing())) {
-    mChild->SendRequestCancel(mTransaction.ref().mId);
-  }
-
-  RejectTransaction(aError);
-}
-
-U2FManager::~U2FManager()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mTransaction.isSome()) {
-    RejectTransaction(NS_ERROR_ABORT);
-  }
-
-  if (mChild) {
-    RefPtr<U2FTransactionChild> c;
-    mChild.swap(c);
-    c->Send__delete__(c);
-  }
-}
-
-bool
-U2FManager::MaybeCreateBackgroundActor()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mChild) {
-    return true;
-  }
-
-  PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
-  if (NS_WARN_IF(!actorChild)) {
-    return false;
-  }
-
-  RefPtr<U2FTransactionChild> mgr(new U2FTransactionChild());
-  PWebAuthnTransactionChild* constructedMgr =
-    actorChild->SendPWebAuthnTransactionConstructor(mgr);
-
-  if (NS_WARN_IF(!constructedMgr)) {
-    return false;
-  }
-
-  MOZ_ASSERT(constructedMgr == mgr);
-  mChild = mgr.forget();
-
-  return true;
-}
-
-//static
-U2FManager*
-U2FManager::GetOrCreate()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (gU2FManager) {
-    return gU2FManager;
-  }
-
-  gU2FManager = new U2FManager();
-  ClearOnShutdown(&gU2FManager);
-  return gU2FManager;
-}
-
-//static
-U2FManager*
-U2FManager::Get()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return gU2FManager;
-}
-
-//static
-nsresult
-U2FManager::BuildTransactionHashes(const nsCString& aRpId,
-                                   const nsCString& aClientDataJSON,
-                                   /* out */ CryptoBuffer& aRpIdHash,
-                                   /* out */ CryptoBuffer& aClientDataHash)
-{
-  nsresult srv;
-  nsCOMPtr<nsICryptoHash> hashService =
-    do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
-  if (NS_FAILED(srv)) {
-    return srv;
-  }
-
-  if (!aRpIdHash.SetLength(SHA256_LENGTH, fallible)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-  srv = HashCString(hashService, aRpId, aRpIdHash);
-  if (NS_WARN_IF(NS_FAILED(srv))) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (!aClientDataHash.SetLength(SHA256_LENGTH, fallible)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-  srv = HashCString(hashService, aClientDataJSON, aClientDataHash);
-  if (NS_WARN_IF(NS_FAILED(srv))) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (MOZ_LOG_TEST(gU2FLog, LogLevel::Debug)) {
-    nsString base64;
-    Unused << NS_WARN_IF(NS_FAILED(aRpIdHash.ToJwkBase64(base64)));
-
-    MOZ_LOG(gU2FLog, LogLevel::Debug,
-            ("dom::U2FManager::RpID: %s", aRpId.get()));
-
-    MOZ_LOG(gU2FLog, LogLevel::Debug,
-            ("dom::U2FManager::Rp ID Hash (base64): %s",
-              NS_ConvertUTF16toUTF8(base64).get()));
-
-    Unused << NS_WARN_IF(NS_FAILED(aClientDataHash.ToJwkBase64(base64)));
-
-    MOZ_LOG(gU2FLog, LogLevel::Debug,
-            ("dom::U2FManager::Client Data JSON: %s", aClientDataJSON.get()));
-
-    MOZ_LOG(gU2FLog, LogLevel::Debug,
-            ("dom::U2FManager::Client Data Hash (base64): %s",
-              NS_ConvertUTF16toUTF8(base64).get()));
-  }
-
-  return NS_OK;
-}
-
-already_AddRefed<U2FPromise>
-U2FManager::Register(nsPIDOMWindowInner* aParent, const nsCString& aRpId,
-               const nsCString& aClientDataJSON,
-               const uint32_t& aTimeoutMillis,
-               const nsTArray<WebAuthnScopedCredentialDescriptor>& aExcludeList)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aParent);
-
-  if (mTransaction.isSome()) {
-    CancelTransaction(NS_ERROR_ABORT);
-  }
-
-  CryptoBuffer rpIdHash, clientDataHash;
-  if (NS_FAILED(BuildTransactionHashes(aRpId, aClientDataJSON,
-                                       rpIdHash, clientDataHash))) {
-    return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
-  }
-
-  if (!MaybeCreateBackgroundActor()) {
-    return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
-  }
-
-  ListenForVisibilityEvents(aParent, this);
-
-  // Always blank for U2F
-  nsTArray<WebAuthnExtension> extensions;
-
-  WebAuthnTransactionInfo info(rpIdHash,
-                               clientDataHash,
-                               aTimeoutMillis,
-                               aExcludeList,
-                               extensions);
-
-  MOZ_ASSERT(mTransaction.isNothing());
-  mTransaction = Some(U2FTransaction(aParent, Move(info), aClientDataJSON));
-
-  mChild->SendRequestRegister(mTransaction.ref().mId, mTransaction.ref().mInfo);
-  return mTransaction.ref().mPromise.Ensure(__func__);
-}
-
-already_AddRefed<U2FPromise>
-U2FManager::Sign(nsPIDOMWindowInner* aParent,
-                 const nsCString& aRpId,
-                 const nsCString& aClientDataJSON,
-                 const uint32_t& aTimeoutMillis,
-                 const nsTArray<WebAuthnScopedCredentialDescriptor>& aAllowList)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aParent);
-
-  if (mTransaction.isSome()) {
-    CancelTransaction(NS_ERROR_ABORT);
-  }
-
-  CryptoBuffer rpIdHash, clientDataHash;
-  if (NS_FAILED(BuildTransactionHashes(aRpId, aClientDataJSON,
-                                       rpIdHash, clientDataHash))) {
-    return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
-  }
-
-  if (!MaybeCreateBackgroundActor()) {
-    return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
-  }
-
-  ListenForVisibilityEvents(aParent, this);
-
-  // Always blank for U2F
-  nsTArray<WebAuthnExtension> extensions;
-
-  WebAuthnTransactionInfo info(rpIdHash,
-                               clientDataHash,
-                               aTimeoutMillis,
-                               aAllowList,
-                               extensions);
-
-  MOZ_ASSERT(mTransaction.isNothing());
-  mTransaction = Some(U2FTransaction(aParent, Move(info), aClientDataJSON));
-
-  mChild->SendRequestSign(mTransaction.ref().mId, mTransaction.ref().mInfo);
-  return mTransaction.ref().mPromise.Ensure(__func__);
-}
-
-void
-U2FManager::FinishRegister(const uint64_t& aTransactionId,
-                           nsTArray<uint8_t>& aRegBuffer)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // Check for a valid transaction.
-  if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
-    return;
-  }
-
-  CryptoBuffer clientDataBuf;
-  if (NS_WARN_IF(!clientDataBuf.Assign(mTransaction.ref().mClientData))) {
-    RejectTransaction(NS_ERROR_ABORT);
-    return;
-  }
-
-  CryptoBuffer regBuf;
-  if (NS_WARN_IF(!regBuf.Assign(aRegBuffer))) {
-    RejectTransaction(NS_ERROR_ABORT);
-    return;
-  }
-
-  nsString clientDataBase64;
-  nsString registrationDataBase64;
-  nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
-  nsresult rvRegistrationData = regBuf.ToJwkBase64(registrationDataBase64);
-
-  if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
-      NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
-    RejectTransaction(NS_ERROR_ABORT);
-    return;
-  }
-
-  // Assemble a response object to return
-  RegisterResponse response;
-  response.mVersion.Construct(kRequiredU2FVersion);
-  response.mClientData.Construct(clientDataBase64);
-  response.mRegistrationData.Construct(registrationDataBase64);
-  response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
-
-  nsString responseStr;
-  if (NS_WARN_IF(!response.ToJSON(responseStr))) {
-    RejectTransaction(NS_ERROR_ABORT);
-    return;
-  }
-
-  mTransaction.ref().mPromise.Resolve(responseStr, __func__);
-  ClearTransaction();
-}
-
-void
-U2FManager::FinishSign(const uint64_t& aTransactionId,
-                       nsTArray<uint8_t>& aCredentialId,
-                       nsTArray<uint8_t>& aSigBuffer)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // Check for a valid transaction.
-  if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
-    return;
-  }
-
-  CryptoBuffer clientDataBuf;
-  if (NS_WARN_IF(!clientDataBuf.Assign(mTransaction.ref().mClientData))) {
-    RejectTransaction(NS_ERROR_ABORT);
-    return;
-  }
-
-  CryptoBuffer credBuf;
-  if (NS_WARN_IF(!credBuf.Assign(aCredentialId))) {
-    RejectTransaction(NS_ERROR_ABORT);
-    return;
-  }
-
-  CryptoBuffer sigBuf;
-  if (NS_WARN_IF(!sigBuf.Assign(aSigBuffer))) {
-    RejectTransaction(NS_ERROR_ABORT);
-    return;
-  }
-
-  // Assemble a response object to return
-  nsString clientDataBase64;
-  nsString signatureDataBase64;
-  nsString keyHandleBase64;
-  nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
-  nsresult rvSignatureData = sigBuf.ToJwkBase64(signatureDataBase64);
-  nsresult rvKeyHandle = credBuf.ToJwkBase64(keyHandleBase64);
-  if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
-      NS_WARN_IF(NS_FAILED(rvSignatureData) ||
-      NS_WARN_IF(NS_FAILED(rvKeyHandle)))) {
-    RejectTransaction(NS_ERROR_ABORT);
-    return;
-  }
-
-  SignResponse response;
-  response.mKeyHandle.Construct(keyHandleBase64);
-  response.mClientData.Construct(clientDataBase64);
-  response.mSignatureData.Construct(signatureDataBase64);
-  response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
-
-  nsString responseStr;
-  if (NS_WARN_IF(!response.ToJSON(responseStr))) {
-    RejectTransaction(NS_ERROR_ABORT);
-    return;
-  }
-
-  mTransaction.ref().mPromise.Resolve(responseStr, __func__);
-  ClearTransaction();
-}
-
-void
-U2FManager::RequestAborted(const uint64_t& aTransactionId,
-                           const nsresult& aError)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mTransaction.isSome() && mTransaction.ref().mId == aTransactionId) {
-    RejectTransaction(aError);
-  }
-}
-
-NS_IMETHODIMP
-U2FManager::HandleEvent(nsIDOMEvent* aEvent)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aEvent);
-
-  nsAutoString type;
-  aEvent->GetType(type);
-  if (!type.Equals(kVisibilityChange)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsCOMPtr<nsIDocument> doc =
-    do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
-  if (NS_WARN_IF(!doc)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (doc->Hidden()) {
-    MOZ_LOG(gU2FManagerLog, LogLevel::Debug,
-            ("Visibility change: U2F window is hidden, cancelling job."));
-
-    CancelTransaction(NS_ERROR_ABORT);
-  }
-
-  return NS_OK;
-}
-
-void
-U2FManager::ActorDestroyed()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  mChild = nullptr;
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/u2f/U2FManager.h
+++ /dev/null
@@ -1,161 +0,0 @@
-/* -*- 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_U2FManager_h
-#define mozilla_dom_U2FManager_h
-
-#include "U2FAuthenticator.h"
-#include "mozilla/MozPromise.h"
-#include "mozilla/dom/Event.h"
-#include "mozilla/dom/PWebAuthnTransaction.h"
-#include "nsIDOMEventListener.h"
-
-/*
- * Content process manager for the U2F protocol. Created on calls to the
- * U2F DOM object, this manager handles establishing IPC channels
- * for U2F transactions, as well as keeping track of MozPromise objects
- * representing transactions in flight.
- *
- * The U2F spec (http://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915.zip)
- * allows for two different types of transactions: registration and signing.
- * When either of these is requested via the DOM API, the following steps are
- * executed in the U2FManager:
- *
- * - Validation of the request. Return a failed promise to the caller if request
- *   does not have correct parameters.
- *
- * - If request is valid, open a new IPC channel for running the transaction. If
- *   another transaction is already running in this content process, cancel it.
- *   Return a pending promise to the caller.
- *
- * - Send transaction information to parent process (by running the Start*
- *   functions of U2FManager). Assuming another transaction is currently in
- *   flight in another content process, parent will handle canceling it.
- *
- * - On return of successful transaction information from parent process, turn
- *   information into DOM object format required by spec, and resolve promise
- *   (by running the Finish* functions of U2FManager). On cancellation request
- *   from parent, reject promise with corresponding error code. Either
- *   outcome will also close the IPC channel.
- *
- */
-
-namespace mozilla {
-namespace dom {
-
-class ArrayBufferViewOrArrayBuffer;
-class OwningArrayBufferViewOrArrayBuffer;
-class Promise;
-class U2FTransactionChild;
-class U2FTransactionInfo;
-
-class U2FTransaction
-{
-public:
-  U2FTransaction(nsPIDOMWindowInner* aParent,
-                 const WebAuthnTransactionInfo&& aInfo,
-                 const nsCString& aClientData)
-    : mParent(aParent)
-    , mInfo(aInfo)
-    , mClientData(aClientData)
-    , mId(NextId())
-  {
-    MOZ_ASSERT(mId > 0);
-  }
-
-  // Parent of the context we're running the transaction in.
-  nsCOMPtr<nsPIDOMWindowInner> mParent;
-
-  // JS Promise representing the transaction status.
-  MozPromiseHolder<U2FPromise> mPromise;
-
-  // Holds the parameters of the current transaction, as we need them both
-  // before the transaction request is sent, and on successful return.
-  WebAuthnTransactionInfo mInfo;
-
-  // Client data used to assemble reply objects.
-  nsCString mClientData;
-
-  // Unique transaction id.
-  uint64_t mId;
-
-private:
-  // Generates a unique id for new transactions. This doesn't have to be unique
-  // forever, it's sufficient to differentiate between temporally close
-  // transactions, where messages can intersect. Can overflow.
-  static uint64_t NextId() {
-    static uint64_t id = 0;
-    return ++id;
-  }
-};
-
-class U2FManager final : public nsIDOMEventListener
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIDOMEVENTLISTENER
-
-  static U2FManager* GetOrCreate();
-  static U2FManager* Get();
-
-  already_AddRefed<U2FPromise> Register(nsPIDOMWindowInner* aParent,
-              const nsCString& aRpId,
-              const nsCString& aClientDataJSON,
-              const uint32_t& aTimeoutMillis,
-              const nsTArray<WebAuthnScopedCredentialDescriptor>& aExcludeList);
-  already_AddRefed<U2FPromise> Sign(nsPIDOMWindowInner* aParent,
-              const nsCString& aRpId,
-              const nsCString& aClientDataJSON,
-              const uint32_t& aTimeoutMillis,
-              const nsTArray<WebAuthnScopedCredentialDescriptor>& aKeyList);
-
-  void FinishRegister(const uint64_t& aTransactionId,
-                      nsTArray<uint8_t>& aRegBuffer);
-  void FinishSign(const uint64_t& aTransactionId,
-                  nsTArray<uint8_t>& aCredentialId,
-                  nsTArray<uint8_t>& aSigBuffer);
-  void RequestAborted(const uint64_t& aTransactionId, const nsresult& aError);
-
-  // XXX This is exposed only until we fix bug 1410346.
-  void MaybeCancelTransaction(const nsresult& aError) {
-    if (mTransaction.isSome()) {
-      CancelTransaction(NS_ERROR_ABORT);
-    }
-  }
-
-  void ActorDestroyed();
-
-private:
-  U2FManager();
-  virtual ~U2FManager();
-
-  static nsresult
-  BuildTransactionHashes(const nsCString& aRpId,
-                         const nsCString& aClientDataJSON,
-                         /* out */ CryptoBuffer& aRpIdHash,
-                         /* out */ CryptoBuffer& aClientDataHash);
-
-  // Clears all information we have about the current transaction.
-  void ClearTransaction();
-  // Rejects the current transaction and calls ClearTransaction().
-  void RejectTransaction(const nsresult& aError);
-  // Cancels the current transaction (by sending a Cancel message to the
-  // parent) and rejects it by calling RejectTransaction().
-  void CancelTransaction(const nsresult& aError);
-
-  bool MaybeCreateBackgroundActor();
-
-  // IPC Channel to the parent process.
-  RefPtr<U2FTransactionChild> mChild;
-
-  // The current transaction, if any.
-  Maybe<U2FTransaction> mTransaction;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_U2FManager_h
--- a/dom/u2f/U2FTransactionChild.cpp
+++ b/dom/u2f/U2FTransactionChild.cpp
@@ -8,47 +8,37 @@
 
 namespace mozilla {
 namespace dom {
 
 mozilla::ipc::IPCResult
 U2FTransactionChild::RecvConfirmRegister(const uint64_t& aTransactionId,
                                          nsTArray<uint8_t>&& aRegBuffer)
 {
-  RefPtr<U2FManager> mgr = U2FManager::Get();
-  MOZ_ASSERT(mgr);
-  mgr->FinishRegister(aTransactionId, aRegBuffer);
+  mU2F->FinishRegister(aTransactionId, aRegBuffer);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 U2FTransactionChild::RecvConfirmSign(const uint64_t& aTransactionId,
                                      nsTArray<uint8_t>&& aCredentialId,
                                      nsTArray<uint8_t>&& aBuffer)
 {
-  RefPtr<U2FManager> mgr = U2FManager::Get();
-  MOZ_ASSERT(mgr);
-  mgr->FinishSign(aTransactionId, aCredentialId, aBuffer);
+  mU2F->FinishSign(aTransactionId, aCredentialId, aBuffer);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 U2FTransactionChild::RecvAbort(const uint64_t& aTransactionId,
                                const nsresult& aError)
 {
-  RefPtr<U2FManager> mgr = U2FManager::Get();
-  MOZ_ASSERT(mgr);
-  mgr->RequestAborted(aTransactionId, aError);
+  mU2F->RequestAborted(aTransactionId, aError);
   return IPC_OK();
 }
 
 void
 U2FTransactionChild::ActorDestroy(ActorDestroyReason why)
 {
-  RefPtr<U2FManager> mgr = U2FManager::Get();
-  // This could happen after the U2FManager has been shut down.
-  if (mgr) {
-    mgr->ActorDestroyed();
-  }
+  mU2F->ActorDestroyed();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/u2f/U2FTransactionChild.h
+++ b/dom/u2f/U2FTransactionChild.h
@@ -16,27 +16,35 @@
  */
 
 namespace mozilla {
 namespace dom {
 
 class U2FTransactionChild final : public WebAuthnTransactionChildBase
 {
 public:
+  explicit U2FTransactionChild(U2F* aU2F) : mU2F(aU2F) {
+    MOZ_ASSERT(mU2F);
+  }
+
   mozilla::ipc::IPCResult
   RecvConfirmRegister(const uint64_t& aTransactionId,
                       nsTArray<uint8_t>&& aRegBuffer) override;
 
   mozilla::ipc::IPCResult
   RecvConfirmSign(const uint64_t& aTransactionId,
                   nsTArray<uint8_t>&& aCredentialId,
                   nsTArray<uint8_t>&& aBuffer) override;
 
   mozilla::ipc::IPCResult
   RecvAbort(const uint64_t& aTransactionId, const nsresult& aError) override;
 
   void ActorDestroy(ActorDestroyReason why) override;
+
+private:
+  // ~U2F() will destroy child actors.
+  U2F* mU2F;
 };
 
 }
 }
 
 #endif //mozilla_dom_U2FTransactionChild_h
--- a/dom/u2f/moz.build
+++ b/dom/u2f/moz.build
@@ -10,17 +10,16 @@ with Files("**"):
 EXPORTS.mozilla.dom += [
     'U2F.h',
     'U2FAuthenticator.h',
     'U2FUtil.h',
 ]
 
 UNIFIED_SOURCES += [
     'U2F.cpp',
-    'U2FManager.cpp',
     'U2FTransactionChild.cpp',
     'U2FTransactionParent.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'