gecko-dev/toolkit/components/places/SQLFunctions.cpp

1068 строки
36 KiB
C++

/* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
* 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/. */
#include "mozilla/storage.h"
#include "nsString.h"
#include "nsUnicharUtils.h"
#include "nsWhitespaceTokenizer.h"
#include "nsEscape.h"
#include "mozIPlacesAutoComplete.h"
#include "SQLFunctions.h"
#include "nsMathUtils.h"
#include "nsUnicodeProperties.h"
#include "nsUTF8Utils.h"
#include "nsINavHistoryService.h"
#include "nsPrintfCString.h"
#include "nsNavHistory.h"
#include "mozilla/Likely.h"
#include "nsVariant.h"
#include "mozilla/HashFunctions.h"
#include <algorithm>
// Maximum number of chars to search through.
// MatchAutoCompleteFunction won't look for matches over this threshold.
#define MAX_CHARS_TO_SEARCH_THROUGH 255
// Maximum number of chars to use for calculating hashes. This value has been
// picked to ensure low hash collisions on a real world common places.sqlite.
// While collisions are not a big deal for functionality, a low ratio allows
// for slightly more efficient SELECTs.
#define MAX_CHARS_TO_HASH 1500U
using namespace mozilla::storage;
////////////////////////////////////////////////////////////////////////////////
//// Anonymous Helpers
namespace {
typedef nsACString::const_char_iterator const_char_iterator;
/**
* Scan forward through UTF-8 text until the next potential character that
* could match a given codepoint when lower-cased (false positives are okay).
* This avoids having to actually parse the UTF-8 text, which is slow.
*
* @param aStart
* An iterator pointing to the first character position considered.
* @param aEnd
* An interator pointing to past-the-end of the string.
*
* @return An iterator pointing to the first potential matching character
* within the range [aStart, aEnd).
*/
static
MOZ_ALWAYS_INLINE const_char_iterator
nextSearchCandidate(const_char_iterator aStart,
const_char_iterator aEnd,
uint32_t aSearchFor)
{
const_char_iterator cur = aStart;
// If the character we search for is ASCII, then we can scan until we find
// it or its ASCII uppercase character, modulo the special cases
// U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE and U+212A KELVIN SIGN
// (which are the only non-ASCII characters that lower-case to ASCII ones).
// Since false positives are okay, we approximate ASCII lower-casing by
// bit-ORing with 0x20, for increased performance.
//
// If the character we search for is *not* ASCII, we can ignore everything
// that is, since all ASCII characters lower-case to ASCII.
//
// Because of how UTF-8 uses high-order bits, this will never land us
// in the middle of a codepoint.
//
// The assumptions about Unicode made here are verified in the test_casing
// gtest.
if (aSearchFor < 128) {
// When searching for I or K, we pick out the first byte of the UTF-8
// encoding of the corresponding special case character, and look for it
// in the loop below. For other characters we fall back to 0xff, which
// is not a valid UTF-8 byte.
unsigned char target = (unsigned char)(aSearchFor | 0x20);
unsigned char special = 0xff;
if (target == 'i' || target == 'k') {
special = (target == 'i' ? 0xc4 : 0xe2);
}
while (cur < aEnd && (unsigned char)(*cur | 0x20) != target &&
(unsigned char)*cur != special) {
cur++;
}
} else {
const_char_iterator cur = aStart;
while (cur < aEnd && (unsigned char)(*cur) < 128) {
cur++;
}
}
return cur;
}
/**
* Check whether a character position is on a word boundary of a UTF-8 string
* (rather than within a word). We define "within word" to be any position
* between [a-zA-Z] and [a-z] -- this lets us match CamelCase words.
* TODO: support non-latin alphabets.
*
* @param aPos
* An iterator pointing to the character position considered. It must
* *not* be the first byte of a string.
*
* @return true if boundary, false otherwise.
*/
static
MOZ_ALWAYS_INLINE bool
isOnBoundary(const_char_iterator aPos) {
if ('a' <= *aPos && *aPos <= 'z') {
char prev = *(aPos - 1) | 0x20;
return !('a' <= prev && prev <= 'z');
}
return true;
}
/**
* Check whether a token string matches a particular position of a source
* string, case insensitively.
*
* @param aTokenStart
* An iterator pointing to the start of the token string.
* @param aTokenEnd
* An iterator pointing past-the-end of the token string.
* @param aSourceStart
* An iterator pointing to the position of source string to start
* matching at.
* @param aSourceEnd
* An iterator pointing past-the-end of the source string.
*
* @return true if the string [aTokenStart, aTokenEnd) matches the start of
* the string [aSourceStart, aSourceEnd, false otherwise.
*/
static
MOZ_ALWAYS_INLINE bool
stringMatch(const_char_iterator aTokenStart,
const_char_iterator aTokenEnd,
const_char_iterator aSourceStart,
const_char_iterator aSourceEnd)
{
const_char_iterator tokenCur = aTokenStart, sourceCur = aSourceStart;
while (tokenCur < aTokenEnd) {
if (sourceCur >= aSourceEnd) {
return false;
}
bool error;
if (!CaseInsensitiveUTF8CharsEqual(sourceCur, tokenCur,
aSourceEnd, aTokenEnd,
&sourceCur, &tokenCur, &error)) {
return false;
}
}
return true;
}
enum FindInStringBehavior {
eFindOnBoundary,
eFindAnywhere
};
/**
* Common implementation for findAnywhere and findOnBoundary.
*
* @param aToken
* The token we're searching for
* @param aSourceString
* The string in which we're searching
* @param aBehavior
* eFindOnBoundary if we should only consider matchines which occur on
* word boundaries, or eFindAnywhere if we should consider matches
* which appear anywhere.
*
* @return true if aToken was found in aSourceString, false otherwise.
*/
static
bool
findInString(const nsDependentCSubstring &aToken,
const nsACString &aSourceString,
FindInStringBehavior aBehavior)
{
// GetLowerUTF8Codepoint assumes that there's at least one byte in
// the string, so don't pass an empty token here.
NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!");
// We cannot match anything if there is nothing to search.
if (aSourceString.IsEmpty()) {
return false;
}
const_char_iterator tokenStart(aToken.BeginReading()),
tokenEnd(aToken.EndReading()),
tokenNext,
sourceStart(aSourceString.BeginReading()),
sourceEnd(aSourceString.EndReading()),
sourceCur(sourceStart),
sourceNext;
uint32_t tokenFirstChar =
GetLowerUTF8Codepoint(tokenStart, tokenEnd, &tokenNext);
if (tokenFirstChar == uint32_t(-1)) {
return false;
}
for (;;) {
// Scan forward to the next viable candidate (if any).
sourceCur = nextSearchCandidate(sourceCur, sourceEnd, tokenFirstChar);
if (sourceCur == sourceEnd) {
break;
}
// Check whether the first character in the token matches the character
// at sourceCur. At the same time, get a pointer to the next character
// in the source.
uint32_t sourceFirstChar =
GetLowerUTF8Codepoint(sourceCur, sourceEnd, &sourceNext);
if (sourceFirstChar == uint32_t(-1)) {
return false;
}
if (sourceFirstChar == tokenFirstChar &&
(aBehavior != eFindOnBoundary || sourceCur == sourceStart ||
isOnBoundary(sourceCur)) &&
stringMatch(tokenNext, tokenEnd, sourceNext, sourceEnd))
{
return true;
}
sourceCur = sourceNext;
}
return false;
}
static
MOZ_ALWAYS_INLINE nsDependentCString
getSharedUTF8String(mozIStorageValueArray* aValues, uint32_t aIndex) {
uint32_t len;
const char* str = aValues->AsSharedUTF8String(aIndex, &len);
if (!str) {
return nsDependentCString("", (uint32_t)0);
}
return nsDependentCString(str, len);
}
} // End anonymous namespace
namespace mozilla {
namespace places {
////////////////////////////////////////////////////////////////////////////////
//// AutoComplete Matching Function
/* static */
nsresult
MatchAutoCompleteFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<MatchAutoCompleteFunction> function =
new MatchAutoCompleteFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("autocomplete_match"), kArgIndexLength, function
);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
/* static */
nsDependentCSubstring
MatchAutoCompleteFunction::fixupURISpec(const nsACString &aURISpec,
int32_t aMatchBehavior,
nsACString &aSpecBuf)
{
nsDependentCSubstring fixedSpec;
// Try to unescape the string. If that succeeds and yields a different
// string which is also valid UTF-8, we'll use it.
// Otherwise, we will simply use our original string.
bool unescaped = NS_UnescapeURL(aURISpec.BeginReading(),
aURISpec.Length(), esc_SkipControl, aSpecBuf);
if (unescaped && IsUTF8(aSpecBuf)) {
fixedSpec.Rebind(aSpecBuf, 0);
} else {
fixedSpec.Rebind(aURISpec, 0);
}
if (aMatchBehavior == mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED)
return fixedSpec;
if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("http://"))) {
fixedSpec.Rebind(fixedSpec, 7);
} else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("https://"))) {
fixedSpec.Rebind(fixedSpec, 8);
} else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("ftp://"))) {
fixedSpec.Rebind(fixedSpec, 6);
}
if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("www."))) {
fixedSpec.Rebind(fixedSpec, 4);
}
return fixedSpec;
}
/* static */
bool
MatchAutoCompleteFunction::findAnywhere(const nsDependentCSubstring &aToken,
const nsACString &aSourceString)
{
// We can't use FindInReadable here; it works only for ASCII.
return findInString(aToken, aSourceString, eFindAnywhere);
}
/* static */
bool
MatchAutoCompleteFunction::findOnBoundary(const nsDependentCSubstring &aToken,
const nsACString &aSourceString)
{
return findInString(aToken, aSourceString, eFindOnBoundary);
}
/* static */
bool
MatchAutoCompleteFunction::findBeginning(const nsDependentCSubstring &aToken,
const nsACString &aSourceString)
{
NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!");
// We can't use StringBeginsWith here, unfortunately. Although it will
// happily take a case-insensitive UTF8 comparator, it eventually calls
// nsACString::Equals, which checks that the two strings contain the same
// number of bytes before calling the comparator. Two characters may be
// case-insensitively equal while taking up different numbers of bytes, so
// this is not what we want.
const_char_iterator tokenStart(aToken.BeginReading()),
tokenEnd(aToken.EndReading()),
sourceStart(aSourceString.BeginReading()),
sourceEnd(aSourceString.EndReading());
bool dummy;
while (sourceStart < sourceEnd &&
CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart,
sourceEnd, tokenEnd,
&sourceStart, &tokenStart, &dummy)) {
// We found the token!
if (tokenStart >= tokenEnd) {
return true;
}
}
// We don't need to check CaseInsensitiveUTF8CharsEqual's error condition
// (stored in |dummy|), since the function will return false if it
// encounters an error.
return false;
}
/* static */
bool
MatchAutoCompleteFunction::findBeginningCaseSensitive(
const nsDependentCSubstring &aToken,
const nsACString &aSourceString)
{
NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!");
return StringBeginsWith(aSourceString, aToken);
}
/* static */
MatchAutoCompleteFunction::searchFunctionPtr
MatchAutoCompleteFunction::getSearchFunction(int32_t aBehavior)
{
switch (aBehavior) {
case mozIPlacesAutoComplete::MATCH_ANYWHERE:
case mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED:
return findAnywhere;
case mozIPlacesAutoComplete::MATCH_BEGINNING:
return findBeginning;
case mozIPlacesAutoComplete::MATCH_BEGINNING_CASE_SENSITIVE:
return findBeginningCaseSensitive;
case mozIPlacesAutoComplete::MATCH_BOUNDARY:
default:
return findOnBoundary;
};
}
NS_IMPL_ISUPPORTS(
MatchAutoCompleteFunction,
mozIStorageFunction
)
MatchAutoCompleteFunction::MatchAutoCompleteFunction()
: mCachedZero(new IntegerVariant(0))
, mCachedOne(new IntegerVariant(1))
{
static_assert(IntegerVariant::HasThreadSafeRefCnt::value,
"Caching assumes that variants have thread-safe refcounting");
}
NS_IMETHODIMP
MatchAutoCompleteFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
nsIVariant **_result)
{
// Macro to make the code a bit cleaner and easier to read. Operates on
// searchBehavior.
int32_t searchBehavior = aArguments->AsInt32(kArgIndexSearchBehavior);
#define HAS_BEHAVIOR(aBitName) \
(searchBehavior & mozIPlacesAutoComplete::BEHAVIOR_##aBitName)
nsDependentCString searchString =
getSharedUTF8String(aArguments, kArgSearchString);
nsDependentCString url =
getSharedUTF8String(aArguments, kArgIndexURL);
int32_t matchBehavior = aArguments->AsInt32(kArgIndexMatchBehavior);
// We only want to filter javascript: URLs if we are not supposed to search
// for them, and the search does not start with "javascript:".
if (matchBehavior != mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED &&
StringBeginsWith(url, NS_LITERAL_CSTRING("javascript:")) &&
!HAS_BEHAVIOR(JAVASCRIPT) &&
!StringBeginsWith(searchString, NS_LITERAL_CSTRING("javascript:"))) {
NS_ADDREF(*_result = mCachedZero);
return NS_OK;
}
int32_t visitCount = aArguments->AsInt32(kArgIndexVisitCount);
bool typed = aArguments->AsInt32(kArgIndexTyped) ? true : false;
bool bookmark = aArguments->AsInt32(kArgIndexBookmark) ? true : false;
nsDependentCString tags = getSharedUTF8String(aArguments, kArgIndexTags);
int32_t openPageCount = aArguments->AsInt32(kArgIndexOpenPageCount);
bool matches = false;
if (HAS_BEHAVIOR(RESTRICT)) {
// Make sure we match all the filter requirements. If a given restriction
// is active, make sure the corresponding condition is not true.
matches = (!HAS_BEHAVIOR(HISTORY) || visitCount > 0) &&
(!HAS_BEHAVIOR(TYPED) || typed) &&
(!HAS_BEHAVIOR(BOOKMARK) || bookmark) &&
(!HAS_BEHAVIOR(TAG) || !tags.IsVoid()) &&
(!HAS_BEHAVIOR(OPENPAGE) || openPageCount > 0);
} else {
// Make sure that we match all the filter requirements and that the
// corresponding condition is true if at least a given restriction is active.
matches = (HAS_BEHAVIOR(HISTORY) && visitCount > 0) ||
(HAS_BEHAVIOR(TYPED) && typed) ||
(HAS_BEHAVIOR(BOOKMARK) && bookmark) ||
(HAS_BEHAVIOR(TAG) && !tags.IsVoid()) ||
(HAS_BEHAVIOR(OPENPAGE) && openPageCount > 0);
}
if (!matches) {
NS_ADDREF(*_result = mCachedZero);
return NS_OK;
}
// Obtain our search function.
searchFunctionPtr searchFunction = getSearchFunction(matchBehavior);
// Clean up our URI spec and prepare it for searching.
nsCString fixedUrlBuf;
nsDependentCSubstring fixedUrl =
fixupURISpec(url, matchBehavior, fixedUrlBuf);
// Limit the number of chars we search through.
const nsDependentCSubstring& trimmedUrl =
Substring(fixedUrl, 0, MAX_CHARS_TO_SEARCH_THROUGH);
nsDependentCString title = getSharedUTF8String(aArguments, kArgIndexTitle);
// Limit the number of chars we search through.
const nsDependentCSubstring& trimmedTitle =
Substring(title, 0, MAX_CHARS_TO_SEARCH_THROUGH);
// Determine if every token matches either the bookmark title, tags, page
// title, or page URL.
nsCWhitespaceTokenizer tokenizer(searchString);
while (matches && tokenizer.hasMoreTokens()) {
const nsDependentCSubstring &token = tokenizer.nextToken();
if (HAS_BEHAVIOR(TITLE) && HAS_BEHAVIOR(URL)) {
matches = (searchFunction(token, trimmedTitle) ||
searchFunction(token, tags)) &&
searchFunction(token, trimmedUrl);
}
else if (HAS_BEHAVIOR(TITLE)) {
matches = searchFunction(token, trimmedTitle) ||
searchFunction(token, tags);
}
else if (HAS_BEHAVIOR(URL)) {
matches = searchFunction(token, trimmedUrl);
}
else {
matches = searchFunction(token, trimmedTitle) ||
searchFunction(token, tags) ||
searchFunction(token, trimmedUrl);
}
}
NS_ADDREF(*_result = (matches ? mCachedOne : mCachedZero));
return NS_OK;
#undef HAS_BEHAVIOR
}
////////////////////////////////////////////////////////////////////////////////
//// Frecency Calculation Function
/* static */
nsresult
CalculateFrecencyFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<CalculateFrecencyFunction> function =
new CalculateFrecencyFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("calculate_frecency"), -1, function
);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(
CalculateFrecencyFunction,
mozIStorageFunction
)
NS_IMETHODIMP
CalculateFrecencyFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
nsIVariant **_result)
{
// Fetch arguments. Use default values if they were omitted.
uint32_t numEntries;
nsresult rv = aArguments->GetNumEntries(&numEntries);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(numEntries <= 2, "unexpected number of arguments");
int64_t pageId = aArguments->AsInt64(0);
MOZ_ASSERT(pageId > 0, "Should always pass a valid page id");
if (pageId <= 0) {
NS_ADDREF(*_result = new IntegerVariant(0));
return NS_OK;
}
enum RedirectState {
eRedirectUnknown,
eIsRedirect,
eIsNotRedirect
};
RedirectState isRedirect = eRedirectUnknown;
if (numEntries > 1) {
isRedirect = aArguments->AsInt32(1) ? eIsRedirect : eIsNotRedirect;
}
int32_t typed = 0;
int32_t visitCount = 0;
bool hasBookmark = false;
int32_t isQuery = 0;
float pointsForSampledVisits = 0.0;
int32_t numSampledVisits = 0;
int32_t bonus = 0;
// This is a const version of the history object for thread-safety.
const nsNavHistory* history = nsNavHistory::GetConstHistoryService();
NS_ENSURE_STATE(history);
RefPtr<Database> DB = Database::GetDatabase();
NS_ENSURE_STATE(DB);
// Fetch the page stats from the database.
{
RefPtr<mozIStorageStatement> getPageInfo = DB->GetStatement(
"SELECT typed, visit_count, foreign_count, "
"(substr(url, 0, 7) = 'place:') "
"FROM moz_places "
"WHERE id = :page_id "
);
NS_ENSURE_STATE(getPageInfo);
mozStorageStatementScoper infoScoper(getPageInfo);
rv = getPageInfo->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId);
NS_ENSURE_SUCCESS(rv, rv);
bool hasResult = false;
rv = getPageInfo->ExecuteStep(&hasResult);
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_UNEXPECTED);
rv = getPageInfo->GetInt32(0, &typed);
NS_ENSURE_SUCCESS(rv, rv);
rv = getPageInfo->GetInt32(1, &visitCount);
NS_ENSURE_SUCCESS(rv, rv);
int32_t foreignCount = 0;
rv = getPageInfo->GetInt32(2, &foreignCount);
NS_ENSURE_SUCCESS(rv, rv);
hasBookmark = foreignCount > 0;
rv = getPageInfo->GetInt32(3, &isQuery);
NS_ENSURE_SUCCESS(rv, rv);
}
if (visitCount > 0) {
// Get a sample of the last visits to the page, to calculate its weight.
// In case of a temporary or permanent redirect, calculate the frecency
// as if the original page was visited.
nsCString redirectsTransitionFragment =
nsPrintfCString("%d AND %d ", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY);
nsCOMPtr<mozIStorageStatement> getVisits = DB->GetStatement(
NS_LITERAL_CSTRING(
"/* do not warn (bug 659740 - SQLite may ignore index if few visits exist) */"
"SELECT "
"ROUND((strftime('%s','now','localtime','utc') - v.visit_date/1000000)/86400), "
"origin.visit_type, "
"v.visit_type, "
"target.id NOTNULL "
"FROM moz_historyvisits v "
"LEFT JOIN moz_historyvisits origin ON origin.id = v.from_visit "
"AND v.visit_type BETWEEN "
) + redirectsTransitionFragment + NS_LITERAL_CSTRING(
"LEFT JOIN moz_historyvisits target ON v.id = target.from_visit "
"AND target.visit_type BETWEEN "
) + redirectsTransitionFragment + NS_LITERAL_CSTRING(
"WHERE v.place_id = :page_id "
"ORDER BY v.visit_date DESC "
)
);
NS_ENSURE_STATE(getVisits);
mozStorageStatementScoper visitsScoper(getVisits);
rv = getVisits->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId);
NS_ENSURE_SUCCESS(rv, rv);
// Fetch only a limited number of recent visits.
bool hasResult = false;
for (int32_t maxVisits = history->GetNumVisitsForFrecency();
numSampledVisits < maxVisits &&
NS_SUCCEEDED(getVisits->ExecuteStep(&hasResult)) && hasResult;
numSampledVisits++) {
int32_t visitType;
bool isNull = false;
rv = getVisits->GetIsNull(1, &isNull);
NS_ENSURE_SUCCESS(rv, rv);
if (isRedirect == eIsRedirect || isNull) {
// Use the main visit_type.
rv = getVisits->GetInt32(2, &visitType);
NS_ENSURE_SUCCESS(rv, rv);
} else {
// This is a redirect target, so use the origin visit_type.
rv = getVisits->GetInt32(1, &visitType);
NS_ENSURE_SUCCESS(rv, rv);
}
RedirectState visitIsRedirect = isRedirect;
// If we don't know if this is a redirect or not, or this is not the
// most recent visit that we're looking at, then we use the redirect
// value from the database.
if (visitIsRedirect == eRedirectUnknown || numSampledVisits >= 1) {
int32_t redirect;
rv = getVisits->GetInt32(3, &redirect);
NS_ENSURE_SUCCESS(rv, rv);
visitIsRedirect = !!redirect ? eIsRedirect : eIsNotRedirect;
}
bonus = history->GetFrecencyTransitionBonus(visitType, true, visitIsRedirect == eIsRedirect);
// Add the bookmark visit bonus.
if (hasBookmark) {
bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, true);
}
// If bonus was zero, we can skip the work to determine the weight.
if (bonus) {
int32_t ageInDays = getVisits->AsInt32(0);
int32_t weight = history->GetFrecencyAgedWeight(ageInDays);
pointsForSampledVisits += (float)(weight * (bonus / 100.0));
}
}
}
// If we sampled some visits for this page, use the calculated weight.
if (numSampledVisits) {
// We were unable to calculate points, maybe cause all the visits in the
// sample had a zero bonus. Though, we know the page has some past valid
// visit, or visit_count would be zero. Thus we set the frecency to
// -1, so they are still shown in autocomplete.
if (!pointsForSampledVisits) {
NS_ADDREF(*_result = new IntegerVariant(-1));
}
else {
// Estimate frecency using the sampled visits.
// Use ceilf() so that we don't round down to 0, which
// would cause us to completely ignore the place during autocomplete.
NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(visitCount * ceilf(pointsForSampledVisits) / numSampledVisits)));
}
return NS_OK;
}
// Otherwise this page has no visits, it may be bookmarked.
if (!hasBookmark || isQuery) {
NS_ADDREF(*_result = new IntegerVariant(0));
return NS_OK;
}
// For unvisited bookmarks, produce a non-zero frecency, so that they show
// up in URL bar autocomplete.
visitCount = 1;
// Make it so something bookmarked and typed will have a higher frecency
// than something just typed or just bookmarked.
bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, false);
if (typed) {
bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_TYPED, false);
}
// Assume "now" as our ageInDays, so use the first bucket.
pointsForSampledVisits = history->GetFrecencyBucketWeight(1) * (bonus / (float)100.0);
// use ceilf() so that we don't round down to 0, which
// would cause us to completely ignore the place during autocomplete
NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(visitCount * ceilf(pointsForSampledVisits))));
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// GUID Creation Function
/* static */
nsresult
GenerateGUIDFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<GenerateGUIDFunction> function = new GenerateGUIDFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("generate_guid"), 0, function
);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(
GenerateGUIDFunction,
mozIStorageFunction
)
NS_IMETHODIMP
GenerateGUIDFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
nsIVariant **_result)
{
nsAutoCString guid;
nsresult rv = GenerateGUID(guid);
NS_ENSURE_SUCCESS(rv, rv);
NS_ADDREF(*_result = new UTF8TextVariant(guid));
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Get Unreversed Host Function
/* static */
nsresult
GetUnreversedHostFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<GetUnreversedHostFunction> function = new GetUnreversedHostFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("get_unreversed_host"), 1, function
);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(
GetUnreversedHostFunction,
mozIStorageFunction
)
NS_IMETHODIMP
GetUnreversedHostFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
nsIVariant **_result)
{
// Must have non-null function arguments.
MOZ_ASSERT(aArguments);
nsAutoString src;
aArguments->GetString(0, src);
RefPtr<nsVariant> result = new nsVariant();
if (src.Length()>1) {
src.Truncate(src.Length() - 1);
nsAutoString dest;
ReverseString(src, dest);
result->SetAsAString(dest);
}
else {
result->SetAsAString(EmptyString());
}
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Fixup URL Function
/* static */
nsresult
FixupURLFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<FixupURLFunction> function = new FixupURLFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("fixup_url"), 1, function
);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(
FixupURLFunction,
mozIStorageFunction
)
NS_IMETHODIMP
FixupURLFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
nsIVariant **_result)
{
// Must have non-null function arguments.
MOZ_ASSERT(aArguments);
nsAutoString src;
aArguments->GetString(0, src);
RefPtr<nsVariant> result = new nsVariant();
if (StringBeginsWith(src, NS_LITERAL_STRING("http://")))
src.Cut(0, 7);
else if (StringBeginsWith(src, NS_LITERAL_STRING("https://")))
src.Cut(0, 8);
else if (StringBeginsWith(src, NS_LITERAL_STRING("ftp://")))
src.Cut(0, 6);
// Remove common URL hostname prefixes
if (StringBeginsWith(src, NS_LITERAL_STRING("www."))) {
src.Cut(0, 4);
}
result->SetAsAString(src);
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Frecency Changed Notification Function
/* static */
nsresult
FrecencyNotificationFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<FrecencyNotificationFunction> function =
new FrecencyNotificationFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("notify_frecency"), 5, function
);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(
FrecencyNotificationFunction,
mozIStorageFunction
)
NS_IMETHODIMP
FrecencyNotificationFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
nsIVariant **_result)
{
uint32_t numArgs;
nsresult rv = aArgs->GetNumEntries(&numArgs);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(numArgs == 5);
int32_t newFrecency = aArgs->AsInt32(0);
nsAutoCString spec;
rv = aArgs->GetUTF8String(1, spec);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString guid;
rv = aArgs->GetUTF8String(2, guid);
NS_ENSURE_SUCCESS(rv, rv);
bool hidden = static_cast<bool>(aArgs->AsInt32(3));
PRTime lastVisitDate = static_cast<PRTime>(aArgs->AsInt64(4));
const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
NS_ENSURE_STATE(navHistory);
navHistory->DispatchFrecencyChangedNotification(spec, newFrecency, guid,
hidden, lastVisitDate);
RefPtr<nsVariant> result = new nsVariant();
rv = result->SetAsInt32(newFrecency);
NS_ENSURE_SUCCESS(rv, rv);
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Store Last Inserted Id Function
/* static */
nsresult
StoreLastInsertedIdFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<StoreLastInsertedIdFunction> function =
new StoreLastInsertedIdFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("store_last_inserted_id"), 2, function
);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(
StoreLastInsertedIdFunction,
mozIStorageFunction
)
NS_IMETHODIMP
StoreLastInsertedIdFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
nsIVariant **_result)
{
uint32_t numArgs;
nsresult rv = aArgs->GetNumEntries(&numArgs);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(numArgs == 2);
nsAutoCString table;
rv = aArgs->GetUTF8String(0, table);
NS_ENSURE_SUCCESS(rv, rv);
int64_t lastInsertedId = aArgs->AsInt64(1);
MOZ_ASSERT(table.EqualsLiteral("moz_places") ||
table.EqualsLiteral("moz_historyvisits") ||
table.EqualsLiteral("moz_bookmarks") ||
table.EqualsLiteral("moz_icons"));
if (table.EqualsLiteral("moz_bookmarks")) {
nsNavBookmarks::StoreLastInsertedId(table, lastInsertedId);
} else if (table.EqualsLiteral("moz_icons")) {
nsFaviconService::StoreLastInsertedId(table, lastInsertedId);
} else {
nsNavHistory::StoreLastInsertedId(table, lastInsertedId);
}
RefPtr<nsVariant> result = new nsVariant();
rv = result->SetAsInt64(lastInsertedId);
NS_ENSURE_SUCCESS(rv, rv);
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Hash Function
/* static */
nsresult
HashFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<HashFunction> function = new HashFunction();
return aDBConn->CreateFunction(
NS_LITERAL_CSTRING("hash"), -1, function
);
}
NS_IMPL_ISUPPORTS(
HashFunction,
mozIStorageFunction
)
NS_IMETHODIMP
HashFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
nsIVariant **_result)
{
// Must have non-null function arguments.
MOZ_ASSERT(aArguments);
// Fetch arguments. Use default values if they were omitted.
uint32_t numEntries;
nsresult rv = aArguments->GetNumEntries(&numEntries);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(numEntries >= 1 && numEntries <= 2, NS_ERROR_FAILURE);
nsDependentCString str = getSharedUTF8String(aArguments, 0);
nsAutoCString mode;
if (numEntries > 1) {
aArguments->GetUTF8String(1, mode);
}
// HashString doesn't stop at the string boundaries if a length is passed to
// it, so ensure to pass a proper value.
const uint32_t maxLenToHash = std::min(static_cast<uint32_t>(str.Length()),
MAX_CHARS_TO_HASH);
RefPtr<nsVariant> result = new nsVariant();
if (mode.IsEmpty()) {
// URI-like strings (having a prefix before a colon), are handled specially,
// as a 48 bit hash, where first 16 bits are the prefix hash, while the
// other 32 are the string hash.
// The 16 bits have been decided based on the fact hashing all of the IANA
// known schemes, plus "places", does not generate collisions.
// Since we only care about schemes, we just search in the first 50 chars.
// The longest known IANA scheme, at this time, is 30 chars.
const nsDependentCSubstring& strHead = StringHead(str, 50);
nsACString::const_iterator start, tip, end;
strHead.BeginReading(tip);
start = tip;
strHead.EndReading(end);
uint32_t strHash = HashString(str.get(), maxLenToHash);
if (FindCharInReadable(':', tip, end)) {
const nsDependentCSubstring& prefix = Substring(start, tip);
uint64_t prefixHash = static_cast<uint64_t>(HashString(prefix) & 0x0000FFFF);
// The second half of the url is more likely to be unique, so we add it.
uint64_t hash = (prefixHash << 32) + strHash;
result->SetAsInt64(hash);
} else {
result->SetAsInt64(strHash);
}
} else if (mode.EqualsLiteral("prefix_lo")) {
// Keep only 16 bits.
uint64_t hash = static_cast<uint64_t>(HashString(str.get(), maxLenToHash) & 0x0000FFFF) << 32;
result->SetAsInt64(hash);
} else if (mode.EqualsLiteral("prefix_hi")) {
// Keep only 16 bits.
uint64_t hash = static_cast<uint64_t>(HashString(str.get(), maxLenToHash) & 0x0000FFFF) << 32;
// Make this a prefix upper bound by filling the lowest 32 bits.
hash += 0xFFFFFFFF;
result->SetAsInt64(hash);
} else {
return NS_ERROR_FAILURE;
}
result.forget(_result);
return NS_OK;
}
} // namespace places
} // namespace mozilla