mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-25 11:15:34 +00:00
Bug 566746 - Changes to form autocomplete to support new asynchronous FormHistory.jsm module, p=enndeakin,felix, r=dteller
This commit is contained in:
parent
6335d6918a
commit
cf435b145a
@ -10,6 +10,11 @@ const Cr = Components.results;
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
|
||||
"resource://gre/modules/Deprecated.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
|
||||
"resource://gre/modules/FormHistory.jsm");
|
||||
|
||||
function FormAutoComplete() {
|
||||
this.init();
|
||||
}
|
||||
@ -18,14 +23,6 @@ FormAutoComplete.prototype = {
|
||||
classID : Components.ID("{c11c21b2-71c9-4f87-a0f8-5e13f50495fd}"),
|
||||
QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormAutoComplete, Ci.nsISupportsWeakReference]),
|
||||
|
||||
__formHistory : null,
|
||||
get _formHistory() {
|
||||
if (!this.__formHistory)
|
||||
this.__formHistory = Cc["@mozilla.org/satchel/form-history;1"].
|
||||
getService(Ci.nsIFormHistory2);
|
||||
return this.__formHistory;
|
||||
},
|
||||
|
||||
_prefBranch : null,
|
||||
_debug : true, // mirrors browser.formfill.debug
|
||||
_enabled : true, // mirrors browser.formfill.enable preference
|
||||
@ -37,6 +34,13 @@ FormAutoComplete.prototype = {
|
||||
_boundaryWeight : 25,
|
||||
_prefixWeight : 5,
|
||||
|
||||
// Only one query is performed at a time, which will be stored in _pendingQuery
|
||||
// while the query is being performed. It will be cleared when the query finishes,
|
||||
// is cancelled, or an error occurs. If a new query occurs while one is already
|
||||
// pending, the existing one is cancelled. The pending query will be an
|
||||
// mozIStoragePendingStatement object.
|
||||
_pendingQuery : null,
|
||||
|
||||
init : function() {
|
||||
// Preferences. Add observer so we get notified of changes.
|
||||
this._prefBranch = Services.prefs.getBranch("browser.formfill.");
|
||||
@ -50,10 +54,6 @@ FormAutoComplete.prototype = {
|
||||
this._maxTimeGroupings = this._prefBranch.getIntPref("maxTimeGroupings");
|
||||
this._timeGroupingSize = this._prefBranch.getIntPref("timeGroupingSize") * 1000 * 1000;
|
||||
this._expireDays = this._prefBranch.getIntPref("expire_days");
|
||||
|
||||
this._dbStmts = {};
|
||||
|
||||
Services.obs.addObserver(this.observer, "profile-before-change", true);
|
||||
},
|
||||
|
||||
observer : {
|
||||
@ -96,12 +96,6 @@ FormAutoComplete.prototype = {
|
||||
default:
|
||||
self.log("Oops! Pref not handled, change ignored.");
|
||||
}
|
||||
} else if (topic == "profile-before-change") {
|
||||
for each (let stmt in self._dbStmts) {
|
||||
stmt.finalize();
|
||||
}
|
||||
self._dbStmts = {};
|
||||
self.__formHistory = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -121,33 +115,64 @@ FormAutoComplete.prototype = {
|
||||
},
|
||||
|
||||
|
||||
autoCompleteSearch : function (aInputName, aUntrimmedSearchString, aField, aPreviousResult) {
|
||||
Deprecated.warning("nsIFormAutoComplete::autoCompleteSearch is deprecated", "https://bugzilla.mozilla.org/show_bug.cgi?id=697377");
|
||||
|
||||
let result = null;
|
||||
let listener = {
|
||||
onSearchCompletion: function (r) result = r
|
||||
};
|
||||
this._autoCompleteSearchShared(aInputName, aUntrimmedSearchString, aField, aPreviousResult, listener);
|
||||
|
||||
// Just wait for the result to to be available.
|
||||
let thread = Components.classes["@mozilla.org/thread-manager;1"].getService().currentThread;
|
||||
while (!result && this._pendingQuery) {
|
||||
thread.processNextEvent(true);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
autoCompleteSearchAsync : function (aInputName, aUntrimmedSearchString, aField, aPreviousResult, aListener) {
|
||||
this._autoCompleteSearchShared(aInputName, aUntrimmedSearchString, aField, aPreviousResult, aListener);
|
||||
},
|
||||
|
||||
/*
|
||||
* autoCompleteSearch
|
||||
* autoCompleteSearchShared
|
||||
*
|
||||
* aInputName -- |name| attribute from the form input being autocompleted.
|
||||
* aUntrimmedSearchString -- current value of the input
|
||||
* aField -- nsIDOMHTMLInputElement being autocompleted (may be null if from chrome)
|
||||
* aPreviousResult -- previous search result, if any.
|
||||
*
|
||||
* Returns: an nsIAutoCompleteResult
|
||||
* aListener -- nsIFormAutoCompleteObserver that listens for the nsIAutoCompleteResult
|
||||
* that may be returned asynchronously.
|
||||
*/
|
||||
autoCompleteSearch : function (aInputName, aUntrimmedSearchString, aField, aPreviousResult) {
|
||||
_autoCompleteSearchShared : function (aInputName, aUntrimmedSearchString, aField, aPreviousResult, aListener) {
|
||||
function sortBytotalScore (a, b) {
|
||||
return b.totalScore - a.totalScore;
|
||||
}
|
||||
|
||||
if (!this._enabled)
|
||||
return null;
|
||||
let result = null;
|
||||
if (!this._enabled) {
|
||||
result = new FormAutoCompleteResult(FormHistory, [], aInputName, aUntrimmedSearchString);
|
||||
if (aListener) {
|
||||
aListener.onSearchCompletion(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// don't allow form inputs (aField != null) to get results from search bar history
|
||||
if (aInputName == 'searchbar-history' && aField) {
|
||||
this.log('autoCompleteSearch for input name "' + aInputName + '" is denied');
|
||||
return null;
|
||||
result = new FormAutoCompleteResult(FormHistory, [], aInputName, aUntrimmedSearchString);
|
||||
if (aListener) {
|
||||
aListener.onSearchCompletion(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.log("AutoCompleteSearch invoked. Search is: " + aUntrimmedSearchString);
|
||||
let searchString = aUntrimmedSearchString.trim().toLowerCase();
|
||||
let result = null;
|
||||
|
||||
// reuse previous results if:
|
||||
// a) length greater than one character (others searches are special cases) AND
|
||||
@ -176,145 +201,76 @@ FormAutoComplete.prototype = {
|
||||
}
|
||||
filteredEntries.sort(sortBytotalScore);
|
||||
result.wrappedJSObject.entries = filteredEntries;
|
||||
|
||||
if (aListener) {
|
||||
aListener.onSearchCompletion(result);
|
||||
}
|
||||
} else {
|
||||
this.log("Creating new autocomplete search result.");
|
||||
let entries = this.getAutoCompleteValues(aInputName, searchString);
|
||||
result = new FormAutoCompleteResult(this._formHistory, entries, aInputName, aUntrimmedSearchString);
|
||||
if (aField && aField.maxLength > -1) {
|
||||
let original = result.wrappedJSObject.entries;
|
||||
let filtered = original.filter(function (el) el.text.length <= this.maxLength, aField);
|
||||
result.wrappedJSObject.entries = filtered;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
// Start with an empty list.
|
||||
result = new FormAutoCompleteResult(FormHistory, [], aInputName, aUntrimmedSearchString);
|
||||
|
||||
let processEntry = function(aEntries) {
|
||||
if (aField && aField.maxLength > -1) {
|
||||
result.entries =
|
||||
aEntries.filter(function (el) { return el.text.length <= aField.maxLength; });
|
||||
} else {
|
||||
result.entries = aEntries;
|
||||
}
|
||||
|
||||
if (aListener) {
|
||||
aListener.onSearchCompletion(result);
|
||||
}
|
||||
}
|
||||
|
||||
this.getAutoCompleteValues(aInputName, searchString, processEntry);
|
||||
}
|
||||
},
|
||||
|
||||
getAutoCompleteValues : function (fieldName, searchString) {
|
||||
let values = [];
|
||||
let searchTokens;
|
||||
stopAutoCompleteSearch : function () {
|
||||
if (this._pendingQuery) {
|
||||
this._pendingQuery.cancel();
|
||||
this._pendingQuery = null;
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Get the values for an autocomplete list given a search string.
|
||||
*
|
||||
* fieldName - fieldname field within form history (the form input name)
|
||||
* searchString - string to search for
|
||||
* callback - called when the values are available. Passed an array of objects,
|
||||
* containing properties for each result. The callback is only called
|
||||
* when successful.
|
||||
*/
|
||||
getAutoCompleteValues : function (fieldName, searchString, callback) {
|
||||
let params = {
|
||||
agedWeight: this._agedWeight,
|
||||
bucketSize: this._bucketSize,
|
||||
expiryDate: 1000 * (Date.now() - this._expireDays * 24 * 60 * 60 * 1000),
|
||||
fieldname: fieldName,
|
||||
maxTimeGroupings: this._maxTimeGroupings,
|
||||
now: Date.now() * 1000, // convert from ms to microseconds
|
||||
timeGroupingSize: this._timeGroupingSize
|
||||
timeGroupingSize: this._timeGroupingSize,
|
||||
prefixWeight: this._prefixWeight,
|
||||
boundaryWeight: this._boundaryWeight
|
||||
}
|
||||
|
||||
// only do substring matching when more than one character is typed
|
||||
let where = ""
|
||||
let boundaryCalc = "";
|
||||
if (searchString.length > 1) {
|
||||
searchTokens = searchString.split(/\s+/);
|
||||
this.stopAutoCompleteSearch();
|
||||
|
||||
// build up the word boundary and prefix match bonus calculation
|
||||
boundaryCalc = "MAX(1, :prefixWeight * (value LIKE :valuePrefix ESCAPE '/') + (";
|
||||
// for each word, calculate word boundary weights for the SELECT clause and
|
||||
// add word to the WHERE clause of the query
|
||||
let tokenCalc = [];
|
||||
for (let i = 0; i < searchTokens.length; i++) {
|
||||
tokenCalc.push("(value LIKE :tokenBegin" + i + " ESCAPE '/') + " +
|
||||
"(value LIKE :tokenBoundary" + i + " ESCAPE '/')");
|
||||
where += "AND (value LIKE :tokenContains" + i + " ESCAPE '/') ";
|
||||
}
|
||||
// add more weight if we have a traditional prefix match and
|
||||
// multiply boundary bonuses by boundary weight
|
||||
boundaryCalc += tokenCalc.join(" + ") + ") * :boundaryWeight)";
|
||||
params.prefixWeight = this._prefixWeight;
|
||||
params.boundaryWeight = this._boundaryWeight;
|
||||
} else if (searchString.length == 1) {
|
||||
where = "AND (value LIKE :valuePrefix ESCAPE '/') ";
|
||||
boundaryCalc = "1";
|
||||
} else {
|
||||
where = "";
|
||||
boundaryCalc = "1";
|
||||
}
|
||||
/* Three factors in the frecency calculation for an entry (in order of use in calculation):
|
||||
* 1) average number of times used - items used more are ranked higher
|
||||
* 2) how recently it was last used - items used recently are ranked higher
|
||||
* 3) additional weight for aged entries surviving expiry - these entries are relevant
|
||||
* since they have been used multiple times over a large time span so rank them higher
|
||||
* The score is then divided by the bucket size and we round the result so that entries
|
||||
* with a very similar frecency are bucketed together with an alphabetical sort. This is
|
||||
* to reduce the amount of moving around by entries while typing.
|
||||
*/
|
||||
let self = this;
|
||||
let processResults = {
|
||||
onSuccess: function(aResults) {
|
||||
self._pendingQuery = null;
|
||||
callback(aResults);
|
||||
},
|
||||
onFailure: function(aError) {
|
||||
self.log("getAutocompleteValues failed: " + aError.message);
|
||||
self._pendingQuery = null;
|
||||
}
|
||||
};
|
||||
|
||||
let query = "/* do not warn (bug 496471): can't use an index */ " +
|
||||
"SELECT value, " +
|
||||
"ROUND( " +
|
||||
"timesUsed / MAX(1.0, (lastUsed - firstUsed) / :timeGroupingSize) * " +
|
||||
"MAX(1.0, :maxTimeGroupings - (:now - lastUsed) / :timeGroupingSize) * "+
|
||||
"MAX(1.0, :agedWeight * (firstUsed < :expiryDate)) / " +
|
||||
":bucketSize "+
|
||||
", 3) AS frecency, " +
|
||||
boundaryCalc + " AS boundaryBonuses " +
|
||||
"FROM moz_formhistory " +
|
||||
"WHERE fieldname=:fieldname " + where +
|
||||
"ORDER BY ROUND(frecency * boundaryBonuses) DESC, UPPER(value) ASC";
|
||||
|
||||
let stmt;
|
||||
try {
|
||||
stmt = this._dbCreateStatement(query, params);
|
||||
|
||||
// Chicken and egg problem: Need the statement to escape the params we
|
||||
// pass to the function that gives us the statement. So, fix it up now.
|
||||
if (searchString.length >= 1)
|
||||
stmt.params.valuePrefix = stmt.escapeStringForLIKE(searchString, "/") + "%";
|
||||
if (searchString.length > 1) {
|
||||
for (let i = 0; i < searchTokens.length; i++) {
|
||||
let escapedToken = stmt.escapeStringForLIKE(searchTokens[i], "/");
|
||||
stmt.params["tokenBegin" + i] = escapedToken + "%";
|
||||
stmt.params["tokenBoundary" + i] = "% " + escapedToken + "%";
|
||||
stmt.params["tokenContains" + i] = "%" + escapedToken + "%";
|
||||
}
|
||||
} else {
|
||||
// no addional params need to be substituted into the query when the
|
||||
// length is zero or one
|
||||
}
|
||||
|
||||
while (stmt.executeStep()) {
|
||||
let entry = {
|
||||
text: stmt.row.value,
|
||||
textLowerCase: stmt.row.value.toLowerCase(),
|
||||
frecency: stmt.row.frecency,
|
||||
totalScore: Math.round(stmt.row.frecency * stmt.row.boundaryBonuses)
|
||||
}
|
||||
values.push(entry);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
this.log("getValues failed: " + e.name + " : " + e.message);
|
||||
throw "DB failed getting form autocomplete values";
|
||||
} finally {
|
||||
if (stmt) {
|
||||
stmt.reset();
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
|
||||
_dbStmts : null,
|
||||
|
||||
_dbCreateStatement : function (query, params) {
|
||||
let stmt = this._dbStmts[query];
|
||||
// Memoize the statements
|
||||
if (!stmt) {
|
||||
this.log("Creating new statement for query: " + query);
|
||||
stmt = this._formHistory.DBConnection.createStatement(query);
|
||||
this._dbStmts[query] = stmt;
|
||||
}
|
||||
// Replace parameters, must be done 1 at a time
|
||||
if (params) {
|
||||
let stmtparams = stmt.params;
|
||||
for (let i in params)
|
||||
stmtparams[i] = params[i];
|
||||
}
|
||||
return stmt;
|
||||
self._pendingQuery = FormHistory.getAutoCompleteResults(searchString, params, processResults);
|
||||
},
|
||||
|
||||
/*
|
||||
@ -422,8 +378,11 @@ FormAutoCompleteResult.prototype = {
|
||||
|
||||
let [removedEntry] = this.entries.splice(index, 1);
|
||||
|
||||
if (removeFromDB)
|
||||
this.formHistory.removeEntry(this.fieldName, removedEntry.text);
|
||||
if (removeFromDB) {
|
||||
this.formHistory.update({ op: "remove",
|
||||
fieldname: this.fieldName,
|
||||
value: removedEntry.text });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -36,11 +36,12 @@
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS5(nsFormFillController,
|
||||
NS_IMPL_ISUPPORTS6(nsFormFillController,
|
||||
nsIFormFillController,
|
||||
nsIAutoCompleteInput,
|
||||
nsIAutoCompleteSearch,
|
||||
nsIDOMEventListener,
|
||||
nsIFormAutoCompleteObserver,
|
||||
nsIMutationObserver)
|
||||
|
||||
nsFormFillController::nsFormFillController() :
|
||||
@ -600,11 +601,15 @@ nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAStrin
|
||||
// XXX aPreviousResult shouldn't ever be a historyResult type, since we're not letting
|
||||
// satchel manage the field?
|
||||
rv = mLoginManager->AutoCompleteSearch(aSearchString,
|
||||
aPreviousResult,
|
||||
mFocusedInput,
|
||||
getter_AddRefs(result));
|
||||
aPreviousResult,
|
||||
mFocusedInput,
|
||||
getter_AddRefs(result));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (aListener) {
|
||||
aListener->OnSearchResult(this, result);
|
||||
}
|
||||
} else {
|
||||
nsCOMPtr<nsIAutoCompleteResult> formHistoryResult;
|
||||
mLastListener = aListener;
|
||||
|
||||
// It appears that mFocusedInput is always null when we are focusing a XUL
|
||||
// element. Scary :)
|
||||
@ -613,48 +618,65 @@ nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAStrin
|
||||
do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = formAutoComplete->AutoCompleteSearch(aSearchParam,
|
||||
formAutoComplete->AutoCompleteSearchAsync(aSearchParam,
|
||||
aSearchString,
|
||||
mFocusedInput,
|
||||
aPreviousResult,
|
||||
getter_AddRefs(formHistoryResult));
|
||||
this);
|
||||
mLastFormAutoComplete = formAutoComplete;
|
||||
} else {
|
||||
mLastSearchString = aSearchString;
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
// Even if autocomplete is disabled, handle the inputlist anyway as that was
|
||||
// specifically requested by the page. This is so a field can have the default
|
||||
// autocomplete disabled and replaced with a custom inputlist autocomplete.
|
||||
return PerformInputListAutoComplete(aPreviousResult);
|
||||
}
|
||||
}
|
||||
|
||||
mLastSearchResult = formHistoryResult;
|
||||
mLastListener = aListener;
|
||||
mLastSearchString = aSearchString;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
|
||||
do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsresult
|
||||
nsFormFillController::PerformInputListAutoComplete(nsIAutoCompleteResult* aPreviousResult)
|
||||
{
|
||||
// If an <input> is focused, check if it has a list="<datalist>" which can
|
||||
// provide the list of suggestions.
|
||||
|
||||
rv = inputListAutoComplete->AutoCompleteSearch(formHistoryResult,
|
||||
aSearchString,
|
||||
mFocusedInput,
|
||||
getter_AddRefs(result));
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIAutoCompleteResult> result;
|
||||
|
||||
if (mFocusedInput) {
|
||||
nsCOMPtr<nsIDOMHTMLElement> list;
|
||||
mFocusedInput->GetList(getter_AddRefs(list));
|
||||
nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
|
||||
do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = inputListAutoComplete->AutoCompleteSearch(aPreviousResult,
|
||||
mLastSearchString,
|
||||
mFocusedInput,
|
||||
getter_AddRefs(result));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsINode> node = do_QueryInterface(list);
|
||||
if (mListNode != node) {
|
||||
if (mListNode) {
|
||||
mListNode->RemoveMutationObserver(this);
|
||||
mListNode = nullptr;
|
||||
}
|
||||
if (node) {
|
||||
node->AddMutationObserverUnlessExists(this);
|
||||
mListNode = node;
|
||||
}
|
||||
if (mFocusedInput) {
|
||||
nsCOMPtr<nsIDOMHTMLElement> list;
|
||||
mFocusedInput->GetList(getter_AddRefs(list));
|
||||
|
||||
// Add a mutation observer to check for changes to the items in the <datalist>
|
||||
// and update the suggestions accordingly.
|
||||
nsCOMPtr<nsINode> node = do_QueryInterface(list);
|
||||
if (mListNode != node) {
|
||||
if (mListNode) {
|
||||
mListNode->RemoveMutationObserver(this);
|
||||
mListNode = nullptr;
|
||||
}
|
||||
if (node) {
|
||||
node->AddMutationObserverUnlessExists(this);
|
||||
mListNode = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
aListener->OnSearchResult(this, result);
|
||||
if (mLastListener) {
|
||||
mLastListener->OnSearchResult(this, result);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@ -707,9 +729,31 @@ void nsFormFillController::RevalidateDataList()
|
||||
NS_IMETHODIMP
|
||||
nsFormFillController::StopSearch()
|
||||
{
|
||||
// Make sure to stop and clear this, otherwise the controller will prevent
|
||||
// mLastFormAutoComplete from being deleted.
|
||||
if (mLastFormAutoComplete) {
|
||||
mLastFormAutoComplete->StopAutoCompleteSearch();
|
||||
mLastFormAutoComplete = nullptr;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//// nsIFormAutoCompleteObserver
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsFormFillController::OnSearchCompletion(nsIAutoCompleteResult *aResult)
|
||||
{
|
||||
nsCOMPtr<nsIAutoCompleteResult> resultParam = do_QueryInterface(aResult);
|
||||
|
||||
nsAutoString searchString;
|
||||
resultParam->GetSearchString(searchString);
|
||||
mLastSearchResult = aResult;
|
||||
mLastSearchString = searchString;
|
||||
|
||||
return PerformInputListAutoComplete(resultParam);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//// nsIDOMEventListener
|
||||
|
||||
@ -1178,4 +1222,3 @@ static const mozilla::Module kSatchelModule = {
|
||||
};
|
||||
|
||||
NSMODULE_DEFN(satchel) = &kSatchelModule;
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "nsIAutoCompleteSearch.h"
|
||||
#include "nsIAutoCompleteController.h"
|
||||
#include "nsIAutoCompletePopup.h"
|
||||
#include "nsIFormAutoComplete.h"
|
||||
#include "nsIDOMEventListener.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsDataHashtable.h"
|
||||
@ -33,6 +34,7 @@ class nsFormFillController : public nsIFormFillController,
|
||||
public nsIAutoCompleteInput,
|
||||
public nsIAutoCompleteSearch,
|
||||
public nsIDOMEventListener,
|
||||
public nsIFormAutoCompleteObserver,
|
||||
public nsIMutationObserver
|
||||
{
|
||||
public:
|
||||
@ -40,6 +42,7 @@ public:
|
||||
NS_DECL_NSIFORMFILLCONTROLLER
|
||||
NS_DECL_NSIAUTOCOMPLETESEARCH
|
||||
NS_DECL_NSIAUTOCOMPLETEINPUT
|
||||
NS_DECL_NSIFORMAUTOCOMPLETEOBSERVER
|
||||
NS_DECL_NSIDOMEVENTLISTENER
|
||||
NS_DECL_NSIMUTATIONOBSERVER
|
||||
|
||||
@ -60,6 +63,8 @@ protected:
|
||||
void StartControllingInput(nsIDOMHTMLInputElement *aInput);
|
||||
void StopControllingInput();
|
||||
|
||||
nsresult PerformInputListAutoComplete(nsIAutoCompleteResult* aPreviousResult);
|
||||
|
||||
void RevalidateDataList();
|
||||
bool RowMatch(nsFormHistory *aHistory, uint32_t aIndex, const nsAString &aInputName, const nsAString &aInputValue);
|
||||
|
||||
@ -79,6 +84,9 @@ protected:
|
||||
nsCOMPtr<nsILoginManager> mLoginManager;
|
||||
nsIDOMHTMLInputElement* mFocusedInput;
|
||||
nsINode* mFocusedInputNode;
|
||||
|
||||
// mListNode is a <datalist> element which, is set, has the form fill controller
|
||||
// as a mutation observer for it.
|
||||
nsINode* mListNode;
|
||||
nsCOMPtr<nsIAutoCompletePopup> mFocusedPopup;
|
||||
|
||||
@ -87,7 +95,13 @@ protected:
|
||||
|
||||
//these are used to dynamically update the autocomplete
|
||||
nsCOMPtr<nsIAutoCompleteResult> mLastSearchResult;
|
||||
|
||||
// The observer passed to StartSearch. It will be notified when the search is
|
||||
// complete or the data from a datalist changes.
|
||||
nsCOMPtr<nsIAutoCompleteObserver> mLastListener;
|
||||
|
||||
// This is cleared by StopSearch().
|
||||
nsCOMPtr<nsIFormAutoComplete> mLastFormAutoComplete;
|
||||
nsString mLastSearchString;
|
||||
|
||||
nsDataHashtable<nsPtrHashKey<const nsINode>, bool> mPwmgrInputs;
|
||||
|
@ -6,17 +6,47 @@
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIAutoCompleteResult;
|
||||
interface nsIFormAutoCompleteObserver;
|
||||
interface nsIDOMHTMLInputElement;
|
||||
|
||||
[scriptable, uuid(997c0c05-5d1d-47e5-9cbc-765c0b8ec699)]
|
||||
[scriptable, uuid(c079f18f-40ab-409d-800e-878889b83b58)]
|
||||
|
||||
interface nsIFormAutoComplete: nsISupports {
|
||||
|
||||
/**
|
||||
* Generate results for a form input autocomplete menu.
|
||||
* Generate results for a form input autocomplete menu synchronously.
|
||||
* This method is deprecated in favour of autoCompleteSearchAsync.
|
||||
*/
|
||||
nsIAutoCompleteResult autoCompleteSearch(
|
||||
in AString aInputName,
|
||||
in AString aSearchString,
|
||||
in nsIDOMHTMLInputElement aField,
|
||||
in nsIAutoCompleteResult aPreviousResult);
|
||||
nsIAutoCompleteResult autoCompleteSearch(in AString aInputName,
|
||||
in AString aSearchString,
|
||||
in nsIDOMHTMLInputElement aField,
|
||||
in nsIAutoCompleteResult aPreviousResult);
|
||||
|
||||
/**
|
||||
* Generate results for a form input autocomplete menu asynchronously.
|
||||
*/
|
||||
void autoCompleteSearchAsync(in AString aInputName,
|
||||
in AString aSearchString,
|
||||
in nsIDOMHTMLInputElement aField,
|
||||
in nsIAutoCompleteResult aPreviousResult,
|
||||
in nsIFormAutoCompleteObserver aListener);
|
||||
|
||||
/**
|
||||
* If a search is in progress, stop it. Otherwise, do nothing. This is used
|
||||
* to cancel an existing search, for example, in preparation for a new search.
|
||||
*/
|
||||
void stopAutoCompleteSearch();
|
||||
};
|
||||
|
||||
[scriptable, function, uuid(604419ab-55a0-4831-9eca-1b9e67cc4751)]
|
||||
interface nsIFormAutoCompleteObserver : nsISupports
|
||||
{
|
||||
/*
|
||||
* Called when a search is complete and the results are ready even if the
|
||||
* result set is empty. If the search is cancelled or a new search is
|
||||
* started, this is not called.
|
||||
*
|
||||
* @param result - The search result object
|
||||
*/
|
||||
void onSearchCompletion(in nsIAutoCompleteResult result);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user