author | Michael Ventnor<ventnor.bugzilla@gmail.com> |
Thu, 19 Jan 2012 12:31:24 +0100 | |
changeset 84888 | 953bde82b7a7d2ea4408b1792791612ac8434214 |
parent 84887 | 3898e1c52fa332cfb80c449bb4e2be48229da8d1 |
child 84889 | 2d06b0b645b57c9bd56cc7cc68d97998eebcbf38 |
push id | 21881 |
push user | mbrubeck@mozilla.com |
push date | Thu, 19 Jan 2012 18:41:36 +0000 |
treeherder | mozilla-central@e5e66f40c35b [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
bugs | 566489 |
milestone | 12.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -276,30 +276,30 @@ pref("browser.urlbar.clickSelectsAll", f #else pref("browser.urlbar.clickSelectsAll", true); #endif #ifdef UNIX_BUT_NOT_MAC 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 .) // 3: Match at the beginning of the url or title pref("browser.urlbar.matchBehavior", 1); pref("browser.urlbar.filter.javascript", true); // the maximum number of results to show in autocomplete when doing richResults 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 // just the title text or url. pref("browser.urlbar.restrict.history", "^"); pref("browser.urlbar.restrict.bookmark", "*"); pref("browser.urlbar.restrict.tag", "+"); pref("browser.urlbar.restrict.openpage", "%");
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -497,17 +497,17 @@ </dummyobservertarget> </toolbaritem> <toolbaritem id="urlbar-container" align="center" flex="400" persist="width" combined="true" title="&locationItem.title;" class="chromeclass-location" removable="true"> <textbox id="urlbar" flex="1" placeholder="&urlbar.placeholder;" type="autocomplete" - autocompletesearch="history" + autocompletesearch="urlinline history" autocompletesearchparam="enable-actions" autocompletepopup="PopupAutoCompleteRichResult" completeselectedindex="true" tabscrolling="true" showcommentcolumn="true" showimagecolumn="true" enablehistory="true" maxrows="6"
--- a/browser/base/content/openLocation.xul +++ b/browser/base/content/openLocation.xul @@ -64,17 +64,17 @@ <separator orient="vertical" class="thin"/> <vbox flex="1"> <description>&enter.label;</description> <separator class="thin"/> <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();"/> <button label="&chooseFile.label;" oncommand="onChooseFile();"/> </hbox> <hbox align="center"> <label value="&openWhere.label;"/> <menulist id="openWhereList">
--- a/toolkit/components/autocomplete/nsAutoCompleteController.cpp +++ b/toolkit/components/autocomplete/nsAutoCompleteController.cpp @@ -1093,16 +1093,22 @@ nsAutoCompleteController::StartSearchTim // If we don't check for this, we won't be able to cancel the original timer // and may crash when it fires (bug 236659). if (mTimer || !mInput) return NS_OK; 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)) return rv; rv = mTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT); if (NS_FAILED(rv)) mTimer = nsnull;
--- a/toolkit/components/places/nsPlacesAutoComplete.js +++ b/toolkit/components/places/nsPlacesAutoComplete.js @@ -89,23 +89,33 @@ const kQueryIndexPlaceId = 8; const kQueryIndexQueryType = 9; const kQueryIndexOpenPageCount = 10; // AutoComplete query type constants. Describes the various types of queries // that we can process. 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 /** * Initializes our temporary table on a given database. * * @param aDatabase @@ -134,16 +144,77 @@ function initTempTable(aDatabase) + "DELETE FROM moz_openpages_temp " + "WHERE url = NEW.url; " + "END " ); stmt.executeAsync(); 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 /** * Wraps a callback and ensures that handleCompletion is not dispatched if the * query is no longer tracked. * @@ -173,18 +244,19 @@ AutoCompleteStatementCallbackWrapper.pro this._callback.handleError.apply(this._callback, arguments); }, handleCompletion: function ACSCW_handleCompletion(aReason) { // 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); + } }, ////////////////////////////////////////////////////////////////////////////// //// AutoCompleteStatementCallbackWrapper /** * Executes the specified query asynchronously. This object will notify * this._callback if we should notify (logic explained in handleCompletion). @@ -270,20 +342,16 @@ function nsPlacesAutoComplete() return db; }); XPCOMUtils.defineLazyServiceGetter(this, "_bh", "@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"); XPCOMUtils.defineLazyServiceGetter(this, "_ioService", "@mozilla.org/network/io-service;1", "nsIIOService"); @@ -435,94 +503,57 @@ function nsPlacesAutoComplete() nsPlacesAutoComplete.prototype = { ////////////////////////////////////////////////////////////////////////////// //// nsIAutoCompleteSearch 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(); - - // 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; + // Stop the search in case the controller has not taken care of it. + this.stopSearch(); - // 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() { // 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; + } }, ////////////////////////////////////////////////////////////////////////////// //// nsIAutoCompleteSimpleResultListener onValueRemoved: function PAC_onValueRemoved(aResult, aURISpec, aRemoveFromDB) { - if (aRemoveFromDB) + if (aRemoveFromDB) { this._bh.removePage(this._ioService.newURI(aURISpec, null, null)); + } }, ////////////////////////////////////////////////////////////////////////////// //// mozIPlacesAutoComplete // If the connection has not yet been started, use this local cache. This // prevents autocomplete from initing the database till the first search. _openPagesCache: [], @@ -570,31 +601,33 @@ nsPlacesAutoComplete.prototype = { // And finish our search. this._finishSearch(true); return; } } // Notify about results if we've gotten them. - if (haveMatches) + if (haveMatches) { this._notifyResults(true); + } }, handleError: function PAC_handleError(aError) { Components.utils.reportError("Places AutoComplete: An async statement encountered an " + "error: " + aError.result + ", '" + aError.message + "'"); }, 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 // results. if (this._matchBehavior == MATCH_BOUNDARY_ANYWHERE && this._result.matchCount < this._maxRichResults && !this._secondPass) { this._secondPass = true; let queries = [ @@ -651,39 +684,76 @@ nsPlacesAutoComplete.prototype = { }, ////////////////////////////////////////////////////////////////////////////// //// nsPlacesAutoComplete 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; + + // 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 = + 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 (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 we are not enabled, we need to return now. + if (!this._enabled) { + this._finishSearch(true); + return; + } - if (uri.indexOf("www.") == 0) - uri = uri.slice(4); + // 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]; - return this._textURIService.unEscapeURIForUI("UTF-8", uri); + // 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 = {}; }, /** * Generates the tokens used in searching from a given string. * * @param aSearchString * The string to generate tokens from. * @return an array of tokens. @@ -701,18 +771,19 @@ nsPlacesAutoComplete.prototype = { * * @param aNotify * Indicates if we should notify the AutoComplete listener about our * results or not. */ _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; delete this._currentSearchString; delete this._searchTokens; delete this._listener; delete this._result; delete this._usedPlaces; @@ -752,20 +823,20 @@ nsPlacesAutoComplete.prototype = { * * @param aSearchOngoing * Indicates if the search is ongoing or not. */ _notifyResults: function PAC_notifyResults(aSearchOngoing) { 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; if (elapsed > 50) { try { Services.telemetry .getHistogramById("PLACES_AUTOCOMPLETE_1ST_RESULT_TIME_MS") .add(elapsed); @@ -782,60 +853,47 @@ nsPlacesAutoComplete.prototype = { * * @param [optional] aRegisterObserver * Indicates if the preference observer should be added or not. The * default value is false. */ _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); pb.addObserver("", this, false); } }, /** @@ -860,18 +918,19 @@ nsPlacesAutoComplete.prototype = { break; case this._restrictBookmarkToken: this._setBehavior("bookmark"); break; case this._restrictTagToken: this._setBehavior("tag"); break; case this._restrictOpenPageToken: - if (!this._enableActions) + if (!this._enableActions) { continue; + } this._setBehavior("openpage"); break; case this._matchTitleToken: this._setBehavior("title"); break; case this._matchURLToken: this._setBehavior("url"); break; @@ -884,18 +943,19 @@ nsPlacesAutoComplete.prototype = { }; aTokens.splice(i, 1); } // 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), tokens: aTokens }; }, /** @@ -973,19 +1033,19 @@ nsPlacesAutoComplete.prototype = { */ _getBoundKeywordQuery: function PAC_getBoundKeywordQuery(aTokens) { // The keyword is the first word in the search string, with the parameters // following it. 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"); // The first word could be a keyword, so that's what we'll search. let keyword = aTokens[0]; let query = this._keywordQuery; let (params = query.params) { @@ -1000,18 +1060,19 @@ nsPlacesAutoComplete.prototype = { /** * Obtains the adaptive query with the properly bound parameters. * * @return the bound adaptive query. */ _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) { params.parent = this._bs.tagsFolder; params.search_string = this._currentSearchString; params.query_type = kQueryTypeFiltered; params.matchBehavior = aMatchBehavior; params.searchBehavior = this._behavior; @@ -1057,50 +1118,55 @@ nsPlacesAutoComplete.prototype = { let title = entryBookmarkTitle || entryTitle; let style; if (aRow.getResultByIndex(kQueryIndexQueryType) == kQueryTypeKeyword) { // If we do not have a title, then we must have a keyword, so let the UI // 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. let showTags = !!entryTags; // However, we'll act as if a page is not bookmarked or tagged if the user // only wants only history and not bookmarks or tags. if (this._hasBehavior("history") && !(this._hasBehavior("bookmark") || this._hasBehavior("tag"))) { showTags = false; style = "favicon"; } // 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. if (!style) { // 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); return true; }, /** * Checks to see if the given place has already been added to the results. @@ -1221,13 +1287,318 @@ nsPlacesAutoComplete.prototype = { classID: Components.ID("d0272978-beab-4adc-a3d4-04b76acfa4e7"), QueryInterface: XPCOMUtils.generateQI([ Ci.nsIAutoCompleteSearch, Ci.nsIAutoCompleteSimpleResultListener, 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);
--- a/toolkit/components/places/nsPlacesAutoComplete.manifest +++ b/toolkit/components/places/nsPlacesAutoComplete.manifest @@ -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} +
--- a/toolkit/content/widgets/autocomplete.xml +++ b/toolkit/content/widgets/autocomplete.xml @@ -161,17 +161,17 @@ onget="return this.getAttribute('showcommentcolumn') == 'true';"/> <property name="showImageColumn" onset="this.setAttribute('showimagecolumn', val); return val;" onget="return this.getAttribute('showimagecolumn') == 'true';"/> <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') || '';" onset="this.setAttribute('autocompletesearchparam', val); return val;"/> <property name="searchCount" readonly="true" onget="this.initSearchNames(); return this.mSearchNames.length;"/>