Bug 1416094 - WIP backup draft
authorDimiL <dlee@mozilla.com>
Thu, 09 Nov 2017 16:12:03 +0800
changeset 696997 8c4d5bab110e6c9fd049aa31ca952f271b8a44cb
parent 694795 9cc6952ed63d7e505b7e40e5ae0a634f652494bc
child 739986 be9465ab1d4dfdf9fc7e22ed3331d9c5ede4722c
push id88854
push userbmo:dlee@mozilla.com
push dateMon, 13 Nov 2017 07:43:08 +0000
bugs1416094
milestone58.0a1
Bug 1416094 - WIP backup MozReview-Commit-ID: IHZW0Zkfnt5
browser/components/nsBrowserGlue.js
modules/libpref/init/all.js
toolkit/components/passwordmgr/LoginHelper.jsm
toolkit/components/passwordmgr/LoginManagerParent.jsm
toolkit/components/passwordmgr/nsLoginManager.js
toolkit/components/reputationservice/ILoginReputation.idl
toolkit/components/reputationservice/LoginReputation.cpp
toolkit/components/reputationservice/LoginReputation.h
toolkit/components/satchel/nsFormFillController.cpp
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1110,18 +1110,19 @@ BrowserGlue.prototype = {
 
     // It's important that SafeBrowsing is initialized reasonably
     // early, so we use a maximum timeout for it.
     Services.tm.idleDispatchToMainThread(() => {
       SafeBrowsing.init();
 
       // Login reputation depends on the Safe Browsing API.
       if (Services.prefs.getBoolPref("browser.safebrowsing.passwords.enabled")) {
-        Cc["@mozilla.org/reputationservice/login-reputation-service;1"]
-        .getService(Ci.ILoginReputationService);
+        let reputationService = Cc["@mozilla.org/reputationservice/login-reputation-service;1"]
+          .getService(Ci.ILoginReputationService);
+        reputationService.init();
       }
     }, 5000);
 
     if (AppConstants.MOZ_CRASHREPORTER) {
       UnsubmittedCrashHandler.scheduleCheckForUnsubmittedCrashReports();
     }
 
     if (AppConstants.platform == "win") {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5452,17 +5452,17 @@ pref("browser.safebrowsing.downloads.rem
 pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
 pref("browser.safebrowsing.downloads.remote.url", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
 pref("browser.safebrowsing.downloads.remote.block_dangerous",            true);
 pref("browser.safebrowsing.downloads.remote.block_dangerous_host",       true);
 pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", true);
 pref("browser.safebrowsing.downloads.remote.block_uncommon",             true);
 
 // Password protection
-pref("browser.safebrowsing.passwords.enabled", false);
+pref("browser.safebrowsing.passwords.enabled", true);
 
 // Google Safe Browsing provider (legacy)
 pref("browser.safebrowsing.provider.google.pver", "2.2");
 pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,googpub-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
 pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
 pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
 pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
 pref("browser.safebrowsing.provider.google.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ b/toolkit/components/passwordmgr/LoginHelper.jsm
@@ -32,17 +32,17 @@ this.LoginHelper = {
   /**
    * Warning: these only update if a logger was created.
    */
   debug: Services.prefs.getBoolPref("signon.debug"),
   formlessCaptureEnabled: Services.prefs.getBoolPref("signon.formlessCapture.enabled"),
   schemeUpgrades: Services.prefs.getBoolPref("signon.schemeUpgrades"),
   insecureAutofill: Services.prefs.getBoolPref("signon.autofillForms.http"),
   showInsecureFieldWarning: Services.prefs.getBoolPref("security.insecure_field_warning.contextual.enabled"),
-  loginReputationEnabled: Services.prefs.getBoolPref("browser.safebrowsing.passwords.enabled");
+  loginReputationEnabled: Services.prefs.getBoolPref("browser.safebrowsing.passwords.enabled"),
 
   createLogger(aLogPrefix) {
     let getMaxLogLevel = () => {
       return this.debug ? "debug" : "warn";
     };
 
     // Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
     let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
@@ -60,18 +60,18 @@ this.LoginHelper = {
       this.insecureAutofill = Services.prefs.getBoolPref("signon.autofillForms.http");
       logger.maxLogLevel = getMaxLogLevel();
     });
 
     Services.prefs.addObserver("security.insecure_field_warning.", () => {
       this.showInsecureFieldWarning = Services.prefs.getBoolPref("security.insecure_field_warning.contextual.enabled");
     });
 
-    Services.prefs.addObserver("browser.safebrowsing.passwords.enabled"), () => {
-      this.loginReputationEnabled =  Services.prefs.getBoolPref("browser.safebrowsing.passwords.enabled");
+    Services.prefs.addObserver("browser.safebrowsing.passwords.enabled", () => {
+      this.loginReputationEnabled = Services.prefs.getBoolPref("browser.safebrowsing.passwords.enabled");
     });
 
     return logger;
   },
 
   /**
    * Due to the way the signons2.txt file is formatted, we need to make
    * sure certain field values or characters do not cause the file to
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -287,18 +287,18 @@ var LoginManagerParent = {
     // doesn't support structured cloning.
     var jsLogins = LoginHelper.loginsToVanillaObjects(matchingLogins);
     target.messageManager.sendAsyncMessage("RemoteLogins:loginsAutoCompleted", {
       requestId,
       logins: jsLogins,
     });
   },
 
-  doQueryLoginReputation(formOrigin, actionOrigin,
-                         requestId, target) {
+  doQueryLoginReputation({ formOrigin, actionOrigin,
+                           requestId }, target) {
     let service = Cc["@mozilla.org/reputationservice/login-reputation-service;1"].
                   getService(Ci.ILoginReputationService);
 
     let param = {
       formUrl: formOrigin,
       actionUrl: actionOrigin,
     };
 
--- a/toolkit/components/passwordmgr/nsLoginManager.js
+++ b/toolkit/components/passwordmgr/nsLoginManager.js
@@ -552,17 +552,18 @@ LoginManager.prototype = {
       if (this._queryLoginReputationPromise !== promise) {
         // If the login reputation query was canceled before we got our
         // results, don't bother reporting them.
         return;
       }
 
       this._queryLoginReputationPromise = null;
 
-      log.debug("QueryLoginReputation invoked. Result is:", aResult);
+      log.debug("QueryLoginReputation invoked. Result is:" + aResult);
+      dump("QueryLoginReputation invoked. Result is:" + aResult + "\n");
 
       aCallback.onQueryReputationCompletion();
     });
   },
 
   stopQueryLoginReputation() {
     this._queryLoginReputationPromise = null;
   }
--- a/toolkit/components/reputationservice/ILoginReputation.idl
+++ b/toolkit/components/reputationservice/ILoginReputation.idl
@@ -29,11 +29,13 @@ interface ILoginReputationQuery : nsISup
 interface ILoginReputationQueryCallback : nsISupports {
   // aResult should be one of the const value defined in ILoginReputationResult
   // interface.
   void onQueryComplete(in uint16_t aResult);
 };
 
 [scriptable, uuid(1b3f1dfe-ce3a-486b-953e-ce5ac863eff9)]
 interface ILoginReputationService : nsISupports {
+  void init();
+
   void queryReputation(in ILoginReputationQuery aQuery,
                        in ILoginReputationQueryCallback aCallback);
 };
--- a/toolkit/components/reputationservice/LoginReputation.cpp
+++ b/toolkit/components/reputationservice/LoginReputation.cpp
@@ -1,22 +1,319 @@
 /* -*- Mode: C++; tab-width: 4; 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 "LoginReputation.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Logging.h"
 
 using namespace mozilla;
 
 // MOZ_LOG=LoginReputation:5
-LazyLogModule LoginReputationService::prlog("LoginReputation");
-#define LR_LOG(args) MOZ_LOG(LoginReputationService::prlog, mozilla::LogLevel::Debug, args)
-#define LR_LOG_ENABLED() MOZ_LOG_TEST(LoginReputationService::prlog, mozilla::LogLevel::Debug)
+LazyLogModule gLoginReputationLogModule("LoginReputation");
+#define LR_LOG(args) MOZ_LOG(gLoginReputationLogModule, mozilla::LogLevel::Debug, args)
+#define LR_LOG_ENABLED() MOZ_LOG_TEST(gLoginReputationLogModule, mozilla::LogLevel::Debug)
+
+static Atomic<bool> gShuttingDown(false);
+
+// This is for debug.
+static
+nsCString ReputationResultToString(ReputationResult aResult)
+{
+  switch(aResult) {
+    case ReputationResult::LOW_REPUTATION:  return nsCString("Low Reputation");
+    case ReputationResult::SAFE:            return nsCString("Safe");
+    case ReputationResult::PHISHING:        return nsCString("Phishing");
+    default:                                return nsCString("Unspecified");
+  }
+}
+
+// -------------------------------------------------------------------------
+// RemoteLookup
+//
+// For non-whitelisted sites, we will do a remote lookup against a google
+// service and receive one of the following verdicts:
+// 1. safe
+// 2. low-reputation
+// 3. phishing
+//
+// This class owns a worker thread which is responsible for sending lookup
+// request to the server and parse response. Response is also stored in verdict
+// cache.
+//
+class RemoteLookup final : public nsISupports
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  RemoteLookup() = default;
+
+  nsresult Init();
+  nsresult Uninit();
+
+  RefPtr<ReputationPromise> QueryRemoteLookup(ILoginReputationQuery* aParam);
+
+private:
+  ~RemoteLookup() = default;
+
+  RefPtr<ReputationPromise> QueryInternal(ILoginReputationQuery* aParam);
+
+  nsCOMPtr<nsIThread> mThread;
+};
+
+NS_IMPL_ISUPPORTS0(RemoteLookup);
+
+nsresult
+RemoteLookup::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsresult rv = NS_NewNamedThread("LR RemoteLookup", getter_AddRefs(mThread));
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to create remote lookup thread for login reputation");
+  }
+
+  // TODO : Bug 1416647 - Support verdict cache for password phishing.
+  //        Load verdict cache when Init, but task should be post to worke
+  //        thread.
+
+  return rv;
+}
+
+nsresult
+RemoteLookup::Uninit()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mThread) {
+    mThread->Shutdown();
+    mThread = nullptr;
+  }
+
+  return NS_OK;
+}
+
+RefPtr<ReputationPromise>
+RemoteLookup::QueryRemoteLookup(ILoginReputationQuery* aParam)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (gShuttingDown) {
+    return ReputationPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
+  }
+
+  RefPtr<RemoteLookup> self = this;
+
+  return InvokeAsync(mThread, __func__, [self, aParam]() {
+    return self->QueryInternal(aParam);
+  });
+}
+
+RefPtr<ReputationPromise>
+RemoteLookup::QueryInternal(ILoginReputationQuery* aParam)
+{
+  // This must be on the worker thread.
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aParam);
+
+  if (gShuttingDown) {
+    return ReputationPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
+  }
+
+  ReputationResult result = ReputationResult::SAFE;
+
+  // TODO: Bug 1413732 - Query the login reputation service.
+  //       This is where we should implement verdict cache and remote lookup.
+
+  return ReputationPromise::CreateAndResolve(result, __func__);
+}
+
+// -------------------------------------------------------------------------
+// TrustBasedWhitelisting
+//
+// Sites that we could trust
+// - bookmark/homepage
+// - permissions
+// - saved password
+// - content setting
+//
+class TrustBasedWhitelisting final : public nsISupports
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  TrustBasedWhitelisting() = default;
+
+  nsresult Init();
+  nsresult Uninit();
+
+  RefPtr<ReputationPromise> QueryTrustBasedWhitelist(ILoginReputationQuery* aParam);
+
+private:
+  ~TrustBasedWhitelisting() = default;
+
+  RefPtr<ReputationPromise> QueryInternal(ILoginReputationQuery* aParam);
+
+  nsCOMPtr<nsIThread> mThread;
+};
+
+NS_IMPL_ISUPPORTS0(TrustBasedWhitelisting);
 
+nsresult
+TrustBasedWhitelisting::Init()
+{
+  nsresult rv = NS_NewNamedThread("LR WhiteList", getter_AddRefs(mThread));
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to create white list thread for login reputation");
+  }
+
+  return rv;
+}
+
+nsresult
+TrustBasedWhitelisting::Uninit()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mThread) {
+    mThread->Shutdown();
+    mThread = nullptr;
+  }
+
+  return NS_OK;
+}
+
+RefPtr<ReputationPromise>
+TrustBasedWhitelisting::QueryTrustBasedWhitelist(ILoginReputationQuery* aParam)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (gShuttingDown) {
+    return ReputationPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
+  }
+
+  RefPtr<TrustBasedWhitelisting> self = this;
+
+  return InvokeAsync(mThread, __func__, [self, aParam]() {
+    return self->QueryInternal(aParam);
+  });
+}
+
+RefPtr<ReputationPromise>
+TrustBasedWhitelisting::QueryInternal(ILoginReputationQuery* aParam)
+{
+  // This must be on the worker thread.
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aParam);
+
+  if (gShuttingDown) {
+    return ReputationPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
+  }
+
+  // TODO : Bug 1416653 - Support trust-based whitelisting heuristics.
+  //        This is where we should implement our trust-based whitelisting
+  //        algorithm. Set result to UNSPECIFIED for now.
+  ReputationResult result = ReputationResult::UNSPECIFIED;
+
+  return ReputationPromise::CreateAndResolve(result, __func__);
+}
+
+// -------------------------------------------------------------------------
+// CSDQuery
+//
+// This class is a wrapper that encapsulate asynchronous callback API provided
+// by DBService into a MozPromise callback.
+//
+class CSDQuery final : public nsIURIClassifierCallback
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIURICLASSIFIERCALLBACK
+
+  RefPtr<ReputationPromise> ClassifyLocalWithTables(nsIURI *aURI);
+
+  CSDQuery() = default;
+
+private:
+  ~CSDQuery() = default;
+
+  void GetWhiteListTables(nsACString& aTables);
+
+  MozPromiseHolder<ReputationPromise> mPromiseHolder;
+};
+
+NS_IMPL_ISUPPORTS(CSDQuery, nsIURIClassifierCallback)
+
+RefPtr<ReputationPromise>
+CSDQuery::ClassifyLocalWithTables(nsIURI *aURI)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsAutoCString tables;
+  GetWhiteListTables(tables);
+
+  nsresult rv;
+  nsCOMPtr<nsIURIClassifier> uriClassifier =
+    do_GetService(NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return ReputationPromise::CreateAndReject(rv, __func__);
+  }
+
+  // AsyncClassifyLocalWithTables won't trigger a gethash request.
+  // So this operation should be fast.
+  rv = uriClassifier->AsyncClassifyLocalWithTables(aURI, tables, this);
+  if (NS_FAILED(rv)) {
+    return ReputationPromise::CreateAndReject(rv, __func__);
+  }
+
+  RefPtr<ReputationPromise> p = mPromiseHolder.Ensure(__func__);
+  return p;
+}
+
+nsresult
+CSDQuery::OnClassifyComplete(nsresult aErrorCode,
+                             const nsACString& aLists,
+                             const nsACString& aProvider,
+                             const nsACString& aFullHash)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  int64_t now = PR_Now() / PR_USEC_PER_MSEC;
+  LR_LOG(("OnClassifyComplete : list = %s", aLists.BeginReading()));
+
+  if (NS_FAILED(aErrorCode)) {
+    mPromiseHolder.Reject(aErrorCode, __func__);
+    return NS_OK;
+  }
+
+  ReputationResult result = aLists.IsEmpty() ?
+    ReputationResult::UNSPECIFIED : ReputationResult::SAFE;
+
+  LR_LOG(("CSD returns login reputation result = %s",
+    ReputationResultToString(result).get()));
+
+  mPromiseHolder.Resolve(result, __func__);
+
+  return NS_OK;
+}
+
+void
+CSDQuery::GetWhiteListTables(nsACString& aTables)
+{
+  aTables.AssignLiteral("goog-passwordwhite-proto");
+}
+
+// -------------------------------------------------------------------------
+// LoginReputationService
+//
+// TODO : Add description
+//
+//
 NS_IMPL_ISUPPORTS(LoginReputationService,
                   ILoginReputationService)
 
 LoginReputationService*
   LoginReputationService::gLoginReputationService = nullptr;
 
 already_AddRefed<LoginReputationService>
 LoginReputationService::GetSingleton()
@@ -30,31 +327,260 @@ LoginReputationService::GetSingleton()
 LoginReputationService::LoginReputationService()
 {
   LR_LOG(("Login reputation service starting up"));
 }
 
 LoginReputationService::~LoginReputationService()
 {
   LR_LOG(("Login reputation service shutting down"));
+
+  gShuttingDown = true;
+  Shutdown();
+}
+
+NS_IMETHODIMP
+LoginReputationService::Init()
+{
+  LR_LOG(("Init login reputation service"));
+
+  nsresult rv;
+
+  mRemote = new RemoteLookup();
+  rv = mRemote->Init();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  mTrust = new TrustBasedWhitelisting();
+  rv = mTrust->Init();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Add an observer for shutdown
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (os) {
+    os->AddObserver(this, "quit-application", false);
+    os->AddObserver(this, "profile-before-change", false);
+  }
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 LoginReputationService::QueryReputation(ILoginReputationQuery* aQuery,
                                         ILoginReputationQueryCallback* aCallback)
 {
-  LR_LOG(("Starting login reputation check [query=%p]", aQuery));
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (gShuttingDown) {
+    return NS_ERROR_ABORT;
+  }
+
   NS_ENSURE_ARG_POINTER(aQuery);
   NS_ENSURE_ARG_POINTER(aCallback);
 
   if (LR_LOG_ENABLED()) {
     nsAutoCString formUrl, actionUrl;
     aQuery->GetFormUrl(formUrl);
     aQuery->GetActionUrl(actionUrl);
-    LR_LOG(("Login reputation query parameters: formUrl(%s), actionUrl(%s)",
+    LR_LOG(("Query login reputation started, formUrl(%s), actionUrl(%s)",
             formUrl.BeginReading(), actionUrl.BeginReading()));
   }
 
-  // XXX: Always return SAFE for now.
-  aCallback->OnQueryComplete(ILoginReputationResult::SAFE);
+  // mQueryRequests is an array used to maintain the ownership of |QueryRequest|
+  // so we could ensure the |QueryRequest| is always valid until Finish() is
+  // called or LoginReputationService is shutdown.
+  auto* request =
+    mQueryRequests.AppendElement(MakeUnique<QueryRequest>(aQuery, aCallback));
+
+  return QueryReputationInternal(request->get());
+}
+
+nsresult
+LoginReputationService::QueryReputationInternal(QueryRequest* aRequest)
+{
+  MOZ_ASSERT(aRequest);
+
+  // Check whitelist first so we don't have to perform a remote lookup for every
+  // request. There are two whitelist we could check now, the first one is
+  // CSD_WHITE list, the second one is trust based whitelist (check if the url
+  // matches bookmarks, history ...etc).
+  // We do these two whitelist checks in parallel to minimize the reputation query
+  // response time.
+  auto csdPromise = QueryCSDWhitelistTable(aRequest);
+  auto trustPromise = QueryTrustBasedWhitelist(aRequest);
+
+  // Both promises must not be empty, if failure happened in above function
+  // calls, rejected promise will be returned.
+  MOZ_ASSERT(csdPromise && trustPromise);
+
+  nsTArray<RefPtr<ReputationPromise>> promises = {
+    csdPromise, trustPromise,
+  };
+
+  // Back to main-thread when both CSD query and trust-based whitelist
+  // check are done. Return if any check return SAFE, otherwise we will trigger
+  // the remote lookup here.
+  RefPtr<LoginReputationService> self = this;
+
+  ReputationPromise::All(GetCurrentThreadSerialEventTarget(), promises)->Then(
+    GetCurrentThreadSerialEventTarget(), __func__,
+    [self, aRequest](const nsTArray<ReputationResult>& aResolveValues) -> void {
+      MOZ_ASSERT(NS_IsMainThread());
+
+      // Promise::All is done when CSD and turst-based whitelist are both resolved.
+      MOZ_ASSERT(aResolveValues.Length() == 2);
+
+      self->mCSDQueries.RemoveElementAt(0);
+
+      // As long as one of the resolved value is SAFE, then we are done.
+      if (aResolveValues.Contains(ReputationResult::SAFE)) {
+        self->Finish(aRequest, ReputationResult::SAFE);
+      } else {
+        // Perform remote lookup when non of any whitelisting method returns
+        // SAFE.
+        self->QueryRemoteLookup(aRequest);
+      }
+    },
+    [self, aRequest]() {
+      self->Finish(aRequest, ReputationResult::UNSPECIFIED);
+    });
 
   return NS_OK;
 }
+
+// Question: If something wrong here, should we still do the remote lookup ???
+RefPtr<ReputationPromise>
+LoginReputationService::QueryCSDWhitelistTable(QueryRequest* aRequest)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsAutoCString formUrl;
+  aRequest->mParam->GetFormUrl(formUrl);
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), formUrl);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return ReputationPromise::CreateAndReject(rv, __func__);
+  }
+
+  RefPtr<CSDQuery> csdQuery = new CSDQuery();
+  mCSDQueries.AppendElement(csdQuery);
+
+  return csdQuery->ClassifyLocalWithTables(uri);
+}
+
+RefPtr<ReputationPromise>
+LoginReputationService::QueryTrustBasedWhitelist(QueryRequest* aRequest)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  return mTrust->QueryTrustBasedWhitelist(aRequest->mParam);
+}
+
+nsresult
+LoginReputationService::QueryRemoteLookup(QueryRequest* aRequest)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RefPtr<LoginReputationService> self = this;
+
+  // This is the last step of query reputation, call Finish() with
+  // the reputation result.
+  mRemote->QueryRemoteLookup(aRequest->mParam)->Then(
+    GetCurrentThreadSerialEventTarget(), __func__,
+    [self, aRequest](ReputationResult aResolveValue) -> void {
+      self->mRemoteLookupRequest.Complete();
+      self->Finish(aRequest, aResolveValue);
+    },
+    [self, aRequest]() {
+      self->mRemoteLookupRequest.Complete();
+      self->Finish(aRequest, ReputationResult::UNSPECIFIED);
+    })->Track(mRemoteLookupRequest);
+
+  return NS_OK;
+}
+
+nsresult
+LoginReputationService::Finish(const QueryRequest* aRequest, ReputationResult aResult)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRequest);
+
+  // Since we are shutting down, don't bother call back to child process.
+  if (gShuttingDown) {
+    return NS_OK;
+  }
+
+  if (LR_LOG_ENABLED()) {
+    LR_LOG(("Query login reputation finished, result is %s",
+      ReputationResultToString(aResult).get()));
+  }
+
+  // TODO : Add a testcase to make sure enum(ReputationResult) is sync with definition
+  //        in ILoginReputationResult and csd.pb.cc
+  aRequest->mCallback->OnQueryComplete(static_cast<uint16_t>(aResult));
+
+  // QueryRequest may not follow the same order when we queued it in ::QueryReputation
+  // because one query request may finish earilier than the other.
+  uint32_t idx = 0;
+  for (; idx < mQueryRequests.Length(); idx++) {
+    if (mQueryRequests[idx].get() == aRequest) {
+      break;
+    }
+  }
+
+  MOZ_ASSERT(idx < mQueryRequests.Length());
+  mQueryRequests.RemoveElementAt(idx);
+
+  return NS_OK;
+}
+
+nsresult
+LoginReputationService::Shutdown()
+{
+  MOZ_ASSERT(gShuttingDown);
+
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (os) {
+    os->RemoveObserver(this, "quit-application");
+    os->RemoveObserver(this, "profile-before-change");
+  }
+
+  mRemoteLookupRequest.DisconnectIfExists();
+
+  // Wait until worker threads are shutdown
+  if (mRemote) {
+    mRemote->Uninit();
+    mRemote = nullptr;
+  }
+
+  if (mTrust) {
+    mTrust->Uninit();
+    mTrust = nullptr;
+  }
+
+  mCSDQueries.Clear();
+  mQueryRequests.Clear();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+LoginReputationService::Observe(nsISupports *aSubject,
+                                const char *aTopic,
+                                const char16_t *aData)
+{
+  if (!strcmp(aTopic, "quit-application")) {
+    // Prepare to shutdown, won't allow any query request after 'gShuttingDown'
+    // is set.
+    gShuttingDown = true;
+  } else if (!strcmp(aTopic, "profile-before-change")) {
+    gShuttingDown = true;
+    Shutdown();
+  } else {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  return NS_OK;
+}
--- a/toolkit/components/reputationservice/LoginReputation.h
+++ b/toolkit/components/reputationservice/LoginReputation.h
@@ -2,35 +2,94 @@
 /* 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 LoginReputation_h__
 #define LoginReputation_h__
 
 #include "ILoginReputation.h"
+#include "nsIURIClassifier.h"
+#include "nsIObserver.h"
 #include "mozilla/Logging.h"
+#include "mozilla/MozPromise.h"
+
+class TrustBasedWhitelisting;
+class RemoteLookup;
+class CSDQuery;
+
+namespace mozilla {
 
-class LoginReputationService final : public ILoginReputationService
+// The values list below should sync with definition in ILoginReputationResult
+enum ReputationResult {
+  UNSPECIFIED = 0,
+  SAFE = 1,
+  LOW_REPUTATION = 2,
+  PHISHING = 3,
+};
+
+typedef MozPromise<ReputationResult, nsresult, false> ReputationPromise;
+
+class LoginReputationService final : public ILoginReputationService,
+                                     public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_ILOGINREPUTATIONSERVICE
+  NS_DECL_NSIOBSERVER
 
 public:
   static already_AddRefed<LoginReputationService> GetSingleton();
 
 private:
+  struct QueryRequest {
+    QueryRequest(ILoginReputationQuery* aParam,
+                 ILoginReputationQueryCallback* aCallback) :
+      mParam(aParam),
+      mCallback(aCallback)
+    {
+    }
+
+    nsCOMPtr<ILoginReputationQuery> mParam;
+    nsCOMPtr<ILoginReputationQueryCallback> mCallback;
+  };
+
   /**
    * Global singleton object for holding this factory service.
    */
   static LoginReputationService* gLoginReputationService;
 
-  /**
-   * MOZ_LOG=LoginReputation:5
-   */
-  static mozilla::LazyLogModule prlog;
-
   LoginReputationService();
   ~LoginReputationService();
+
+  nsresult QueryReputationInternal(QueryRequest* aRequest);
+
+  RefPtr<ReputationPromise> QueryCSDWhitelistTable(QueryRequest* aRequest);
+
+  RefPtr<ReputationPromise> QueryTrustBasedWhitelist(QueryRequest* aRequest);
+
+  nsresult QueryRemoteLookup(QueryRequest* aRequest);
+
+  // Called when a query request is finished.
+  nsresult Finish(const QueryRequest* aRequest, ReputationResult aResult);
+
+  // Join the worker threads.
+  nsresult Shutdown();
+
+  // This class implements trust-based whitelisting algorithm.
+  RefPtr<TrustBasedWhitelisting> mTrust;
+
+  // This class implements remote server lookup.
+  RefPtr<RemoteLookup> mRemote;
+
+  // Ongoing query requests which is added when ::QueryReputation API is called.
+  nsTArray<UniquePtr<QueryRequest>> mQueryRequests;
+
+  // Queries that are waiting for callback from ::AsyncClassifyLocalWithTables.
+  nsTArray<RefPtr<CSDQuery>> mCSDQueries;
+
+  // RemoteLookup promise request tracker.
+  MozPromiseRequestHolder<ReputationPromise> mRemoteLookupRequest;
 };
 
+}  // namespace mozilla
+
 #endif  // LoginReputation_h__
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -1447,20 +1447,16 @@ nsFormFillController::StopControllingInp
     mController->GetInput(getter_AddRefs(input));
     if (input == this) {
       MOZ_LOG(sLogger, LogLevel::Verbose,
               ("StopControllingInput: Nulled controller input for %p", this));
       mController->SetInput(nullptr);
     }
   }
 
-  if (mLoginManager) {
-    mLoginManager->StopQueryLoginReputation();
-  }
-
   MOZ_LOG(sLogger, LogLevel::Verbose,
           ("StopControllingInput: Stopped controlling %p", mFocusedInput));
   if (mFocusedInputNode) {
     MaybeRemoveMutationObserver(mFocusedInputNode);
 
     mFocusedInputNode = nullptr;
     mFocusedInput = nullptr;
   }