Bug 566489 - Enable inline autocomplete again, but make it smarter.

Original patch by Michael Ventnor, further improved by David Dahl <ddahl@mozilla.com>.
r=mak
This commit is contained in:
Michael Ventnor 2012-01-19 12:31:24 +01:00
parent ce713fe857
commit 5c706d5982
7 changed files with 522 additions and 141 deletions

View File

@ -281,7 +281,7 @@ pref("browser.urlbar.doubleClickSelectsAll", true);
#else
pref("browser.urlbar.doubleClickSelectsAll", false);
#endif
pref("browser.urlbar.autoFill", false);
pref("browser.urlbar.autoFill", true);
// 0: Match anywhere (e.g., middle of words)
// 1: Match on word boundaries and then try matching anywhere
// 2: Match only on word boundaries (e.g., after / or .)
@ -294,7 +294,7 @@ pref("browser.urlbar.maxRichResults", 12);
// The amount of time (ms) to wait after the user has stopped typing
// before starting to perform autocomplete. 50 is the default set in
// autocomplete.xml.
pref("browser.urlbar.delay", 50);
pref("browser.urlbar.delay", 0);
// The special characters below can be typed into the urlbar to either restrict
// the search to visited history, bookmarked, tagged pages; or force a match on

View File

@ -502,7 +502,7 @@
<textbox id="urlbar" flex="1"
placeholder="&urlbar.placeholder;"
type="autocomplete"
autocompletesearch="history"
autocompletesearch="urlinline history"
autocompletesearchparam="enable-actions"
autocompletepopup="PopupAutoCompleteRichResult"
completeselectedindex="true"

View File

@ -69,7 +69,7 @@
<hbox align="center">
<textbox id="dialog.input" flex="1" type="autocomplete"
completeselectedindex="true"
autocompletesearch="history"
autocompletesearch="urlinline history"
enablehistory="true"
class="uri-element"
oninput="doEnabling();"/>

View File

@ -1098,6 +1098,12 @@ nsAutoCompleteController::StartSearchTimer()
PRUint32 timeout;
mInput->GetTimeout(&timeout);
if (timeout == 0) {
// The consumer wants to execute the search synchronously
StartSearch();
return NS_OK;
}
nsresult rv;
mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
if (NS_FAILED(rv))

View File

@ -94,13 +94,23 @@ const kQueryIndexOpenPageCount = 10;
const kQueryTypeKeyword = 0;
const kQueryTypeFiltered = 1;
// Autocomplete minimum time before query is executed
const kAsyncQueriesWaitTime = 50;
// This separator is used as an RTL-friendly way to split the title and tags.
// It can also be used by an nsIAutoCompleteResult consumer to re-split the
// "comment" back into the title and the tag.
const kTitleTagsSeparator = " \u2013 ";
const kBrowserUrlbarBranch = "browser.urlbar.";
const kBrowserUrlbarAutofillPref = "browser.urlbar.autoFill";
////////////////////////////////////////////////////////////////////////////////
//// Globals
XPCOMUtils.defineLazyServiceGetter(this, "gTextURIService",
"@mozilla.org/intl/texttosuburi;1",
"nsITextToSubURI");
////////////////////////////////////////////////////////////////////////////////
//// Helpers
@ -139,6 +149,67 @@ function initTempTable(aDatabase)
stmt.finalize();
}
/**
* Used to unescape encoded URI strings, and drop information that we do not
* care about for searching.
*
* @param aURIString
* The text to unescape and modify.
* @return the modified uri.
*/
function fixupSearchText(aURIString)
{
let uri = aURIString;
if (uri.indexOf("http://") == 0) {
uri = uri.slice(7);
}
else if (uri.indexOf("https://") == 0) {
uri = uri.slice(8);
}
else if (uri.indexOf("ftp://") == 0) {
uri = uri.slice(6);
}
if (uri.indexOf("www.") == 0) {
uri = uri.slice(4);
}
return gTextURIService.unEscapeURIForUI("UTF-8", uri);
}
/**
* safePrefGetter get the pref with typo safety.
* This will return the default value provided if no pref is set.
*
* @param aPrefBranch
* The nsIPrefBranch2 containing the required preference
* @param aName
* A preference name
* @param aDefault
* The preference's default value
* @return the preference value or provided default
*/
function safePrefGetter(aPrefBranch, aName, aDefault) {
let types = {
boolean: "Bool",
number: "Int",
string: "Char"
};
let type = types[typeof(aDefault)];
if (!type) {
throw "Unknown type!";
}
// If the pref isn't set, we want to use the default.
try {
return aPrefBranch["get" + type + "Pref"](aName);
}
catch (e) {
return aDefault;
}
}
////////////////////////////////////////////////////////////////////////////////
//// AutoCompleteStatementCallbackWrapper class
@ -178,8 +249,9 @@ AutoCompleteStatementCallbackWrapper.prototype = {
// Only dispatch handleCompletion if we are not done searching and are a
// pending search.
let callback = this._callback;
if (!callback.isSearchComplete() && callback.isPendingSearch(this._handle))
if (!callback.isSearchComplete() && callback.isPendingSearch(this._handle)) {
callback.handleCompletion.apply(callback, arguments);
}
},
//////////////////////////////////////////////////////////////////////////////
@ -275,10 +347,6 @@ function nsPlacesAutoComplete()
"@mozilla.org/browser/global-history;2",
"nsIBrowserHistory");
XPCOMUtils.defineLazyServiceGetter(this, "_textURIService",
"@mozilla.org/intl/texttosuburi;1",
"nsITextToSubURI");
XPCOMUtils.defineLazyServiceGetter(this, "_bs",
"@mozilla.org/browser/nav-bookmarks-service;1",
"nsINavBookmarksService");
@ -440,64 +508,20 @@ nsPlacesAutoComplete.prototype = {
startSearch: function PAC_startSearch(aSearchString, aSearchParam,
aPreviousResult, aListener)
{
// If a previous query is running and the controller has not taken care
// of stopping it, kill it.
if ("_pendingQuery" in this)
this.stopSearch();
// Stop the search in case the controller has not taken care of it.
this.stopSearch();
// Note: We don't use aPreviousResult to make sure ordering of results are
// consistent. See bug 412730 for more details.
// We want to store the original string with no leading or trailing
// whitespace for case sensitive searches.
this._originalSearchString = aSearchString.trim();
this._currentSearchString =
this._fixupSearchText(this._originalSearchString.toLowerCase());
var searchParamParts = aSearchParam.split(" ");
this._enableActions = searchParamParts.indexOf("enable-actions") != -1;
this._listener = aListener;
let result = Cc["@mozilla.org/autocomplete/simple-result;1"].
createInstance(Ci.nsIAutoCompleteSimpleResult);
result.setSearchString(aSearchString);
result.setListener(this);
this._result = result;
// If we are not enabled, we need to return now.
if (!this._enabled) {
this._finishSearch(true);
return;
}
// Reset our search behavior to the default.
if (this._currentSearchString)
this._behavior = this._defaultBehavior;
else
this._behavior = this._emptySearchDefaultBehavior;
// For any given search, we run up to four queries:
// 1) keywords (this._keywordQuery)
// 2) adaptive learning (this._adaptiveQuery)
// 3) open pages not supported by history (this._openPagesQuery)
// 4) query from this._getSearch
// (1) only gets ran if we get any filtered tokens from this._getSearch,
// since if there are no tokens, there is nothing to match, so there is no
// reason to run the query).
let {query, tokens} =
this._getSearch(this._getUnfilteredSearchTokens(this._currentSearchString));
let queries = tokens.length ?
[this._getBoundKeywordQuery(tokens), this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(tokens), query] :
[this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(tokens), query];
// Start executing our queries.
this._telemetryStartTime = Date.now();
this._executeQueries(queries);
// Set up our persistent state for the duration of the search.
this._searchTokens = tokens;
this._usedPlaces = {};
// Unlike urlInlineComplete, we don't want this search to start
// synchronously. Wait kAsyncQueriesWaitTime before launching the query.
this._startTimer = Cc["@mozilla.org/timer;1"]
.createInstance(Ci.nsITimer);
let timerCallback = (function() {
this._doStartSearch(aSearchString, aSearchParam,
aPreviousResult, aListener);
}).bind(this);
this._startTimer.initWithCallback(timerCallback,
kAsyncQueriesWaitTime,
Ci.nsITimer.TYPE_ONE_SHOT);
},
stopSearch: function PAC_stopSearch()
@ -505,10 +529,16 @@ nsPlacesAutoComplete.prototype = {
// We need to cancel our searches so we do not get any [more] results.
// However, it's possible we haven't actually started any searches, so this
// method may throw because this._pendingQuery may be undefined.
if (this._pendingQuery)
if (this._pendingQuery) {
this._stopActiveQuery();
}
this._finishSearch(false);
if (this._startTimer) {
this._startTimer.cancel();
delete this._startTimer;
}
},
//////////////////////////////////////////////////////////////////////////////
@ -516,8 +546,9 @@ nsPlacesAutoComplete.prototype = {
onValueRemoved: function PAC_onValueRemoved(aResult, aURISpec, aRemoveFromDB)
{
if (aRemoveFromDB)
if (aRemoveFromDB) {
this._bh.removePage(this._ioService.newURI(aURISpec, null, null));
}
},
//////////////////////////////////////////////////////////////////////////////
@ -575,8 +606,9 @@ nsPlacesAutoComplete.prototype = {
}
// Notify about results if we've gotten them.
if (haveMatches)
if (haveMatches) {
this._notifyResults(true);
}
},
handleError: function PAC_handleError(aError)
@ -588,8 +620,9 @@ nsPlacesAutoComplete.prototype = {
handleCompletion: function PAC_handleCompletion(aReason)
{
// If we have already finished our search, we should bail out early.
if (this.isSearchComplete())
if (this.isSearchComplete()) {
return;
}
// If we do not have enough results, and our match type is
// MATCH_BOUNDARY_ANYWHERE, search again with MATCH_ANYWHERE to get more
@ -656,29 +689,66 @@ nsPlacesAutoComplete.prototype = {
get _databaseInitialized()
Object.getOwnPropertyDescriptor(this, "_db").value !== undefined,
/**
* Used to unescape encoded URI strings, and drop information that we do not
* care about for searching.
*
* @param aURIString
* The text to unescape and modify.
* @return the modified uri.
*/
_fixupSearchText: function PAC_fixupSearchText(aURIString)
_doStartSearch: function PAC_doStartSearch(aSearchString, aSearchParam,
aPreviousResult, aListener)
{
let uri = aURIString;
this._startTimer.cancel();
delete this._startTimer;
if (uri.indexOf("http://") == 0)
uri = uri.slice(7);
else if (uri.indexOf("https://") == 0)
uri = uri.slice(8);
else if (uri.indexOf("ftp://") == 0)
uri = uri.slice(6);
// Note: We don't use aPreviousResult to make sure ordering of results are
// consistent. See bug 412730 for more details.
if (uri.indexOf("www.") == 0)
uri = uri.slice(4);
// We want to store the original string with no leading or trailing
// whitespace for case sensitive searches.
this._originalSearchString = aSearchString.trim();
return this._textURIService.unEscapeURIForUI("UTF-8", uri);
this._currentSearchString =
fixupSearchText(this._originalSearchString.toLowerCase());
let searchParamParts = aSearchParam.split(" ");
this._enableActions = searchParamParts.indexOf("enable-actions") != -1;
this._listener = aListener;
let result = Cc["@mozilla.org/autocomplete/simple-result;1"].
createInstance(Ci.nsIAutoCompleteSimpleResult);
result.setSearchString(aSearchString);
result.setListener(this);
this._result = result;
// If we are not enabled, we need to return now.
if (!this._enabled) {
this._finishSearch(true);
return;
}
// Reset our search behavior to the default.
if (this._currentSearchString) {
this._behavior = this._defaultBehavior;
}
else {
this._behavior = this._emptySearchDefaultBehavior;
}
// For any given search, we run up to four queries:
// 1) keywords (this._keywordQuery)
// 2) adaptive learning (this._adaptiveQuery)
// 3) open pages not supported by history (this._openPagesQuery)
// 4) query from this._getSearch
// (1) only gets ran if we get any filtered tokens from this._getSearch,
// since if there are no tokens, there is nothing to match, so there is no
// reason to run the query).
let {query, tokens} =
this._getSearch(this._getUnfilteredSearchTokens(this._currentSearchString));
let queries = tokens.length ?
[this._getBoundKeywordQuery(tokens), this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(tokens), query] :
[this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(tokens), query];
// Start executing our queries.
this._telemetryStartTime = Date.now();
this._executeQueries(queries);
// Set up our persistent state for the duration of the search.
this._searchTokens = tokens;
this._usedPlaces = {};
},
/**
@ -706,8 +776,9 @@ nsPlacesAutoComplete.prototype = {
_finishSearch: function PAC_finishSearch(aNotify)
{
// Notify about results if we are supposed to.
if (aNotify)
if (aNotify) {
this._notifyResults(false);
}
// Clear our state
delete this._originalSearchString;
@ -757,10 +828,10 @@ nsPlacesAutoComplete.prototype = {
{
let result = this._result;
let resultCode = result.matchCount ? "RESULT_SUCCESS" : "RESULT_NOMATCH";
if (aSearchOngoing)
if (aSearchOngoing) {
resultCode += "_ONGOING";
}
result.setSearchResult(Ci.nsIAutoCompleteResult[resultCode]);
result.setDefaultIndex(result.matchCount ? 0 : -1);
this._listener.onSearchResult(this, result);
if (this._telemetryStartTime) {
let elapsed = Date.now() - this._telemetryStartTime;
@ -787,50 +858,37 @@ nsPlacesAutoComplete.prototype = {
_loadPrefs: function PAC_loadPrefs(aRegisterObserver)
{
let self = this;
function safeGetter(aName, aDefault) {
let types = {
boolean: "Bool",
number: "Int",
string: "Char"
};
let type = types[typeof(aDefault)];
if (!type)
throw "Unknown type!";
// If the pref isn't set, we want to use the default.
try {
return self._prefs["get" + type + "Pref"](aName);
}
catch (e) {
return aDefault;
}
}
this._enabled = safeGetter("autocomplete.enabled", true);
this._matchBehavior = safeGetter("matchBehavior", MATCH_BOUNDARY_ANYWHERE);
this._filterJavaScript = safeGetter("filter.javascript", true);
this._maxRichResults = safeGetter("maxRichResults", 25);
this._restrictHistoryToken = safeGetter("restrict.history", "^");
this._restrictBookmarkToken = safeGetter("restrict.bookmark", "*");
this._restrictTypedToken = safeGetter("restrict.typed", "~");
this._restrictTagToken = safeGetter("restrict.tag", "+");
this._restrictOpenPageToken = safeGetter("restrict.openpage", "%");
this._matchTitleToken = safeGetter("match.title", "#");
this._matchURLToken = safeGetter("match.url", "@");
this._defaultBehavior = safeGetter("default.behavior", 0);
this._enabled = safePrefGetter(this._prefs, "autocomplete.enabled", true);
this._matchBehavior = safePrefGetter(this._prefs,
"matchBehavior",
MATCH_BOUNDARY_ANYWHERE);
this._filterJavaScript = safePrefGetter(this._prefs, "filter.javascript", true);
this._maxRichResults = safePrefGetter(this._prefs, "maxRichResults", 25);
this._restrictHistoryToken = safePrefGetter(this._prefs,
"restrict.history", "^");
this._restrictBookmarkToken = safePrefGetter(this._prefs,
"restrict.bookmark", "*");
this._restrictTypedToken = safePrefGetter(this._prefs, "restrict.typed", "~");
this._restrictTagToken = safePrefGetter(this._prefs, "restrict.tag", "+");
this._restrictOpenPageToken = safePrefGetter(this._prefs,
"restrict.openpage", "%");
this._matchTitleToken = safePrefGetter(this._prefs, "match.title", "#");
this._matchURLToken = safePrefGetter(this._prefs, "match.url", "@");
this._defaultBehavior = safePrefGetter(this._prefs, "default.behavior", 0);
// Further restrictions to apply for "empty searches" (i.e. searches for "").
this._emptySearchDefaultBehavior =
this._defaultBehavior |
safeGetter("default.behavior.emptyRestriction",
Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY |
Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED);
safePrefGetter(this._prefs, "default.behavior.emptyRestriction",
Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY |
Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED);
// Validate matchBehavior; default to MATCH_BOUNDARY_ANYWHERE.
if (this._matchBehavior != MATCH_ANYWHERE &&
this._matchBehavior != MATCH_BOUNDARY &&
this._matchBehavior != MATCH_BEGINNING)
this._matchBehavior != MATCH_BEGINNING) {
this._matchBehavior = MATCH_BOUNDARY_ANYWHERE;
}
// register observer
if (aRegisterObserver) {
let pb = this._prefs.QueryInterface(Ci.nsIPrefBranch2);
@ -865,8 +923,9 @@ nsPlacesAutoComplete.prototype = {
this._setBehavior("tag");
break;
case this._restrictOpenPageToken:
if (!this._enableActions)
if (!this._enableActions) {
continue;
}
this._setBehavior("openpage");
break;
case this._matchTitleToken:
@ -889,8 +948,9 @@ nsPlacesAutoComplete.prototype = {
// Set the right JavaScript behavior based on our preference. Note that the
// preference is whether or not we should filter JavaScript, and the
// behavior is if we should search it or not.
if (!this._filterJavaScript)
if (!this._filterJavaScript) {
this._setBehavior("javascript");
}
return {
query: this._getBoundSearchQuery(this._matchBehavior, aTokens),
@ -978,9 +1038,9 @@ nsPlacesAutoComplete.prototype = {
let searchString = this._originalSearchString;
let queryString = "";
let queryIndex = searchString.indexOf(" ");
if (queryIndex != -1)
if (queryIndex != -1) {
queryString = searchString.substring(queryIndex + 1);
}
// We need to escape the parameters as if they were the query in a URL
queryString = encodeURIComponent(queryString).replace("%20", "+", "g");
@ -1005,8 +1065,9 @@ nsPlacesAutoComplete.prototype = {
_getBoundAdaptiveQuery: function PAC_getBoundAdaptiveQuery(aMatchBehavior)
{
// If we were not given a match behavior, use the stored match behavior.
if (arguments.length == 0)
if (arguments.length == 0) {
aMatchBehavior = this._matchBehavior;
}
let query = this._adaptiveQuery;
let (params = query.params) {
@ -1062,10 +1123,12 @@ nsPlacesAutoComplete.prototype = {
// know it is a keyword. Otherwise, we found an exact page match, so just
// show the page like a regular result. Because the page title is likely
// going to be more specific than the bookmark title (keyword title).
if (!entryTitle)
if (!entryTitle) {
style = "keyword";
else
}
else {
title = entryTitle;
}
}
// We will always prefer to show tags if we have them.
@ -1080,9 +1143,9 @@ nsPlacesAutoComplete.prototype = {
}
// If we have tags and should show them, we need to add them to the title.
if (showTags)
if (showTags) {
title += kTitleTagsSeparator + entryTags;
}
// We have to determine the right style to display. Tags show the tag icon,
// bookmarks get the bookmark icon, and keywords get the keyword icon. If
// the result does not fall into any of those, it just gets the favicon.
@ -1090,12 +1153,15 @@ nsPlacesAutoComplete.prototype = {
// It is possible that we already have a style set (from a keyword
// search or because of the user's preferences), so only set it if we
// haven't already done so.
if (showTags)
if (showTags) {
style = "tag";
else if (entryBookmarked)
}
else if (entryBookmarked) {
style = "bookmark";
else
}
else {
style = "favicon";
}
}
this._addToResults(entryId, url, title, entryFavicon, action + style);
@ -1226,8 +1292,313 @@ nsPlacesAutoComplete.prototype = {
Ci.mozIPlacesAutoComplete,
Ci.mozIStorageStatementCallback,
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
])
};
let components = [nsPlacesAutoComplete];
////////////////////////////////////////////////////////////////////////////////
//// urlInlineComplete class
function urlInlineComplete()
{
this._loadPrefs(true);
// register observers
Services.obs.addObserver(this, kTopicShutdown, false);
}
urlInlineComplete.prototype = {
/////////////////////////////////////////////////////////////////////////////////
//// Database and query getters
__db: null,
get _db()
{
if (!this.__db && this._autofill) {
this.__db = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsPIPlacesDatabase).
DBConnection.
clone(true);
}
return this.__db;
},
__syncQuery: null,
get _syncQuery()
{
if (!this.__syncQuery) {
// Add a trailing slash at the end of the hostname, since we always
// want to complete up to and including a URL separator.
this.__syncQuery = this._db.createStatement(
"SELECT host || '/' "
+ "FROM moz_hosts "
+ "WHERE host BETWEEN :search_string AND :search_string || X'FFFF' "
+ "ORDER BY frecency DESC "
+ "LIMIT 1"
);
}
return this.__syncQuery;
},
__asyncQuery: null,
get _asyncQuery()
{
if (!this.__asyncQuery) {
this.__asyncQuery = this._db.createAsyncStatement(
"SELECT h.url "
+ "FROM moz_places h "
+ "WHERE h.frecency <> 0 "
+ "AND AUTOCOMPLETE_MATCH(:searchString, h.url, "
+ "h.title, '', "
+ "h.visit_count, h.typed, 0, 0, "
+ ":matchBehavior, :searchBehavior) "
+ "ORDER BY h.frecency DESC, h.id DESC "
+ "LIMIT 1"
);
}
return this.__asyncQuery;
},
//////////////////////////////////////////////////////////////////////////////
//// nsIAutoCompleteSearch
startSearch: function UIC_startSearch(aSearchString, aSearchParam,
aPreviousResult, aListener)
{
// Stop the search in case the controller has not taken care of it.
if (this._pendingQuery) {
this.stopSearch();
}
// We want to store the original string with no leading or trailing
// whitespace for case sensitive searches.
this._originalSearchString = aSearchString;
this._currentSearchString =
fixupSearchText(this._originalSearchString.toLowerCase());
let result = Cc["@mozilla.org/autocomplete/simple-result;1"].
createInstance(Ci.nsIAutoCompleteSimpleResult);
result.setSearchString(aSearchString);
result.setTypeAheadResult(true);
this._result = result;
this._listener = aListener;
if (this._currentSearchString.length == 0 || !this._db) {
this._finishSearch();
return;
}
// Do a synchronous search on the table of domains.
let query = this._syncQuery;
query.params.search_string = this._currentSearchString.toLowerCase();
// Domains have no "/" in them.
let lastSlashIndex = this._currentSearchString.lastIndexOf("/");
if (lastSlashIndex == -1) {
var hasDomainResult = false;
var domain;
try {
hasDomainResult = query.executeStep();
if (hasDomainResult) {
domain = query.getString(0);
}
} finally {
query.reset();
}
if (hasDomainResult) {
// We got a match for a domain, we can add it immediately.
let appendResult = domain.slice(this._currentSearchString.length);
result.appendMatch(aSearchString + appendResult, "");
this._finishSearch();
return;
}
}
// We did not get a result from the synchronous domain search.
// We now do an asynchronous search through places, and complete
// up to the next URL separator.
// First, check if this is necessary.
// We don't need to search if we have no "/" separator, or if it's at
// the end of the search text.
if (lastSlashIndex == -1 ||
lastSlashIndex == this._currentSearchString.length - 1) {
this._finishSearch();
return;
}
// Within the standard autocomplete query, we only search the beginning
// of URLs for 1 result.
let query = this._asyncQuery;
let (params = query.params) {
params.matchBehavior = MATCH_BEGINNING;
params.searchBehavior = Ci.mozIPlacesAutoComplete["BEHAVIOR_URL"];
params.searchString = this._currentSearchString;
}
// Execute the async query
let wrapper = new AutoCompleteStatementCallbackWrapper(this, this._db);
this._pendingQuery = wrapper.executeAsync([query]);
},
stopSearch: function UIC_stopSearch()
{
delete this._originalSearchString;
delete this._currentSearchString;
delete this._result;
delete this._listener;
if (this._pendingQuery) {
this._pendingQuery.cancel();
delete this._pendingQuery;
}
},
/**
* Loads the preferences that we care about.
*
* @param [optional] aRegisterObserver
* Indicates if the preference observer should be added or not. The
* default value is false.
*/
_loadPrefs: function UIC_loadPrefs(aRegisterObserver)
{
this._autofill = safePrefGetter(Services.prefs,
kBrowserUrlbarAutofillPref,
true);
if (aRegisterObserver) {
Services.prefs.addObserver(kBrowserUrlbarAutofillPref, this, true);
}
},
//////////////////////////////////////////////////////////////////////////////
//// mozIStorageStatementCallback
handleResult: function UIC_handleResult(aResultSet)
{
let row = aResultSet.getNextRow();
let url = fixupSearchText(row.getResultByIndex(0));
// We must complete the URL up to the next separator (which is /, ? or #).
let appendText = url.slice(this._currentSearchString.length);
let separatorIndex = appendText.search(/[\/\?\#]/);
if (separatorIndex != -1) {
if (appendText[separatorIndex] == "/") {
separatorIndex++; // Include the "/" separator
}
appendText = appendText.slice(0, separatorIndex);
}
// Add the result
this._result.appendMatch(this._originalSearchString + appendText, "");
// handleCompletion() will cause the result listener to be called, and
// will display the result in the UI.
},
handleError: function UIC_handleError(aError)
{
Components.utils.reportError("URL Inline Complete: An async statement encountered an " +
"error: " + aError.result + ", '" + aError.message + "'");
},
handleCompletion: function UIC_handleCompletion(aReason)
{
this._finishSearch();
},
//////////////////////////////////////////////////////////////////////////////
//// nsIObserver
observe: function PAC_observe(aSubject, aTopic, aData)
{
if (aTopic == kTopicShutdown) {
Services.obs.removeObserver(this, kTopicShutdown);
this._closeDatabase();
}
else if (aTopic == kPrefChanged) {
this._loadPrefs();
if (!this._autofill) {
this.stopSearch();
this._closeDatabase();
}
}
},
/**
*
* Finalize and close the database safely
*
**/
_closeDatabase: function UIC_closeDatabase()
{
// Finalize the statements that we have used.
let stmts = [
"__syncQuery",
"__asyncQuery",
];
for (let i = 0; i < stmts.length; i++) {
// We do not want to create any query we haven't already created, so
// see if it is a getter first.
if (this[stmts[i]]) {
this[stmts[i]].finalize();
this[stmts[i]] = null;
}
}
if (this.__db) {
this._db.asyncClose();
this.__db = null;
}
},
//////////////////////////////////////////////////////////////////////////////
//// urlInlineComplete
_finishSearch: function UIC_finishSearch()
{
// Notify the result object
let result = this._result;
if (result.matchCount) {
result.setDefaultIndex(0);
result.setSearchResult(Ci.nsIAutoCompleteResult["RESULT_SUCCESS"]);
} else {
result.setDefaultIndex(-1);
result.setSearchResult(Ci.nsIAutoCompleteResult["RESULT_NOMATCH"]);
}
this._listener.onSearchResult(this, result);
this.stopSearch();
},
isSearchComplete: function UIC_isSearchComplete()
{
return this._pendingQuery == null;
},
isPendingSearch: function UIC_isPendingSearch(aHandle)
{
return this._pendingQuery == aHandle;
},
//////////////////////////////////////////////////////////////////////////////
//// nsISupports
classID: Components.ID("c88fae2d-25cf-4338-a1f4-64a320ea7440"),
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIAutoCompleteSearch,
Ci.mozIStorageStatementCallback,
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
])
};
let components = [nsPlacesAutoComplete, urlInlineComplete];
const NSGetFactory = XPCOMUtils.generateNSGetFactory(components);

View File

@ -1,2 +1,6 @@
component {d0272978-beab-4adc-a3d4-04b76acfa4e7} nsPlacesAutoComplete.js
contract @mozilla.org/autocomplete/search;1?name=history {d0272978-beab-4adc-a3d4-04b76acfa4e7}
component {c88fae2d-25cf-4338-a1f4-64a320ea7440} nsPlacesAutoComplete.js
contract @mozilla.org/autocomplete/search;1?name=urlinline {c88fae2d-25cf-4338-a1f4-64a320ea7440}

View File

@ -166,7 +166,7 @@
<property name="timeout"
onset="this.setAttribute('timeout', val); return val;"
onget="return parseInt(this.getAttribute('timeout')) || 50;"/>
onget="var t = parseInt(this.getAttribute('timeout')); return isNaN(t) ? 50 : t;"/>
<property name="searchParam"
onget="return this.getAttribute('autocompletesearchparam') || '';"