summaryrefslogtreecommitdiffstats
path: root/application/palemoon/components/places/nsPlacesAutoComplete.js
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2018-04-16 16:53:03 -0400
committerMatt A. Tobin <email@mattatobin.com>2018-04-16 16:53:03 -0400
commite719d7b3be222dfafad78c71761bad2bafb1243d (patch)
tree562657b1c16acb53f967450143fe5e82d61a43a8 /application/palemoon/components/places/nsPlacesAutoComplete.js
parent72e2a43c01d721c7740deed6a2fcd8c10adf05f0 (diff)
downloadUXP-e719d7b3be222dfafad78c71761bad2bafb1243d.tar
UXP-e719d7b3be222dfafad78c71761bad2bafb1243d.tar.gz
UXP-e719d7b3be222dfafad78c71761bad2bafb1243d.tar.lz
UXP-e719d7b3be222dfafad78c71761bad2bafb1243d.tar.xz
UXP-e719d7b3be222dfafad78c71761bad2bafb1243d.zip
Revert "[PALEMOON] Source nsPlacesAutoComplete from the suite"
This reverts commit 532de8c09d10767960cdecbb4fcaa4262884016e.
Diffstat (limited to 'application/palemoon/components/places/nsPlacesAutoComplete.js')
-rw-r--r--application/palemoon/components/places/nsPlacesAutoComplete.js1369
1 files changed, 0 insertions, 1369 deletions
diff --git a/application/palemoon/components/places/nsPlacesAutoComplete.js b/application/palemoon/components/places/nsPlacesAutoComplete.js
deleted file mode 100644
index 681a2aea1..000000000
--- a/application/palemoon/components/places/nsPlacesAutoComplete.js
+++ /dev/null
@@ -1,1369 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * vim: sw=2 ts=2 sts=2 expandtab
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-Components.utils.import("resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
- "resource://gre/modules/PlacesUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
- "resource://gre/modules/TelemetryStopwatch.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
- "resource://gre/modules/NetUtil.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
- "resource://gre/modules/Task.jsm");
-
-////////////////////////////////////////////////////////////////////////////////
-//// Constants
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cr = Components.results;
-
-// This SQL query fragment provides the following:
-// - whether the entry is bookmarked (kQueryIndexBookmarked)
-// - the bookmark title, if it is a bookmark (kQueryIndexBookmarkTitle)
-// - the tags associated with a bookmarked entry (kQueryIndexTags)
-const kBookTagSQLFragment =
- `EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked,
- (
- SELECT title FROM moz_bookmarks WHERE fk = h.id AND title NOTNULL
- ORDER BY lastModified DESC LIMIT 1
- ) AS btitle,
- (
- SELECT GROUP_CONCAT(t.title, ',')
- FROM moz_bookmarks b
- JOIN moz_bookmarks t ON t.id = +b.parent AND t.parent = :parent
- WHERE b.fk = h.id
- ) AS tags`;
-
-// observer topics
-const kTopicShutdown = "places-shutdown";
-const kPrefChanged = "nsPref:changed";
-
-// Match type constants. These indicate what type of search function we should
-// be using.
-const MATCH_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE;
-const MATCH_BOUNDARY_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY_ANYWHERE;
-const MATCH_BOUNDARY = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY;
-const MATCH_BEGINNING = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING;
-const MATCH_BEGINNING_CASE_SENSITIVE = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING_CASE_SENSITIVE;
-
-// AutoComplete index constants. All AutoComplete queries will provide these
-// columns in this order.
-const kQueryIndexURL = 0;
-const kQueryIndexTitle = 1;
-const kQueryIndexFaviconURL = 2;
-const kQueryIndexBookmarked = 3;
-const kQueryIndexBookmarkTitle = 4;
-const kQueryIndexTags = 5;
-const kQueryIndexVisitCount = 6;
-const kQueryIndexTyped = 7;
-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;
-
-// 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.";
-// Toggle autocomplete.
-const kBrowserUrlbarAutocompleteEnabledPref = "autocomplete.enabled";
-
-////////////////////////////////////////////////////////////////////////////////
-//// Globals
-
-XPCOMUtils.defineLazyServiceGetter(this, "gTextURIService",
- "@mozilla.org/intl/texttosuburi;1",
- "nsITextToSubURI");
-
-////////////////////////////////////////////////////////////////////////////////
-//// Helpers
-
-/**
- * Initializes our temporary table on a given database.
- *
- * @param aDatabase
- * The mozIStorageConnection to set up the temp table on.
- */
-function initTempTable(aDatabase)
-{
- // Note: this should be kept up-to-date with the definition in
- // nsPlacesTables.h.
- let stmt = aDatabase.createAsyncStatement(
- `CREATE TEMP TABLE moz_openpages_temp (
- url TEXT PRIMARY KEY
- , open_count INTEGER
- )`
- );
- stmt.executeAsync();
- stmt.finalize();
-
- // Note: this should be kept up-to-date with the definition in
- // nsPlacesTriggers.h.
- stmt = aDatabase.createAsyncStatement(
- `CREATE TEMPORARY TRIGGER moz_openpages_temp_afterupdate_trigger
- AFTER UPDATE OF open_count ON moz_openpages_temp FOR EACH ROW
- WHEN NEW.open_count = 0
- BEGIN
- 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 = stripPrefix(aURIString);
- return gTextURIService.unEscapeURIForUI("UTF-8", uri);
-}
-
-/**
- * Strip prefixes from the URI that we don't care about for searching.
- *
- * @param aURIString
- * The text to modify.
- * @return the modified uri.
- */
-function stripPrefix(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 uri;
-}
-
-/**
- * safePrefGetter get the pref with type safety.
- * This will return the default value provided if no pref is set.
- *
- * @param aPrefBranch
- * The nsIPrefBranch 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.
- if (aPrefBranch.getPrefType(aName) == Ci.nsIPrefBranch.PREF_INVALID) {
- return aDefault;
- }
- try {
- return aPrefBranch["get" + type + "Pref"](aName);
- }
- catch (e) {
- return aDefault;
- }
-}
-
-/**
- * Whether UnifiedComplete is alive.
- */
-function isUnifiedCompleteInstantiated() {
- try {
- return Components.manager.QueryInterface(Ci.nsIServiceManager)
- .isServiceInstantiated(Cc["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"],
- Ci.mozIPlacesAutoComplete);
- } catch (ex) {
- return false;
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//// AutoCompleteStatementCallbackWrapper class
-
-/**
- * Wraps a callback and ensures that handleCompletion is not dispatched if the
- * query is no longer tracked.
- *
- * @param aAutocomplete
- * A reference to a nsPlacesAutoComplete.
- * @param aCallback
- * A reference to a mozIStorageStatementCallback
- * @param aDBConnection
- * The database connection to execute the queries on.
- */
-function AutoCompleteStatementCallbackWrapper(aAutocomplete, aCallback,
- aDBConnection)
-{
- this._autocomplete = aAutocomplete;
- this._callback = aCallback;
- this._db = aDBConnection;
-}
-
-AutoCompleteStatementCallbackWrapper.prototype = {
- //////////////////////////////////////////////////////////////////////////////
- //// mozIStorageStatementCallback
-
- handleResult: function ACSCW_handleResult(aResultSet)
- {
- this._callback.handleResult.apply(this._callback, arguments);
- },
-
- handleError: function ACSCW_handleError(aError)
- {
- 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.
- if (!this._autocomplete.isSearchComplete() &&
- this._autocomplete.isPendingSearch(this._handle)) {
- this._callback.handleCompletion.apply(this._callback, arguments);
- }
- },
-
- //////////////////////////////////////////////////////////////////////////////
- //// AutoCompleteStatementCallbackWrapper
-
- /**
- * Executes the specified query asynchronously. This object will notify
- * this._callback if we should notify (logic explained in handleCompletion).
- *
- * @param aQueries
- * The queries to execute asynchronously.
- * @return a mozIStoragePendingStatement that can be used to cancel the
- * queries.
- */
- executeAsync: function ACSCW_executeAsync(aQueries)
- {
- return this._handle = this._db.executeAsync(aQueries, aQueries.length,
- this);
- },
-
- //////////////////////////////////////////////////////////////////////////////
- //// nsISupports
-
- QueryInterface: XPCOMUtils.generateQI([
- Ci.mozIStorageStatementCallback,
- ])
-};
-
-////////////////////////////////////////////////////////////////////////////////
-//// nsPlacesAutoComplete class
-//// @mozilla.org/autocomplete/search;1?name=history
-
-function nsPlacesAutoComplete()
-{
- //////////////////////////////////////////////////////////////////////////////
- //// Shared Constants for Smart Getters
-
- // TODO bug 412736 in case of a frecency tie, break it with h.typed and
- // h.visit_count which is better than nothing. This is slow, so not doing it
- // yet...
- function baseQuery(conditions = "") {
- let query = `SELECT h.url, h.title, f.url, ${kBookTagSQLFragment},
- h.visit_count, h.typed, h.id, :query_type,
- t.open_count
- FROM moz_places h
- LEFT JOIN moz_favicons f ON f.id = h.favicon_id
- LEFT JOIN moz_openpages_temp t ON t.url = h.url
- WHERE h.frecency <> 0
- AND AUTOCOMPLETE_MATCH(:searchString, h.url,
- IFNULL(btitle, h.title), tags,
- h.visit_count, h.typed,
- bookmarked, t.open_count,
- :matchBehavior, :searchBehavior)
- ${conditions}
- ORDER BY h.frecency DESC, h.id DESC
- LIMIT :maxResults`;
- return query;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- //// Smart Getters
-
- XPCOMUtils.defineLazyGetter(this, "_db", function() {
- // Get a cloned, read-only version of the database. We'll only ever write
- // to our own in-memory temp table, and having a cloned copy means we do not
- // run the risk of our queries taking longer due to the main database
- // connection performing a long-running task.
- let db = PlacesUtils.history.DBConnection.clone(true);
-
- // Autocomplete often fallbacks to a table scan due to lack of text indices.
- // In such cases a larger cache helps reducing IO. The default Storage
- // value is MAX_CACHE_SIZE_BYTES in storage/mozStorageConnection.cpp.
- let stmt = db.createAsyncStatement("PRAGMA cache_size = -6144"); // 6MiB
- stmt.executeAsync();
- stmt.finalize();
-
- // Create our in-memory tables for tab tracking.
- initTempTable(db);
-
- // Populate the table with current open pages cache contents.
- if (this._openPagesCache.length > 0) {
- // Avoid getter re-entrance from the _registerOpenPageQuery lazy getter.
- let stmt = this._registerOpenPageQuery =
- db.createAsyncStatement(this._registerOpenPageQuerySQL);
- let params = stmt.newBindingParamsArray();
- for (let i = 0; i < this._openPagesCache.length; i++) {
- let bp = params.newBindingParams();
- bp.bindByName("page_url", this._openPagesCache[i]);
- params.addParams(bp);
- }
- stmt.bindParameters(params);
- stmt.executeAsync();
- stmt.finalize();
- delete this._openPagesCache;
- }
-
- return db;
- });
-
- this._customQuery = (conditions = "") => {
- return this._db.createAsyncStatement(baseQuery(conditions));
- };
-
- XPCOMUtils.defineLazyGetter(this, "_defaultQuery", function() {
- return this._db.createAsyncStatement(baseQuery());
- });
-
- XPCOMUtils.defineLazyGetter(this, "_historyQuery", function() {
- // Enforce ignoring the visit_count index, since the frecency one is much
- // faster in this case. ANALYZE helps the query planner to figure out the
- // faster path, but it may not have run yet.
- return this._db.createAsyncStatement(baseQuery("AND +h.visit_count > 0"));
- });
-
- XPCOMUtils.defineLazyGetter(this, "_bookmarkQuery", function() {
- return this._db.createAsyncStatement(baseQuery("AND bookmarked"));
- });
-
- XPCOMUtils.defineLazyGetter(this, "_tagsQuery", function() {
- return this._db.createAsyncStatement(baseQuery("AND tags IS NOT NULL"));
- });
-
- XPCOMUtils.defineLazyGetter(this, "_openPagesQuery", function() {
- return this._db.createAsyncStatement(
- `SELECT t.url, t.url, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- :query_type, t.open_count, NULL
- FROM moz_openpages_temp t
- LEFT JOIN moz_places h ON h.url = t.url
- WHERE h.id IS NULL
- AND AUTOCOMPLETE_MATCH(:searchString, t.url, t.url, NULL,
- NULL, NULL, NULL, t.open_count,
- :matchBehavior, :searchBehavior)
- ORDER BY t.ROWID DESC
- LIMIT :maxResults`
- );
- });
-
- XPCOMUtils.defineLazyGetter(this, "_typedQuery", function() {
- return this._db.createAsyncStatement(baseQuery("AND h.typed = 1"));
- });
-
- XPCOMUtils.defineLazyGetter(this, "_adaptiveQuery", function() {
- return this._db.createAsyncStatement(
- `/* do not warn (bug 487789) */
- SELECT h.url, h.title, f.url, ${kBookTagSQLFragment},
- h.visit_count, h.typed, h.id, :query_type, t.open_count
- FROM (
- SELECT ROUND(
- MAX(use_count) * (1 + (input = :search_string)), 1
- ) AS rank, place_id
- FROM moz_inputhistory
- WHERE input BETWEEN :search_string AND :search_string || X'FFFF'
- GROUP BY place_id
- ) AS i
- JOIN moz_places h ON h.id = i.place_id
- LEFT JOIN moz_favicons f ON f.id = h.favicon_id
- LEFT JOIN moz_openpages_temp t ON t.url = h.url
- WHERE AUTOCOMPLETE_MATCH(NULL, h.url,
- IFNULL(btitle, h.title), tags,
- h.visit_count, h.typed, bookmarked,
- t.open_count,
- :matchBehavior, :searchBehavior)
- ORDER BY rank DESC, h.frecency DESC`
- );
- });
-
- XPCOMUtils.defineLazyGetter(this, "_keywordQuery", function() {
- return this._db.createAsyncStatement(
- `/* do not warn (bug 487787) */
- SELECT REPLACE(h.url, '%s', :query_string) AS search_url, h.title,
- IFNULL(f.url, (SELECT f.url
- FROM moz_places
- JOIN moz_favicons f ON f.id = favicon_id
- WHERE rev_host = h.rev_host
- ORDER BY frecency DESC
- LIMIT 1)
- ), 1, NULL, NULL, h.visit_count, h.typed, h.id,
- :query_type, t.open_count
- FROM moz_keywords k
- JOIN moz_places h ON k.place_id = h.id
- LEFT JOIN moz_favicons f ON f.id = h.favicon_id
- LEFT JOIN moz_openpages_temp t ON t.url = search_url
- WHERE k.keyword = LOWER(:keyword)`
- );
- });
-
- this._registerOpenPageQuerySQL =
- `INSERT OR REPLACE INTO moz_openpages_temp (url, open_count)
- VALUES (:page_url,
- IFNULL(
- (
- SELECT open_count + 1
- FROM moz_openpages_temp
- WHERE url = :page_url
- ),
- 1
- )
- )`;
- XPCOMUtils.defineLazyGetter(this, "_registerOpenPageQuery", function() {
- return this._db.createAsyncStatement(this._registerOpenPageQuerySQL);
- });
-
- XPCOMUtils.defineLazyGetter(this, "_unregisterOpenPageQuery", function() {
- return this._db.createAsyncStatement(
- `UPDATE moz_openpages_temp
- SET open_count = open_count - 1
- WHERE url = :page_url`
- );
- });
-
- //////////////////////////////////////////////////////////////////////////////
- //// Initialization
-
- // load preferences
- this._prefs = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefService).
- getBranch(kBrowserUrlbarBranch);
- this._syncEnabledPref();
- this._loadPrefs(true);
-
- // register observers
- this._os = Cc["@mozilla.org/observer-service;1"].
- getService(Ci.nsIObserverService);
- this._os.addObserver(this, kTopicShutdown, false);
-
-}
-
-nsPlacesAutoComplete.prototype = {
- //////////////////////////////////////////////////////////////////////////////
- //// nsIAutoCompleteSearch
-
- startSearch: function PAC_startSearch(aSearchString, aSearchParam,
- aPreviousResult, aListener)
- {
- // 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 =
- fixupSearchText(this._originalSearchString.toLowerCase());
-
- let params = new Set(aSearchParam.split(" "));
- this._enableActions = params.has("enable-actions");
- this._disablePrivateActions = params.has("disable-private-actions");
-
- 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._getBoundAdaptiveQuery()];
-
- if (this._hasBehavior("openpage")) {
- queries.push(this._getBoundOpenPagesQuery(tokens));
- }
- queries.push(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 = {};
- },
-
- 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) {
- this._stopActiveQuery();
- }
-
- this._finishSearch(false);
- },
-
- //////////////////////////////////////////////////////////////////////////////
- //// nsIAutoCompleteSimpleResultListener
-
- onValueRemoved: function PAC_onValueRemoved(aResult, aURISpec, aRemoveFromDB)
- {
- if (aRemoveFromDB) {
- PlacesUtils.history.removePage(NetUtil.newURI(aURISpec));
- }
- },
-
- //////////////////////////////////////////////////////////////////////////////
- //// 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: [],
- registerOpenPage: function PAC_registerOpenPage(aURI)
- {
- if (!this._databaseInitialized) {
- this._openPagesCache.push(aURI.spec);
- return;
- }
-
- let stmt = this._registerOpenPageQuery;
- stmt.params.page_url = aURI.spec;
- stmt.executeAsync();
- },
-
- unregisterOpenPage: function PAC_unregisterOpenPage(aURI)
- {
- if (!this._databaseInitialized) {
- let index = this._openPagesCache.indexOf(aURI.spec);
- if (index != -1) {
- this._openPagesCache.splice(index, 1);
- }
- return;
- }
-
- let stmt = this._unregisterOpenPageQuery;
- stmt.params.page_url = aURI.spec;
- stmt.executeAsync();
- },
-
- //////////////////////////////////////////////////////////////////////////////
- //// mozIStorageStatementCallback
-
- handleResult: function PAC_handleResult(aResultSet)
- {
- let row, haveMatches = false;
- while ((row = aResultSet.getNextRow())) {
- let match = this._processRow(row);
- haveMatches = haveMatches || match;
-
- if (this._result.matchCount == this._maxRichResults) {
- // We have enough results, so stop running our search.
- this._stopActiveQuery();
-
- // And finish our search.
- this._finishSearch(true);
- return;
- }
-
- }
-
- // Notify about results if we've gotten them.
- 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()) {
- 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 = [
- this._getBoundAdaptiveQuery(MATCH_ANYWHERE),
- this._getBoundSearchQuery(MATCH_ANYWHERE, this._searchTokens),
- ];
- this._executeQueries(queries);
- return;
- }
-
- this._finishSearch(true);
- },
-
- //////////////////////////////////////////////////////////////////////////////
- //// nsIObserver
-
- observe: function PAC_observe(aSubject, aTopic, aData)
- {
- if (aTopic == kTopicShutdown) {
- this._os.removeObserver(this, kTopicShutdown);
-
- // Remove our preference observer.
- this._prefs.removeObserver("", this);
- delete this._prefs;
-
- // Finalize the statements that we have used.
- let stmts = [
- "_defaultQuery",
- "_historyQuery",
- "_bookmarkQuery",
- "_tagsQuery",
- "_openPagesQuery",
- "_typedQuery",
- "_adaptiveQuery",
- "_keywordQuery",
- "_registerOpenPageQuery",
- "_unregisterOpenPageQuery",
- ];
- 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 (Object.getOwnPropertyDescriptor(this, stmts[i]).value !== undefined) {
- this[stmts[i]].finalize();
- }
- }
-
- if (this._databaseInitialized) {
- this._db.asyncClose();
- }
- }
- else if (aTopic == kPrefChanged) {
- // Avoid re-entrancy when flipping linked preferences.
- if (this._ignoreNotifications)
- return;
- this._ignoreNotifications = true;
- this._loadPrefs(false, aTopic, aData);
- this._ignoreNotifications = false;
- }
- },
-
- //////////////////////////////////////////////////////////////////////////////
- //// nsPlacesAutoComplete
-
- get _databaseInitialized() {
- return Object.getOwnPropertyDescriptor(this, "_db").value !== undefined;
- },
-
- /**
- * Generates the tokens used in searching from a given string.
- *
- * @param aSearchString
- * The string to generate tokens from.
- * @return an array of tokens.
- */
- _getUnfilteredSearchTokens: function PAC_unfilteredSearchTokens(aSearchString)
- {
- // Calling split on an empty string will return an array containing one
- // empty string. We don't want that, as it'll break our logic, so return an
- // empty array then.
- return aSearchString.length ? aSearchString.split(" ") : [];
- },
-
- /**
- * Properly cleans up when searching is completed.
- *
- * @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) {
- this._notifyResults(false);
- }
-
- // Clear our state
- delete this._originalSearchString;
- delete this._currentSearchString;
- delete this._strippedPrefix;
- delete this._searchTokens;
- delete this._listener;
- delete this._result;
- delete this._usedPlaces;
- delete this._pendingQuery;
- this._secondPass = false;
- this._enableActions = false;
- },
-
- /**
- * Executes the given queries asynchronously.
- *
- * @param aQueries
- * The queries to execute.
- */
- _executeQueries: function PAC_executeQueries(aQueries)
- {
- // Because we might get a handleCompletion for canceled queries, we want to
- // filter out queries we no longer care about (described in the
- // handleCompletion implementation of AutoCompleteStatementCallbackWrapper).
-
- // Create our wrapper object and execute the queries.
- let wrapper = new AutoCompleteStatementCallbackWrapper(this, this, this._db);
- this._pendingQuery = wrapper.executeAsync(aQueries);
- },
-
- /**
- * Stops executing our active query.
- */
- _stopActiveQuery: function PAC_stopActiveQuery()
- {
- this._pendingQuery.cancel();
- delete this._pendingQuery;
- },
-
- /**
- * Notifies the listener about results.
- *
- * @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) {
- resultCode += "_ONGOING";
- }
- result.setSearchResult(Ci.nsIAutoCompleteResult[resultCode]);
- 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);
- } catch (ex) {
- Components.utils.reportError("Unable to report telemetry.");
- }
- }
- this._telemetryStartTime = null;
- }
- },
-
- /**
- * Synchronize suggest.* prefs with autocomplete.enabled.
- */
- _syncEnabledPref: function PAC_syncEnabledPref()
- {
- let suggestPrefs = ["suggest.history", "suggest.bookmark", "suggest.openpage"];
- let types = ["History", "Bookmark", "Openpage"];
-
- this._enabled = safePrefGetter(this._prefs, kBrowserUrlbarAutocompleteEnabledPref,
- true);
- this._suggestHistory = safePrefGetter(this._prefs, "suggest.history", true);
- this._suggestBookmark = safePrefGetter(this._prefs, "suggest.bookmark", true);
- this._suggestOpenpage = safePrefGetter(this._prefs, "suggest.openpage", true);
-
- if (this._enabled) {
- // If the autocomplete preference is active, activate all suggest
- // preferences only if all of them are false.
- if (types.every(type => this["_suggest" + type] == false)) {
- for (let type of suggestPrefs) {
- this._prefs.setBoolPref(type, true);
- }
- }
- } else {
- // If the preference was deactivated, deactivate all suggest preferences.
- for (let type of suggestPrefs) {
- this._prefs.setBoolPref(type, false);
- }
- }
- },
-
- /**
- * 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.
- * @param [optional] aTopic
- * Observer's topic, if any.
- * @param [optional] aSubject
- * Observer's subject, if any.
- */
- _loadPrefs: function PAC_loadPrefs(aRegisterObserver, aTopic, aData)
- {
- // Avoid race conditions with UnifiedComplete component.
- if (aData && !isUnifiedCompleteInstantiated()) {
- // Synchronize suggest.* prefs with autocomplete.enabled.
- if (aData == kBrowserUrlbarAutocompleteEnabledPref) {
- this._syncEnabledPref();
- } else if (aData.startsWith("suggest.")) {
- let suggestPrefs = ["suggest.history", "suggest.bookmark", "suggest.openpage"];
- this._prefs.setBoolPref(kBrowserUrlbarAutocompleteEnabledPref,
- suggestPrefs.some(pref => safePrefGetter(this._prefs, pref, true)));
- }
- }
-
- this._enabled = safePrefGetter(this._prefs,
- kBrowserUrlbarAutocompleteEnabledPref,
- 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._suggestHistory = safePrefGetter(this._prefs, "suggest.history", true);
- this._suggestBookmark = safePrefGetter(this._prefs, "suggest.bookmark", true);
- this._suggestOpenpage = safePrefGetter(this._prefs, "suggest.openpage", true);
- this._suggestTyped = safePrefGetter(this._prefs, "suggest.history.onlyTyped", false);
-
- // If history is not set, onlyTyped value should be ignored.
- if (!this._suggestHistory) {
- this._suggestTyped = false;
- }
- let types = ["History", "Bookmark", "Openpage", "Typed"];
- this._defaultBehavior = types.reduce((memo, type) => {
- let prefValue = this["_suggest" + type];
- return memo | (prefValue &&
- Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type.toUpperCase()]);
- }, 0);
-
- // Further restrictions to apply for "empty searches" (i.e. searches for "").
- // The empty behavior is typed history, if history is enabled. Otherwise,
- // it is bookmarks, if they are enabled. If both history and bookmarks are disabled,
- // it defaults to open pages.
- this._emptySearchDefaultBehavior = Ci.mozIPlacesAutoComplete.BEHAVIOR_RESTRICT;
- if (this._suggestHistory) {
- this._emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY |
- Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED;
- } else if (this._suggestBookmark) {
- this._emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK;
- } else {
- this._emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE;
- }
-
- // Validate matchBehavior; default to MATCH_BOUNDARY_ANYWHERE.
- if (this._matchBehavior != MATCH_ANYWHERE &&
- this._matchBehavior != MATCH_BOUNDARY &&
- this._matchBehavior != MATCH_BEGINNING) {
- this._matchBehavior = MATCH_BOUNDARY_ANYWHERE;
- }
- // register observer
- if (aRegisterObserver) {
- this._prefs.addObserver("", this, false);
- }
- },
-
- /**
- * Given an array of tokens, this function determines which query should be
- * ran. It also removes any special search tokens.
- *
- * @param aTokens
- * An array of search tokens.
- * @return an object with two properties:
- * query: the correctly optimized, bound query to search the database
- * with.
- * tokens: the filtered list of tokens to search with.
- */
- _getSearch: function PAC_getSearch(aTokens)
- {
- let foundToken = false;
- let restrict = (behavior) => {
- if (!foundToken) {
- this._behavior = 0;
- this._setBehavior("restrict");
- foundToken = true;
- }
- this._setBehavior(behavior);
- };
-
- // Set the proper behavior so our call to _getBoundSearchQuery gives us the
- // correct query.
- for (let i = aTokens.length - 1; i >= 0; i--) {
- switch (aTokens[i]) {
- case this._restrictHistoryToken:
- restrict("history");
- break;
- case this._restrictBookmarkToken:
- restrict("bookmark");
- break;
- case this._restrictTagToken:
- restrict("tag");
- break;
- case this._restrictOpenPageToken:
- if (!this._enableActions) {
- continue;
- }
- restrict("openpage");
- break;
- case this._matchTitleToken:
- restrict("title");
- break;
- case this._matchURLToken:
- restrict("url");
- break;
- case this._restrictTypedToken:
- restrict("typed");
- break;
- default:
- // We do not want to remove the token if we did not match.
- continue;
- }
-
- 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) {
- this._setBehavior("javascript");
- }
-
- return {
- query: this._getBoundSearchQuery(this._matchBehavior, aTokens),
- tokens: aTokens
- };
- },
-
- /**
- * @return a string consisting of the search query to be used based on the
- * previously set urlbar suggestion preferences.
- */
- _getSuggestionPrefQuery: function PAC_getSuggestionPrefQuery()
- {
- if (!this._hasBehavior("restrict") && this._hasBehavior("history") &&
- this._hasBehavior("bookmark")) {
- return this._hasBehavior("typed") ? this._customQuery("AND h.typed = 1")
- : this._defaultQuery;
- }
- let conditions = [];
- if (this._hasBehavior("history")) {
- // Enforce ignoring the visit_count index, since the frecency one is much
- // faster in this case. ANALYZE helps the query planner to figure out the
- // faster path, but it may not have up-to-date information yet.
- conditions.push("+h.visit_count > 0");
- }
- if (this._hasBehavior("typed")) {
- conditions.push("h.typed = 1");
- }
- if (this._hasBehavior("bookmark")) {
- conditions.push("bookmarked");
- }
- if (this._hasBehavior("tag")) {
- conditions.push("tags NOTNULL");
- }
-
- return conditions.length ? this._customQuery("AND " + conditions.join(" AND "))
- : this._defaultQuery;
- },
-
- /**
- * Obtains the search query to be used based on the previously set search
- * behaviors (accessed by this._hasBehavior). The query is bound and ready to
- * execute.
- *
- * @param aMatchBehavior
- * How this query should match its tokens to the search string.
- * @param aTokens
- * An array of search tokens.
- * @return the correctly optimized query to search the database with and the
- * new list of tokens to search with. The query has all the needed
- * parameters bound, so consumers can execute it without doing any
- * additional work.
- */
- _getBoundSearchQuery: function PAC_getBoundSearchQuery(aMatchBehavior,
- aTokens)
- {
- let query = this._getSuggestionPrefQuery();
-
- // Bind the needed parameters to the query so consumers can use it.
- let params = query.params;
- params.parent = PlacesUtils.tagsFolderId;
- params.query_type = kQueryTypeFiltered;
- params.matchBehavior = aMatchBehavior;
- params.searchBehavior = this._behavior;
-
- // We only want to search the tokens that we are left with - not the
- // original search string.
- params.searchString = aTokens.join(" ");
-
- // Limit the query to the the maximum number of desired results.
- // This way we can avoid doing more work than needed.
- params.maxResults = this._maxRichResults;
-
- return query;
- },
-
- _getBoundOpenPagesQuery: function PAC_getBoundOpenPagesQuery(aTokens)
- {
- let query = this._openPagesQuery;
-
- // Bind the needed parameters to the query so consumers can use it.
- let params = query.params;
- params.query_type = kQueryTypeFiltered;
- params.matchBehavior = this._matchBehavior;
- params.searchBehavior = this._behavior;
-
- // We only want to search the tokens that we are left with - not the
- // original search string.
- params.searchString = aTokens.join(" ");
- params.maxResults = this._maxRichResults;
-
- return query;
- },
-
- /**
- * Obtains the keyword query with the properly bound parameters.
- *
- * @param aTokens
- * The array of search tokens to check against.
- * @return the bound keyword query.
- */
- _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) {
- 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;
- params.keyword = keyword;
- params.query_string = queryString;
- params.query_type = kQueryTypeKeyword;
-
- return query;
- },
-
- /**
- * 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) {
- aMatchBehavior = this._matchBehavior;
- }
-
- let query = this._adaptiveQuery;
- let params = query.params;
- params.parent = PlacesUtils.tagsFolderId;
- params.search_string = this._currentSearchString;
- params.query_type = kQueryTypeFiltered;
- params.matchBehavior = aMatchBehavior;
- params.searchBehavior = this._behavior;
-
- return query;
- },
-
- /**
- * Processes a mozIStorageRow to generate the proper data for the AutoComplete
- * result. This will add an entry to the current result if it matches the
- * criteria.
- *
- * @param aRow
- * The row to process.
- * @return true if the row is accepted, and false if not.
- */
- _processRow: function PAC_processRow(aRow)
- {
- // Before we do any work, make sure this entry isn't already in our results.
- let entryId = aRow.getResultByIndex(kQueryIndexPlaceId);
- let escapedEntryURL = aRow.getResultByIndex(kQueryIndexURL);
- let openPageCount = aRow.getResultByIndex(kQueryIndexOpenPageCount) || 0;
-
- // If actions are enabled and the page is open, add only the switch-to-tab
- // result. Otherwise, add the normal result.
- let [url, action] = this._enableActions && openPageCount > 0 && this._hasBehavior("openpage") ?
- ["moz-action:switchtab," + escapedEntryURL, "action "] :
- [escapedEntryURL, ""];
-
- if (this._inResults(entryId, url)) {
- return false;
- }
-
- let entryTitle = aRow.getResultByIndex(kQueryIndexTitle) || "";
- let entryFavicon = aRow.getResultByIndex(kQueryIndexFaviconURL) || "";
- let entryBookmarked = aRow.getResultByIndex(kQueryIndexBookmarked);
- let entryBookmarkTitle = entryBookmarked ?
- aRow.getResultByIndex(kQueryIndexBookmarkTitle) : null;
- let entryTags = aRow.getResultByIndex(kQueryIndexTags) || "";
-
- // Always prefer the bookmark title unless it is empty
- let title = entryBookmarkTitle || entryTitle;
-
- let style;
- if (aRow.getResultByIndex(kQueryIndexQueryType) == kQueryTypeKeyword) {
- style = "keyword";
- title = NetUtil.newURI(escapedEntryURL).host;
- }
-
- // 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 if the user wants
- // only history and not bookmarks and there are no tags.
- if (this._hasBehavior("history") && !this._hasBehavior("bookmark") &&
- !showTags) {
- showTags = false;
- style = "favicon";
- }
-
- // If we have tags and should show them, we need to add them to the title.
- 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) {
- style = "tag";
- }
- else if (entryBookmarked) {
- style = "bookmark";
- }
- 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.
- *
- * @param aPlaceId
- * The place id to check for, may be null.
- * @param aUrl
- * The url to check for.
- * @return true if the place has been added, false otherwise.
- *
- * @note Must check both the id and the url for a negative match, since
- * autocomplete may run in the middle of a new page addition. In such
- * a case the switch-to-tab query would hash the page by url, then a
- * next query, running after the page addition, would hash it by id.
- * It's not possible to just rely on url though, since keywords
- * dynamically modify the url to include their search string.
- */
- _inResults: function PAC_inResults(aPlaceId, aUrl)
- {
- if (aPlaceId && aPlaceId in this._usedPlaces) {
- return true;
- }
- return aUrl in this._usedPlaces;
- },
-
- /**
- * Adds a result to the AutoComplete results. Also tracks that we've added
- * this place_id into the result set.
- *
- * @param aPlaceId
- * The place_id of the item to be added to the result set. This is
- * used by _inResults.
- * @param aURISpec
- * The URI spec for the entry.
- * @param aTitle
- * The title to give the entry.
- * @param aFaviconSpec
- * The favicon to give to the entry.
- * @param aStyle
- * Indicates how the entry should be styled when displayed.
- */
- _addToResults: function PAC_addToResults(aPlaceId, aURISpec, aTitle,
- aFaviconSpec, aStyle)
- {
- // Add this to our internal tracker to ensure duplicates do not end up in
- // the result. _usedPlaces is an Object that is being used as a set.
- // Not all entries have a place id, thus we fallback to the url for them.
- // We cannot use only the url since keywords entries are modified to
- // include the search string, and would be returned multiple times. Ids
- // are faster too.
- this._usedPlaces[aPlaceId || aURISpec] = true;
-
- // Obtain the favicon for this URI.
- let favicon;
- if (aFaviconSpec) {
- let uri = NetUtil.newURI(aFaviconSpec);
- favicon = PlacesUtils.favicons.getFaviconLinkForIcon(uri).spec;
- }
- favicon = favicon || PlacesUtils.favicons.defaultFavicon.spec;
-
- this._result.appendMatch(aURISpec, aTitle, favicon, aStyle);
- },
-
- /**
- * Determines if the specified AutoComplete behavior is set.
- *
- * @param aType
- * The behavior type to test for.
- * @return true if the behavior is set, false otherwise.
- */
- _hasBehavior: function PAC_hasBehavior(aType)
- {
- let behavior = Ci.mozIPlacesAutoComplete["BEHAVIOR_" + aType.toUpperCase()];
-
- if (this._disablePrivateActions &&
- behavior == Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE) {
- return false;
- }
-
- return this._behavior & behavior;
- },
-
- /**
- * Enables the desired AutoComplete behavior.
- *
- * @param aType
- * The behavior type to set.
- */
- _setBehavior: function PAC_setBehavior(aType)
- {
- this._behavior |=
- Ci.mozIPlacesAutoComplete["BEHAVIOR_" + aType.toUpperCase()];
- },
-
- /**
- * Determines if we are done searching or not.
- *
- * @return true if we have completed searching, false otherwise.
- */
- isSearchComplete: function PAC_isSearchComplete()
- {
- // If _pendingQuery is null, we should no longer do any work since we have
- // already called _finishSearch. This means we completed our search.
- return this._pendingQuery == null;
- },
-
- /**
- * Determines if the given handle of a pending statement is a pending search
- * or not.
- *
- * @param aHandle
- * A mozIStoragePendingStatement to check and see if we are waiting for
- * results from it still.
- * @return true if it is a pending query, false otherwise.
- */
- isPendingSearch: function PAC_isPendingSearch(aHandle)
- {
- return this._pendingQuery == aHandle;
- },
-
- //////////////////////////////////////////////////////////////////////////////
- //// nsISupports
-
- classID: Components.ID("d0272978-beab-4adc-a3d4-04b76acfa4e7"),
-
- _xpcom_factory: XPCOMUtils.generateSingletonFactory(nsPlacesAutoComplete),
-
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsIAutoCompleteSearch,
- Ci.nsIAutoCompleteSimpleResultListener,
- Ci.mozIPlacesAutoComplete,
- Ci.mozIStorageStatementCallback,
- Ci.nsIObserver,
- Ci.nsISupportsWeakReference,
- ])
-};
-
-var components = [nsPlacesAutoComplete];
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);