Bug 1407878 - Check URLs against the login reputation service when a password field is focused. r?francois, MattN draft
authorDimiL <dlee@mozilla.com>
Tue, 21 Nov 2017 09:28:05 +0800
changeset 703969 f7f3e7ad18fc9931d0b72c2b45d12e0ac44c8b0b
parent 700905 081c06e175b2b4431b7af5ea594ff0373e97b70a
child 741948 c13d28789044d0e3d4bf60749d22533db5da721b
push id91017
push userbmo:dlee@mozilla.com
push dateTue, 28 Nov 2017 01:00:58 +0000
reviewersfrancois, MattN
bugs1407878
milestone59.0a1
Bug 1407878 - Check URLs against the login reputation service when a password field is focused. r?francois, MattN MozReview-Commit-ID: 8csNJU5hK7h
browser/components/nsBrowserGlue.js
mobile/android/components/BrowserCLH.js
toolkit/components/passwordmgr/LoginHelper.jsm
toolkit/components/passwordmgr/LoginManagerContent.jsm
toolkit/components/passwordmgr/LoginManagerParent.jsm
toolkit/components/passwordmgr/nsILoginManager.idl
toolkit/components/passwordmgr/nsLoginManager.js
toolkit/components/reputationservice/ILoginReputation.idl
toolkit/components/reputationservice/IQueryReputationObserver.idl
toolkit/components/reputationservice/LoginReputation.cpp
toolkit/components/reputationservice/moz.build
toolkit/components/satchel/nsFormFillController.cpp
toolkit/components/satchel/nsFormFillController.h
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -152,16 +152,17 @@ const listeners = {
     "Reader:UpdateReaderButton": ["ReaderParent"],
     // PLEASE KEEP THIS LIST IN SYNC WITH THE MOBILE LISTENERS IN BrowserCLH.js
     "RemoteLogins:findLogins": ["LoginManagerParent"],
     "RemoteLogins:findRecipes": ["LoginManagerParent"],
     "RemoteLogins:onFormSubmit": ["LoginManagerParent"],
     "RemoteLogins:autoCompleteLogins": ["LoginManagerParent"],
     "RemoteLogins:removeLogin": ["LoginManagerParent"],
     "RemoteLogins:insecureLoginFormPresent": ["LoginManagerParent"],
+    "RemoteLogins:queryLoginReputation": ["LoginManagerParent"],
     // PLEASE KEEP THIS LIST IN SYNC WITH THE MOBILE LISTENERS IN BrowserCLH.js
     "WCCR:registerProtocolHandler": ["Feeds"],
     "WCCR:registerContentHandler": ["Feeds"],
     "rtcpeer:CancelRequest": ["webrtcUI"],
     "rtcpeer:Request": ["webrtcUI"],
     "webrtc:CancelRequest": ["webrtcUI"],
     "webrtc:Request": ["webrtcUI"],
     "webrtc:StopRecording": ["webrtcUI"],
--- a/mobile/android/components/BrowserCLH.js
+++ b/mobile/android/components/BrowserCLH.js
@@ -87,16 +87,17 @@ BrowserCLH.prototype = {
           mm: [
             // PLEASE KEEP THIS LIST IN SYNC WITH THE DESKTOP LIST IN nsBrowserGlue.js
             "RemoteLogins:findLogins",
             "RemoteLogins:findRecipes",
             "RemoteLogins:onFormSubmit",
             "RemoteLogins:autoCompleteLogins",
             "RemoteLogins:removeLogin",
             "RemoteLogins:insecureLoginFormPresent",
+            "RemoteLogins:queryLoginReputation",
             // PLEASE KEEP THIS LIST IN SYNC WITH THE DESKTOP LIST IN nsBrowserGlue.js
           ],
         });
         GeckoViewUtils.addLazyGetter(this, "LoginManagerContent", {
           module: "resource://gre/modules/LoginManagerContent.jsm",
         });
 
         GeckoViewUtils.addLazyGetter(this, "ActionBarHandler", {
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ b/toolkit/components/passwordmgr/LoginHelper.jsm
@@ -32,16 +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"),
 
   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;
@@ -59,16 +60,20 @@ 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");
+    });
+
     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
    * be parsed incorrectly.  Reject hostnames that we can't store correctly.
    *
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -176,17 +176,18 @@ var LoginManagerContent = {
   },
 
   _getRandomId() {
     return Cc["@mozilla.org/uuid-generator;1"]
              .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
   },
 
   _messages: [ "RemoteLogins:loginsFound",
-               "RemoteLogins:loginsAutoCompleted" ],
+               "RemoteLogins:loginsAutoCompleted",
+               "RemoteLogins:queryReputationCompleted"],
 
   /**
    * WeakMap of the root element of a FormLike to the FormLike representing its fields.
    *
    * This is used to be able to lookup an existing FormLike for a given root element since multiple
    * calls to LoginFormFactory won't give the exact same object. When batching fills we don't always
    * want to use the most recent list of elements for a FormLike since we may end up doing multiple
    * fills for the same set of elements when a field gets added between arming and running the
@@ -281,16 +282,21 @@ var LoginManagerContent = {
 
       case "RemoteLogins:loginsAutoCompleted": {
         let loginsFound =
           LoginHelper.vanillaObjectsToLogins(msg.data.logins);
         let messageManager = msg.target;
         request.promise.resolve({ logins: loginsFound, messageManager });
         break;
       }
+
+      case "RemoteLogins:queryReputationCompleted": {
+        request.promise.resolve(msg.data.result);
+        break;
+      }
     }
   },
 
   /**
    * Get relevant logins and recipes from the parent
    *
    * @param {HTMLFormElement} form - form to get login data for
    * @param {Object} options
@@ -345,16 +351,36 @@ var LoginManagerContent = {
                         isPasswordField: aElement.type == "password",
                       };
 
     return this._sendRequest(messageManager, requestData,
                              "RemoteLogins:autoCompleteLogins",
                              messageData);
   },
 
+  _queryLoginReputationAsync(aElement) {
+    let doc = aElement.ownerDocument;
+    let form = LoginFormFactory.createFromField(aElement);
+    let win = doc.defaultView;
+
+    let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
+    let actionOrigin = LoginUtils._getActionOrigin(form);
+
+    let messageManager = messageManagerFromWindow(win);
+
+    let requestData = {};
+    let messageData = { formOrigin,
+                        actionOrigin,
+                      };
+
+    return this._sendRequest(messageManager, requestData,
+                             "RemoteLogins:queryLoginReputation",
+                             messageData);
+  },
+
   setupProgressListener(window) {
     if (!LoginHelper.formlessCaptureEnabled) {
       return;
     }
 
     try {
       let webProgress = window.QueryInterface(Ci.nsIInterfaceRequestor).
                         getInterface(Ci.nsIWebNavigation).
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -108,16 +108,24 @@ var LoginManagerParent = {
         break;
       }
 
       case "RemoteLogins:removeLogin": {
         let login = LoginHelper.vanillaObjectToLogin(data.login);
         AutoCompletePopup.removeLogin(login);
         break;
       }
+
+      case "RemoteLogins:queryLoginReputation": {
+        this.doQueryLoginReputation(data.formOrigin,
+                                    data.actionOrigin,
+                                    data.requestId,
+                                    msg.target);
+        break;
+      }
     }
 
     return undefined;
   },
 
   /**
    * Trigger a login form fill and send relevant data (e.g. logins and recipes)
    * to the child process (LoginManagerContent).
@@ -282,16 +290,39 @@ 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) {
+    let service = Cc["@mozilla.org/reputationservice/login-reputation-service;1"].
+                  getService(Ci.ILoginReputationService);
+
+    let param = {
+      formOrigin,
+      actionOrigin,
+    };
+
+    service.queryReputation(param, {
+      onQueryComplete(aResult) {
+        if (!target.messageManager) {
+          // It is possible that child process is removed when the query completed.
+          return;
+        }
+        target.messageManager.sendAsyncMessage("RemoteLogins:queryReputationCompleted", {
+          requestId,
+          result: aResult
+        });
+      }
+    });
+  },
+
   onFormSubmit(hostname, formSubmitURL,
                          usernameField, newPasswordField,
                          oldPasswordField, openerTopWindow,
                          target) {
     function getPrompter() {
       var prompterSvc = Cc["@mozilla.org/login-manager/prompter;1"].
                         createInstance(Ci.nsILoginManagerPrompter);
       prompterSvc.init(target.ownerGlobal);
--- a/toolkit/components/passwordmgr/nsILoginManager.idl
+++ b/toolkit/components/passwordmgr/nsILoginManager.idl
@@ -7,16 +7,17 @@
 
 interface nsIURI;
 interface nsILoginInfo;
 interface nsIAutoCompleteResult;
 interface nsIFormAutoCompleteObserver;
 interface nsIDOMHTMLInputElement;
 interface nsIDOMHTMLFormElement;
 interface nsIPropertyBag;
+interface IQueryReputationObserver;
 
 [scriptable, uuid(38c7f6af-7df9-49c7-b558-2776b24e6cc1)]
 interface nsILoginManager : nsISupports {
   /**
    * This promise is resolved when initialization is complete, and is rejected
    * in case initialization failed.  This includes the initial loading of the
    * login data as well as any migration from previous versions.
    *
@@ -216,16 +217,26 @@ interface nsILoginManager : nsISupports 
                                in nsIDOMHTMLInputElement aElement,
                                in nsIFormAutoCompleteObserver aListener);
 
   /**
    * Stop a previously-started async search.
    */
   void stopSearch();
 
+
+  /**
+   * Query the login reputation service for the URLs associated with a password field.
+   *
+   * NOTE: This is only enabled when 'browser.safebrowsing.passwords.enabled'
+   *       preference is on.
+   */
+  void queryLoginReputationAsync(in nsIDOMHTMLInputElement aElement,
+                                 in IQueryReputationObserver aListener);
+
   /**
    * Search for logins in the login manager. An array is always returned;
    * if there are no logins the array is empty.
    *
    * @param count
    *        The number of elements in the array. JS callers can simply use
    *        the array's .length property, and supply an dummy object for
    *        this out param. For example: |searchLogins({}, matchData)|
--- a/toolkit/components/passwordmgr/nsLoginManager.js
+++ b/toolkit/components/passwordmgr/nsLoginManager.js
@@ -87,16 +87,20 @@ LoginManager.prototype = {
 
     // Preferences. Add observer so we get notified of changes.
     this._prefBranch = Services.prefs.getBranch("signon.");
     this._prefBranch.addObserver("rememberSignons", this._observer);
 
     this._remember = this._prefBranch.getBoolPref("rememberSignons");
     this._autoCompleteLookupPromise = null;
 
+    // Cache ongoing queries to avoid requesting same query while one is
+    // still in flight.
+    this._queryLoginReputationElements = new Set();
+
     // Form submit observer checks forms for new logins and pw changes.
     Services.obs.addObserver(this._observer, "xpcom-shutdown");
 
     if (Services.appinfo.processType ===
         Services.appinfo.PROCESS_TYPE_DEFAULT) {
       Services.obs.addObserver(this._observer, "passwordmgr-storage-replace");
 
       // Initialize storage so that asynchronous data loading can start.
@@ -526,11 +530,45 @@ LoginManager.prototype = {
                                                    aElement, rect);
     acLookupPromise.then(completeSearch.bind(this, acLookupPromise))
                              .catch(Cu.reportError);
   },
 
   stopSearch() {
     this._autoCompleteLookupPromise = null;
   },
+
+  queryLoginReputationAsync(aElement, aCallback) {
+    if (!LoginHelper.loginReputationEnabled) {
+      // Skip when login reputation is disabled.
+      return;
+    }
+
+    if (aElement.type !== "password") {
+      // Don't query login reputation when it's not a password field.
+      log.debug("QueryLoginReputation is not invoked because this isn't a password field");
+      return;
+    }
+
+    // We already have a request in flight for this field.
+    if (this._queryLoginReputationElements.has(aElement)) {
+      return;
+    }
+
+    this._queryLoginReputationElements.add(aElement);
+
+    LoginManagerContent._queryLoginReputationAsync(aElement)
+     .then((aResult) => {
+        log.debug("QueryLoginReputation invoked. Result is:" + aResult);
+        try {
+          this._queryLoginReputationElements.delete(aElement);
+          aCallback.onQueryReputationCompletion(aElement, aResult);
+        } catch (e) {
+          // Exception happens when the document is removed and we are still trying to
+          // access the input element. This may occur when page is navigated, for this
+          // case we should clear the cache.
+          this._queryLoginReputationElements.clear();
+        }
+    });
+  },
 }; // end of LoginManager implementation
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LoginManager]);
--- a/toolkit/components/reputationservice/ILoginReputation.idl
+++ b/toolkit/components/reputationservice/ILoginReputation.idl
@@ -1,17 +1,39 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=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 "nsISupports.idl"
 
+[scriptable, uuid(6219f9da-297e-446d-8d47-ccdd8e72a1d5)]
+interface ILoginReputationResult : nsISupports {
+
+  // These should sync with 'VerdictType' defined in
+  // LoginReputationClientResponse in csd.proto.
+  const uint16_t UNSPECIFIED      = 0;
+  const uint16_t SAFE             = 1;
+  const uint16_t LOW_REPUTATION   = 2;
+  const uint16_t PHISHING         = 3;
+};
+
+[scriptable, uuid(c21ffe59-595f-46c8-9052-fefb639e196e)]
+interface ILoginReputationQuery : nsISupports {
+
+  readonly attribute ACString formOrigin;
+
+  readonly attribute ACString actionOrigin;
+};
+
 [scriptable, uuid(b527be1e-8fbb-41d9-bee4-267a71236368)]
 interface ILoginReputationQueryCallback : nsISupports {
-  void onQueryComplete();
+  // 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 {
-  // XXX : Add QueryReputation interface
+  void queryReputation(in ILoginReputationQuery aQuery,
+                       in ILoginReputationQueryCallback aCallback);
 };
new file mode 100644
--- /dev/null
+++ b/toolkit/components/reputationservice/IQueryReputationObserver.idl
@@ -0,0 +1,14 @@
+/* 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 "nsISupports.idl"
+
+interface nsIDOMHTMLInputElement;
+
+[scriptable, function, uuid(d7e7ab50-13d4-4279-af2e-2762b172eb48)]
+interface IQueryReputationObserver : nsISupports
+{
+  void onQueryReputationCompletion(in nsIDOMHTMLInputElement aElement,
+                                   in uint16_t aResult);
+};
--- a/toolkit/components/reputationservice/LoginReputation.cpp
+++ b/toolkit/components/reputationservice/LoginReputation.cpp
@@ -31,8 +31,29 @@ LoginReputationService::LoginReputationS
 {
   LR_LOG(("Login reputation service starting up"));
 }
 
 LoginReputationService::~LoginReputationService()
 {
   LR_LOG(("Login reputation service shutting down"));
 }
+
+NS_IMETHODIMP
+LoginReputationService::QueryReputation(ILoginReputationQuery* aQuery,
+                                        ILoginReputationQueryCallback* aCallback)
+{
+  NS_ENSURE_ARG_POINTER(aQuery);
+  NS_ENSURE_ARG_POINTER(aCallback);
+
+  if (LR_LOG_ENABLED()) {
+    nsAutoCString formOrigin, actionOrigin;
+    aQuery->GetFormOrigin(formOrigin);
+    aQuery->GetActionOrigin(actionOrigin);
+    LR_LOG(("Login reputation query parameters: formOrigin(%s), actionOrigin(%s)",
+            formOrigin.BeginReading(), actionOrigin.BeginReading()));
+  }
+
+  // Return SAFE until we add support for the remote service (bug 1413732).
+  aCallback->OnQueryComplete(ILoginReputationResult::SAFE);
+
+  return NS_OK;
+}
--- a/toolkit/components/reputationservice/moz.build
+++ b/toolkit/components/reputationservice/moz.build
@@ -6,16 +6,17 @@
 
 with Files('*'):
     BUG_COMPONENT = ('Toolkit', 'Safe Browsing')
 
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
 XPIDL_SOURCES += [
     'ILoginReputation.idl',
+    'IQueryReputationObserver.idl',
     'nsIApplicationReputation.idl',
 ]
 
 XPIDL_MODULE = 'reputationservice'
 
 UNIFIED_SOURCES += [
     'ApplicationReputation.cpp',
     'chromium/chrome/common/safe_browsing/csd.pb.cc',
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -78,16 +78,17 @@ NS_IMPL_CYCLE_COLLECTION(nsFormFillContr
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFormFillController)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFormFillController)
   NS_INTERFACE_MAP_ENTRY(nsIFormFillController)
   NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteInput)
   NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteSearch)
   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
   NS_INTERFACE_MAP_ENTRY(nsIFormAutoCompleteObserver)
   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+  NS_INTERFACE_MAP_ENTRY(IQueryReputationObserver)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFormFillController)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFormFillController)
 
 
 
 nsFormFillController::nsFormFillController() :
@@ -892,16 +893,31 @@ nsFormFillController::StopSearch()
     mLastFormAutoComplete->StopAutoCompleteSearch();
     mLastFormAutoComplete = nullptr;
   } else if (mLoginManager) {
     mLoginManager->StopSearch();
   }
   return NS_OK;
 }
 
+nsresult
+nsFormFillController::StartQueryLoginReputation(nsIDOMHTMLInputElement *aInput)
+{
+  if (!mLoginManager) {
+    mLoginManager = do_GetService("@mozilla.org/login-manager;1");
+    if (NS_WARN_IF(!mLoginManager)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  mLoginManager->QueryLoginReputationAsync(aInput, this);
+
+  return NS_OK;
+}
+
 ////////////////////////////////////////////////////////////////////////
 //// nsIFormAutoCompleteObserver
 
 NS_IMETHODIMP
 nsFormFillController::OnSearchCompletion(nsIAutoCompleteResult *aResult)
 {
   nsAutoString searchString;
   aResult->GetSearchString(searchString);
@@ -911,16 +927,34 @@ nsFormFillController::OnSearchCompletion
   if (mLastListener) {
     mLastListener->OnSearchResult(this, aResult);
   }
 
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////
+//// IQueryReputationObserver
+
+NS_IMETHODIMP
+nsFormFillController::OnQueryReputationCompletion(nsIDOMHTMLInputElement* aElement,
+                                                  uint16_t aResult)
+{
+  MOZ_ASSERT(aElement);
+
+  MOZ_LOG(sLogger, LogLevel::Verbose, ("OnQueryReputationCompletion (%d)", aResult));
+
+  // TODO: integrate with browser UI (bug 1413389)
+  // Note that it is possible that the element is not visible to the user when we
+  // receive the callback (for example, page is navigated).
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+////////////////////////////////////////////////////////////////////////
 //// nsIDOMEventListener
 
 NS_IMETHODIMP
 nsFormFillController::HandleEvent(nsIDOMEvent* aEvent)
 {
   WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
   NS_ENSURE_STATE(internalEvent);
 
@@ -1070,16 +1104,22 @@ nsFormFillController::MaybeStartControll
   bool isAutofillInput = false;
   if (mAutofillInputs.Get(inputNode)) {
     isAutofillInput = true;
   }
 
   if (isAutofillInput || isPwmgrInput ||  hasList || autocomplete) {
     StartControllingInput(aInput);
   }
+
+  // Trigger an asynchronous login reputation query when user focuses on the
+  // password field.
+  if (formControl->ControlType() == NS_FORM_INPUT_PASSWORD) {
+    StartQueryLoginReputation(aInput);
+  }
 }
 
 nsresult
 nsFormFillController::Focus(nsIDOMEvent* aEvent)
 {
   nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(
     aEvent->InternalDOMEvent()->GetTarget());
   MaybeStartControllingInput(input);
--- a/toolkit/components/satchel/nsFormFillController.h
+++ b/toolkit/components/satchel/nsFormFillController.h
@@ -16,41 +16,44 @@
 #include "nsCOMPtr.h"
 #include "nsDataHashtable.h"
 #include "nsIDocShell.h"
 #include "nsIDOMHTMLInputElement.h"
 #include "nsILoginManager.h"
 #include "nsIMutationObserver.h"
 #include "nsTArray.h"
 #include "nsCycleCollectionParticipant.h"
+#include "IQueryReputationObserver.h"
 
 // X.h defines KeyPress
 #ifdef KeyPress
 #undef KeyPress
 #endif
 
 class nsFormHistory;
 class nsINode;
 class nsPIDOMWindowOuter;
 
 class nsFormFillController final : public nsIFormFillController,
                                    public nsIAutoCompleteInput,
                                    public nsIAutoCompleteSearch,
                                    public nsIDOMEventListener,
                                    public nsIFormAutoCompleteObserver,
-                                   public nsIMutationObserver
+                                   public nsIMutationObserver,
+                                   public IQueryReputationObserver
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIFORMFILLCONTROLLER
   NS_DECL_NSIAUTOCOMPLETESEARCH
   NS_DECL_NSIAUTOCOMPLETEINPUT
   NS_DECL_NSIFORMAUTOCOMPLETEOBSERVER
   NS_DECL_NSIDOMEVENTLISTENER
   NS_DECL_NSIMUTATIONOBSERVER
+  NS_DECL_IQUERYREPUTATIONOBSERVER
 
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsFormFillController, nsIFormFillController)
 
   nsresult Focus(nsIDOMEvent* aEvent);
   nsresult KeyPress(nsIDOMEvent* aKeyEvent);
   nsresult MouseDown(nsIDOMEvent* aMouseEvent);
 
   nsFormFillController();
@@ -84,16 +87,18 @@ protected:
 
   void MaybeRemoveMutationObserver(nsINode* aNode);
 
   void RemoveForDocument(nsIDocument* aDoc);
   bool IsEventTrusted(nsIDOMEvent *aEvent);
 
   bool IsTextControl(nsINode* aNode);
 
+  nsresult StartQueryLoginReputation(nsIDOMHTMLInputElement *aInput);
+
   // members //////////////////////////////////////////
 
   nsCOMPtr<nsIAutoCompleteController> mController;
   nsCOMPtr<nsILoginManager> mLoginManager;
   nsIDOMHTMLInputElement* mFocusedInput;
   nsINode* mFocusedInputNode;
 
   // mListNode is a <datalist> element which, is set, has the form fill controller