2009-07-13 12:19:03 -07:00
|
|
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
|
|
* vim: sw=2 ts=2 sts=2 expandtab
|
|
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
*
|
|
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
|
|
* the License. You may obtain a copy of the License at
|
|
|
|
* http://www.mozilla.org/MPL/
|
|
|
|
*
|
|
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
|
|
* for the specific language governing rights and limitations under the
|
|
|
|
* License.
|
|
|
|
*
|
|
|
|
* The Original Code is mozilla.org code.
|
|
|
|
*
|
|
|
|
* The Initial Developer of the Original Code is
|
|
|
|
* Mozilla Corporation.
|
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2008
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
* Shawn Wilsher <me@shawnwilsher.com> (Original Author)
|
|
|
|
*
|
|
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
|
|
* the provisions above, a recipient may use your version of this file under
|
|
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
*
|
|
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
|
|
|
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Constants
|
|
|
|
|
|
|
|
const Cc = Components.classes;
|
|
|
|
const Ci = Components.interfaces;
|
|
|
|
const Cr = Components.results;
|
|
|
|
|
|
|
|
// This is just a helper for the next constant.
|
|
|
|
function book_tag_sql_fragment(aName, aColumn, aForTag)
|
|
|
|
{
|
|
|
|
return ["(",
|
|
|
|
"SELECT ", aColumn, " ",
|
|
|
|
"FROM moz_bookmarks b ",
|
|
|
|
"JOIN moz_bookmarks t ",
|
|
|
|
"ON t.id = b.parent ",
|
|
|
|
"AND t.parent ", (aForTag ? "" : "!"), "= :parent ",
|
|
|
|
"WHERE b.type = ", Ci.nsINavBookmarksService.TYPE_BOOKMARK, " ",
|
|
|
|
"AND b.fk = h.id ",
|
|
|
|
(aForTag ? "AND LENGTH(t.title) > 0" :
|
|
|
|
"ORDER BY b.lastModified DESC LIMIT 1"),
|
|
|
|
") AS ", aName].join("");
|
|
|
|
}
|
|
|
|
|
|
|
|
// This SQL query fragment provides the following:
|
|
|
|
// - the parent folder for bookmarked entries (kQueryIndexParent)
|
|
|
|
// - the bookmark title, if it is a bookmark (kQueryIndexBookmarkTitle)
|
|
|
|
// - the tags associated with a bookmarked entry (kQueryIndexTags)
|
|
|
|
const kBookTagSQLFragment =
|
|
|
|
book_tag_sql_fragment("parent", "b.parent", false) + ", " +
|
|
|
|
book_tag_sql_fragment("bookmark", "b.title", false) + ", " +
|
|
|
|
book_tag_sql_fragment("tags", "GROUP_CONCAT(t.title, ',')", true);
|
|
|
|
|
|
|
|
// observer topics
|
2010-04-22 05:53:54 -07:00
|
|
|
const kTopicShutdown = "places-shutdown";
|
2009-07-13 12:19:03 -07:00
|
|
|
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;
|
|
|
|
|
|
|
|
// AutoComplete index constants. All AutoComplete queries will provide these
|
|
|
|
// columns in this order.
|
|
|
|
const kQueryIndexURL = 0;
|
|
|
|
const kQueryIndexTitle = 1;
|
|
|
|
const kQueryIndexFaviconURL = 2;
|
|
|
|
const kQueryIndexParentId = 3;
|
|
|
|
const kQueryIndexBookmarkTitle = 4;
|
|
|
|
const kQueryIndexTags = 5;
|
|
|
|
const kQueryIndexVisitCount = 6;
|
|
|
|
const kQueryIndexTyped = 7;
|
|
|
|
const kQueryIndexPlaceId = 8;
|
|
|
|
const kQueryIndexQueryType = 9;
|
2010-03-26 14:59:02 -07:00
|
|
|
const kQueryIndexOpenPageCount = 10;
|
2009-07-13 12:19:03 -07:00
|
|
|
|
|
|
|
// 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.";
|
|
|
|
|
2010-08-09 15:14:59 -07:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Global Functions
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generates the SQL subquery to get the best favicon for a given revhost. This
|
|
|
|
* is the favicon for the most recent visit.
|
|
|
|
*
|
|
|
|
* @param aTableName
|
|
|
|
* The table to join to the moz_favicons table with. This must have a
|
|
|
|
* column called favicon_id.
|
|
|
|
* @return the SQL subquery (in string form) to get the best favicon.
|
|
|
|
*/
|
|
|
|
function best_favicon_for_revhost(aTableName)
|
|
|
|
{
|
|
|
|
return "(" +
|
|
|
|
"SELECT f.url " +
|
|
|
|
"FROM " + aTableName + " " +
|
|
|
|
"JOIN moz_favicons f ON f.id = favicon_id " +
|
|
|
|
"WHERE rev_host = IFNULL( " +
|
|
|
|
"(SELECT rev_host FROM moz_places_temp WHERE id = b.fk), " +
|
|
|
|
"(SELECT rev_host FROM moz_places WHERE id = b.fk) " +
|
|
|
|
") " +
|
|
|
|
"ORDER BY frecency DESC " +
|
|
|
|
"LIMIT 1 " +
|
|
|
|
")";
|
|
|
|
}
|
|
|
|
|
2009-07-13 12:19:03 -07:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// AutoCompleteStatementCallbackWrapper class
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wraps a callback and ensures that handleCompletion is not dispatched if the
|
|
|
|
* query is no longer tracked.
|
|
|
|
*
|
|
|
|
* @param aCallback
|
|
|
|
* A reference to a nsPlacesAutoComplete.
|
2009-07-22 15:18:34 -07:00
|
|
|
* @param aDBConnection
|
|
|
|
* The database connection to execute the queries on.
|
2009-07-13 12:19:03 -07:00
|
|
|
*/
|
2009-07-22 15:18:34 -07:00
|
|
|
function AutoCompleteStatementCallbackWrapper(aCallback,
|
|
|
|
aDBConnection)
|
2009-07-13 12:19:03 -07:00
|
|
|
{
|
|
|
|
this._callback = aCallback;
|
2009-07-22 15:18:34 -07:00
|
|
|
this._db = aDBConnection;
|
2009-07-13 12:19:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
|
|
|
let callback = this._callback;
|
|
|
|
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).
|
|
|
|
*
|
2009-07-22 15:18:34 -07:00
|
|
|
* @param aQueries
|
|
|
|
* The queries to execute asynchronously.
|
|
|
|
* @return a mozIStoragePendingStatement that can be used to cancel the
|
|
|
|
* queries.
|
2009-07-13 12:19:03 -07:00
|
|
|
*/
|
2009-07-22 15:18:34 -07:00
|
|
|
executeAsync: function ACSCW_executeAsync(aQueries)
|
2009-07-13 12:19:03 -07:00
|
|
|
{
|
2009-07-22 15:18:34 -07:00
|
|
|
return this._handle = this._db.executeAsync(aQueries, aQueries.length,
|
|
|
|
this);
|
2009-07-13 12:19:03 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// nsISupports
|
|
|
|
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([
|
|
|
|
Ci.mozIStorageStatementCallback,
|
|
|
|
])
|
|
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// nsPlacesAutoComplete class
|
|
|
|
|
|
|
|
function nsPlacesAutoComplete()
|
|
|
|
{
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Shared Constants for Smart Getters
|
|
|
|
|
2010-08-09 15:14:59 -07:00
|
|
|
// Define common pieces of various queries.
|
2009-07-13 12:19:03 -07:00
|
|
|
// 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...
|
2010-08-09 15:14:59 -07:00
|
|
|
// Note: h.frecency is only selected because we need it for ordering.
|
|
|
|
function sql_base_fragment(aTableName) {
|
|
|
|
return "SELECT h.url, h.title, f.url, " + kBookTagSQLFragment + ", " +
|
|
|
|
"h.visit_count, h.typed, h.id, :query_type, t.open_count, " +
|
|
|
|
"h.frecency " +
|
|
|
|
"FROM " + aTableName + " h " +
|
|
|
|
"LEFT OUTER JOIN moz_favicons f ON f.id = h.favicon_id " +
|
2010-08-13 18:53:05 -07:00
|
|
|
"LEFT OUTER JOIN moz_openpages_temp t ON t.url = h.url " +
|
2010-08-09 15:14:59 -07:00
|
|
|
"WHERE h.frecency <> 0 " +
|
|
|
|
"AND AUTOCOMPLETE_MATCH(:searchString, h.url, " +
|
|
|
|
"IFNULL(bookmark, h.title), tags, " +
|
|
|
|
"h.visit_count, h.typed, parent, " +
|
|
|
|
"t.open_count, " +
|
|
|
|
":matchBehavior, :searchBehavior) " +
|
|
|
|
"{ADDITIONAL_CONDITIONS} ";
|
|
|
|
}
|
|
|
|
const SQL_BASE = sql_base_fragment("moz_places_temp") +
|
|
|
|
"UNION ALL " +
|
|
|
|
sql_base_fragment("moz_places") +
|
|
|
|
"AND +h.id NOT IN (SELECT id FROM moz_places_temp) " +
|
|
|
|
"ORDER BY h.frecency DESC, h.id DESC " +
|
|
|
|
"LIMIT :maxResults";
|
2009-07-13 12:19:03 -07:00
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Smart Getters
|
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "_db", function() {
|
|
|
|
return Cc["@mozilla.org/browser/nav-history-service;1"].
|
|
|
|
getService(Ci.nsPIPlacesDatabase).
|
|
|
|
DBConnection;
|
2009-07-13 12:19:03 -07:00
|
|
|
});
|
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "_bh",
|
|
|
|
"@mozilla.org/browser/global-history;2",
|
|
|
|
"nsIBrowserHistory");
|
2009-07-13 12:19:03 -07:00
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "_textURIService",
|
|
|
|
"@mozilla.org/intl/texttosuburi;1",
|
|
|
|
"nsITextToSubURI");
|
2009-07-13 12:19:03 -07:00
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "_bs",
|
|
|
|
"@mozilla.org/browser/nav-bookmarks-service;1",
|
|
|
|
"nsINavBookmarksService");
|
2009-07-13 12:19:03 -07:00
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "_ioService",
|
|
|
|
"@mozilla.org/network/io-service;1",
|
|
|
|
"nsIIOService");
|
2009-07-13 12:19:03 -07:00
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "_faviconService",
|
|
|
|
"@mozilla.org/browser/favicon-service;1",
|
|
|
|
"nsIFaviconService");
|
2009-07-13 12:19:03 -07:00
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "_defaultQuery", function() {
|
2009-07-13 12:19:03 -07:00
|
|
|
let replacementText = "";
|
2010-08-09 15:14:59 -07:00
|
|
|
return this._db.createStatement(
|
2009-07-13 12:19:03 -07:00
|
|
|
SQL_BASE.replace("{ADDITIONAL_CONDITIONS}", replacementText, "g")
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "_historyQuery", function() {
|
2009-07-13 12:19:03 -07:00
|
|
|
let replacementText = "AND h.visit_count > 0";
|
2010-08-09 15:14:59 -07:00
|
|
|
return this._db.createStatement(
|
2009-07-13 12:19:03 -07:00
|
|
|
SQL_BASE.replace("{ADDITIONAL_CONDITIONS}", replacementText, "g")
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "_bookmarkQuery", function() {
|
2009-07-13 12:19:03 -07:00
|
|
|
let replacementText = "AND bookmark IS NOT NULL";
|
2010-08-09 15:14:59 -07:00
|
|
|
return this._db.createStatement(
|
2009-07-13 12:19:03 -07:00
|
|
|
SQL_BASE.replace("{ADDITIONAL_CONDITIONS}", replacementText, "g")
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "_tagsQuery", function() {
|
2009-07-13 12:19:03 -07:00
|
|
|
let replacementText = "AND tags IS NOT NULL";
|
2010-08-09 15:14:59 -07:00
|
|
|
return this._db.createStatement(
|
2009-07-13 12:19:03 -07:00
|
|
|
SQL_BASE.replace("{ADDITIONAL_CONDITIONS}", replacementText, "g")
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2010-03-26 14:59:02 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "_openPagesQuery", function() {
|
2010-08-09 15:14:59 -07:00
|
|
|
return this._db.createStatement(
|
2010-08-13 18:53:05 -07:00
|
|
|
"/* do not warn (bug 487789) */ " +
|
|
|
|
"SELECT t.url, " +
|
|
|
|
"COALESCE(h_t.title, h.title, t.url) AS c_title, f.url, " +
|
|
|
|
kBookTagSQLFragment + ", " +
|
|
|
|
"IFNULL(h_t.visit_count, h.visit_count) AS c_visit_count, " +
|
|
|
|
"IFNULL(h_t.typed, h.typed) AS c_typed, " +
|
|
|
|
"IFNULL(h_t.id, h.id), :query_type, t.open_count, " +
|
|
|
|
"IFNULL(h_t.frecency, h.frecency) AS c_frecency " +
|
|
|
|
"FROM moz_openpages_temp t " +
|
|
|
|
"LEFT JOIN moz_places_temp h_t ON h_t.url = t.url " +
|
|
|
|
"LEFT JOIN moz_places h ON h.url = t.url " +
|
|
|
|
"LEFT JOIN moz_favicons f ON f.id = IFNULL(h_t.favicon_id, h.favicon_id) " +
|
|
|
|
"WHERE t.open_count > 0 " +
|
|
|
|
"AND AUTOCOMPLETE_MATCH(:searchString, t.url, " +
|
|
|
|
"COALESCE(bookmark, c_title, t.url), tags, " +
|
|
|
|
"c_visit_count, c_typed, parent, " +
|
|
|
|
"t.open_count, " +
|
|
|
|
":matchBehavior, :searchBehavior) " +
|
|
|
|
"ORDER BY c_frecency DESC, t.ROWID DESC " +
|
|
|
|
"LIMIT :maxResults"
|
2010-03-26 14:59:02 -07:00
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "_typedQuery", function() {
|
2009-07-13 12:19:03 -07:00
|
|
|
let replacementText = "AND h.typed = 1";
|
2010-08-09 15:14:59 -07:00
|
|
|
return this._db.createStatement(
|
2009-07-13 12:19:03 -07:00
|
|
|
SQL_BASE.replace("{ADDITIONAL_CONDITIONS}", replacementText, "g")
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "_adaptiveQuery", function() {
|
2009-07-13 12:19:03 -07:00
|
|
|
// In this query, we are taking kBookTagSQLFragment only for h.id because it
|
|
|
|
// uses data from the moz_bookmarks table and we sync tables on bookmark
|
|
|
|
// insert. So, most likely, h.id will always be populated when we have any
|
2010-08-09 15:14:59 -07:00
|
|
|
// bookmark. We still need to join on moz_places_temp for other data (eg.
|
|
|
|
// title).
|
|
|
|
return this._db.createStatement(
|
|
|
|
"/* do not warn (bug 487789) */ " +
|
|
|
|
"SELECT IFNULL(h_t.url, h.url) AS c_url, " +
|
|
|
|
"IFNULL(h_t.title, h.title) AS c_title, f.url, " +
|
|
|
|
kBookTagSQLFragment + ", " +
|
|
|
|
"IFNULL(h_t.visit_count, h.visit_count) AS c_visit_count, " +
|
|
|
|
"IFNULL(h_t.typed, h.typed) AS c_typed, " +
|
|
|
|
"IFNULL(h_t.id, h.id), :query_type, t.open_count, rank " +
|
|
|
|
"FROM ( " +
|
|
|
|
"SELECT ROUND(MAX(((i.input = :search_string) + " +
|
|
|
|
"(SUBSTR(i.input, 1, LENGTH(:search_string)) = :search_string)) * " +
|
|
|
|
"i.use_count), 1) AS rank, place_id " +
|
|
|
|
"FROM moz_inputhistory i " +
|
|
|
|
"GROUP BY i.place_id " +
|
|
|
|
"HAVING rank > 0 " +
|
|
|
|
") AS i " +
|
|
|
|
"LEFT JOIN moz_places h ON h.id = i.place_id " +
|
|
|
|
"LEFT JOIN moz_places_temp h_t ON h_t.id = i.place_id " +
|
|
|
|
"LEFT JOIN moz_favicons f ON f.id = IFNULL(h_t.favicon_id, h.favicon_id) " +
|
2010-08-13 18:53:05 -07:00
|
|
|
"LEFT JOIN moz_openpages_temp t ON t.url = c_url " +
|
2010-08-09 15:14:59 -07:00
|
|
|
"WHERE c_url NOTNULL " +
|
|
|
|
"AND AUTOCOMPLETE_MATCH(:searchString, c_url, " +
|
|
|
|
"IFNULL(bookmark, c_title), tags, " +
|
|
|
|
"c_visit_count, c_typed, parent, " +
|
|
|
|
"t.open_count, " +
|
|
|
|
":matchBehavior, :searchBehavior) " +
|
|
|
|
"ORDER BY rank DESC, IFNULL(h_t.frecency, h.frecency) DESC"
|
2009-07-13 12:19:03 -07:00
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2009-09-02 13:55:16 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "_keywordQuery", function() {
|
2010-08-09 15:14:59 -07:00
|
|
|
return this._db.createStatement(
|
|
|
|
"/* do not warn (bug 487787) */ " +
|
|
|
|
"SELECT IFNULL( " +
|
|
|
|
"(SELECT REPLACE(url, '%s', :query_string) FROM moz_places_temp WHERE id = b.fk), " +
|
|
|
|
"(SELECT REPLACE(url, '%s', :query_string) FROM moz_places WHERE id = b.fk) " +
|
|
|
|
") AS search_url, IFNULL(h_t.title, h.title), " +
|
|
|
|
"COALESCE(f.url, " + best_favicon_for_revhost("moz_places_temp") + "," +
|
|
|
|
best_favicon_for_revhost("moz_places") + "), b.parent, " +
|
|
|
|
"b.title, NULL, IFNULL(h_t.visit_count, h.visit_count), " +
|
|
|
|
"IFNULL(h_t.typed, h.typed), COALESCE(h_t.id, h.id, b.fk), " +
|
|
|
|
":query_type, t.open_count " +
|
|
|
|
"FROM moz_keywords k " +
|
|
|
|
"JOIN moz_bookmarks b ON b.keyword_id = k.id " +
|
|
|
|
"LEFT JOIN moz_places AS h ON h.url = search_url " +
|
|
|
|
"LEFT JOIN moz_places_temp AS h_t ON h_t.url = search_url " +
|
|
|
|
"LEFT JOIN moz_favicons f ON f.id = IFNULL(h_t.favicon_id, h.favicon_id) " +
|
2010-08-13 18:53:05 -07:00
|
|
|
"LEFT JOIN moz_openpages_temp t ON t.url = search_url " +
|
2010-08-09 15:14:59 -07:00
|
|
|
"WHERE LOWER(k.keyword) = LOWER(:keyword) " +
|
|
|
|
"ORDER BY IFNULL(h_t.frecency, h.frecency) DESC"
|
2009-07-13 12:19:03 -07:00
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Initialization
|
|
|
|
|
|
|
|
// load preferences
|
2009-09-29 03:44:00 -07:00
|
|
|
this._prefs = Cc["@mozilla.org/preferences-service;1"].
|
|
|
|
getService(Ci.nsIPrefService).
|
|
|
|
getBranch(kBrowserUrlbarBranch);
|
2009-07-13 12:19:03 -07:00
|
|
|
this._loadPrefs(true);
|
|
|
|
|
|
|
|
// register observers
|
|
|
|
this._os = Cc["@mozilla.org/observer-service;1"].
|
|
|
|
getService(Ci.nsIObserverService);
|
2010-04-22 05:53:54 -07:00
|
|
|
this._os.addObserver(this, kTopicShutdown, false);
|
2009-07-13 12:19:03 -07:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
nsPlacesAutoComplete.prototype = {
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// nsIAutoCompleteSearch
|
|
|
|
|
|
|
|
startSearch: function PAC_startSearch(aSearchString, aSearchParam,
|
|
|
|
aPreviousResult, aListener)
|
|
|
|
{
|
2010-06-21 17:01:35 -07:00
|
|
|
// If a previous query is running and the controller has not taken care
|
|
|
|
// of stopping it, kill it.
|
|
|
|
if ("_pendingQuery" in this)
|
|
|
|
this.stopSearch();
|
|
|
|
|
2009-07-13 12:19:03 -07:00
|
|
|
// 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());
|
|
|
|
|
2010-03-26 14:59:02 -07:00
|
|
|
var searchParamParts = aSearchParam.split(" ");
|
|
|
|
this._enableActions = searchParamParts.indexOf("enable-actions") != -1;
|
|
|
|
|
2009-07-13 12:19:03 -07:00
|
|
|
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.
|
2009-09-10 13:16:42 -07:00
|
|
|
if (this._currentSearchString)
|
|
|
|
this._behavior = this._defaultBehavior;
|
|
|
|
else
|
|
|
|
this._behavior = this._emptySearchDefaultBehavior;
|
2009-07-13 12:19:03 -07:00
|
|
|
|
2010-08-13 18:53:05 -07:00
|
|
|
// For any given search, we run up to four queries:
|
2009-07-13 12:19:03 -07:00
|
|
|
// 1) keywords (this._keywordQuery)
|
|
|
|
// 2) adaptive learning (this._adaptiveQuery)
|
2010-08-13 18:53:05 -07:00
|
|
|
// 3) openPages (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).
|
2009-07-13 12:19:03 -07:00
|
|
|
let {query, tokens} =
|
|
|
|
this._getSearch(this._getUnfilteredSearchTokens(this._currentSearchString));
|
|
|
|
let queries = tokens.length ?
|
2010-08-13 18:53:05 -07:00
|
|
|
[this._getBoundKeywordQuery(tokens), this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(), query] :
|
|
|
|
[this._getBoundAdaptiveQuery(), this._getBoundOpenPagesQuery(), query];
|
2009-07-13 12:19:03 -07:00
|
|
|
|
|
|
|
// Start executing our queries.
|
|
|
|
this._executeQueries(queries);
|
|
|
|
|
|
|
|
// Set up our persistent state for the duration of the search.
|
|
|
|
this._searchTokens = tokens;
|
|
|
|
this._usedPlaceIds = {};
|
|
|
|
},
|
|
|
|
|
|
|
|
stopSearch: function PAC_stopSearch()
|
|
|
|
{
|
|
|
|
// We need to cancel our searches so we do not get any [more] results.
|
2009-09-10 10:55:33 -07:00
|
|
|
// 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();
|
2009-07-13 12:19:03 -07:00
|
|
|
|
|
|
|
this._finishSearch(false);
|
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// nsIAutoCompleteSimpleResultListener
|
|
|
|
|
|
|
|
onValueRemoved: function PAC_onValueRemoved(aResult, aURISpec, aRemoveFromDB)
|
|
|
|
{
|
|
|
|
if (aRemoveFromDB)
|
|
|
|
this._bh.removePage(this._ioService.newURI(aURISpec, null, null));
|
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// 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) {
|
2009-07-22 15:18:34 -07:00
|
|
|
// We have enough results, so stop running our search.
|
|
|
|
this._stopActiveQuery();
|
2009-07-13 12:19:03 -07:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
{
|
2010-08-05 10:06:21 -07:00
|
|
|
Cu.reportError("Places AutoComplete: An async statement encountered an " +
|
|
|
|
"error: " + aError.result + ", '" + aError.message + "'");
|
2009-07-13 12:19:03 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
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;
|
2009-09-23 10:19:27 -07:00
|
|
|
let queries = [
|
|
|
|
this._getBoundAdaptiveQuery(MATCH_ANYWHERE),
|
|
|
|
this._getBoundSearchQuery(MATCH_ANYWHERE, this._searchTokens),
|
|
|
|
];
|
|
|
|
this._executeQueries(queries);
|
2009-07-13 12:19:03 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._finishSearch(true);
|
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// nsIObserver
|
|
|
|
|
|
|
|
observe: function PAC_observe(aSubject, aTopic, aData)
|
|
|
|
{
|
2010-04-22 05:53:54 -07:00
|
|
|
if (aTopic == kTopicShutdown) {
|
|
|
|
this._os.removeObserver(this, kTopicShutdown);
|
2009-07-13 12:19:03 -07:00
|
|
|
|
|
|
|
// Remove our preference observer.
|
2010-01-15 08:40:19 -08:00
|
|
|
this._prefs.QueryInterface(Ci.nsIPrefBranch2).removeObserver("", this);
|
2009-09-29 03:44:00 -07:00
|
|
|
delete this._prefs;
|
2009-07-13 12:19:03 -07:00
|
|
|
|
|
|
|
// Finalize the statements that we have used.
|
|
|
|
let stmts = [
|
|
|
|
"_defaultQuery",
|
|
|
|
"_historyQuery",
|
|
|
|
"_bookmarkQuery",
|
|
|
|
"_tagsQuery",
|
2010-03-26 14:59:02 -07:00
|
|
|
"_openPagesQuery",
|
2009-07-13 12:19:03 -07:00
|
|
|
"_typedQuery",
|
|
|
|
"_adaptiveQuery",
|
|
|
|
"_keywordQuery",
|
|
|
|
];
|
|
|
|
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. __lookupGetter__ returns null if it is
|
|
|
|
// actually a statement.
|
|
|
|
if (!this.__lookupGetter__(stmts[i]))
|
|
|
|
this[stmts[i]].finalize();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (aTopic == kPrefChanged) {
|
|
|
|
this._loadPrefs();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// nsPlacesAutoComplete
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
|
2009-07-24 17:57:26 -07:00
|
|
|
if (uri.indexOf("www.") == 0)
|
|
|
|
uri = uri.slice(4);
|
|
|
|
|
2009-07-13 12:19:03 -07:00
|
|
|
return this._textURIService.unEscapeURIForUI("UTF-8", uri);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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._searchTokens;
|
|
|
|
delete this._listener;
|
|
|
|
delete this._result;
|
|
|
|
delete this._usedPlaceIds;
|
2009-07-22 15:18:34 -07:00
|
|
|
delete this._pendingQuery;
|
2009-07-13 12:19:03 -07:00
|
|
|
this._secondPass = false;
|
2010-03-26 14:59:02 -07:00
|
|
|
this._enableActions = false;
|
2009-07-13 12:19:03 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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).
|
2009-07-22 15:18:34 -07:00
|
|
|
|
|
|
|
// Create our wrapper object and execute the queries.
|
|
|
|
let wrapper = new AutoCompleteStatementCallbackWrapper(this, this._db);
|
|
|
|
this._pendingQuery = wrapper.executeAsync(aQueries);
|
2009-07-13 12:19:03 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2009-07-22 15:18:34 -07:00
|
|
|
* Stops executing our active query.
|
2009-07-13 12:19:03 -07:00
|
|
|
*/
|
2009-07-22 15:18:34 -07:00
|
|
|
_stopActiveQuery: function PAC_stopActiveQuery()
|
2009-07-13 12:19:03 -07:00
|
|
|
{
|
2009-07-22 15:18:34 -07:00
|
|
|
this._pendingQuery.cancel();
|
|
|
|
delete this._pendingQuery;
|
2009-07-13 12:19:03 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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]);
|
|
|
|
result.setDefaultIndex(result.matchCount ? 0 : -1);
|
|
|
|
this._listener.onSearchResult(this, result);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 PAC_loadPrefs(aRegisterObserver)
|
|
|
|
{
|
2009-09-29 03:44:00 -07:00
|
|
|
let self = this;
|
2009-07-13 12:19:03 -07:00
|
|
|
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 {
|
2009-09-29 03:44:00 -07:00
|
|
|
return self._prefs["get" + type + "Pref"](aName);
|
2009-07-13 12:19:03 -07:00
|
|
|
}
|
|
|
|
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", "~");
|
2009-08-11 13:38:10 -07:00
|
|
|
this._restrictTagToken = safeGetter("restrict.tag", "+");
|
2010-03-26 14:59:02 -07:00
|
|
|
this._restrictOpenPageToken = safeGetter("restrict.openpage", "%");
|
2009-07-13 12:19:03 -07:00
|
|
|
this._matchTitleToken = safeGetter("match.title", "#");
|
|
|
|
this._matchURLToken = safeGetter("match.url", "@");
|
|
|
|
this._defaultBehavior = safeGetter("default.behavior", 0);
|
2009-09-10 13:16:42 -07:00
|
|
|
// Further restrictions to apply for "empty searches" (i.e. searches for "").
|
2010-06-14 11:51:53 -07:00
|
|
|
this._emptySearchDefaultBehavior =
|
|
|
|
this._defaultBehavior |
|
|
|
|
safeGetter("default.behavior.emptyRestriction",
|
|
|
|
Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY |
|
|
|
|
Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED);
|
2009-07-13 12:19:03 -07:00
|
|
|
|
|
|
|
// 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) {
|
2009-09-29 03:44:00 -07:00
|
|
|
let pb = this._prefs.QueryInterface(Ci.nsIPrefBranch2);
|
2009-07-13 12:19:03 -07:00
|
|
|
pb.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)
|
|
|
|
{
|
|
|
|
// 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:
|
|
|
|
this._setBehavior("history");
|
|
|
|
break;
|
|
|
|
case this._restrictBookmarkToken:
|
|
|
|
this._setBehavior("bookmark");
|
|
|
|
break;
|
|
|
|
case this._restrictTagToken:
|
|
|
|
this._setBehavior("tag");
|
|
|
|
break;
|
2010-03-26 14:59:02 -07:00
|
|
|
case this._restrictOpenPageToken:
|
|
|
|
if (!this._enableActions)
|
|
|
|
continue;
|
|
|
|
this._setBehavior("openpage");
|
|
|
|
break;
|
2009-07-13 12:19:03 -07:00
|
|
|
case this._matchTitleToken:
|
|
|
|
this._setBehavior("title");
|
|
|
|
break;
|
|
|
|
case this._matchURLToken:
|
|
|
|
this._setBehavior("url");
|
|
|
|
break;
|
|
|
|
case this._restrictTypedToken:
|
|
|
|
this._setBehavior("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
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
{
|
|
|
|
// We use more optimized queries for restricted searches, so we will always
|
|
|
|
// return the most restrictive one to the least restrictive one if more than
|
|
|
|
// one token is found.
|
|
|
|
let query = this._hasBehavior("tag") ? this._tagsQuery :
|
|
|
|
this._hasBehavior("bookmark") ? this._bookmarkQuery :
|
|
|
|
this._hasBehavior("typed") ? this._typedQuery :
|
|
|
|
this._hasBehavior("history") ? this._historyQuery :
|
2010-03-26 14:59:02 -07:00
|
|
|
this._hasBehavior("openpage") ? this._openPagesQuery :
|
2009-07-13 12:19:03 -07:00
|
|
|
this._defaultQuery;
|
|
|
|
|
|
|
|
// Bind the needed parameters to the query so consumers can use it.
|
|
|
|
let (params = query.params) {
|
|
|
|
params.parent = this._bs.tagsFolder;
|
|
|
|
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(" ");
|
2009-07-13 12:19:16 -07:00
|
|
|
|
|
|
|
// 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;
|
2009-07-13 12:19:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return query;
|
|
|
|
},
|
|
|
|
|
2010-08-13 18:53:05 -07:00
|
|
|
_getBoundOpenPagesQuery: function PAC_getBoundOpenPagesQuery()
|
|
|
|
{
|
|
|
|
let query = this._openPagesQuery;
|
|
|
|
|
|
|
|
// Bind the needed parameters to the query so consumers can use it.
|
|
|
|
let (params = query.params) {
|
|
|
|
params.parent = this._bs.tagsFolder;
|
|
|
|
params.query_type = kQueryTypeFiltered;
|
|
|
|
params.matchBehavior = this._matchBehavior;
|
|
|
|
params.searchBehavior = this._behavior;
|
|
|
|
params.searchString = this._currentSearchString;
|
|
|
|
params.maxResults = this._maxRichResults;
|
|
|
|
}
|
|
|
|
|
|
|
|
return query;
|
|
|
|
},
|
|
|
|
|
2009-07-13 12:19:03 -07:00
|
|
|
/**
|
|
|
|
* 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;
|
2009-10-07 01:38:15 -07:00
|
|
|
let queryString = "";
|
|
|
|
let queryIndex = searchString.indexOf(" ");
|
|
|
|
if (queryIndex != -1)
|
|
|
|
queryString = searchString.substring(queryIndex + 1);
|
2009-07-13 12:19:03 -07:00
|
|
|
|
|
|
|
// 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.
|
|
|
|
*/
|
2009-09-23 10:19:27 -07:00
|
|
|
_getBoundAdaptiveQuery: function PAC_getBoundAdaptiveQuery(aMatchBehavior)
|
2009-07-13 12:19:03 -07:00
|
|
|
{
|
2009-09-23 10:19:27 -07:00
|
|
|
// If we were not given a match behavior, use the stored match behavior.
|
|
|
|
if (arguments.length == 0)
|
|
|
|
aMatchBehavior = this._matchBehavior;
|
|
|
|
|
2009-07-13 12:19:03 -07:00
|
|
|
let query = this._adaptiveQuery;
|
|
|
|
let (params = query.params) {
|
|
|
|
params.parent = this._bs.tagsFolder;
|
|
|
|
params.search_string = this._currentSearchString;
|
|
|
|
params.query_type = kQueryTypeFiltered;
|
2009-09-23 10:19:27 -07:00
|
|
|
params.matchBehavior = aMatchBehavior;
|
|
|
|
params.searchBehavior = this._behavior;
|
2009-07-13 12:19:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
if (this._inResults(entryId))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
let escapedEntryURL = aRow.getResultByIndex(kQueryIndexURL);
|
|
|
|
let entryTitle = aRow.getResultByIndex(kQueryIndexTitle) || "";
|
|
|
|
let entryFavicon = aRow.getResultByIndex(kQueryIndexFaviconURL) || "";
|
|
|
|
let entryParentId = aRow.getResultByIndex(kQueryIndexParentId);
|
|
|
|
let entryBookmarkTitle = entryParentId ?
|
|
|
|
aRow.getResultByIndex(kQueryIndexBookmarkTitle) : null;
|
|
|
|
let entryTags = aRow.getResultByIndex(kQueryIndexTags) || "";
|
2010-03-26 14:59:02 -07:00
|
|
|
let openPageCount = aRow.getResultByIndex(kQueryIndexOpenPageCount) || 0;
|
2009-07-13 12:19:03 -07:00
|
|
|
|
|
|
|
// Always prefer the bookmark title unless it is empty
|
|
|
|
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)
|
|
|
|
style = "keyword";
|
|
|
|
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)
|
|
|
|
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
|
2009-09-23 10:19:27 -07:00
|
|
|
// search or because of the user's preferences), so only set it if we
|
|
|
|
// haven't already done so.
|
2009-07-13 12:19:03 -07:00
|
|
|
if (showTags)
|
|
|
|
style = "tag";
|
|
|
|
else if (entryParentId)
|
|
|
|
style = "bookmark";
|
|
|
|
else
|
|
|
|
style = "favicon";
|
|
|
|
}
|
|
|
|
|
2010-06-14 11:51:53 -07:00
|
|
|
// If actions are enabled and the page is open, add only the switch-to-tab
|
|
|
|
// result. Otherwise, add the normal result.
|
2010-08-25 21:01:22 -07:00
|
|
|
let [url, action] = this._enableActions && openPageCount > 0 ?
|
|
|
|
["moz-action:switchtab," + escapedEntryURL, "action "] :
|
|
|
|
[escapedEntryURL, ""];
|
|
|
|
this._addToResults(entryId, url, title, entryFavicon, action + style);
|
2009-07-13 12:19:03 -07:00
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks to see if the given place has already been added to the results.
|
|
|
|
*
|
|
|
|
* @param aPlaceId
|
|
|
|
* The place_id to check for.
|
|
|
|
* @return true if the place has been added, false otherwise.
|
|
|
|
*/
|
|
|
|
_inResults: function PAC_inResults(aPlaceId)
|
|
|
|
{
|
|
|
|
return (aPlaceId in this._usedPlaceIds);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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. _usedPlaceIds is an Object that is being used as a set.
|
|
|
|
this._usedPlaceIds[aPlaceId] = true;
|
|
|
|
|
|
|
|
// Obtain the favicon for this URI.
|
|
|
|
let favicon;
|
|
|
|
if (aFaviconSpec) {
|
|
|
|
let uri = this._ioService.newURI(aFaviconSpec, null, null);
|
|
|
|
favicon = this._faviconService.getFaviconLinkForIcon(uri).spec;
|
|
|
|
}
|
|
|
|
favicon = favicon || this._faviconService.defaultFavicon.spec;
|
|
|
|
|
|
|
|
this._result.appendMatch(aURISpec, aTitle, favicon, aStyle);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if the specified AutoComplete behavior is set.
|
|
|
|
*
|
|
|
|
* @param aType
|
2010-06-14 11:51:53 -07:00
|
|
|
* The behavior type to test for.
|
2009-07-13 12:19:03 -07:00
|
|
|
* @return true if the behavior is set, false otherwise.
|
|
|
|
*/
|
|
|
|
_hasBehavior: function PAC_hasBehavior(aType)
|
|
|
|
{
|
2009-07-14 09:31:37 -07:00
|
|
|
return (this._behavior &
|
|
|
|
Ci.mozIPlacesAutoComplete["BEHAVIOR_" + aType.toUpperCase()]);
|
2009-07-13 12:19:03 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enables the desired AutoComplete behavior.
|
|
|
|
*
|
|
|
|
* @param aType
|
|
|
|
* The behavior type to set.
|
|
|
|
*/
|
|
|
|
_setBehavior: function PAC_setBehavior(aType)
|
|
|
|
{
|
2009-07-14 09:31:37 -07:00
|
|
|
this._behavior |=
|
|
|
|
Ci.mozIPlacesAutoComplete["BEHAVIOR_" + aType.toUpperCase()];
|
2009-07-13 12:19:03 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if we are done searching or not.
|
|
|
|
*
|
|
|
|
* @return true if we have completed searching, false otherwise.
|
|
|
|
*/
|
|
|
|
isSearchComplete: function PAC_isSearchComplete()
|
|
|
|
{
|
2009-07-22 15:18:34 -07:00
|
|
|
// If _pendingQuery is null, we should no longer do any work since we have
|
2009-07-13 12:19:03 -07:00
|
|
|
// already called _finishSearch. This means we completed our search.
|
2009-07-22 15:18:34 -07:00
|
|
|
return this._pendingQuery == null;
|
2009-07-13 12:19:03 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
{
|
2009-07-22 15:18:34 -07:00
|
|
|
return this._pendingQuery == aHandle;
|
2009-07-13 12:19:03 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
//// nsISupports
|
|
|
|
|
|
|
|
classID: Components.ID("d0272978-beab-4adc-a3d4-04b76acfa4e7"),
|
|
|
|
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([
|
|
|
|
Ci.nsIAutoCompleteSearch,
|
|
|
|
Ci.nsIAutoCompleteSimpleResultListener,
|
|
|
|
Ci.mozIStorageStatementCallback,
|
|
|
|
Ci.nsIObserver,
|
|
|
|
])
|
|
|
|
};
|
|
|
|
|
|
|
|
let components = [nsPlacesAutoComplete];
|
2010-06-25 19:17:31 -07:00
|
|
|
const NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
|