|
|
|
@ -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);
|
|
|
|
|