зеркало из https://github.com/mozilla/gecko-dev.git
Bug 566746 - Changes to form autocomplete to support new asynchronous FormHistory.jsm module, p=enndeakin,felix, r=dteller
This commit is contained in:
Родитель
6335d6918a
Коммит
cf435b145a
|
@ -10,6 +10,11 @@ const Cr = Components.results;
|
||||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
Components.utils.import("resource://gre/modules/Services.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() {
|
function FormAutoComplete() {
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
@ -18,14 +23,6 @@ FormAutoComplete.prototype = {
|
||||||
classID : Components.ID("{c11c21b2-71c9-4f87-a0f8-5e13f50495fd}"),
|
classID : Components.ID("{c11c21b2-71c9-4f87-a0f8-5e13f50495fd}"),
|
||||||
QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormAutoComplete, Ci.nsISupportsWeakReference]),
|
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,
|
_prefBranch : null,
|
||||||
_debug : true, // mirrors browser.formfill.debug
|
_debug : true, // mirrors browser.formfill.debug
|
||||||
_enabled : true, // mirrors browser.formfill.enable preference
|
_enabled : true, // mirrors browser.formfill.enable preference
|
||||||
|
@ -37,6 +34,13 @@ FormAutoComplete.prototype = {
|
||||||
_boundaryWeight : 25,
|
_boundaryWeight : 25,
|
||||||
_prefixWeight : 5,
|
_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() {
|
init : function() {
|
||||||
// Preferences. Add observer so we get notified of changes.
|
// Preferences. Add observer so we get notified of changes.
|
||||||
this._prefBranch = Services.prefs.getBranch("browser.formfill.");
|
this._prefBranch = Services.prefs.getBranch("browser.formfill.");
|
||||||
|
@ -50,10 +54,6 @@ FormAutoComplete.prototype = {
|
||||||
this._maxTimeGroupings = this._prefBranch.getIntPref("maxTimeGroupings");
|
this._maxTimeGroupings = this._prefBranch.getIntPref("maxTimeGroupings");
|
||||||
this._timeGroupingSize = this._prefBranch.getIntPref("timeGroupingSize") * 1000 * 1000;
|
this._timeGroupingSize = this._prefBranch.getIntPref("timeGroupingSize") * 1000 * 1000;
|
||||||
this._expireDays = this._prefBranch.getIntPref("expire_days");
|
this._expireDays = this._prefBranch.getIntPref("expire_days");
|
||||||
|
|
||||||
this._dbStmts = {};
|
|
||||||
|
|
||||||
Services.obs.addObserver(this.observer, "profile-before-change", true);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
observer : {
|
observer : {
|
||||||
|
@ -96,12 +96,6 @@ FormAutoComplete.prototype = {
|
||||||
default:
|
default:
|
||||||
self.log("Oops! Pref not handled, change ignored.");
|
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.
|
* aInputName -- |name| attribute from the form input being autocompleted.
|
||||||
* aUntrimmedSearchString -- current value of the input
|
* aUntrimmedSearchString -- current value of the input
|
||||||
* aField -- nsIDOMHTMLInputElement being autocompleted (may be null if from chrome)
|
* aField -- nsIDOMHTMLInputElement being autocompleted (may be null if from chrome)
|
||||||
* aPreviousResult -- previous search result, if any.
|
* aPreviousResult -- previous search result, if any.
|
||||||
*
|
* aListener -- nsIFormAutoCompleteObserver that listens for the nsIAutoCompleteResult
|
||||||
* Returns: an nsIAutoCompleteResult
|
* that may be returned asynchronously.
|
||||||
*/
|
*/
|
||||||
autoCompleteSearch : function (aInputName, aUntrimmedSearchString, aField, aPreviousResult) {
|
_autoCompleteSearchShared : function (aInputName, aUntrimmedSearchString, aField, aPreviousResult, aListener) {
|
||||||
function sortBytotalScore (a, b) {
|
function sortBytotalScore (a, b) {
|
||||||
return b.totalScore - a.totalScore;
|
return b.totalScore - a.totalScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._enabled)
|
let result = null;
|
||||||
return 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
|
// don't allow form inputs (aField != null) to get results from search bar history
|
||||||
if (aInputName == 'searchbar-history' && aField) {
|
if (aInputName == 'searchbar-history' && aField) {
|
||||||
this.log('autoCompleteSearch for input name "' + aInputName + '" is denied');
|
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);
|
this.log("AutoCompleteSearch invoked. Search is: " + aUntrimmedSearchString);
|
||||||
let searchString = aUntrimmedSearchString.trim().toLowerCase();
|
let searchString = aUntrimmedSearchString.trim().toLowerCase();
|
||||||
let result = null;
|
|
||||||
|
|
||||||
// reuse previous results if:
|
// reuse previous results if:
|
||||||
// a) length greater than one character (others searches are special cases) AND
|
// a) length greater than one character (others searches are special cases) AND
|
||||||
|
@ -176,145 +201,76 @@ FormAutoComplete.prototype = {
|
||||||
}
|
}
|
||||||
filteredEntries.sort(sortBytotalScore);
|
filteredEntries.sort(sortBytotalScore);
|
||||||
result.wrappedJSObject.entries = filteredEntries;
|
result.wrappedJSObject.entries = filteredEntries;
|
||||||
|
|
||||||
|
if (aListener) {
|
||||||
|
aListener.onSearchCompletion(result);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.log("Creating new autocomplete search result.");
|
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) {
|
stopAutoCompleteSearch : function () {
|
||||||
let values = [];
|
if (this._pendingQuery) {
|
||||||
let searchTokens;
|
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 = {
|
let params = {
|
||||||
agedWeight: this._agedWeight,
|
agedWeight: this._agedWeight,
|
||||||
bucketSize: this._bucketSize,
|
bucketSize: this._bucketSize,
|
||||||
expiryDate: 1000 * (Date.now() - this._expireDays * 24 * 60 * 60 * 1000),
|
expiryDate: 1000 * (Date.now() - this._expireDays * 24 * 60 * 60 * 1000),
|
||||||
fieldname: fieldName,
|
fieldname: fieldName,
|
||||||
maxTimeGroupings: this._maxTimeGroupings,
|
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
|
this.stopAutoCompleteSearch();
|
||||||
let where = ""
|
|
||||||
let boundaryCalc = "";
|
|
||||||
if (searchString.length > 1) {
|
|
||||||
searchTokens = searchString.split(/\s+/);
|
|
||||||
|
|
||||||
// build up the word boundary and prefix match bonus calculation
|
let self = this;
|
||||||
boundaryCalc = "MAX(1, :prefixWeight * (value LIKE :valuePrefix ESCAPE '/') + (";
|
let processResults = {
|
||||||
// for each word, calculate word boundary weights for the SELECT clause and
|
onSuccess: function(aResults) {
|
||||||
// add word to the WHERE clause of the query
|
self._pendingQuery = null;
|
||||||
let tokenCalc = [];
|
callback(aResults);
|
||||||
for (let i = 0; i < searchTokens.length; i++) {
|
},
|
||||||
tokenCalc.push("(value LIKE :tokenBegin" + i + " ESCAPE '/') + " +
|
onFailure: function(aError) {
|
||||||
"(value LIKE :tokenBoundary" + i + " ESCAPE '/')");
|
self.log("getAutocompleteValues failed: " + aError.message);
|
||||||
where += "AND (value LIKE :tokenContains" + i + " ESCAPE '/') ";
|
self._pendingQuery = null;
|
||||||
}
|
}
|
||||||
// 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 query = "/* do not warn (bug 496471): can't use an index */ " +
|
self._pendingQuery = FormHistory.getAutoCompleteResults(searchString, params, processResults);
|
||||||
"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;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -422,8 +378,11 @@ FormAutoCompleteResult.prototype = {
|
||||||
|
|
||||||
let [removedEntry] = this.entries.splice(index, 1);
|
let [removedEntry] = this.entries.splice(index, 1);
|
||||||
|
|
||||||
if (removeFromDB)
|
if (removeFromDB) {
|
||||||
this.formHistory.removeEntry(this.fieldName, removedEntry.text);
|
this.formHistory.update({ op: "remove",
|
||||||
|
fieldname: this.fieldName,
|
||||||
|
value: removedEntry.text });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -36,11 +36,12 @@
|
||||||
#include "mozilla/dom/Element.h"
|
#include "mozilla/dom/Element.h"
|
||||||
#include "nsContentUtils.h"
|
#include "nsContentUtils.h"
|
||||||
|
|
||||||
NS_IMPL_ISUPPORTS5(nsFormFillController,
|
NS_IMPL_ISUPPORTS6(nsFormFillController,
|
||||||
nsIFormFillController,
|
nsIFormFillController,
|
||||||
nsIAutoCompleteInput,
|
nsIAutoCompleteInput,
|
||||||
nsIAutoCompleteSearch,
|
nsIAutoCompleteSearch,
|
||||||
nsIDOMEventListener,
|
nsIDOMEventListener,
|
||||||
|
nsIFormAutoCompleteObserver,
|
||||||
nsIMutationObserver)
|
nsIMutationObserver)
|
||||||
|
|
||||||
nsFormFillController::nsFormFillController() :
|
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
|
// XXX aPreviousResult shouldn't ever be a historyResult type, since we're not letting
|
||||||
// satchel manage the field?
|
// satchel manage the field?
|
||||||
rv = mLoginManager->AutoCompleteSearch(aSearchString,
|
rv = mLoginManager->AutoCompleteSearch(aSearchString,
|
||||||
aPreviousResult,
|
aPreviousResult,
|
||||||
mFocusedInput,
|
mFocusedInput,
|
||||||
getter_AddRefs(result));
|
getter_AddRefs(result));
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
if (aListener) {
|
||||||
|
aListener->OnSearchResult(this, result);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
nsCOMPtr<nsIAutoCompleteResult> formHistoryResult;
|
mLastListener = aListener;
|
||||||
|
|
||||||
// It appears that mFocusedInput is always null when we are focusing a XUL
|
// It appears that mFocusedInput is always null when we are focusing a XUL
|
||||||
// element. Scary :)
|
// element. Scary :)
|
||||||
|
@ -613,48 +618,65 @@ nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAStrin
|
||||||
do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
|
do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
rv = formAutoComplete->AutoCompleteSearch(aSearchParam,
|
formAutoComplete->AutoCompleteSearchAsync(aSearchParam,
|
||||||
aSearchString,
|
aSearchString,
|
||||||
mFocusedInput,
|
mFocusedInput,
|
||||||
aPreviousResult,
|
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;
|
return NS_OK;
|
||||||
mLastListener = aListener;
|
}
|
||||||
mLastSearchString = aSearchString;
|
|
||||||
|
|
||||||
nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
|
nsresult
|
||||||
do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
|
nsFormFillController::PerformInputListAutoComplete(nsIAutoCompleteResult* aPreviousResult)
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
{
|
||||||
|
// If an <input> is focused, check if it has a list="<datalist>" which can
|
||||||
|
// provide the list of suggestions.
|
||||||
|
|
||||||
rv = inputListAutoComplete->AutoCompleteSearch(formHistoryResult,
|
nsresult rv;
|
||||||
aSearchString,
|
nsCOMPtr<nsIAutoCompleteResult> result;
|
||||||
mFocusedInput,
|
|
||||||
getter_AddRefs(result));
|
|
||||||
|
|
||||||
if (mFocusedInput) {
|
nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
|
||||||
nsCOMPtr<nsIDOMHTMLElement> list;
|
do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
|
||||||
mFocusedInput->GetList(getter_AddRefs(list));
|
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 (mFocusedInput) {
|
||||||
if (mListNode != node) {
|
nsCOMPtr<nsIDOMHTMLElement> list;
|
||||||
if (mListNode) {
|
mFocusedInput->GetList(getter_AddRefs(list));
|
||||||
mListNode->RemoveMutationObserver(this);
|
|
||||||
mListNode = nullptr;
|
// Add a mutation observer to check for changes to the items in the <datalist>
|
||||||
}
|
// and update the suggestions accordingly.
|
||||||
if (node) {
|
nsCOMPtr<nsINode> node = do_QueryInterface(list);
|
||||||
node->AddMutationObserverUnlessExists(this);
|
if (mListNode != node) {
|
||||||
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;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
@ -707,9 +729,31 @@ void nsFormFillController::RevalidateDataList()
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
nsFormFillController::StopSearch()
|
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;
|
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
|
//// nsIDOMEventListener
|
||||||
|
|
||||||
|
@ -1178,4 +1222,3 @@ static const mozilla::Module kSatchelModule = {
|
||||||
};
|
};
|
||||||
|
|
||||||
NSMODULE_DEFN(satchel) = &kSatchelModule;
|
NSMODULE_DEFN(satchel) = &kSatchelModule;
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "nsIAutoCompleteSearch.h"
|
#include "nsIAutoCompleteSearch.h"
|
||||||
#include "nsIAutoCompleteController.h"
|
#include "nsIAutoCompleteController.h"
|
||||||
#include "nsIAutoCompletePopup.h"
|
#include "nsIAutoCompletePopup.h"
|
||||||
|
#include "nsIFormAutoComplete.h"
|
||||||
#include "nsIDOMEventListener.h"
|
#include "nsIDOMEventListener.h"
|
||||||
#include "nsCOMPtr.h"
|
#include "nsCOMPtr.h"
|
||||||
#include "nsDataHashtable.h"
|
#include "nsDataHashtable.h"
|
||||||
|
@ -33,6 +34,7 @@ class nsFormFillController : public nsIFormFillController,
|
||||||
public nsIAutoCompleteInput,
|
public nsIAutoCompleteInput,
|
||||||
public nsIAutoCompleteSearch,
|
public nsIAutoCompleteSearch,
|
||||||
public nsIDOMEventListener,
|
public nsIDOMEventListener,
|
||||||
|
public nsIFormAutoCompleteObserver,
|
||||||
public nsIMutationObserver
|
public nsIMutationObserver
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -40,6 +42,7 @@ public:
|
||||||
NS_DECL_NSIFORMFILLCONTROLLER
|
NS_DECL_NSIFORMFILLCONTROLLER
|
||||||
NS_DECL_NSIAUTOCOMPLETESEARCH
|
NS_DECL_NSIAUTOCOMPLETESEARCH
|
||||||
NS_DECL_NSIAUTOCOMPLETEINPUT
|
NS_DECL_NSIAUTOCOMPLETEINPUT
|
||||||
|
NS_DECL_NSIFORMAUTOCOMPLETEOBSERVER
|
||||||
NS_DECL_NSIDOMEVENTLISTENER
|
NS_DECL_NSIDOMEVENTLISTENER
|
||||||
NS_DECL_NSIMUTATIONOBSERVER
|
NS_DECL_NSIMUTATIONOBSERVER
|
||||||
|
|
||||||
|
@ -60,6 +63,8 @@ protected:
|
||||||
void StartControllingInput(nsIDOMHTMLInputElement *aInput);
|
void StartControllingInput(nsIDOMHTMLInputElement *aInput);
|
||||||
void StopControllingInput();
|
void StopControllingInput();
|
||||||
|
|
||||||
|
nsresult PerformInputListAutoComplete(nsIAutoCompleteResult* aPreviousResult);
|
||||||
|
|
||||||
void RevalidateDataList();
|
void RevalidateDataList();
|
||||||
bool RowMatch(nsFormHistory *aHistory, uint32_t aIndex, const nsAString &aInputName, const nsAString &aInputValue);
|
bool RowMatch(nsFormHistory *aHistory, uint32_t aIndex, const nsAString &aInputName, const nsAString &aInputValue);
|
||||||
|
|
||||||
|
@ -79,6 +84,9 @@ protected:
|
||||||
nsCOMPtr<nsILoginManager> mLoginManager;
|
nsCOMPtr<nsILoginManager> mLoginManager;
|
||||||
nsIDOMHTMLInputElement* mFocusedInput;
|
nsIDOMHTMLInputElement* mFocusedInput;
|
||||||
nsINode* mFocusedInputNode;
|
nsINode* mFocusedInputNode;
|
||||||
|
|
||||||
|
// mListNode is a <datalist> element which, is set, has the form fill controller
|
||||||
|
// as a mutation observer for it.
|
||||||
nsINode* mListNode;
|
nsINode* mListNode;
|
||||||
nsCOMPtr<nsIAutoCompletePopup> mFocusedPopup;
|
nsCOMPtr<nsIAutoCompletePopup> mFocusedPopup;
|
||||||
|
|
||||||
|
@ -87,7 +95,13 @@ protected:
|
||||||
|
|
||||||
//these are used to dynamically update the autocomplete
|
//these are used to dynamically update the autocomplete
|
||||||
nsCOMPtr<nsIAutoCompleteResult> mLastSearchResult;
|
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;
|
nsCOMPtr<nsIAutoCompleteObserver> mLastListener;
|
||||||
|
|
||||||
|
// This is cleared by StopSearch().
|
||||||
|
nsCOMPtr<nsIFormAutoComplete> mLastFormAutoComplete;
|
||||||
nsString mLastSearchString;
|
nsString mLastSearchString;
|
||||||
|
|
||||||
nsDataHashtable<nsPtrHashKey<const nsINode>, bool> mPwmgrInputs;
|
nsDataHashtable<nsPtrHashKey<const nsINode>, bool> mPwmgrInputs;
|
||||||
|
|
|
@ -6,17 +6,47 @@
|
||||||
#include "nsISupports.idl"
|
#include "nsISupports.idl"
|
||||||
|
|
||||||
interface nsIAutoCompleteResult;
|
interface nsIAutoCompleteResult;
|
||||||
|
interface nsIFormAutoCompleteObserver;
|
||||||
interface nsIDOMHTMLInputElement;
|
interface nsIDOMHTMLInputElement;
|
||||||
|
|
||||||
[scriptable, uuid(997c0c05-5d1d-47e5-9cbc-765c0b8ec699)]
|
[scriptable, uuid(c079f18f-40ab-409d-800e-878889b83b58)]
|
||||||
|
|
||||||
interface nsIFormAutoComplete: nsISupports {
|
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(
|
nsIAutoCompleteResult autoCompleteSearch(in AString aInputName,
|
||||||
in AString aInputName,
|
in AString aSearchString,
|
||||||
in AString aSearchString,
|
in nsIDOMHTMLInputElement aField,
|
||||||
in nsIDOMHTMLInputElement aField,
|
in nsIAutoCompleteResult aPreviousResult);
|
||||||
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);
|
||||||
};
|
};
|
||||||
|
|
Загрузка…
Ссылка в новой задаче