Bug 320466 r=annie.sullivan Move Autocomplete code from nsNavHistory.cpp to new

file nsNavHistoryAutoComplete.cpp (no changes to code)

This file was copied in CVS from the following location:
mozilla/browser/components/places/src/nsNavHistoryAutoComplete.cpp
Original committer: brettw%gmail.com
Original revision: 1.1
Original date: 2005/12/16 00:16:14
This commit is contained in:
benjamin%smedbergs.us 2006-07-18 15:54:59 +00:00
Родитель 226b2c6769
Коммит b08d3344e2
1 изменённых файлов: 506 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,506 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 Places code.
*
* The Initial Developer of the Original Code is
* Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2005
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Brett Wilson <brettw@gmail.com>
* Joe Hewitt <hewitt@netscape.com>
* Blake Ross <blaker@netscape.com>
*
* 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 ***** */
#include "nsNavHistory.h"
#include "mozIStorageService.h"
#include "mozIStorageConnection.h"
#include "mozIStorageValueArray.h"
#include "mozIStorageStatement.h"
#include "mozIStorageFunction.h"
#include "mozStorageCID.h"
#include "mozStorageHelper.h"
#define NS_AUTOCOMPLETESIMPLERESULT_CONTRACTID \
"@mozilla.org/autocomplete/simple-result;1"
PRInt32 ComputeAutoCompletePriority(const nsAString& aUrl, PRInt32 aVisitCount,
PRBool aWasTyped);
// nsIAutoCompleteSearch *******************************************************
// AutoCompleteIntermediateResult/Set
//
// This class holds intermediate autocomplete results so that they can be
// sorted. This list is then handed off to a result using FillResult. The
// major reason for this is so that we can use nsArray's sorting functions,
// not use COM, yet have well-defined lifetimes for the objects. This uses
// a void array, but makes sure to delete the objects on desctruction.
struct AutoCompleteIntermediateResult
{
nsString url;
nsString title;
PRUint32 priority;
};
class AutoCompleteIntermediateResultSet
{
public:
AutoCompleteIntermediateResultSet()
{
}
~AutoCompleteIntermediateResultSet()
{
for (PRInt32 i = 0; i < mArray.Count(); i ++) {
AutoCompleteIntermediateResult* cur = NS_STATIC_CAST(
AutoCompleteIntermediateResult*, mArray[i]);
delete cur;
}
}
PRBool Add(const nsString& aURL, const nsString& aTitle, PRInt32 aPriority)
{
AutoCompleteIntermediateResult* cur = new AutoCompleteIntermediateResult;
if (! cur)
return PR_FALSE;
cur->url = aURL;
cur->title = aTitle;
cur->priority = aPriority;
mArray.AppendElement(cur);
return PR_TRUE;
}
void Sort(nsNavHistory* navHistory)
{
mArray.Sort(nsNavHistory::AutoCompleteSortComparison, navHistory);
}
nsresult FillResult(nsIAutoCompleteSimpleResult* aResult)
{
for (PRInt32 i = 0; i < mArray.Count(); i ++)
{
AutoCompleteIntermediateResult* cur = NS_STATIC_CAST(
AutoCompleteIntermediateResult*, mArray[i]);
nsresult rv = aResult->AppendMatch(cur->url, cur->title);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
protected:
nsVoidArray mArray;
};
// nsNavHistory::StartSearch
//
NS_IMETHODIMP
nsNavHistory::StartSearch(const nsAString & aSearchString,
const nsAString & aSearchParam,
nsIAutoCompleteResult *aPreviousResult,
nsIAutoCompleteObserver *aListener)
{
nsresult rv;
NS_ENSURE_ARG_POINTER(aListener);
nsCOMPtr<nsIAutoCompleteSimpleResult> result =
do_CreateInstance(NS_AUTOCOMPLETESIMPLERESULT_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
result->SetSearchString(aSearchString);
PRBool usePrevious = (aPreviousResult != nsnull);
// throw away previous results if the search string is empty after it has had
// the prefixes removed: if the user types "http://" throw away the search
// for "http:/" and start fresh with the new query.
if (usePrevious) {
nsAutoString cut(aSearchString);
AutoCompleteCutPrefix(&cut, nsnull);
if (cut.IsEmpty())
usePrevious = PR_FALSE;
}
// throw away previous results if the new string doesn't begin with the
// previous search string
if (usePrevious) {
nsAutoString previousSearch;
aPreviousResult->GetSearchString(previousSearch);
if (previousSearch.Length() > aSearchString.Length() ||
!Substring(aSearchString, 0, previousSearch.Length()).Equals(previousSearch)) {
usePrevious = PR_FALSE;
}
}
if (aSearchString.IsEmpty()) {
rv = AutoCompleteTypedSearch(result);
} else if (usePrevious) {
rv = AutoCompleteRefineHistorySearch(aSearchString, aPreviousResult, result);
} else {
rv = AutoCompleteFullHistorySearch(aSearchString, result);
}
NS_ENSURE_SUCCESS(rv, rv);
// Determine the result of the search
PRUint32 count;
result->GetMatchCount(&count);
if (count > 0) {
result->SetSearchResult(nsIAutoCompleteResult::RESULT_SUCCESS);
result->SetDefaultIndex(0);
} else {
result->SetSearchResult(nsIAutoCompleteResult::RESULT_NOMATCH);
result->SetDefaultIndex(-1);
}
aListener->OnSearchResult(this, result);
return NS_OK;
}
// nsNavHistory::StopSearch
NS_IMETHODIMP
nsNavHistory::StopSearch()
{
return NS_OK;
}
// nsNavHistory::AutoCompleteTypedSearch
//
// Called when there is no search string. This happens when you press
// down arrow from the URL bar: the most recent things you typed are listed.
//
// Ordering here is simpler because there are no boosts for typing, and there
// is no URL information to use. The ordering just comes out of the DB by
// visit count (primary) and time since last visited (secondary).
nsresult nsNavHistory::AutoCompleteTypedSearch(
nsIAutoCompleteSimpleResult* result)
{
// note: need to test against hidden = 1 since the column can also be null
// (meaning not hidden)
nsCOMPtr<mozIStorageStatement> dbSelectStatement;
nsresult rv = mDBConn->CreateStatement(
NS_LITERAL_CSTRING("SELECT url,title FROM moz_history WHERE typed = 1 AND hidden <> 1 ORDER BY visit_count DESC LIMIT 1000"),
getter_AddRefs(dbSelectStatement));
NS_ENSURE_SUCCESS(rv, rv);
PRBool hasMore = PR_FALSE;
while (NS_SUCCEEDED(dbSelectStatement->ExecuteStep(&hasMore)) && hasMore) {
nsAutoString entryURL, entryTitle;
dbSelectStatement->GetString(0, entryURL);
dbSelectStatement->GetString(1, entryTitle);
rv = result->AppendMatch(entryURL, entryTitle);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
// nsNavHistory::AutoCompleteFullHistorySearch
//
// A brute-force search of the entire history. This matches the given input
// with every possible history entry, and sorts them by likelihood.
//
// This may be slow for people on slow computers with large histories.
nsresult
nsNavHistory::AutoCompleteFullHistorySearch(const nsAString& aSearchString,
nsIAutoCompleteSimpleResult* result)
{
// figure out whether any known prefixes are present in the search string
// so we know not to ignore them when comparing results later
AutoCompleteExclude exclude;
AutoCompleteGetExcludeInfo(aSearchString, &exclude);
mozStorageStatementScoper scoper(mDBFullAutoComplete);
nsresult rv = mDBFullAutoComplete->BindInt32Parameter(0, mAutoCompleteMaxCount);
NS_ENSURE_SUCCESS(rv, rv);
// load the intermediate result so it can be sorted
AutoCompleteIntermediateResultSet resultSet;
PRBool hasMore = PR_FALSE;
while (NS_SUCCEEDED(mDBFullAutoComplete->ExecuteStep(&hasMore)) && hasMore) {
PRBool typed = mDBFullAutoComplete->AsInt32(kAutoCompleteIndex_Typed);
if (!typed) {
if (mAutoCompleteOnlyTyped)
continue;
}
nsAutoString entryurl;
mDBFullAutoComplete->GetString(kAutoCompleteIndex_URL, entryurl);
if (AutoCompleteCompare(entryurl, aSearchString, exclude)) {
nsAutoString entrytitle;
rv = mDBFullAutoComplete->GetString(kAutoCompleteIndex_Title, entrytitle);
NS_ENSURE_SUCCESS(rv, rv);
PRInt32 priority = ComputeAutoCompletePriority(entryurl,
mDBFullAutoComplete->AsInt32(kAutoCompleteIndex_VisitCount),
typed);
if (! resultSet.Add(entryurl, entrytitle, priority))
return NS_ERROR_OUT_OF_MEMORY;
}
}
// sort and populate the final result
resultSet.Sort(this);
return resultSet.FillResult(result);
}
// nsNavHistory::AutoCompleteRefineHistorySearch
//
// Called when a previous autocomplete result exists and we are adding to
// the query (making it more specific). The list is already nicely sorted,
// so we can just remove all the old elements that DON'T match the new query.
nsresult
nsNavHistory::AutoCompleteRefineHistorySearch(
const nsAString& aSearchString,
nsIAutoCompleteResult* aPreviousResult,
nsIAutoCompleteSimpleResult* aNewResult)
{
// figure out whether any known prefixes are present in the search string
// so we know not to ignore them when comparing results later
AutoCompleteExclude exclude;
AutoCompleteGetExcludeInfo(aSearchString, &exclude);
PRUint32 matchCount;
aPreviousResult->GetMatchCount(&matchCount);
for (PRUint32 i = 0; i < matchCount; i ++) {
nsAutoString cururl;
nsresult rv = aPreviousResult->GetValueAt(i, cururl);
NS_ENSURE_SUCCESS(rv, rv);
if (AutoCompleteCompare(cururl, aSearchString, exclude)) {
nsAutoString curcomment;
rv = aPreviousResult->GetCommentAt(i, curcomment);
NS_ENSURE_SUCCESS(rv, rv);
rv = aNewResult->AppendMatch(cururl, curcomment);
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
// nsNavHistory::AutoCompleteGetExcludeInfo
//
// If aURL begins with a protocol or domain prefix from our lists, then mark
// their index in an AutocompleteExclude struct.
void
nsNavHistory::AutoCompleteGetExcludeInfo(const nsAString& aURL,
AutoCompleteExclude* aExclude)
{
aExclude->schemePrefix = -1;
aExclude->hostnamePrefix = -1;
PRInt32 index = 0;
PRInt32 i;
for (i = 0; i < mIgnoreSchemes.Count(); ++i) {
nsString* string = mIgnoreSchemes.StringAt(i);
if (Substring(aURL, 0, string->Length()).Equals(*string)) {
aExclude->schemePrefix = i;
index = string->Length();
break;
}
}
for (i = 0; i < mIgnoreHostnames.Count(); ++i) {
nsString* string = mIgnoreHostnames.StringAt(i);
if (Substring(aURL, index, string->Length()).Equals(*string)) {
aExclude->hostnamePrefix = i;
index += string->Length();
break;
}
}
aExclude->postPrefixOffset = index;
}
// nsNavHistory::AutoCompleteCutPrefix
//
// Cut any protocol and domain prefixes from aURL, except for those which
// are specified in aExclude.
//
// aExclude can be NULL to cut all possible prefixes
//
// This comparison is case-sensitive. Therefore, it assumes that aUserURL
// is a potential URL whose host name is in all lower case.
//
// The aExclude identifies which prefixes were present in the user's typed
// entry, which we DON'T want to cut so that it can match the user input.
// For example. Typed "xyz.com" matches "xyz.com" AND "http://www.xyz.com"
// but "www.xyz.com" still matches "www.xyz.com".
void
nsNavHistory::AutoCompleteCutPrefix(nsString* aURL,
const AutoCompleteExclude* aExclude)
{
PRInt32 idx = 0;
PRInt32 i;
for (i = 0; i < mIgnoreSchemes.Count(); ++i) {
if (aExclude && i == aExclude->schemePrefix)
continue;
nsString* string = mIgnoreSchemes.StringAt(i);
if (Substring(*aURL, 0, string->Length()).Equals(*string)) {
idx = string->Length();
break;
}
}
if (idx > 0)
aURL->Cut(0, idx);
idx = 0;
for (i = 0; i < mIgnoreHostnames.Count(); ++i) {
if (aExclude && i == aExclude->hostnamePrefix)
continue;
nsString* string = mIgnoreHostnames.StringAt(i);
if (Substring(*aURL, 0, string->Length()).Equals(*string)) {
idx = string->Length();
break;
}
}
if (idx > 0)
aURL->Cut(0, idx);
}
// nsNavHistory::AutoCompleteCompare
//
// Determines if a given URL from the history matches the user input.
// See AutoCompleteCutPrefix for the meaning of aExclude.
PRBool
nsNavHistory::AutoCompleteCompare(const nsAString& aHistoryURL,
const nsAString& aUserURL,
const AutoCompleteExclude& aExclude)
{
nsAutoString cutHistoryURL(aHistoryURL);
AutoCompleteCutPrefix(&cutHistoryURL, &aExclude);
return StringBeginsWith(cutHistoryURL, aUserURL);
}
// nsGlobalHistory::AutoCompleteSortComparison
//
// The design and reasoning behind the following autocomplete sort
// implementation is documented in bug 78270. In most cases, the precomputed
// priorities (by ComputeAutoCompletePriority) are used without further
// computation.
PRInt32 PR_CALLBACK // static
nsNavHistory::AutoCompleteSortComparison(const void* match1Void,
const void* match2Void,
void *navHistoryVoid)
{
// get our class pointer (this is a static function)
nsNavHistory* navHistory = NS_STATIC_CAST(nsNavHistory*, navHistoryVoid);
const AutoCompleteIntermediateResult* match1 = NS_STATIC_CAST(
const AutoCompleteIntermediateResult*, match1Void);
const AutoCompleteIntermediateResult* match2 = NS_STATIC_CAST(
const AutoCompleteIntermediateResult*, match2Void);
// In most cases the priorities will be different and we just use them
if (match1->priority != match2->priority)
{
return match2->priority - match1->priority;
}
else
{
// secondary sorting gives priority to site names and paths (ending in a /)
PRBool isPath1 = PR_FALSE, isPath2 = PR_FALSE;
if (!match1->url.IsEmpty())
isPath1 = (match1->url.Last() == PRUnichar('/'));
if (!match2->url.IsEmpty())
isPath2 = (match2->url.Last() == PRUnichar('/'));
if (isPath1 && !isPath2) return -1; // match1->url is a website/path, match2->url isn't
if (!isPath1 && isPath2) return 1; // match1->url isn't a website/path, match2->url is
// find the prefixes so we can sort by the stuff after the prefixes
AutoCompleteExclude prefix1, prefix2;
navHistory->AutoCompleteGetExcludeInfo(match1->url, &prefix1);
navHistory->AutoCompleteGetExcludeInfo(match2->url, &prefix2);
// compare non-prefixed urls
PRInt32 ret = Compare(
Substring(match1->url, prefix1.postPrefixOffset, match1->url.Length()),
Substring(match2->url, prefix2.postPrefixOffset, match2->url.Length()));
if (ret != 0)
return ret;
// sort http://xyz.com before http://www.xyz.com
return prefix1.postPrefixOffset - prefix2.postPrefixOffset;
}
return 0;
}
// ComputeAutoCompletePriority
//
// Favor websites and webpaths more than webpages by boosting
// their visit counts. This assumes that URLs have been normalized,
// appending a trailing '/'.
//
// We use addition to boost the visit count rather than multiplication
// since we want URLs with large visit counts to remain pretty much
// in raw visit count order - we assume the user has visited these urls
// often for a reason and there shouldn't be a problem with putting them
// high in the autocomplete list regardless of whether they are sites/
// paths or pages. However for URLs visited only a few times, sites
// & paths should be presented before pages since they are generally
// more likely to be visited again.
PRInt32
ComputeAutoCompletePriority(const nsAString& aUrl, PRInt32 aVisitCount,
PRBool aWasTyped)
{
PRInt32 aPriority = aVisitCount;
if (!aUrl.IsEmpty()) {
// url is a site/path if it has a trailing slash
if (aUrl.Last() == PRUnichar('/'))
aPriority += AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST;
}
if (aWasTyped)
aPriority += AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST;
return aPriority;
}