diff --git a/toolkit/actors/AutoCompleteChild.sys.mjs b/toolkit/actors/AutoCompleteChild.sys.mjs
index 3694e497d5c7..d2a6cb23ad46 100644
--- a/toolkit/actors/AutoCompleteChild.sys.mjs
+++ b/toolkit/actors/AutoCompleteChild.sys.mjs
@@ -13,6 +13,10 @@ ChromeUtils.defineESModuleGetters(lazy, {
LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});
+const gFormFillController = Cc[
+ "@mozilla.org/satchel/form-fill-controller;1"
+].getService(Ci.nsIFormFillController);
+
let autoCompleteListeners = new Set();
export class AutoCompleteChild extends JSWindowActorChild {
@@ -190,6 +194,129 @@ export class AutoCompleteChild extends JSWindowActorChild {
return results;
}
+
+ // Store the input to interested autocomplete providers mapping
+ #providersByInput = new WeakMap();
+
+ providersByInput(input) {
+ // This functions returns the interested providers that have called
+ // `markAsAutoCompletableField` for the given input and also the hard-coded
+ // autocomplete providers based on input type.
+ const providers = new Set(this.#providersByInput.get(input));
+
+ // The current design is that FormHisotry doesn't call `markAsAutoCompletable`
+ // for every eligilbe input. Instead, when FormFillController receives a focus event,
+ // it would control the if the is eligible to show form history.
+ // Because of the design, we need to ask FormHistory whether to search for autocomplete entries
+ // for every startSearch call
+ // TODO: uncomment after integrating FormHisotry with AutoCompleteChild
+ // providers.add(input.ownerGlobal.windowGlobalChild.getActor("FormHistory"));
+ return providers;
+ }
+
+ /**
+ * This API should be used by an autocomplete entry provider to mark an input field
+ * as eligible for autocomplete for its type.
+ * When users click on an autocompletable input, we will search autocomplete entries
+ * from all the providers that have called this API for the given .
+ *
+ * An autocomplete provider should be a JSWindowActor and implements the following
+ * functions:
+ * - string actorName()
+ * - bool shouldSearchForAutoComplete(element);
+ * - jsval getAutoCompleteSearchOption(element);
+ * - jsval searchResultToAutoCompleteResult(searchString, element, record);
+ * See `FormAutofillChild` for example
+ *
+ * @param input - The HTML element that is considered autocompletable by the
+ * given provider
+ * @param provider - A module that provides autocomplete entries for a , for example,
+ * FormAutofill provides address or credit card autocomplete entries,
+ * LoginManager provides logins entreis.
+ */
+ markAsAutoCompletableField(input, provider) {
+ gFormFillController.markAsAutoCompletableField(input);
+
+ let providers = this.#providersByInput.get(input);
+ if (!providers) {
+ providers = new Set();
+ this.#providersByInput.set(input, providers);
+ }
+ providers.add(provider);
+ }
+
+ // Record the current ongoing search request. This is used by stopSearch
+ // to prevent notifying the autocomplete controller after receiving search request
+ // results that were issued prior to the call to stop the search.
+ #ongoingSearches = new Set();
+
+ async startSearch(searchString, input, listener) {
+ // TODO: This should be removed once we implement triggering autocomplete
+ // from the parent.
+ this.lastProfileAutoCompleteFocusedInput = input;
+
+ // For all the autocomplete entry providers that previsouly marked
+ // this as autocompletable, ask the provider whether we should
+ // search for autocomplete entries in the parent. This is because the current
+ // design doesn't rely on the provider constantly monitor the and
+ // then mark/unmark an input. The provider generally calls the
+ // `markAsAutoCompletbleField` when it sees an is eliglbe for autocomplete.
+ // Here we ask the provider to exam the more detailedly to see
+ // whether we need to search for autocomplete entries at the time users
+ // click on the
+ const providers = this.providersByInput(input);
+ const data = Array.from(providers)
+ .filter(p => p.shouldSearchForAutoComplete(input, searchString))
+ .map(p => ({
+ actorName: p.actorName,
+ options: p.getAutoCompleteSearchOption(input, searchString),
+ }));
+
+ let result = [];
+
+ // We don't return empty result when no provider requests seaching entries in the
+ // parent because for some special cases, the autocomplete entries are coming
+ // from the content. For example, .
+ if (data.length) {
+ const promise = this.sendQuery("AutoComplete:StartSearch", {
+ searchString,
+ data,
+ });
+ this.#ongoingSearches.add(promise);
+ result = await promise;
+
+ // If the search is stopped, don't report back.
+ if (!this.#ongoingSearches.delete(promise)) {
+ return;
+ }
+ }
+
+ for (const provider of providers) {
+ // Search result could be empty. However, an autocomplete provider might
+ // want to show an autoclmplete popup when there is no search result. For example,
+ // for FormHisotry, insecure warning for LoginManager.
+ const searchResult = result.find(r => r.actorName == provider.actorName);
+ const acResult = provider.searchResultToAutoCompleteResult(
+ searchString,
+ input,
+ searchResult
+ );
+
+ // We have not yet supported showing autocomplete entries from multiple providers,
+ // Note: The prioty is defined in AutoCompleteParent.
+ if (acResult) {
+ this.lastProfileAutoCompleteResult = acResult;
+ listener.onSearchCompletion(acResult);
+ return;
+ }
+ }
+ this.lastProfileAutoCompleteResult = null;
+ }
+
+ stopSearch() {
+ this.lastProfileAutoCompleteResult = null;
+ this.#ongoingSearches.clear();
+ }
}
AutoCompleteChild.prototype.QueryInterface = ChromeUtils.generateQI([
diff --git a/toolkit/actors/AutoCompleteParent.sys.mjs b/toolkit/actors/AutoCompleteParent.sys.mjs
index 70cbcea44de2..8cb51c8e5892 100644
--- a/toolkit/actors/AutoCompleteParent.sys.mjs
+++ b/toolkit/actors/AutoCompleteParent.sys.mjs
@@ -376,7 +376,7 @@ export class AutoCompleteParent extends JSWindowActorParent {
}
}
- receiveMessage(message) {
+ async receiveMessage(message) {
let browser = this.browsingContext.top.embedderElement;
if (
@@ -433,6 +433,12 @@ export class AutoCompleteParent extends JSWindowActorParent {
this.closePopup();
break;
}
+
+ case "AutoComplete:StartSearch": {
+ const { searchString, data } = message.data;
+ const result = await this.#startSearch(searchString, data);
+ return result;
+ }
}
// Returning false to pacify ESLint, but this return value is
// ignored by the messaging infrastructure.
@@ -493,6 +499,45 @@ export class AutoCompleteParent extends JSWindowActorParent {
}
}
+ // This defines the supported autocomplete providers and the prioity to show the autocomplete
+ // entry.
+ #AUTOCOMPLETE_PROVIDERS = ["FormAutofill", "LoginManager", "FormHistory"];
+
+ /**
+ * Search across multiple module to gather autocomplete entries for a given search string.
+ *
+ * @param {string} searchString
+ * The input string used to query autocomplete entries across different
+ * autocomplete providers.
+ * @param {Array} providers
+ * An array of objects where each object has a `name` used to identify the actor
+ * name of the provider and `options` that are passed to the `searchAutoCompleteEntries`
+ * method of the actor.
+ * @returns {Array} An array of results objects with `name` of the provider and `entries`
+ * that are returned from the provider module's `searchAutoCompleteEntries` method.
+ */
+ async #startSearch(searchString, providers) {
+ for (const name of this.#AUTOCOMPLETE_PROVIDERS) {
+ const provider = providers.find(p => p.actorName == name);
+ if (!provider) {
+ continue;
+ }
+ const { actorName, options } = provider;
+ const actor =
+ this.browsingContext.currentWindowGlobal.getActor(actorName);
+ const entries = await actor?.searchAutoCompleteEntries(
+ searchString,
+ options
+ );
+
+ // We have not yet supported showing autocomplete entries from multiple providers,
+ if (entries) {
+ return [{ actorName, ...entries }];
+ }
+ }
+ return [];
+ }
+
stopSearch() {}
/**
diff --git a/toolkit/components/autocomplete/nsIAutoCompletePopup.idl b/toolkit/components/autocomplete/nsIAutoCompletePopup.idl
index 607863c9a7cd..0c8e3c99d655 100644
--- a/toolkit/components/autocomplete/nsIAutoCompletePopup.idl
+++ b/toolkit/components/autocomplete/nsIAutoCompletePopup.idl
@@ -5,6 +5,7 @@
#include "nsISupports.idl"
interface nsIAutoCompleteInput;
+interface nsIFormFillCompleteObserver;
webidl Element;
@@ -69,4 +70,21 @@ interface nsIAutoCompletePopup : nsISupports
* @return The currently selected result item index
*/
void selectBy(in boolean reverse, in boolean page);
+
+ /*
+ * Search for a given string and notify a listener (either synchronously
+ * or asynchronously) of the result
+ *
+ * @param searchString - The string to search for
+ * @param searchParam - An extra parameter
+ * @param previousResult - A previous result to use for faster searching
+ * @param listener - A listener to notify when the search is complete
+ */
+ void startSearch(in AString searchString, in Element element, in nsIFormFillCompleteObserver listener);
+
+ /*
+ * Stop the search that is in progress
+ */
+ void stopSearch();
+
};
diff --git a/toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs b/toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs
index 7594fc8fcf5f..56c5c29825b5 100644
--- a/toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs
+++ b/toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs
@@ -296,8 +296,6 @@ AutofillProfileAutoCompleteSearch.prototype = {
export const ProfileAutocomplete = {
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
- lastProfileAutoCompleteResult: null,
- lastProfileAutoCompleteFocusedInput: null,
_registered: false,
_factory: null,
@@ -329,7 +327,6 @@ export const ProfileAutocomplete = {
this._factory.unregister();
this._factory = null;
this._registered = false;
- this._lastAutoCompleteResult = null;
Services.obs.removeObserver(this, "autocomplete-will-enter-text");
},
@@ -362,6 +359,17 @@ export const ProfileAutocomplete = {
return actor.selectedIndex;
},
+ // TODO: This will be removed after we implement triggering autofill from the parent
+ get lastProfileAutoCompleteResult() {
+ return lazy.FormAutofillContent.activeAutofillChild
+ .lastProfileAutoCompleteResult;
+ },
+
+ get lastProfileAutoCompleteFocusedInput() {
+ return lazy.FormAutofillContent.activeAutofillChild
+ .lastProfileAutoCompleteFocusedInput;
+ },
+
async _fillFromAutocompleteRow(focusedInput) {
this.debug("_fillFromAutocompleteRow:", focusedInput);
let formDetails = lazy.FormAutofillContent.activeFormDetails;
diff --git a/toolkit/components/formautofill/FormAutofillChild.sys.mjs b/toolkit/components/formautofill/FormAutofillChild.sys.mjs
index af8445943291..6eef2047146b 100644
--- a/toolkit/components/formautofill/FormAutofillChild.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillChild.sys.mjs
@@ -8,11 +8,16 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
+ AddressResult: "resource://autofill/ProfileAutoCompleteResult.sys.mjs",
AutoCompleteChild: "resource://gre/actors/AutoCompleteChild.sys.mjs",
AutofillTelemetry: "resource://gre/modules/shared/AutofillTelemetry.sys.mjs",
+ CreditCardResult: "resource://autofill/ProfileAutoCompleteResult.sys.mjs",
+ GenericAutocompleteItem: "resource://gre/modules/FillHelpers.sys.mjs",
+ InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.sys.mjs",
FormAutofill: "resource://autofill/FormAutofill.sys.mjs",
FormAutofillContent: "resource://autofill/FormAutofillContent.sys.mjs",
FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
+ FormScenarios: "resource://gre/modules/FormScenarios.sys.mjs",
FormStateManager: "resource://gre/modules/shared/FormStateManager.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
ProfileAutocomplete:
@@ -656,16 +661,21 @@ export class FormAutofillChild extends JSWindowActorChild {
}
}
- previewProfile(selectedIndex) {
- let lastAutoCompleteResult =
- lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
- let focusedInput = this.activeInput;
+ get lastProfileAutoCompleteResult() {
+ return this.manager.getActor("AutoComplete")?.lastProfileAutoCompleteResult;
+ }
+ get lastProfileAutoCompleteFocusedInput() {
+ return this.manager.getActor("AutoComplete")
+ ?.lastProfileAutoCompleteFocusedInput;
+ }
+
+ previewProfile(selectedIndex) {
if (
selectedIndex === -1 ||
- !focusedInput ||
- !lastAutoCompleteResult ||
- lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill"
+ !this.activeInput ||
+ this.lastProfileAutoCompleteResult?.getStyleAt(selectedIndex) !=
+ "autofill"
) {
lazy.ProfileAutocomplete._clearProfilePreview();
} else {
@@ -701,6 +711,149 @@ export class FormAutofillChild extends JSWindowActorChild {
return;
}
- formFillController.markAsAutofillField(field);
+ this.manager
+ .getActor("AutoComplete")
+ ?.markAsAutoCompletableField(field, this);
+ }
+
+ get actorName() {
+ return "FormAutofill";
+ }
+
+ /**
+ * Get the search options when searching for autocomplete entries in the parent
+ *
+ * @param {HTMLInputElement} input - The input element to search for autocompelte entries
+ * @returns {object} the search options for the input
+ */
+ getAutoCompleteSearchOption(input) {
+ const fieldDetail = this._fieldDetailsManager
+ ._getFormHandler(input)
+ ?.getFieldDetailByElement(input);
+
+ const scenarioName = lazy.FormScenarios.detect({ input }).signUpForm
+ ? "SignUpFormScenario"
+ : "";
+ return { fieldName: fieldDetail?.fieldName, scenarioName };
+ }
+
+ /**
+ * Ask the provider whether it might have autocomplete entry to show
+ * for the given input.
+ *
+ * @param {HTMLInputElement} input - The input element to search for autocompelte entries
+ * @returns {boolean} true if we shold search for autocomplete entries
+ */
+ shouldSearchForAutoComplete(input) {
+ const fieldDetail = this._fieldDetailsManager
+ ._getFormHandler(input)
+ ?.getFieldDetailByElement(input);
+ if (!fieldDetail) {
+ return false;
+ }
+ const fieldName = fieldDetail.fieldName;
+ const isAddressField = lazy.FormAutofillUtils.isAddressField(fieldName);
+ const searchPermitted = isAddressField
+ ? lazy.FormAutofill.isAutofillAddressesEnabled
+ : lazy.FormAutofill.isAutofillCreditCardsEnabled;
+ // If the specified autofill feature is pref off, do not search
+ if (!searchPermitted) {
+ return false;
+ }
+
+ // No profile can fill the currently-focused input.
+ if (!lazy.FormAutofillContent.savedFieldNames.has(fieldName)) {
+ return false;
+ }
+
+ // The current form has already been populated and the field is not
+ // an empty credit card field.
+ const isCreditCardField =
+ lazy.FormAutofillUtils.isCreditCardField(fieldName);
+ const isInputAutofilled =
+ this.activeHandler.getFilledStateByElement(input) ==
+ lazy.FormAutofillUtils.FIELD_STATES.AUTO_FILLED;
+ const filledRecordGUID = this.activeSection.filledRecordGUID;
+ if (
+ !isInputAutofilled &&
+ filledRecordGUID &&
+ !(isCreditCardField && this.activeInput.value === "")
+ ) {
+ return false;
+ }
+
+ // (address only) less than 3 inputs are covered by all saved fields in the storage.
+ if (
+ isAddressField &&
+ this.activeSection.allFieldNames.filter(field =>
+ lazy.FormAutofillContent.savedFieldNames.has(field)
+ ).length < lazy.FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Convert the search result to autocomplete results
+ *
+ * @param {string} searchString - The string to search for
+ * @param {HTMLInputElement} input - The input element to search for autocompelte entries
+ * @param {Array} records - autocomplete records
+ * @returns {AutocompleteResult}
+ */
+ searchResultToAutoCompleteResult(searchString, input, records) {
+ if (!records) {
+ return null;
+ }
+
+ const entries = records.records;
+ const externalEntries = records.externalEntries;
+
+ const fieldDetail = this._fieldDetailsManager
+ ._getFormHandler(input)
+ ?.getFieldDetailByElement(input);
+ if (!fieldDetail) {
+ return null;
+ }
+
+ const adaptedRecords = this.activeSection.getAdaptedProfiles(entries);
+ const isSecure = lazy.InsecurePasswordUtils.isFormSecure(
+ this.activeHandler.form
+ );
+ const isInputAutofilled =
+ this.activeHandler.getFilledStateByElement(input) ==
+ lazy.FormAutofillUtils.FIELD_STATES.AUTO_FILLED;
+ const allFieldNames = this.activeSection.allFieldNames;
+
+ const AutocompleteResult = lazy.FormAutofillUtils.isAddressField(
+ fieldDetail.fieldName
+ )
+ ? lazy.AddressResult
+ : lazy.CreditCardResult;
+
+ const acResult = new AutocompleteResult(
+ searchString,
+ fieldDetail.fieldName,
+ allFieldNames,
+ adaptedRecords,
+ { isSecure, isInputAutofilled }
+ );
+
+ acResult.externalEntries.push(
+ ...externalEntries.map(
+ entry =>
+ new lazy.GenericAutocompleteItem(
+ entry.image,
+ entry.title,
+ entry.subtitle,
+ entry.fillMessageName,
+ entry.fillMessageData
+ )
+ )
+ );
+
+ return acResult;
}
}
diff --git a/toolkit/components/formautofill/FormAutofillParent.sys.mjs b/toolkit/components/formautofill/FormAutofillParent.sys.mjs
index 34dac8ce1502..bc56baeea7fd 100644
--- a/toolkit/components/formautofill/FormAutofillParent.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillParent.sys.mjs
@@ -267,17 +267,8 @@ export class FormAutofillParent extends JSWindowActorParent {
break;
}
case "FormAutofill:GetRecords": {
- const relayPromise = lazy.FirefoxRelay.autocompleteItemsAsync({
- formOrigin: this.formOrigin,
- scenarioName: data.scenarioName,
- hasInput: !!data.searchString?.length,
- });
- const recordsPromise = FormAutofillParent.getRecords(data);
- const [records, externalEntries] = await Promise.all([
- recordsPromise,
- relayPromise,
- ]);
- return { records, externalEntries };
+ const records = await FormAutofillParent.getRecords(data);
+ return { records };
}
case "FormAutofill:OnFormSubmit": {
this.notifyMessageObservers("onFormSubmitted", data);
@@ -401,6 +392,43 @@ export class FormAutofillParent extends JSWindowActorParent {
}
}
+ /**
+ * Retrieves autocomplete entries for a given search string and data context.
+ *
+ * @param {string} searchString
+ * The search string used to filter autocomplete entries.
+ * @param {object} options
+ * @param {string} options.fieldName
+ * The name of the field for which autocomplete entries are being fetched.
+ * @param {string} options.scenarioName
+ * The scenario name used in the autocomplete operation to fetch external entries.
+ * @returns {Promise} A promise that resolves to an object containing two properties: `records` and `externalEntries`.
+ * `records` is an array of autofill records from the form's internal data, sorted by `timeLastUsed`.
+ * `externalEntries` is an array of external autocomplete items fetched based on the scenario.
+ */
+ async searchAutoCompleteEntries(searchString, options) {
+ const { fieldName, scenarioName } = options;
+ const relayPromise = lazy.FirefoxRelay.autocompleteItemsAsync({
+ formOrigin: this.formOrigin,
+ scenarioName,
+ hasInput: !!searchString?.length,
+ });
+
+ const recordsPromise = FormAutofillParent.getRecords({
+ searchString,
+ fieldName,
+ });
+ const [records, externalEntries] = await Promise.all([
+ recordsPromise,
+ relayPromise,
+ ]);
+
+ // Sort addresses by timeLastUsed for showing the lastest used address at top.
+ records.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
+
+ return { records, externalEntries };
+ }
+
/**
* Get the records from profile store and return results back to content
* process. It will decrypt the credit card number and append
diff --git a/toolkit/components/satchel/components.conf b/toolkit/components/satchel/components.conf
index d5a670efd91c..88c98708bc05 100644
--- a/toolkit/components/satchel/components.conf
+++ b/toolkit/components/satchel/components.conf
@@ -9,7 +9,7 @@ Classes = [
'cid': '{895db6c7-dbdf-40ea-9f64-b175033243dc}',
'contract_ids': [
'@mozilla.org/satchel/form-fill-controller;1',
- '@mozilla.org/autocomplete/search;1?name=form-history',
+ '@mozilla.org/autocomplete/search;1?name=form-fill-controller',
],
'type': 'nsFormFillController',
'constructor': 'nsFormFillController::GetSingleton',
diff --git a/toolkit/components/satchel/nsFormFillController.cpp b/toolkit/components/satchel/nsFormFillController.cpp
index 61d23d157cef..eee75ceac1e1 100644
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -522,18 +522,15 @@ nsFormFillController::GetSearchCount(uint32_t* aSearchCount) {
NS_IMETHODIMP
nsFormFillController::GetSearchAt(uint32_t index, nsACString& _retval) {
- if (mAutofillInputs.Get(mFocusedInput)) {
- MOZ_LOG(sLogger, LogLevel::Debug, ("GetSearchAt: autofill-profiles field"));
- nsCOMPtr profileSearch = do_GetService(
- "@mozilla.org/autocomplete/search;1?name=autofill-profiles");
- if (profileSearch) {
- _retval.AssignLiteral("autofill-profiles");
- return NS_OK;
- }
- }
+ MOZ_LOG(sLogger, LogLevel::Debug,
+ ("GetSearchAt: form-fill-controller field"));
- MOZ_LOG(sLogger, LogLevel::Debug, ("GetSearchAt: form-history field"));
- _retval.AssignLiteral("form-history");
+ // The better solution should be AutoCompleteController gets the
+ // nsIAutoCompleteSearch interface from AutoCompletePopup and invokes the
+ // StartSearch without going through FormFillController. Currently
+ // FormFillController acts as the proxy to find the AutoCompletePopup for
+ // AutoCompleteController.
+ _retval.AssignLiteral("form-fill-controller");
return NS_OK;
}
@@ -669,52 +666,56 @@ nsFormFillController::StartSearch(const nsAString& aSearchString,
nsIAutoCompleteObserver* aListener) {
MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch for %p", mFocusedInput));
- nsresult rv;
+ if (mFocusedInput && mFocusedPopup) {
+ if (mAutofillInputs.Get(mFocusedInput)) {
+ MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: formautofill field"));
- // If the login manager has indicated it's responsible for this field, let it
- // handle the autocomplete. Otherwise, handle with form history.
- // This method is sometimes called in unit tests and from XUL without a
- // focused node.
- if (mFocusedInput && (mPwmgrInputs.Get(mFocusedInput) ||
- mFocusedInput->HasBeenTypePassword())) {
- MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: login field"));
+ mLastListener = aListener;
+ return mFocusedPopup->StartSearch(aSearchString, mFocusedInput, this);
+ // If the login manager has indicated it's responsible for this field, let
+ // it handle the autocomplete. Otherwise, handle with form history. This
+ // method is sometimes called in unit tests and from XUL without a focused
+ // node.
+ } else if (mPwmgrInputs.Get(mFocusedInput) ||
+ mFocusedInput->HasBeenTypePassword()) {
+ MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: login field"));
- // Handle the case where a password field is focused but
- // MarkAsLoginManagerField wasn't called because password manager is
- // disabled.
- if (!mLoginManagerAC) {
- mLoginManagerAC =
- do_GetService("@mozilla.org/login-manager/autocompletesearch;1");
+ // Handle the case where a password field is focused but
+ // MarkAsLoginManagerField wasn't called because password manager is
+ // disabled.
+ if (!mLoginManagerAC) {
+ mLoginManagerAC =
+ do_GetService("@mozilla.org/login-manager/autocompletesearch;1");
+ }
+
+ if (NS_WARN_IF(!mLoginManagerAC)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // XXX aPreviousResult shouldn't ever be a historyResult type, since we're
+ // not letting satchel manage the field?
+ mLastListener = aListener;
+ return mLoginManagerAC->StartSearch(aSearchString, aPreviousResult,
+ mFocusedInput, this);
}
-
- if (NS_WARN_IF(!mLoginManagerAC)) {
- return NS_ERROR_FAILURE;
- }
-
- // XXX aPreviousResult shouldn't ever be a historyResult type, since we're
- // not letting satchel manage the field?
- mLastListener = aListener;
- rv = mLoginManagerAC->StartSearch(aSearchString, aPreviousResult,
- mFocusedInput, this);
- NS_ENSURE_SUCCESS(rv, rv);
- } else {
- MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: non-login field"));
- mLastListener = aListener;
-
- bool addDataList = IsTextControl(mFocusedInput);
- if (addDataList) {
- MaybeObserveDataListMutations();
- }
-
- auto* formHistoryAutoComplete = GetFormHistoryAutoComplete();
- NS_ENSURE_TRUE(formHistoryAutoComplete, NS_ERROR_FAILURE);
-
- formHistoryAutoComplete->AutoCompleteSearchAsync(
- aSearchParam, aSearchString, mFocusedInput, aPreviousResult,
- addDataList, this);
- mLastFormHistoryAutoComplete = formHistoryAutoComplete;
}
+ MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: non-login field"));
+ mLastListener = aListener;
+
+ bool addDataList = IsTextControl(mFocusedInput);
+ if (addDataList) {
+ MaybeObserveDataListMutations();
+ }
+
+ auto* formHistoryAutoComplete = GetFormHistoryAutoComplete();
+ NS_ENSURE_TRUE(formHistoryAutoComplete, NS_ERROR_FAILURE);
+
+ formHistoryAutoComplete->AutoCompleteSearchAsync(
+ aSearchParam, aSearchString, mFocusedInput, aPreviousResult, addDataList,
+ this);
+ mLastFormHistoryAutoComplete = formHistoryAutoComplete;
+
return NS_OK;
}
@@ -767,6 +768,10 @@ nsFormFillController::StopSearch() {
mLastFormHistoryAutoComplete = nullptr;
}
+ if (mFocusedPopup) {
+ mFocusedPopup->StopSearch();
+ }
+
if (mLoginManagerAC) {
mLoginManagerAC->StopSearch();
}
diff --git a/toolkit/components/satchel/test/unit/test_previous_result.js b/toolkit/components/satchel/test/unit/test_previous_result.js
index a782832db7aa..e9583d8d61c9 100644
--- a/toolkit/components/satchel/test/unit/test_previous_result.js
+++ b/toolkit/components/satchel/test/unit/test_previous_result.js
@@ -19,7 +19,7 @@ var aaListener = {
function run_test() {
do_test_pending();
let search = Cc[
- "@mozilla.org/autocomplete/search;1?name=form-history"
+ "@mozilla.org/autocomplete/search;1?name=form-fill-controller"
].getService(Ci.nsIAutoCompleteSearch);
search.startSearch("aa", "", null, aaListener);
}