mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 13:51:41 +00:00
Bug 446247 - Autocomplete should match any part of the string. r=dolske
This commit is contained in:
parent
240a6d45f0
commit
5b85b7cf95
@ -2730,9 +2730,11 @@ pref("signon.debug", false); // logs to Error Console
|
||||
pref("browser.formfill.debug", false);
|
||||
pref("browser.formfill.enable", true);
|
||||
pref("browser.formfill.agedWeight", 2);
|
||||
pref("browser.formfill.bucketSize", 5);
|
||||
pref("browser.formfill.bucketSize", 1);
|
||||
pref("browser.formfill.maxTimeGroupings", 25);
|
||||
pref("browser.formfill.timeGroupingSize", 604800);
|
||||
pref("browser.formfill.boundaryWeight", 25);
|
||||
pref("browser.formfill.prefixWeight", 5);
|
||||
|
||||
// Zoom prefs
|
||||
pref("browser.zoom.full", false);
|
||||
|
@ -75,14 +75,16 @@ FormAutoComplete.prototype = {
|
||||
return this.__observerService;
|
||||
},
|
||||
|
||||
_prefBranch : null,
|
||||
_debug : false, // mirrors browser.formfill.debug
|
||||
_enabled : true, // mirrors browser.formfill.enable preference
|
||||
_agedWeight : 2,
|
||||
_bucketSize : 5,
|
||||
_maxTimeGroupings : 25,
|
||||
_timeGroupingSize : 7 * 24 * 60 * 60 * 1000 * 1000,
|
||||
_expireDays : null,
|
||||
_prefBranch : null,
|
||||
_debug : false, // mirrors browser.formfill.debug
|
||||
_enabled : true, // mirrors browser.formfill.enable preference
|
||||
_agedWeight : 2,
|
||||
_bucketSize : 1,
|
||||
_maxTimeGroupings : 25,
|
||||
_timeGroupingSize : 7 * 24 * 60 * 60 * 1000 * 1000,
|
||||
_expireDays : null,
|
||||
_boundaryWeight : 25,
|
||||
_prefixWeight : 5,
|
||||
|
||||
init : function() {
|
||||
// Preferences. Add observer so we get notified of changes.
|
||||
@ -136,6 +138,12 @@ FormAutoComplete.prototype = {
|
||||
case "bucketSize":
|
||||
self._bucketSize = self._prefBranch.getIntPref(prefName);
|
||||
break;
|
||||
case "boundaryWeight":
|
||||
self._boundaryWeight = self._prefBranch.getIntPref(prefName);
|
||||
break;
|
||||
case "prefixWeight":
|
||||
self._prefixWeight = self._prefBranch.getIntPref(prefName);
|
||||
break;
|
||||
default:
|
||||
self.log("Oops! Pref not handled, change ignored.");
|
||||
}
|
||||
@ -164,46 +172,56 @@ FormAutoComplete.prototype = {
|
||||
* autoCompleteSearch
|
||||
*
|
||||
* aInputName -- |name| attribute from the form input being autocompleted.
|
||||
* aSearchString -- current value of the input
|
||||
* aUntrimmedSearchString -- current value of the input
|
||||
* aPreviousResult -- previous search result, if any.
|
||||
*
|
||||
* Returns: an nsIAutoCompleteResult
|
||||
*/
|
||||
autoCompleteSearch : function (aInputName, aSearchString, aPreviousResult) {
|
||||
autoCompleteSearch : function (aInputName, aUntrimmedSearchString, aPreviousResult) {
|
||||
function sortBytotalScore (a, b) {
|
||||
let x = a.totalScore;
|
||||
let y = b.totalScore;
|
||||
return ((x > y) ? -1 : ((x < y) ? 1 : 0));
|
||||
}
|
||||
|
||||
if (!this._enabled)
|
||||
return null;
|
||||
|
||||
this.log("AutoCompleteSearch invoked. Search is: " + aSearchString);
|
||||
|
||||
this.log("AutoCompleteSearch invoked. Search is: " + aUntrimmedSearchString);
|
||||
let searchString = aUntrimmedSearchString.trim().toLowerCase();
|
||||
let result = null;
|
||||
|
||||
if (aPreviousResult &&
|
||||
aSearchString.substr(0, aPreviousResult.searchString.length) == aPreviousResult.searchString) {
|
||||
// reuse previous results if:
|
||||
// a) length greater than one character (others searches are special cases) AND
|
||||
// b) the the new results will be a subset of the previous results
|
||||
if (aPreviousResult && aPreviousResult.searchString.trim().length > 1 &&
|
||||
searchString.indexOf(aPreviousResult.searchString.trim().toLowerCase()) >= 0) {
|
||||
this.log("Using previous autocomplete result");
|
||||
result = aPreviousResult;
|
||||
result.wrappedJSObject.searchString = aSearchString;
|
||||
result.wrappedJSObject.searchString = aUntrimmedSearchString;
|
||||
|
||||
let searchTokens = searchString.split(/\s+/);
|
||||
// We have a list of results for a shorter search string, so just
|
||||
// filter them further based on the new search string.
|
||||
// Count backwards, because result.matchCount is decremented
|
||||
// when we remove an entry.
|
||||
for (let i = result.matchCount - 1; i >= 0; i--) {
|
||||
let match = result.getValueAt(i);
|
||||
|
||||
// Remove results that are too short, or have different prefix.
|
||||
// filter them further based on the new search string and add to a new array.
|
||||
let entries = result.wrappedJSObject.entries;
|
||||
let filteredEntries = [];
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
let entry = entries[i];
|
||||
// Remove results that do not contain the token
|
||||
// XXX bug 394604 -- .toLowerCase can be wrong for some intl chars
|
||||
if (aSearchString.length > match.length ||
|
||||
aSearchString.toLowerCase() !=
|
||||
match.substr(0, aSearchString.length).toLowerCase())
|
||||
{
|
||||
this.log("Removing autocomplete entry '" + match + "'");
|
||||
result.removeValueAt(i, false);
|
||||
}
|
||||
if(searchTokens.some(function (tok) entry.textLowerCase.indexOf(tok) < 0))
|
||||
continue;
|
||||
this._calculateScore(entry, searchString, searchTokens);
|
||||
this.log("Reusing autocomplete entry '" + entry.text +
|
||||
"' (" + entry.frecency +" / " + entry.totalScore + ")");
|
||||
filteredEntries.push(entry);
|
||||
}
|
||||
filteredEntries.sort(sortBytotalScore);
|
||||
result.wrappedJSObject.entries = filteredEntries;
|
||||
} else {
|
||||
this.log("Creating new autocomplete search result.");
|
||||
let entries = this.getAutoCompleteValues(aInputName, aSearchString);
|
||||
result = new FormAutoCompleteResult(this._formHistory, entries, aInputName, aSearchString);
|
||||
let entries = this.getAutoCompleteValues(aInputName, searchString);
|
||||
result = new FormAutoCompleteResult(this._formHistory, entries, aInputName, aUntrimmedSearchString);
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -211,6 +229,46 @@ FormAutoComplete.prototype = {
|
||||
|
||||
getAutoCompleteValues : function (fieldName, searchString) {
|
||||
let values = [];
|
||||
let searchTokens;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// only do substring matching when more than one character is typed
|
||||
let where = ""
|
||||
let boundaryCalc = "";
|
||||
if (searchString.length > 1) {
|
||||
searchTokens = searchString.split(/\s+/);
|
||||
|
||||
// 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
|
||||
@ -220,27 +278,18 @@ FormAutoComplete.prototype = {
|
||||
* 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 = "SELECT value, " +
|
||||
"ROUND( " +
|
||||
"timesUsed / MAX(1.0, (lastUsed - firstUsed) / :timeGroupingSize) * " +
|
||||
"MAX(1.0, :maxTimeGroupings - (:now - lastUsed) / :timeGroupingSize) * "+
|
||||
"MAX(1.0, :agedWeight * (firstUsed < :expiryDate)) / " +
|
||||
":bucketSize "+
|
||||
") AS frecency " +
|
||||
", 3) AS frecency, " +
|
||||
boundaryCalc + " AS boundaryBonuses " +
|
||||
"FROM moz_formhistory " +
|
||||
"WHERE fieldname=:fieldname AND value LIKE :valuePrefix ESCAPE '/' " +
|
||||
"ORDER BY frecency DESC, UPPER(value) ASC";
|
||||
|
||||
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,
|
||||
valuePrefix: null // set below...
|
||||
}
|
||||
"WHERE fieldname=:fieldname " + where +
|
||||
"ORDER BY ROUND(frecency * boundaryBonuses) DESC, UPPER(value) ASC";
|
||||
|
||||
let stmt;
|
||||
try {
|
||||
@ -248,10 +297,29 @@ FormAutoComplete.prototype = {
|
||||
|
||||
// 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.
|
||||
stmt.params.valuePrefix = stmt.escapeStringForLIKE(searchString, "/") + "%";
|
||||
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.step())
|
||||
values.push(stmt.row.value);
|
||||
while (stmt.step()) {
|
||||
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);
|
||||
@ -290,6 +358,31 @@ FormAutoComplete.prototype = {
|
||||
return prefsBranch.getIntPref("browser.formfill.expire_days");
|
||||
else
|
||||
return prefsBranch.getIntPref("browser.history_expire_days");
|
||||
},
|
||||
|
||||
/*
|
||||
* _calculateScore
|
||||
*
|
||||
* entry -- an nsIAutoCompleteResult entry
|
||||
* aSearchString -- current value of the input (lowercase)
|
||||
* searchTokens -- array of tokens of the search string
|
||||
*
|
||||
* Returns: an int
|
||||
*/
|
||||
_calculateScore : function (entry, aSearchString, searchTokens) {
|
||||
let boundaryCalc = 0;
|
||||
// for each word, calculate word boundary weights
|
||||
for each (let token in searchTokens) {
|
||||
boundaryCalc += (entry.textLowerCase.indexOf(token) == 0);
|
||||
boundaryCalc += (entry.textLowerCase.indexOf(" " + token) >= 0);
|
||||
}
|
||||
boundaryCalc = boundaryCalc * this._boundaryWeight;
|
||||
// now add more weight if we have a traditional prefix match and
|
||||
// multiply boundary bonuses by boundary weight
|
||||
boundaryCalc += this._prefixWeight *
|
||||
(entry.textLowerCase.
|
||||
indexOf(aSearchString) == 0);
|
||||
entry.totalScore = Math.round(entry.frecency * Math.max(1, boundaryCalc));
|
||||
}
|
||||
|
||||
}; // end of FormAutoComplete implementation
|
||||
@ -303,11 +396,6 @@ function FormAutoCompleteResult (formHistory, entries, fieldName, searchString)
|
||||
this.entries = entries;
|
||||
this.fieldName = fieldName;
|
||||
this.searchString = searchString;
|
||||
|
||||
if (entries.length > 0) {
|
||||
this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
|
||||
this.defaultIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
FormAutoCompleteResult.prototype = {
|
||||
@ -332,16 +420,25 @@ FormAutoCompleteResult.prototype = {
|
||||
|
||||
// Interfaces from idl...
|
||||
searchString : null,
|
||||
searchResult : Ci.nsIAutoCompleteResult.RESULT_NOMATCH,
|
||||
defaultIndex : -1,
|
||||
errorDescription : "",
|
||||
get defaultIndex() {
|
||||
if (entries.length == 0)
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
},
|
||||
get searchResult() {
|
||||
if (this.entries.length == 0)
|
||||
return Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
|
||||
return Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
|
||||
},
|
||||
get matchCount() {
|
||||
return this.entries.length;
|
||||
},
|
||||
|
||||
getValueAt : function (index) {
|
||||
this._checkIndexBounds(index);
|
||||
return this.entries[index];
|
||||
return this.entries[index].text;
|
||||
},
|
||||
|
||||
getCommentAt : function (index) {
|
||||
@ -364,11 +461,8 @@ FormAutoCompleteResult.prototype = {
|
||||
|
||||
let [removedEntry] = this.entries.splice(index, 1);
|
||||
|
||||
if (this.defaultIndex > this.entries.length)
|
||||
this.defaultIndex--;
|
||||
|
||||
if (removeFromDB)
|
||||
this.formHistory.removeEntry(this.fieldName, removedEntry);
|
||||
this.formHistory.removeEntry(this.fieldName, removedEntry.text);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -45,6 +45,12 @@ Form History test: form field autocomplete
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
<!-- normal form for testing word boundary filtering -->
|
||||
<form id="form6" onsubmit="return false;">
|
||||
<input type="text" name="field4">
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
@ -74,6 +80,10 @@ fh.addEntry("field3", "aaz");
|
||||
fh.addEntry("field3", "aa\xe6"); // 0xae == latin ae pair (0xc6 == AE)
|
||||
fh.addEntry("field3", "az");
|
||||
fh.addEntry("field3", "z");
|
||||
fh.addEntry("field4", "a\xe6");
|
||||
fh.addEntry("field4", "aa a\xe6");
|
||||
fh.addEntry("field4", "aba\xe6");
|
||||
fh.addEntry("field4", "bc d\xe6");
|
||||
|
||||
// Restore the form to the default state.
|
||||
function restoreForm() {
|
||||
@ -436,7 +446,7 @@ function runTest(testNum) {
|
||||
break;
|
||||
|
||||
case 205:
|
||||
checkMenuEntries(["az"]);
|
||||
ok(getMenuEntries().length > 0, "checking typing in middle of text");
|
||||
doKey("left");
|
||||
sendChar("a", input);
|
||||
break;
|
||||
@ -460,6 +470,57 @@ function runTest(testNum) {
|
||||
checkMenuEntries([]);
|
||||
doKey("escape");
|
||||
|
||||
// Look at form 6, try to trigger autocomplete popup
|
||||
input = $_(6, "field4");
|
||||
restoreForm();
|
||||
testNum = 249;
|
||||
sendChar("a", input);
|
||||
break;
|
||||
|
||||
/* Test substring matches and word boundary bonuses */
|
||||
|
||||
case 250:
|
||||
// alphabetical results for first character
|
||||
checkMenuEntries(["aa a\xe6", "aba\xe6", "a\xe6"]);
|
||||
sendChar("\xc6", input);
|
||||
break;
|
||||
|
||||
case 251:
|
||||
// prefix match comes first, then word boundary match
|
||||
// followed by substring match
|
||||
checkMenuEntries(["a\xe6", "aa a\xe6", "aba\xe6"]);
|
||||
|
||||
restoreForm();
|
||||
sendChar("b", input);
|
||||
break;
|
||||
|
||||
case 252:
|
||||
checkMenuEntries(["bc d\xe6"]);
|
||||
sendChar(" ", input);
|
||||
break;
|
||||
|
||||
case 253:
|
||||
// check that trailing space has no effect after single char.
|
||||
checkMenuEntries(["bc d\xe6"]);
|
||||
sendChar("\xc6", input);
|
||||
break;
|
||||
|
||||
case 254:
|
||||
// check multi-word substring matches
|
||||
checkMenuEntries(["bc d\xe6", "aba\xe6"]);
|
||||
doKey("left");
|
||||
sendChar("d", input);
|
||||
break;
|
||||
|
||||
case 255:
|
||||
// check inserting in multi-word searches
|
||||
checkMenuEntries(["bc d\xe6"]);
|
||||
sendChar("z", input);
|
||||
break;
|
||||
|
||||
case 256:
|
||||
checkMenuEntries([]);
|
||||
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
|
||||
|
BIN
toolkit/components/satchel/test/unit/formhistory_1000.sqlite
Normal file
BIN
toolkit/components/satchel/test/unit/formhistory_1000.sqlite
Normal file
Binary file not shown.
173
toolkit/components/satchel/test/unit/perf_autocomplete.js
Normal file
173
toolkit/components/satchel/test/unit/perf_autocomplete.js
Normal file
@ -0,0 +1,173 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Satchel Test Code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Corporation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Matthew Noorenberghe <mnoorenberghe@mozilla.com> (Original Author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
var testnum = 0;
|
||||
var fh;
|
||||
var fac;
|
||||
var prefs;
|
||||
|
||||
function countAllEntries() {
|
||||
let stmt = fh.DBConnection.createStatement("SELECT COUNT(*) as numEntries FROM moz_formhistory");
|
||||
do_check_true(stmt.step());
|
||||
let numEntries = stmt.row.numEntries;
|
||||
stmt.finalize();
|
||||
return numEntries;
|
||||
}
|
||||
|
||||
function do_AC_search(searchTerm, previousResult) {
|
||||
var duration = 0;
|
||||
var searchCount = 5;
|
||||
var tempPrevious = null;
|
||||
var startTime;
|
||||
for (var i = 0; i < searchCount; i++) {
|
||||
if (previousResult !== null)
|
||||
tempPrevious = fac.autoCompleteSearch("searchbar-history", previousResult, null, null);
|
||||
startTime = Date.now();
|
||||
results = fac.autoCompleteSearch("searchbar-history", searchTerm, null, tempPrevious);
|
||||
duration += Date.now() - startTime;
|
||||
}
|
||||
dump("[autoCompleteSearch][test " + testnum + "] for '" + searchTerm + "' ");
|
||||
if (previousResult !== null)
|
||||
dump("with '" + previousResult + "' previous result ");
|
||||
else
|
||||
dump("w/o previous result ");
|
||||
dump("took " + duration + " ms with " + results.matchCount + " matches. ");
|
||||
dump("Average of " + Math.round(duration / searchCount) + " ms per search\n");
|
||||
return results;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
try {
|
||||
|
||||
// ===== test init =====
|
||||
var testfile = do_get_file("formhistory_1000.sqlite");
|
||||
var profileDir = dirSvc.get("ProfD", Ci.nsIFile);
|
||||
var results;
|
||||
|
||||
// Cleanup from any previous tests or failures.
|
||||
var destFile = profileDir.clone();
|
||||
destFile.append("formhistory.sqlite");
|
||||
if (destFile.exists())
|
||||
destFile.remove(false);
|
||||
|
||||
testfile.copyTo(profileDir, "formhistory.sqlite");
|
||||
|
||||
fh = Cc["@mozilla.org/satchel/form-history;1"].
|
||||
getService(Ci.nsIFormHistory2);
|
||||
fac = Cc["@mozilla.org/satchel/form-autocomplete;1"].
|
||||
getService(Ci.nsIFormAutoComplete);
|
||||
prefs = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefBranch);
|
||||
|
||||
timeGroupingSize = prefs.getIntPref("browser.formfill.timeGroupingSize") * 1000 * 1000;
|
||||
maxTimeGroupings = prefs.getIntPref("browser.formfill.maxTimeGroupings");
|
||||
bucketSize = prefs.getIntPref("browser.formfill.bucketSize");
|
||||
|
||||
// ===== 1 =====
|
||||
// Check initial state is as expected
|
||||
testnum++;
|
||||
do_check_true(fh.hasEntries);
|
||||
do_check_eq(1000, countAllEntries());
|
||||
fac.autoCompleteSearch("searchbar-history", "zzzzzzzzzz", null, null); // warm-up search
|
||||
do_check_true(fh.nameExists("searchbar-history"));
|
||||
|
||||
// ===== 2 =====
|
||||
// Search for '' with no previous result
|
||||
testnum++;
|
||||
results = do_AC_search("", null);
|
||||
do_check_true(results.matchCount > 0);
|
||||
|
||||
// ===== 3 =====
|
||||
// Search for 'r' with no previous result
|
||||
testnum++;
|
||||
results = do_AC_search("r", null);
|
||||
do_check_true(results.matchCount > 0);
|
||||
|
||||
// ===== 4 =====
|
||||
// Search for 'r' with '' previous result
|
||||
testnum++;
|
||||
results = do_AC_search("r", "");
|
||||
do_check_true(results.matchCount > 0);
|
||||
|
||||
// ===== 5 =====
|
||||
// Search for 're' with no previous result
|
||||
testnum++;
|
||||
results = do_AC_search("re", null);
|
||||
do_check_true(results.matchCount > 0);
|
||||
|
||||
// ===== 6 =====
|
||||
// Search for 're' with 'r' previous result
|
||||
testnum++;
|
||||
results = do_AC_search("re", "r");
|
||||
do_check_true(results.matchCount > 0);
|
||||
|
||||
// ===== 7 =====
|
||||
// Search for 'rea' without previous result
|
||||
testnum++;
|
||||
results = do_AC_search("rea", null);
|
||||
let countREA = results.matchCount;
|
||||
|
||||
// ===== 8 =====
|
||||
// Search for 'rea' with 're' previous result
|
||||
testnum++;
|
||||
results = do_AC_search("rea", "re");
|
||||
do_check_eq(countREA, results.matchCount);
|
||||
|
||||
// ===== 9 =====
|
||||
// Search for 'real' with 'rea' previous result
|
||||
testnum++;
|
||||
results = do_AC_search("real", "rea");
|
||||
let countREAL = results.matchCount;
|
||||
do_check_true(results.matchCount <= countREA);
|
||||
|
||||
// ===== 10 =====
|
||||
// Search for 'real' with 're' previous result
|
||||
testnum++;
|
||||
results = do_AC_search("real", "re");
|
||||
do_check_eq(countREAL, results.matchCount);
|
||||
|
||||
// ===== 11 =====
|
||||
// Search for 'real' with no previous result
|
||||
testnum++;
|
||||
results = do_AC_search("real", null);
|
||||
do_check_eq(countREAL, results.matchCount);
|
||||
|
||||
|
||||
} catch (e) {
|
||||
throw "FAILED in test #" + testnum + " -- " + e;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user