From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- toolkit/components/places/SQLFunctions.cpp | 1520 ++++++++++++++++++++++++++++ 1 file changed, 1520 insertions(+) create mode 100644 toolkit/components/places/SQLFunctions.cpp (limited to 'toolkit/components/places/SQLFunctions.cpp') diff --git a/toolkit/components/places/SQLFunctions.cpp b/toolkit/components/places/SQLFunctions.cpp new file mode 100644 index 0000000000..e625f3fa09 --- /dev/null +++ b/toolkit/components/places/SQLFunctions.cpp @@ -0,0 +1,1520 @@ +/* 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(*(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: + * + * 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(64), aSpec.Length()); + for (size_type i = 0; i < length; ++i) { + if (aSpec[i] == static_cast(':')) { + // Found the ':'. Now skip past "//", if present. + if (i + 2 < aSpec.Length() && + aSpec[i + 1] == static_cast('/') && + aSpec[i + 2] == static_cast('/')) { + 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('/') || + aSpec[i] == static_cast('?') || + aSpec[i] == static_cast('#')) { + break; + } + // RFC 3986: '@' marks the end of the userinfo component. + if (aSpec[i] == static_cast('@')) { + 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 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 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(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 DB = Database::GetDatabase(); + NS_ENSURE_STATE(DB); + + // Fetch the page stats from the database. + { + nsCOMPtr 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 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(-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( + (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(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((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((int32_t)ceilf(pointsForSampledVisits)) + .take(); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// Frecency Calculation Function + +/* static */ +nsresult CalculateAltFrecencyFunction::create(mozIStorageConnection* aDBConn) { + RefPtr 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(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 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 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().take(); + } else { + int32_t score; + rv = stmt->GetInt32(0, &score); + NS_ENSURE_SUCCESS(rv, rv); + *_result = MakeAndAddRef(score).take(); + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// GUID Creation Function + +/* static */ +nsresult GenerateGUIDFunction::create(mozIStorageConnection* aDBConn) { + RefPtr 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(guid).take(); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// GUID Validation Function + +/* static */ +nsresult IsValidGUIDFunction::create(mozIStorageConnection* aDBConn) { + RefPtr 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 result = new nsVariant(); + result->SetAsBool(IsValidGUID(guid)); + result.forget(_result); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// Get Unreversed Host Function + +/* static */ +nsresult GetUnreversedHostFunction::create(mozIStorageConnection* aDBConn) { + RefPtr 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 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 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 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 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 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 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 result = new nsVariant(); + if (!queryString.IsEmpty() && !paramName.IsEmpty()) { + URLParams::Parse( + queryString, + [¶mName, &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 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 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; +} + +//////////////////////////////////////////////////////////////////////////////// +//// MD5 Function + +/* static */ +nsresult MD5HexFunction::create(mozIStorageConnection* aDBConn) { + RefPtr function = new MD5HexFunction(); + return aDBConn->CreateFunction("md5hex"_ns, -1, function); +} + +NS_IMPL_ISUPPORTS(MD5HexFunction, mozIStorageFunction) + +NS_IMETHODIMP +MD5HexFunction::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 hasher = + do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + // MD5 is not a secure hash function, but it's ok for this use. + rv = hasher->Init(nsICryptoHash::MD5); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Update(reinterpret_cast(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(binaryHash[i]); + hashString.Append(hex[(c >> 4) & 0x0F]); + hashString.Append(hex[c & 0x0F]); + } + + RefPtr result = new nsVariant(); + result->SetAsACString(hashString); + result.forget(_result); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// Get prefix function + +/* static */ +nsresult GetPrefixFunction::create(mozIStorageConnection* aDBConn) { + RefPtr 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 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 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 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 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 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 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 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 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 os = services::GetObserverService(); + if (os) { + mozilla::Unused << os->NotifyObservers( + nullptr, "frecency-recalculation-needed", nullptr); + } + })); + } + + RefPtr 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 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 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 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 numArgs = 0; + MOZ_ASSERT(NS_SUCCEEDED(aArguments->GetNumEntries(&numArgs)) && numArgs == 1, + "unexpected number of arguments"); + + nsDependentCString queryURI = getSharedUTF8String(aArguments, 0); + Maybe targetFolderGuid = + nsNavHistory::GetTargetFolderGuid(queryURI); + + if (targetFolderGuid.isSome()) { + RefPtr result = new nsVariant(); + result->SetAsACString(*targetFolderGuid); + result.forget(_result); + } else { + *_result = MakeAndAddRef().take(); + } + + return NS_OK; +} + +} // namespace mozilla::places -- cgit v1.2.3