mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
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:
parent
ce713fe857
commit
5c706d5982
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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();"/>
|
||||
|
@ -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))
|
||||
|
@ -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);
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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') || '';"
|
||||
|
Loading…
Reference in New Issue
Block a user