Bug 1837812 - Merging InputListAutoComplete.sys.mjs into FormAutoComplete.sys.mjs r=issammani

Differential Revision: https://phabricator.services.mozilla.com/D180612
This commit is contained in:
Sergey Galich
2023-06-12 21:19:00 +00:00
parent 0a1a8b289d
commit f12a077af1
9 changed files with 80 additions and 128 deletions

View File

@@ -3,6 +3,14 @@
* 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/. */
// This is ugly: there are two FormAutoCompleteResult classes in the
// tree, one in a module and one in this file. Datalist results need to
// use the one defined in the module but the rest of this file assumes
// that we use the one defined here. To get around that, we explicitly
// import the module here, out of the way of the other uses of
// FormAutoCompleteResult.
import { FormAutoCompleteResult as DataListAutoCompleteResult } from "resource://gre/modules/nsFormAutoCompleteResult.sys.mjs";
function isAutocompleteDisabled(aField) {
if (!aField) {
return false;
@@ -341,7 +349,7 @@ export class FormAutoComplete {
* aUntrimmedSearchString -- current value of the input
* aField -- HTMLInputElement being autocompleted (may be null if from chrome)
* aPreviousResult -- previous search result, if any.
* aDatalistResult -- results from list=datalist for aField.
* aAddDataList -- add results from list=datalist for aField.
* aListener -- nsIFormAutoCompleteObserver that listens for the nsIAutoCompleteResult
* that may be returned asynchronously.
*/
@@ -350,7 +358,7 @@ export class FormAutoComplete {
aUntrimmedSearchString,
aField,
aPreviousResult,
aDatalistResult,
aAddDataList,
aListener
) {
// Guard against void DOM strings filtering into this code.
@@ -370,9 +378,13 @@ export class FormAutoComplete {
aListener?.onSearchCompletion(result);
}
const dataListResult = aAddDataList
? this.getDataListResult(aField, aUntrimmedSearchString)
: null;
// If we have datalist results, they become our "empty" result.
const emptyResult =
aDatalistResult ||
dataListResult ||
new FormAutoCompleteResult(
client,
[],
@@ -487,7 +499,7 @@ export class FormAutoComplete {
this.log("Creating new autocomplete search result.");
// Start with an empty list.
let result = aDatalistResult
let result = dataListResult
? new FormAutoCompleteResult(
client,
[],
@@ -505,8 +517,8 @@ export class FormAutoComplete {
result.entries = aEntries;
}
if (aDatalistResult?.matchCount > 0) {
result = this.mergeResults(result, aDatalistResult);
if (dataListResult?.matchCount > 0) {
result = this.mergeResults(result, dataListResult);
}
maybeNotifyListener(result);
@@ -521,6 +533,50 @@ export class FormAutoComplete {
}
}
getDataListResult(aField, aUntrimmedSearchString) {
const items = this.getDataListSuggestions(aField);
const searchResult = items.length
? Ci.nsIAutoCompleteResult.RESULT_SUCCESS
: Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
const defaultIndex = items.length ? 0 : -1;
return new DataListAutoCompleteResult(
aUntrimmedSearchString,
searchResult,
defaultIndex,
"",
items,
null
);
}
getDataListSuggestions(aField) {
const items = [];
if (!aField?.list) {
return items;
}
const upperFieldValue = aField.value.toUpperCase();
for (const option of aField.list.options) {
const label = option.label || option.text || option.value || "";
if (!label.toUpperCase().includes(upperFieldValue)) {
continue;
}
items.push({
label,
value: option.value,
comment: "",
removable: false,
});
}
return items;
}
mergeResults(historyResult, datalistResult) {
const items = datalistResult.wrappedJSObject._items;
@@ -554,16 +610,7 @@ export class FormAutoComplete {
entry => !isInArray(entry.text, items, "value")
);
// This is ugly: there are two FormAutoCompleteResult classes in the
// tree, one in a module and one in this file. Datalist results need to
// use the one defined in the module but the rest of this file assumes
// that we use the one defined here. To get around that, we explicitly
// import the module here, out of the way of the other uses of
// FormAutoCompleteResult.
const { FormAutoCompleteResult } = ChromeUtils.importESModule(
"resource://gre/modules/nsFormAutoCompleteResult.sys.mjs"
);
return new FormAutoCompleteResult(
return new DataListAutoCompleteResult(
datalistResult.searchString,
Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
0,

View File

@@ -1,54 +0,0 @@
/* 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/. */
import { FormAutoCompleteResult } from "resource://gre/modules/nsFormAutoCompleteResult.sys.mjs";
export class InputListAutoComplete {
classID = Components.ID("{bf1e01d0-953e-11df-981c-0800200c9a66}");
QueryInterface = ChromeUtils.generateQI(["nsIInputListAutoComplete"]);
autoCompleteSearch(aUntrimmedSearchString, aField) {
const items = this.getListSuggestions(aField);
const searchResult = items.length
? Ci.nsIAutoCompleteResult.RESULT_SUCCESS
: Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
const defaultIndex = items.length ? 0 : -1;
return new FormAutoCompleteResult(
aUntrimmedSearchString,
searchResult,
defaultIndex,
"",
items,
null
);
}
getListSuggestions(aField) {
const items = [];
if (!aField?.list) {
return items;
}
const lowerFieldValue = aField.value.toLowerCase();
for (const option of aField.list.options) {
const label = option.label || option.text || option.value || "";
if (!label.toLowerCase().includes(lowerFieldValue)) {
continue;
}
items.push({
label,
value: option.value,
comment: "",
removable: false,
});
}
return items;
}
}

View File

@@ -23,12 +23,6 @@ Classes = [
'esModule': 'resource://gre/modules/FormAutoComplete.sys.mjs',
'constructor': 'FormAutoComplete',
},
{
'cid': '{bf1e01d0-953e-11df-981c-0800200c9a66}',
'contract_ids': ['@mozilla.org/satchel/inputlist-autocomplete;1'],
'esModule': 'resource://gre/modules/InputListAutoComplete.sys.mjs',
'constructor': 'InputListAutoComplete',
},
{
'cid': '{3a0012eb-007f-4bb8-aa81-a07385f77a25}',
'contract_ids': ['@mozilla.org/satchel/form-history-startup;1'],

View File

@@ -14,7 +14,6 @@ BROWSER_CHROME_MANIFESTS += ["test/browser/browser.ini"]
XPIDL_SOURCES += [
"nsIFormAutoComplete.idl",
"nsIFormFillController.idl",
"nsIInputListAutoComplete.idl",
]
XPIDL_MODULE = "satchel"
@@ -32,7 +31,6 @@ EXTRA_JS_MODULES += [
"FormAutoComplete.sys.mjs",
"FormHistory.sys.mjs",
"FormHistoryStartup.sys.mjs",
"InputListAutoComplete.sys.mjs",
"nsFormAutoCompleteResult.sys.mjs",
]

View File

@@ -24,7 +24,6 @@
#include "mozilla/StaticPrefs_ui.h"
#include "nsCRT.h"
#include "nsIFormAutoComplete.h"
#include "nsIInputListAutoComplete.h"
#include "nsString.h"
#include "nsPIDOMWindow.h"
#include "nsIAutoCompleteResult.h"
@@ -732,11 +731,9 @@ nsFormFillController::StartSearch(const nsAString& aSearchString,
MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: non-login field"));
mLastListener = aListener;
nsCOMPtr<nsIAutoCompleteResult> datalistResult;
if (IsTextControl(mFocusedInput)) {
rv = PerformInputListAutoComplete(aSearchString,
getter_AddRefs(datalistResult));
NS_ENSURE_SUCCESS(rv, rv);
bool addDataList = IsTextControl(mFocusedInput);
if (addDataList) {
MaybeObserveDataListMutations();
}
auto formAutoComplete = GetFormAutoComplete();
@@ -744,27 +741,18 @@ nsFormFillController::StartSearch(const nsAString& aSearchString,
formAutoComplete->AutoCompleteSearchAsync(aSearchParam, aSearchString,
mFocusedInput, aPreviousResult,
datalistResult, this);
addDataList, this);
mLastFormAutoComplete = formAutoComplete;
}
return NS_OK;
}
nsresult nsFormFillController::PerformInputListAutoComplete(
const nsAString& aSearch, nsIAutoCompleteResult** aResult) {
void nsFormFillController::MaybeObserveDataListMutations() {
// If an <input> is focused, check if it has a list="<datalist>" which can
// provide the list of suggestions.
MOZ_ASSERT(!mPwmgrInputs.Get(mFocusedInput));
nsresult rv;
nsCOMPtr<nsIInputListAutoComplete> inputListAutoComplete =
do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = inputListAutoComplete->AutoCompleteSearch(aSearch, mFocusedInput,
aResult);
NS_ENSURE_SUCCESS(rv, rv);
if (mFocusedInput) {
Element* list = mFocusedInput->GetList();
@@ -782,8 +770,6 @@ nsresult nsFormFillController::PerformInputListAutoComplete(
}
}
}
return NS_OK;
}
void nsFormFillController::RevalidateDataList() {

View File

@@ -84,8 +84,7 @@ class nsFormFillController final : public nsIFormFillController,
MOZ_CAN_RUN_SCRIPT
void MaybeStartControllingInput(mozilla::dom::HTMLInputElement* aElement);
nsresult PerformInputListAutoComplete(const nsAString& aSearch,
nsIAutoCompleteResult** aResult);
void MaybeObserveDataListMutations();
MOZ_CAN_RUN_SCRIPT void RevalidateDataList();
bool RowMatch(nsFormHistory* aHistory, uint32_t aIndex,

View File

@@ -20,7 +20,7 @@ interface nsIFormAutoComplete: nsISupports {
in AString aSearchString,
in HTMLInputElement aField,
in nsIAutoCompleteResult aPreviousResult,
in nsIAutoCompleteResult aDatalistResult,
in bool aAddDatalist,
in nsIFormAutoCompleteObserver aListener);
/**

View File

@@ -1,18 +0,0 @@
/* 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 nsIAutoCompleteResult;
webidl HTMLInputElement;
[scriptable, uuid(0e33de3e-4faf-4a1a-b96e-24115b8bfd45)]
interface nsIInputListAutoComplete: nsISupports {
/**
* Generate results for a form input autocomplete menu.
*/
nsIAutoCompleteResult autoCompleteSearch(in AString aSearchString,
in HTMLInputElement aField);
};

View File

@@ -101,7 +101,7 @@ add_test(function test1() {
add_test(function test2() {
do_log_info("Check search contains all entries");
fac.autoCompleteSearchAsync("field1", "", null, null, null, {
fac.autoCompleteSearchAsync("field1", "", null, null, false, {
onSearchCompletion(aResults) {
Assert.equal(numRecords, aResults.matchCount);
run_next_test();
@@ -113,7 +113,7 @@ add_test(function test3() {
do_log_info("Check search result ordering with empty search term");
let lastFound = numRecords;
fac.autoCompleteSearchAsync("field1", "", null, null, null, {
fac.autoCompleteSearchAsync("field1", "", null, null, false, {
onSearchCompletion(aResults) {
for (let i = 0; i < numRecords; i += 2) {
Assert.equal(
@@ -134,7 +134,7 @@ add_test(function test4() {
do_log_info('Check search result ordering with "v"');
let lastFound = numRecords;
fac.autoCompleteSearchAsync("field1", "v", null, null, null, {
fac.autoCompleteSearchAsync("field1", "v", null, null, false, {
onSearchCompletion(aResults) {
for (let i = 0; i < numRecords; i += 2) {
Assert.equal(
@@ -176,7 +176,7 @@ add_test(function test6() {
do_log_info("Check search result ordering with empty search term");
let lastFound = timesUsedSamples;
fac.autoCompleteSearchAsync("field2", "", null, null, null, {
fac.autoCompleteSearchAsync("field2", "", null, null, false, {
onSearchCompletion(aResults) {
for (let i = 0; i < timesUsedSamples; i++) {
Assert.equal(
@@ -193,7 +193,7 @@ add_test(function test7() {
do_log_info('Check search result ordering with "v"');
let lastFound = timesUsedSamples;
fac.autoCompleteSearchAsync("field2", "v", null, null, null, {
fac.autoCompleteSearchAsync("field2", "v", null, null, false, {
onSearchCompletion(aResults) {
for (let i = 0; i < timesUsedSamples; i++) {
Assert.equal(
@@ -235,7 +235,7 @@ add_test(function test8() {
});
add_test(function test9() {
fac.autoCompleteSearchAsync("field3", "", null, null, null, {
fac.autoCompleteSearchAsync("field3", "", null, null, false, {
onSearchCompletion(aResults) {
Assert.equal(aResults.getValueAt(0), "senior citizen");
Assert.equal(aResults.getValueAt(1), "old but not senior");
@@ -276,7 +276,7 @@ add_test(function test10() {
});
add_test(function test11() {
fac.autoCompleteSearchAsync("field4", "", null, null, null, {
fac.autoCompleteSearchAsync("field4", "", null, null, false, {
onSearchCompletion(aResults) {
Assert.equal(aResults.matchCount, 3);
run_next_test();
@@ -310,7 +310,7 @@ add_test(function test_token_limit_DB() {
"a b c d e f g h i j .",
null,
previousResult,
null,
false,
{
onSearchCompletion(aResults) {
Assert.equal(
@@ -346,7 +346,7 @@ add_test(function test_token_limit_DB() {
"a b c d e f g h i j .",
null,
null,
null,
false,
{
onSearchCompletion(aResults) {
Assert.equal(
@@ -377,7 +377,7 @@ add_test(async function can_search_escape_marker() {
"/* Further reading */ t",
null,
null,
null,
false,
{
onSearchCompletion(aResults) {
Assert.equal(1, aResults.matchCount);