Source code

Revision control

Copy as Markdown

Other Tools

/* 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 "mozilla/StaticPrefs_places.h"
#include "nsString.h"
#include "nsFaviconService.h"
#include "nsNavBookmarks.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/Services.h"
#include "mozilla/Utf8.h"
#include "nsURLHelper.h"
#include "nsVariant.h"
#include "nsICryptoHash.h"
// Maximum number of chars to search through.
// MatchAutoCompleteFunction won't look for matches over this threshold.
#define MAX_CHARS_TO_SEARCH_THROUGH 255
#define SECONDS_PER_DAY 86400
using namespace mozilla::storage;
////////////////////////////////////////////////////////////////////////////////
//// Anonymous Helpers
namespace {
using const_char_iterator = nsACString::const_char_iterator;
using size_type = nsACString::size_type;
using char_type = nsACString::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 = static_cast<char>(*(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("", (size_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:
*
* ~~~~~~~
* => 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:
*
* ~~~~~~~~~~~
* => index == 7, length == 11
*
* ~~~~~~~~~~~~~~~~
* => index == 7, length == 16
*
* ~~~~~~~~~~~
* => 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::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(), (int32_t)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)) {
*_result = do_AddRef(mCachedZero).take();
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) != 0;
bool bookmark = aArguments->AsInt32(kArgIndexBookmark) != 0;
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) {
*_result = do_AddRef(mCachedZero).take();
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);
// Caller may pass a fallback title, for example in case of bookmarks or
// snapshots, one may want to search both the user provided title and the
// history one.
nsDependentCString fallbackTitle =
getSharedUTF8String(aArguments, kArgIndexFallbackTitle);
// Limit the number of chars we search through.
const nsDependentCSubstring& trimmedFallbackTitle =
Substring(fallbackTitle, 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, trimmedFallbackTitle) ||
searchFunction(token, tags)) &&
searchFunction(token, trimmedUrl);
} else if (HAS_BEHAVIOR(TITLE)) {
matches = searchFunction(token, trimmedTitle) ||
searchFunction(token, trimmedFallbackTitle) ||
searchFunction(token, tags);
} else if (HAS_BEHAVIOR(URL)) {
matches = searchFunction(token, trimmedUrl);
} else {
matches = searchFunction(token, trimmedTitle) ||
searchFunction(token, trimmedFallbackTitle) ||
searchFunction(token, tags) ||
searchFunction(token, trimmedUrl);
}
}
*_result = do_AddRef(matches ? mCachedOne : mCachedZero).take();
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) {
*_result = MakeAndAddRef<IntegerVariant>(0).take();
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;
PRTime mostRecentBookmarkTime = 0;
int32_t isQuery = 0;
float pointsForSampledVisits = 0.0f;
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, MAX(dateAdded), "
"(substr(url, 0, 7) = 'place:') "
"FROM moz_places h "
"LEFT JOIN moz_bookmarks ON fk = h.id "
"WHERE h.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);
rv = getPageInfo->GetInt64(2, &mostRecentBookmarkTime);
NS_ENSURE_SUCCESS(rv, rv);
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, "
"v.source AS visit_source "
"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);
}
uint32_t visitSource = getVisits->AsInt32(3);
if (mostRecentBookmarkTime) {
// For bookmarked visit, add full bonus.
bonus = history->GetFrecencyTransitionBonus(visitType, true,
useRedirectBonus);
bonus += history->GetFrecencyTransitionBonus(
nsINavHistoryService::TRANSITION_BOOKMARK, true);
} else if (visitSource == nsINavHistoryService::VISIT_SOURCE_ORGANIC) {
bonus = history->GetFrecencyTransitionBonus(visitType, true,
useRedirectBonus);
} else if (visitSource == nsINavHistoryService::VISIT_SOURCE_SEARCHED) {
bonus = history->GetFrecencyTransitionBonus(
nsINavHistoryService::TRANSITION_LINK, true, useRedirectBonus);
}
// 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 * ((float)bonus / 100.0f));
}
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 == 0.0f) {
*_result = MakeAndAddRef<IntegerVariant>(-1).take();
} 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.
*_result =
MakeAndAddRef<IntegerVariant>(
(int32_t)ceilf((float)visitCount * ceilf(pointsForSampledVisits) /
(float)numSampledVisits))
.take();
}
return NS_OK;
}
// Otherwise this page has no visits, it may be bookmarked.
if (!mostRecentBookmarkTime || isQuery) {
*_result = MakeAndAddRef<IntegerVariant>(0).take();
return NS_OK;
}
MOZ_ASSERT(bonus == 0, "Pages should arrive here with 0 bonus");
MOZ_ASSERT(mostRecentBookmarkTime > 0, "This should be a bookmarked page");
// For unvisited bookmarks, produce a non-zero frecency, so that they show
// up in URL bar autocomplete.
// 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);
}
// Use an appropriate bucket depending on the bookmark creation date.
int32_t bookmarkAgeInDays =
static_cast<int32_t>((PR_Now() - mostRecentBookmarkTime) /
((PRTime)SECONDS_PER_DAY * (PRTime)PR_USEC_PER_SEC));
pointsForSampledVisits =
(float)history->GetFrecencyAgedWeight(bookmarkAgeInDays) *
((float)bonus / 100.0f);
// use ceilf() so that we don't round down to 0, which
// would cause us to completely ignore the place during autocomplete
*_result =
MakeAndAddRef<IntegerVariant>((int32_t)ceilf(pointsForSampledVisits))
.take();
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Frecency Calculation Function
/* static */
nsresult CalculateAltFrecencyFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<CalculateAltFrecencyFunction> function =
new CalculateAltFrecencyFunction();
nsresult rv =
aDBConn->CreateFunction("calculate_alt_frecency"_ns, -1, function);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(CalculateAltFrecencyFunction, mozIStorageFunction)
NS_IMETHODIMP
CalculateAltFrecencyFunction::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) {
*_result = MakeAndAddRef<IntegerVariant>(0).take();
return NS_OK;
}
int32_t isRedirect = 0;
if (numEntries > 1) {
isRedirect = aArguments->AsInt32(1);
}
// 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);
/*
Exponentially decay each visit with an half-life of halfLifeDays.
Score per each visit is a weight exponentially decayed depending on how
far away is from a reference date, that is the most recent visit date.
The weight for each visit is assigned depending on the visit type and other
information (bookmarked, a redirect, a typed entry).
If a page has no visits, consider a single visit with an high weight and
decay its score using the bookmark date as reference time.
Frecency is the sum of all the scores / number of samples.
The final score is further decayed using the same half-life.
To avoid having to decay the score manually, the stored value is the number
of days after which the score would become 1.
TODO: Add reference link to source docs here.
*/
nsCOMPtr<mozIStorageStatement> stmt = DB->GetStatement(
"WITH "
"lambda (lambda) AS ( "
" SELECT ln(2) / :halfLifeDays "
"), "
"visits (days, weight) AS ( "
" SELECT "
" v.visit_date / 86400000000, "
" (SELECT CASE "
" WHEN IFNULL(s.visit_type, v.visit_type) = 3 " // from a bookmark
" OR v.source = 2 " // is a bookmark
" OR ( IFNULL(s.visit_type, v.visit_type) = 2 " // is typed
" AND v.source <> 3 " // not a search
" AND t.id IS NULL AND NOT :isRedirect " // not a redirect
" ) "
" THEN :highWeight "
" WHEN t.id IS NULL AND NOT :isRedirect " // not a redirect
" AND IFNULL(s.visit_type, v.visit_type) NOT IN (4, 8, 9) "
" THEN :mediumWeight "
" ELSE :lowWeight "
" END) "
" FROM moz_historyvisits v "
// If it's a redirect target, use the visit_type of the source.
" LEFT JOIN moz_historyvisits s ON s.id = v.from_visit "
" AND v.visit_type IN (5,6) "
// If it's a redirect, use a low weight.
" LEFT JOIN moz_historyvisits t ON t.from_visit = v.id "
" AND t.visit_type IN (5,6) "
" WHERE v.place_id = :pageId "
" ORDER BY v.visit_date DESC "
" LIMIT :numSampledVisits "
"), "
"bookmark (days, weight) AS ( "
" SELECT dateAdded / 86400000000, 100 "
" FROM moz_bookmarks "
" WHERE fk = :pageId "
" ORDER BY dateAdded DESC "
" LIMIT 1 "
"), "
"samples (days, weight) AS ( "
" SELECT * FROM bookmark WHERE (SELECT count(*) FROM visits) = 0 "
" UNION ALL "
" SELECT * FROM visits "
"), "
"reference (days, samples_count) AS ( "
" SELECT max(samples.days), count(*) FROM samples "
"), "
"scores (score) AS ( "
" SELECT (weight * exp(-lambda * (samples.days - reference.days))) "
" FROM samples, reference, lambda "
") "
"SELECT CASE "
"WHEN (substr(url, 0, 7) = 'place:') THEN 0 "
"ELSE "
" reference.days + CAST (( "
" ln( "
" sum(score) / samples_count * MAX(visit_count, samples_count) "
" ) / lambda "
" ) AS INTEGER) "
"END "
"FROM moz_places h, reference, lambda, scores "
"WHERE h.id = :pageId");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper infoScoper(stmt);
rv = stmt->BindInt64ByName("pageId"_ns, pageId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName("isRedirect"_ns, isRedirect);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(
"halfLifeDays"_ns,
StaticPrefs::places_frecency_pages_alternative_halfLifeDays_AtStartup());
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(
"numSampledVisits"_ns,
StaticPrefs::
places_frecency_pages_alternative_numSampledVisits_AtStartup());
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(
"lowWeight"_ns,
StaticPrefs::places_frecency_pages_alternative_lowWeight_AtStartup());
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(
"mediumWeight"_ns,
StaticPrefs::places_frecency_pages_alternative_mediumWeight_AtStartup());
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName(
"highWeight"_ns,
StaticPrefs::places_frecency_pages_alternative_highWeight_AtStartup());
NS_ENSURE_SUCCESS(rv, rv);
bool hasResult = false;
rv = stmt->ExecuteStep(&hasResult);
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_UNEXPECTED);
bool isNull;
if (NS_SUCCEEDED(stmt->GetIsNull(0, &isNull)) && isNull) {
*_result = MakeAndAddRef<NullVariant>().take();
} else {
int32_t score;
rv = stmt->GetInt32(0, &score);
NS_ENSURE_SUCCESS(rv, rv);
*_result = MakeAndAddRef<IntegerVariant>(score).take();
}
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);
*_result = MakeAndAddRef<UTF8TextVariant>(guid).take();
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, true,
[&paramName, &result](const nsACString& aName,
const nsACString& aValue) {
if (!paramName.Equals(aName)) {
return true;
}
result->SetAsACString(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 = mozilla::places::HashURL(str, mode, &hash);
NS_ENSURE_SUCCESS(rv, rv);
rv = result->SetAsInt64((int64_t)hash);
NS_ENSURE_SUCCESS(rv, rv);
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// SHA256Hex Function
/* static */
nsresult SHA256HexFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<SHA256HexFunction> function = new SHA256HexFunction();
return aDBConn->CreateFunction("sha256hex"_ns, -1, function);
}
NS_IMPL_ISUPPORTS(SHA256HexFunction, mozIStorageFunction)
NS_IMETHODIMP
SHA256HexFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
nsIVariant** _result) {
// Must have non-null function arguments.
MOZ_ASSERT(aArguments);
// Fetch arguments.
uint32_t numEntries;
nsresult rv = aArguments->GetNumEntries(&numEntries);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(numEntries == 1, NS_ERROR_FAILURE);
nsDependentCString str = getSharedUTF8String(aArguments, 0);
nsCOMPtr<nsICryptoHash> hasher =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// SHA256 is not super strong but fine for our mapping needs.
rv = hasher->Init(nsICryptoHash::SHA256);
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Update(reinterpret_cast<const uint8_t*>(str.BeginReading()),
str.Length());
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString binaryHash, hashString;
rv = hasher->Finish(false, binaryHash);
NS_ENSURE_SUCCESS(rv, rv);
// Convert to HEX.
static const char* const hex = "0123456789abcdef";
hashString.SetCapacity(2 * binaryHash.Length());
for (size_t i = 0; i < binaryHash.Length(); ++i) {
auto c = static_cast<unsigned char>(binaryHash[i]);
hashString.Append(hex[(c >> 4) & 0x0F]);
hashString.Append(hex[c & 0x0F]);
}
RefPtr<nsVariant> result = new nsVariant();
result->SetAsACString(hashString);
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, nullptr);
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);
#ifdef DEBUG
uint32_t numArgs;
MOZ_ASSERT(NS_SUCCEEDED(aArgs->GetNumEntries(&numArgs)) && numArgs == 0);
#endif
RefPtr<nsVariant> result = new nsVariant();
nsresult rv = result->SetAsBool(nsNavHistory::sIsFrecencyDecaying);
NS_ENSURE_SUCCESS(rv, rv);
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Should start frecency recalculation function
/* static */
nsresult SetShouldStartFrecencyRecalculationFunction::create(
mozIStorageConnection* aDBConn) {
RefPtr<SetShouldStartFrecencyRecalculationFunction> function =
new SetShouldStartFrecencyRecalculationFunction();
nsresult rv = aDBConn->CreateFunction(
"set_should_start_frecency_recalculation"_ns, 0, function);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(SetShouldStartFrecencyRecalculationFunction,
mozIStorageFunction)
NS_IMETHODIMP
SetShouldStartFrecencyRecalculationFunction::OnFunctionCall(
mozIStorageValueArray* aArgs, nsIVariant** _result) {
MOZ_ASSERT(aArgs);
#ifdef DEBUG
uint32_t numArgs;
MOZ_ASSERT(NS_SUCCEEDED(aArgs->GetNumEntries(&numArgs)) && numArgs == 0);
#endif
// When changing from false to true, dispatch a runnable to the main-thread
// to start a recalculation. Once there's nothing left to recalculathe this
// boolean will be set back to false. Note this means there will be a short
// interval between completing a recalculation and setting this back to false
// where we could potentially lose a recalculation request. That should not be
// a big deal, since the recalculation will just happen at the next operation
// changing frecency or, in the worst case, at the next session.
if (!nsNavHistory::sShouldStartFrecencyRecalculation.exchange(true)) {
mozilla::Unused << NS_DispatchToMainThread(NS_NewRunnableFunction(
"SetShouldStartFrecencyRecalculationFunction::Notify", [] {
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
mozilla::Unused << os->NotifyObservers(
nullptr, "frecency-recalculation-needed", nullptr);
}
}));
}
RefPtr<nsVariant> result = new nsVariant();
nsresult rv = result->SetAsBool(true);
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;
}
////////////////////////////////////////////////////////////////////////////////
//// Target folder guid from places query Function
/* static */
nsresult TargetFolderGuidFunction::create(mozIStorageConnection* aDBConn) {
RefPtr<TargetFolderGuidFunction> function = new TargetFolderGuidFunction();
nsresult rv = aDBConn->CreateFunction("target_folder_guid"_ns, 1, function);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMPL_ISUPPORTS(TargetFolderGuidFunction, mozIStorageFunction)
NS_IMETHODIMP
TargetFolderGuidFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
nsIVariant** _result) {
// Must have non-null function arguments.
MOZ_ASSERT(aArguments);
// Must have one argument.
DebugOnly<uint32_t> numArgs = 0;
MOZ_ASSERT(NS_SUCCEEDED(aArguments->GetNumEntries(&numArgs)) && numArgs == 1,
"unexpected number of arguments");
nsDependentCString queryURI = getSharedUTF8String(aArguments, 0);
Maybe<nsCString> targetFolderGuid =
nsNavHistory::GetTargetFolderGuid(queryURI);
if (targetFolderGuid.isSome()) {
RefPtr<nsVariant> result = new nsVariant();
result->SetAsACString(*targetFolderGuid);
result.forget(_result);
} else {
*_result = MakeAndAddRef<NullVariant>().take();
}
return NS_OK;
}
} // namespace mozilla::places