зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1887007 - P1. Move formautofill autocomplete seach logic from AutofillProfileAutoComplete to AutoCompleteChild r=credential-management-reviewers,sgalich
This commit makes two significant changes: 1. Refactors AutofillProfileAutoComplete Logic to AutoCompleteChild: Migrates logic from AutofillProfileAutoComplete to AutoCompleteChild, transitioning state management from a per-process to a per-frame basis. This lays the groundwork for future support of autofill functionality across iframes. 2. Implements the Concept of "Autocomplete Entry Providers": Introduces a framework for autocomplete providers (e.g., FormAutofill, LoginManager, FormHistory) to integrate with the autocomplete system through a set of APIs, including: JSWindowActorChild: - string actorName() - bool shouldSearchForAutoComplete(element); - jsval getAutoCompleteSearchOption(element); - jsval recordToAutoCompleteResult(searchString, element, record); JSWindowActorParent: - searchAutoCompleteEntries(searchString, options) Besides implement the above API, autocomplete provider must use `markAsAutoCompletableField` in AutoCompleteChild to register fields for autocomplete, enabling the FormFillController to initiate autocomplete searches when users click on the input field. Note: This patch only integrates FormAutofill, integrating FormHistory and LoginManager will be in other patches. Differential Revision: https://phabricator.services.mozilla.com/D205444
This commit is contained in:
Родитель
ff1ff3ee70
Коммит
c5078faae0
|
@ -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 <input> if the <input> 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 <input>.
|
||||
*
|
||||
* 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 <input> element that is considered autocompletable by the
|
||||
* given provider
|
||||
* @param provider - A module that provides autocomplete entries for a <input>, 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 <input> 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 <input> and
|
||||
// then mark/unmark an input. The provider generally calls the
|
||||
// `markAsAutoCompletbleField` when it sees an <input> is eliglbe for autocomplete.
|
||||
// Here we ask the provider to exam the <input> more detailedly to see
|
||||
// whether we need to search for autocomplete entries at the time users
|
||||
// click on the <input>
|
||||
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, <datalist>.
|
||||
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,
|
||||
// <datalist> 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([
|
||||
|
|
|
@ -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<Object>} 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<Object>} 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() {}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<object>} 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<object>} 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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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<nsIAutoCompleteSearch> 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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче