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

1179 lines
39 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 "mozilla/Utf8.h"
#include "nsURLHelper.h"
#include "nsVariant.h"
// Maximum number of chars to search through.
// MatchAutoCompleteFunction won't look for matches over this threshold.
#define MAX_CHARS_TO_SEARCH_THROUGH 255
using namespace mozilla::storage;
////////////////////////////////////////////////////////////////////////////////
//// Anonymous Helpers
namespace {
typedef nsACString::const_char_iterator const_char_iterator;
typedef nsACString::size_type size_type;
typedef nsACString::char_type char_type;
/**
* 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.
* It will be updated by this function.
* @param aEnd
* An interator pointing to past-the-end of the string.
*/
static MOZ_ALWAYS_INLINE void goToNextSearchCandidate(
const_char_iterator& aStart, const const_char_iterator& aEnd,
uint32_t aSearchFor) {
// 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 (aStart < aEnd && (unsigned char)(*aStart | 0x20) != target &&
(unsigned char)*aStart != special) {
aStart++;
}
} else {
while (aStart < aEnd && (unsigned char)(*aStart) < 128) {
aStart++;
}
}
}
/**
* 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 (or optionally, case and diacritic 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.
* @param aMatchDiacritics
* Whether or not the match is diacritic-sensitive.
*
* @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,
bool aMatchDiacritics) {
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,
aMatchDiacritics)) {
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.
MOZ_ASSERT(!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 nsNavHistory* history = nsNavHistory::GetConstHistoryService();
bool matchDiacritics = history && history->MatchDiacritics();
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;
}
if (!matchDiacritics) {
tokenFirstChar = ToNaked(tokenFirstChar);
}
for (;;) {
if (matchDiacritics) {
// Scan forward to the next viable candidate (if any).
goToNextSearchCandidate(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 (!matchDiacritics) {
sourceFirstChar = ToNaked(sourceFirstChar);
}
if (sourceFirstChar == tokenFirstChar &&
(aBehavior != eFindOnBoundary || sourceCur == sourceStart ||
isOnBoundary(sourceCur)) &&
stringMatch(tokenNext, tokenEnd, sourceNext, sourceEnd,
matchDiacritics)) {
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);
}
/**
* Gets the length of the prefix in a URI spec. "Prefix" is defined to be the
* scheme, colon, and, if present, two slashes.
*
* Examples:
*
* http://example.com
* ~~~~~~~
* => length == 7
*
* foo:example
* ~~~~
* => length == 4
*
* not a spec
* => length == 0
*
* @param aSpec
* A URI spec, or a string that may be a URI spec.
* @return The length of the prefix in the spec. If there isn't a prefix,
* returns 0.
*/
static MOZ_ALWAYS_INLINE size_type getPrefixLength(const nsACString& aSpec) {
// To keep the search bounded, look at 64 characters at most. The longest
// IANA schemes are ~30, so double that and round up to a nice number.
size_type length = std::min(static_cast<size_type>(64), aSpec.Length());
for (size_type i = 0; i < length; ++i) {
if (aSpec[i] == static_cast<char_type>(':')) {
// Found the ':'. Now skip past "//", if present.
if (i + 2 < aSpec.Length() &&
aSpec[i + 1] == static_cast<char_type>('/') &&
aSpec[i + 2] == static_cast<char_type>('/')) {
i += 2;
}
return i + 1;
}
}
return 0;
}
/**
* Gets the index in a URI spec of the host and port substring and optionally
* its length.
*
* Examples:
*
* http://example.com/
* ~~~~~~~~~~~
* => index == 7, length == 11
*
* http://example.com:8888/
* ~~~~~~~~~~~~~~~~
* => index == 7, length == 16
*
* http://user:pass@example.com/
* ~~~~~~~~~~~
* => index == 17, length == 11
*
* foo:example
* ~~~~~~~
* => index == 4, length == 7
*
* not a spec
* ~~~~~~~~~~
* => index == 0, length == 10
*
* @param aSpec
* A URI spec, or a string that may be a URI spec.
* @param _hostAndPortLength
* The length of the host and port substring is returned through this
* param. Pass null if you don't care.
* @return The length of the host and port substring in the spec. If aSpec
* doesn't look like a URI, then the entire aSpec is assumed to be a
* "host and port", and this returns 0, and _hostAndPortLength will be
* the length of aSpec.
*/
static MOZ_ALWAYS_INLINE size_type
indexOfHostAndPort(const nsACString& aSpec, size_type* _hostAndPortLength) {
size_type index = getPrefixLength(aSpec);
size_type i = index;
for (; i < aSpec.Length(); ++i) {
// RFC 3986 (URIs): The origin ("authority") is terminated by '/', '?', or
// '#' (or the end of the URI).
if (aSpec[i] == static_cast<char_type>('/') ||
aSpec[i] == static_cast<char_type>('?') ||
aSpec[i] == static_cast<char_type>('#')) {
break;
}
// RFC 3986: '@' marks the end of the userinfo component.
if (aSpec[i] == static_cast<char_type>('@')) {
index = i + 1;
}
}
if (_hostAndPortLength) {
*_hostAndPortLength = i - index;
}
return index;
}
} // 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("autocomplete_match"_ns,
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, "http://"_ns)) {
fixedSpec.Rebind(fixedSpec, 7);
} else if (StringBeginsWith(fixedSpec, "https://"_ns)) {
fixedSpec.Rebind(fixedSpec, 8);
} else if (StringBeginsWith(fixedSpec, "ftp://"_ns)) {
fixedSpec.Rebind(fixedSpec, 6);
}
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 */
MatchAutoCompleteFunction::searchFunctionPtr
MatchAutoCompleteFunction::getSearchFunction(int32_t aBehavior) {
switch (aBehavior) {
case mozIPlacesAutoComplete::MATCH_ANYWHERE:
case mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED:
return findAnywhere;
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, "javascript:"_ns) && !HAS_BEHAVIOR(JAVASCRIPT) &&
!StringBeginsWith(searchString, "javascript:"_ns)) {
NS_ADDREF(*_result = mCachedZero);
return NS_OK;
}
int32_t visitCount = aArguments->AsInt32(kArgIndexVisitCount);
// Filtering on typed is no more used by Firefox, it is still being used by
// comm-central clients.
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("calculate_frecency"_ns, -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 RedirectBonus { eUnknown, eRedirect, eNormal };
RedirectBonus mostRecentVisitBonus = eUnknown;
if (numEntries > 1) {
mostRecentVisitBonus = aArguments->AsInt32(1) ? eRedirect : eNormal;
}
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.
{
nsCOMPtr<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("page_id"_ns, 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 the visit is a redirect target, calculate the frecency
// as if the original page was visited.
// If it's a redirect source, we may want to use a lower bonus.
nsCString redirectsTransitionFragment = nsPrintfCString(
"%d AND %d ", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY);
nsCOMPtr<mozIStorageStatement> getVisits = DB->GetStatement(
nsLiteralCString(
"/* do not warn (bug 659740 - SQLite may ignore index if few "
"visits exist) */"
"SELECT "
"IFNULL(origin.visit_type, v.visit_type) AS visit_type, "
"target.visit_type AS target_visit_type, "
"ROUND((strftime('%s','now','localtime','utc') - "
"v.visit_date/1000000)/86400) AS age_in_days "
"FROM moz_historyvisits v "
"LEFT JOIN moz_historyvisits origin ON origin.id = v.from_visit "
"AND v.visit_type BETWEEN ") +
redirectsTransitionFragment +
nsLiteralCString(
"LEFT JOIN moz_historyvisits target ON v.id = target.from_visit "
"AND target.visit_type BETWEEN ") +
redirectsTransitionFragment +
nsLiteralCString("WHERE v.place_id = :page_id "
"ORDER BY v.visit_date DESC "
"LIMIT :max_visits "));
NS_ENSURE_STATE(getVisits);
mozStorageStatementScoper visitsScoper(getVisits);
rv = getVisits->BindInt64ByName("page_id"_ns, pageId);
NS_ENSURE_SUCCESS(rv, rv);
rv = getVisits->BindInt32ByName("max_visits"_ns,
history->GetNumVisitsForFrecency());
NS_ENSURE_SUCCESS(rv, rv);
// Fetch only a limited number of recent visits.
bool hasResult = false;
while (NS_SUCCEEDED(getVisits->ExecuteStep(&hasResult)) && hasResult) {
// If this is a redirect target, we'll use the visitType of the source,
// otherwise the actual visitType.
int32_t visitType = getVisits->AsInt32(0);
// When adding a new visit, we should haved passed-in whether we should
// use the redirect bonus. We can't fetch this information from the
// database, because we only store redirect targets.
// For older visits we extract the value from the database.
bool useRedirectBonus = mostRecentVisitBonus == eRedirect;
if (mostRecentVisitBonus == eUnknown || numSampledVisits > 0) {
int32_t targetVisitType = getVisits->AsInt32(1);
useRedirectBonus =
targetVisitType ==
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT ||
(targetVisitType ==
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY &&
visitType != nsINavHistoryService::TRANSITION_TYPED);
}
bonus = history->GetFrecencyTransitionBonus(visitType, true,
useRedirectBonus);
// 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(2);
int32_t weight = history->GetFrecencyAgedWeight(ageInDays);
pointsForSampledVisits += (float)(weight * (bonus / 100.0));
}
numSampledVisits++;
}
}
// 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("generate_guid"_ns, 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;
}
////////////////////////////////////////////////////////////////////////////////
//// GUID Validation Function
/* static */
nsresult IsValidGUIDFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<IsValidGUIDFunction> function = new IsValidGUIDFunction();
return aDBConn->CreateFunction("is_valid_guid"_ns, 1, function);
}
NS_IMPL_ISUPPORTS(IsValidGUIDFunction, mozIStorageFunction)
NS_IMETHODIMP
IsValidGUIDFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
nsIVariant** _result) {
// Must have non-null function arguments.
MOZ_ASSERT(aArguments);
nsAutoCString guid;
aArguments->GetUTF8String(0, guid);
RefPtr<nsVariant> result = new nsVariant();
result->SetAsBool(IsValidGUID(guid));
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Get Unreversed Host Function
/* static */
nsresult GetUnreversedHostFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<GetUnreversedHostFunction> function = new GetUnreversedHostFunction();
nsresult rv = aDBConn->CreateFunction("get_unreversed_host"_ns, 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(u""_ns);
}
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Fixup URL Function
/* static */
nsresult FixupURLFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<FixupURLFunction> function = new FixupURLFunction();
nsresult rv = aDBConn->CreateFunction("fixup_url"_ns, 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, u"http://"_ns))
src.Cut(0, 7);
else if (StringBeginsWith(src, u"https://"_ns))
src.Cut(0, 8);
else if (StringBeginsWith(src, u"ftp://"_ns))
src.Cut(0, 6);
// Remove common URL hostname prefixes
if (StringBeginsWith(src, u"www."_ns)) {
src.Cut(0, 4);
}
result->SetAsAString(src);
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("store_last_inserted_id"_ns, 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;
}
////////////////////////////////////////////////////////////////////////////////
//// Get Query Param Function
/* static */
nsresult GetQueryParamFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<GetQueryParamFunction> function = new GetQueryParamFunction();
return aDBConn->CreateFunction("get_query_param"_ns, 2, function);
}
NS_IMPL_ISUPPORTS(GetQueryParamFunction, mozIStorageFunction)
NS_IMETHODIMP
GetQueryParamFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
nsIVariant** _result) {
// Must have non-null function arguments.
MOZ_ASSERT(aArguments);
nsDependentCString queryString = getSharedUTF8String(aArguments, 0);
nsDependentCString paramName = getSharedUTF8String(aArguments, 1);
RefPtr<nsVariant> result = new nsVariant();
if (!queryString.IsEmpty() && !paramName.IsEmpty()) {
URLParams::Parse(
queryString,
[&paramName, &result](const nsAString& aName, const nsAString& aValue) {
NS_ConvertUTF16toUTF8 name(aName);
if (!paramName.Equals(name)) {
return true;
}
result->SetAsAString(aValue);
return false;
});
}
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Hash Function
/* static */
nsresult HashFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<HashFunction> function = new HashFunction();
return aDBConn->CreateFunction("hash"_ns, -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);
}
RefPtr<nsVariant> result = new nsVariant();
uint64_t hash;
rv = HashURL(str, mode, &hash);
NS_ENSURE_SUCCESS(rv, rv);
rv = result->SetAsInt64(hash);
NS_ENSURE_SUCCESS(rv, rv);
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Get prefix function
/* static */
nsresult GetPrefixFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<GetPrefixFunction> function = new GetPrefixFunction();
nsresult rv = aDBConn->CreateFunction("get_prefix"_ns, 1, function);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(GetPrefixFunction, mozIStorageFunction)
NS_IMETHODIMP
GetPrefixFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
nsIVariant** _result) {
MOZ_ASSERT(aArgs);
uint32_t numArgs;
nsresult rv = aArgs->GetNumEntries(&numArgs);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(numArgs == 1);
nsDependentCString spec(getSharedUTF8String(aArgs, 0));
RefPtr<nsVariant> result = new nsVariant();
result->SetAsACString(Substring(spec, 0, getPrefixLength(spec)));
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Get host and port function
/* static */
nsresult GetHostAndPortFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<GetHostAndPortFunction> function = new GetHostAndPortFunction();
nsresult rv = aDBConn->CreateFunction("get_host_and_port"_ns, 1, function);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(GetHostAndPortFunction, mozIStorageFunction)
NS_IMETHODIMP
GetHostAndPortFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
nsIVariant** _result) {
MOZ_ASSERT(aArgs);
uint32_t numArgs;
nsresult rv = aArgs->GetNumEntries(&numArgs);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(numArgs == 1);
nsDependentCString spec(getSharedUTF8String(aArgs, 0));
RefPtr<nsVariant> result = new nsVariant();
size_type length;
size_type index = indexOfHostAndPort(spec, &length);
result->SetAsACString(Substring(spec, index, length));
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Strip prefix and userinfo function
/* static */
nsresult StripPrefixAndUserinfoFunction::create(
mozIStorageConnection* aDBConn) {
RefPtr<StripPrefixAndUserinfoFunction> function =
new StripPrefixAndUserinfoFunction();
nsresult rv =
aDBConn->CreateFunction("strip_prefix_and_userinfo"_ns, 1, function);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(StripPrefixAndUserinfoFunction, mozIStorageFunction)
NS_IMETHODIMP
StripPrefixAndUserinfoFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
nsIVariant** _result) {
MOZ_ASSERT(aArgs);
uint32_t numArgs;
nsresult rv = aArgs->GetNumEntries(&numArgs);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(numArgs == 1);
nsDependentCString spec(getSharedUTF8String(aArgs, 0));
RefPtr<nsVariant> result = new nsVariant();
size_type index = indexOfHostAndPort(spec, NULL);
result->SetAsACString(Substring(spec, index, spec.Length() - index));
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Is frecency decaying function
/* static */
nsresult IsFrecencyDecayingFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<IsFrecencyDecayingFunction> function =
new IsFrecencyDecayingFunction();
nsresult rv = aDBConn->CreateFunction("is_frecency_decaying"_ns, 0, function);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(IsFrecencyDecayingFunction, mozIStorageFunction)
NS_IMETHODIMP
IsFrecencyDecayingFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
nsIVariant** _result) {
MOZ_ASSERT(aArgs);
uint32_t numArgs;
nsresult rv = aArgs->GetNumEntries(&numArgs);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(numArgs == 0);
const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
NS_ENSURE_STATE(navHistory);
RefPtr<nsVariant> result = new nsVariant();
rv = result->SetAsBool(navHistory->IsFrecencyDecaying());
NS_ENSURE_SUCCESS(rv, rv);
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Note Sync Change Function
/* static */
nsresult NoteSyncChangeFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<NoteSyncChangeFunction> function = new NoteSyncChangeFunction();
nsresult rv = aDBConn->CreateFunction("note_sync_change"_ns, 0, function);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(NoteSyncChangeFunction, mozIStorageFunction)
NS_IMETHODIMP
NoteSyncChangeFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
nsIVariant** _result) {
nsNavBookmarks::NoteSyncChange();
*_result = nullptr;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Invalidate days of history Function
/* static */
nsresult InvalidateDaysOfHistoryFunction::create(
mozIStorageConnection* aDBConn) {
RefPtr<InvalidateDaysOfHistoryFunction> function =
new InvalidateDaysOfHistoryFunction();
nsresult rv =
aDBConn->CreateFunction("invalidate_days_of_history"_ns, 0, function);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(InvalidateDaysOfHistoryFunction, mozIStorageFunction)
NS_IMETHODIMP
InvalidateDaysOfHistoryFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
nsIVariant** _result) {
nsNavHistory::InvalidateDaysOfHistory();
return NS_OK;
}
} // namespace places
} // namespace mozilla