summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/SQLFunctions.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/places/SQLFunctions.cpp')
-rw-r--r--toolkit/components/places/SQLFunctions.cpp941
1 files changed, 941 insertions, 0 deletions
diff --git a/toolkit/components/places/SQLFunctions.cpp b/toolkit/components/places/SQLFunctions.cpp
new file mode 100644
index 000000000..e3cc7d7f0
--- /dev/null
+++ b/toolkit/components/places/SQLFunctions.cpp
@@ -0,0 +1,941 @@
+/* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/storage.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsEscape.h"
+#include "mozIPlacesAutoComplete.h"
+#include "SQLFunctions.h"
+#include "nsMathUtils.h"
+#include "nsUTF8Utils.h"
+#include "nsINavHistoryService.h"
+#include "nsPrintfCString.h"
+#include "nsNavHistory.h"
+#include "mozilla/Likely.h"
+#include "nsVariant.h"
+#include "mozilla/HashFunctions.h"
+
+// Maximum number of chars to search through.
+// MatchAutoCompleteFunction won't look for matches over this threshold.
+#define MAX_CHARS_TO_SEARCH_THROUGH 255
+
+using namespace mozilla::storage;
+
+// Keep the GUID-related parts of this file in sync with toolkit/downloads/SQLFunctions.cpp!
+
+////////////////////////////////////////////////////////////////////////////////
+//// Anonymous Helpers
+
+namespace {
+
+ typedef nsACString::const_char_iterator const_char_iterator;
+
+ /**
+ * Get a pointer to the word boundary after aStart if aStart points to an
+ * ASCII letter (i.e. [a-zA-Z]). Otherwise, return aNext, which we assume
+ * points to the next character in the UTF-8 sequence.
+ *
+ * We define a word boundary as anything that's not [a-z] -- this lets us
+ * match CamelCase words.
+ *
+ * @param aStart the beginning of the UTF-8 sequence
+ * @param aNext the next character in the sequence
+ * @param aEnd the first byte which is not part of the sequence
+ *
+ * @return a pointer to the next word boundary after aStart
+ */
+ static
+ MOZ_ALWAYS_INLINE const_char_iterator
+ nextWordBoundary(const_char_iterator const aStart,
+ const_char_iterator const aNext,
+ const_char_iterator const aEnd) {
+
+ const_char_iterator cur = aStart;
+ if (('a' <= *cur && *cur <= 'z') ||
+ ('A' <= *cur && *cur <= 'Z')) {
+
+ // Since we'll halt as soon as we see a non-ASCII letter, we can do a
+ // simple byte-by-byte comparison here and avoid the overhead of a
+ // UTF8CharEnumerator.
+ do {
+ cur++;
+ } while (cur < aEnd && 'a' <= *cur && *cur <= 'z');
+ }
+ else {
+ cur = aNext;
+ }
+
+ return cur;
+ }
+
+ enum FindInStringBehavior {
+ eFindOnBoundary,
+ eFindAnywhere
+ };
+
+ /**
+ * findAnywhere and findOnBoundary do almost the same thing, so it's natural
+ * to implement them in terms of a single function. They're both
+ * performance-critical functions, however, and checking aBehavior makes them
+ * a bit slower. Our solution is to define findInString as MOZ_ALWAYS_INLINE
+ * and rely on the compiler to optimize out the aBehavior check.
+ *
+ * @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
+ MOZ_ALWAYS_INLINE bool
+ findInString(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString,
+ FindInStringBehavior aBehavior)
+ {
+ // CaseInsensitiveUTF8CharsEqual assumes that there's at least one byte in
+ // the both strings, so don't pass an empty token here.
+ NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!");
+
+ // We cannot match anything if there is nothing to search.
+ if (aSourceString.IsEmpty()) {
+ return false;
+ }
+
+ const_char_iterator tokenStart(aToken.BeginReading()),
+ tokenEnd(aToken.EndReading()),
+ sourceStart(aSourceString.BeginReading()),
+ sourceEnd(aSourceString.EndReading());
+
+ do {
+ // We are on a word boundary (if aBehavior == eFindOnBoundary). See if
+ // aToken matches sourceStart.
+
+ // Check whether the first character in the token matches the character
+ // at sourceStart. At the same time, get a pointer to the next character
+ // in both the token and the source.
+ const_char_iterator sourceNext, tokenCur;
+ bool error;
+ if (CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart,
+ sourceEnd, tokenEnd,
+ &sourceNext, &tokenCur, &error)) {
+
+ // We don't need to check |error| here -- if
+ // CaseInsensitiveUTF8CharCompare encounters an error, it'll also
+ // return false and we'll catch the error outside the if.
+
+ const_char_iterator sourceCur = sourceNext;
+ while (true) {
+ if (tokenCur >= tokenEnd) {
+ // We matched the whole token!
+ return true;
+ }
+
+ if (sourceCur >= sourceEnd) {
+ // We ran into the end of source while matching a token. This
+ // means we'll never find the token we're looking for.
+ return false;
+ }
+
+ if (!CaseInsensitiveUTF8CharsEqual(sourceCur, tokenCur,
+ sourceEnd, tokenEnd,
+ &sourceCur, &tokenCur, &error)) {
+ // sourceCur doesn't match tokenCur (or there's an error), so break
+ // out of this loop.
+ break;
+ }
+ }
+ }
+
+ // If something went wrong above, get out of here!
+ if (MOZ_UNLIKELY(error)) {
+ return false;
+ }
+
+ // We didn't match the token. If we're searching for matches on word
+ // boundaries, skip to the next word boundary. Otherwise, advance
+ // forward one character, using the sourceNext pointer we saved earlier.
+
+ if (aBehavior == eFindOnBoundary) {
+ sourceStart = nextWordBoundary(sourceStart, sourceNext, sourceEnd);
+ }
+ else {
+ sourceStart = sourceNext;
+ }
+
+ } while (sourceStart < sourceEnd);
+
+ return false;
+ }
+
+ static
+ MOZ_ALWAYS_INLINE nsDependentCString
+ getSharedString(mozIStorageValueArray* aValues, uint32_t aIndex) {
+ uint32_t len;
+ const char* str = aValues->AsSharedUTF8String(aIndex, &len);
+ if (!str) {
+ return nsDependentCString("", (uint32_t)0);
+ }
+ return nsDependentCString(str, len);
+ }
+
+} // End anonymous namespace
+
+namespace mozilla {
+namespace places {
+
+////////////////////////////////////////////////////////////////////////////////
+//// AutoComplete Matching Function
+
+ /* static */
+ nsresult
+ MatchAutoCompleteFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<MatchAutoCompleteFunction> function =
+ new MatchAutoCompleteFunction();
+
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("autocomplete_match"), kArgIndexLength, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ /* static */
+ nsDependentCSubstring
+ MatchAutoCompleteFunction::fixupURISpec(const nsACString &aURISpec,
+ int32_t aMatchBehavior,
+ nsACString &aSpecBuf)
+ {
+ nsDependentCSubstring fixedSpec;
+
+ // Try to unescape the string. If that succeeds and yields a different
+ // string which is also valid UTF-8, we'll use it.
+ // Otherwise, we will simply use our original string.
+ bool unescaped = NS_UnescapeURL(aURISpec.BeginReading(),
+ aURISpec.Length(), esc_SkipControl, aSpecBuf);
+ if (unescaped && IsUTF8(aSpecBuf)) {
+ fixedSpec.Rebind(aSpecBuf, 0);
+ } else {
+ fixedSpec.Rebind(aURISpec, 0);
+ }
+
+ if (aMatchBehavior == mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED)
+ return fixedSpec;
+
+ if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("http://"))) {
+ fixedSpec.Rebind(fixedSpec, 7);
+ } else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("https://"))) {
+ fixedSpec.Rebind(fixedSpec, 8);
+ } else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("ftp://"))) {
+ fixedSpec.Rebind(fixedSpec, 6);
+ }
+
+ if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("www."))) {
+ fixedSpec.Rebind(fixedSpec, 4);
+ }
+
+ return fixedSpec;
+ }
+
+ /* static */
+ bool
+ MatchAutoCompleteFunction::findAnywhere(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString)
+ {
+ // We can't use FindInReadable here; it works only for ASCII.
+
+ return findInString(aToken, aSourceString, eFindAnywhere);
+ }
+
+ /* static */
+ bool
+ MatchAutoCompleteFunction::findOnBoundary(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString)
+ {
+ return findInString(aToken, aSourceString, eFindOnBoundary);
+ }
+
+ /* static */
+ bool
+ MatchAutoCompleteFunction::findBeginning(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString)
+ {
+ NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!");
+
+ // We can't use StringBeginsWith here, unfortunately. Although it will
+ // happily take a case-insensitive UTF8 comparator, it eventually calls
+ // nsACString::Equals, which checks that the two strings contain the same
+ // number of bytes before calling the comparator. Two characters may be
+ // case-insensitively equal while taking up different numbers of bytes, so
+ // this is not what we want.
+
+ const_char_iterator tokenStart(aToken.BeginReading()),
+ tokenEnd(aToken.EndReading()),
+ sourceStart(aSourceString.BeginReading()),
+ sourceEnd(aSourceString.EndReading());
+
+ bool dummy;
+ while (sourceStart < sourceEnd &&
+ CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart,
+ sourceEnd, tokenEnd,
+ &sourceStart, &tokenStart, &dummy)) {
+
+ // We found the token!
+ if (tokenStart >= tokenEnd) {
+ return true;
+ }
+ }
+
+ // We don't need to check CaseInsensitiveUTF8CharsEqual's error condition
+ // (stored in |dummy|), since the function will return false if it
+ // encounters an error.
+
+ return false;
+ }
+
+ /* static */
+ bool
+ MatchAutoCompleteFunction::findBeginningCaseSensitive(
+ const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString)
+ {
+ NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!");
+
+ return StringBeginsWith(aSourceString, aToken);
+ }
+
+ /* static */
+ MatchAutoCompleteFunction::searchFunctionPtr
+ MatchAutoCompleteFunction::getSearchFunction(int32_t aBehavior)
+ {
+ switch (aBehavior) {
+ case mozIPlacesAutoComplete::MATCH_ANYWHERE:
+ case mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED:
+ return findAnywhere;
+ case mozIPlacesAutoComplete::MATCH_BEGINNING:
+ return findBeginning;
+ case mozIPlacesAutoComplete::MATCH_BEGINNING_CASE_SENSITIVE:
+ return findBeginningCaseSensitive;
+ case mozIPlacesAutoComplete::MATCH_BOUNDARY:
+ default:
+ return findOnBoundary;
+ };
+ }
+
+ NS_IMPL_ISUPPORTS(
+ MatchAutoCompleteFunction,
+ mozIStorageFunction
+ )
+
+ 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 =
+ getSharedString(aArguments, kArgSearchString);
+ nsDependentCString url =
+ getSharedString(aArguments, kArgIndexURL);
+
+ int32_t matchBehavior = aArguments->AsInt32(kArgIndexMatchBehavior);
+
+ // We only want to filter javascript: URLs if we are not supposed to search
+ // for them, and the search does not start with "javascript:".
+ if (matchBehavior != mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED &&
+ StringBeginsWith(url, NS_LITERAL_CSTRING("javascript:")) &&
+ !HAS_BEHAVIOR(JAVASCRIPT) &&
+ !StringBeginsWith(searchString, NS_LITERAL_CSTRING("javascript:"))) {
+ NS_ADDREF(*_result = new IntegerVariant(0));
+ return NS_OK;
+ }
+
+ int32_t visitCount = aArguments->AsInt32(kArgIndexVisitCount);
+ bool typed = aArguments->AsInt32(kArgIndexTyped) ? true : false;
+ bool bookmark = aArguments->AsInt32(kArgIndexBookmark) ? true : false;
+ nsDependentCString tags = getSharedString(aArguments, kArgIndexTags);
+ int32_t openPageCount = aArguments->AsInt32(kArgIndexOpenPageCount);
+ bool matches = false;
+ if (HAS_BEHAVIOR(RESTRICT)) {
+ // Make sure we match all the filter requirements. If a given restriction
+ // is active, make sure the corresponding condition is not true.
+ matches = (!HAS_BEHAVIOR(HISTORY) || visitCount > 0) &&
+ (!HAS_BEHAVIOR(TYPED) || typed) &&
+ (!HAS_BEHAVIOR(BOOKMARK) || bookmark) &&
+ (!HAS_BEHAVIOR(TAG) || !tags.IsVoid()) &&
+ (!HAS_BEHAVIOR(OPENPAGE) || openPageCount > 0);
+ } else {
+ // Make sure that we match all the filter requirements and that the
+ // corresponding condition is true if at least a given restriction is active.
+ matches = (HAS_BEHAVIOR(HISTORY) && visitCount > 0) ||
+ (HAS_BEHAVIOR(TYPED) && typed) ||
+ (HAS_BEHAVIOR(BOOKMARK) && bookmark) ||
+ (HAS_BEHAVIOR(TAG) && !tags.IsVoid()) ||
+ (HAS_BEHAVIOR(OPENPAGE) && openPageCount > 0);
+ }
+
+ if (!matches) {
+ NS_ADDREF(*_result = new IntegerVariant(0));
+ 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 = getSharedString(aArguments, kArgIndexTitle);
+ // Limit the number of chars we search through.
+ const nsDependentCSubstring& trimmedTitle =
+ Substring(title, 0, MAX_CHARS_TO_SEARCH_THROUGH);
+
+ // Determine if every token matches either the bookmark title, tags, page
+ // title, or page URL.
+ nsCWhitespaceTokenizer tokenizer(searchString);
+ while (matches && tokenizer.hasMoreTokens()) {
+ const nsDependentCSubstring &token = tokenizer.nextToken();
+
+ if (HAS_BEHAVIOR(TITLE) && HAS_BEHAVIOR(URL)) {
+ matches = (searchFunction(token, trimmedTitle) ||
+ searchFunction(token, tags)) &&
+ searchFunction(token, trimmedUrl);
+ }
+ else if (HAS_BEHAVIOR(TITLE)) {
+ matches = searchFunction(token, trimmedTitle) ||
+ searchFunction(token, tags);
+ }
+ else if (HAS_BEHAVIOR(URL)) {
+ matches = searchFunction(token, trimmedUrl);
+ }
+ else {
+ matches = searchFunction(token, trimmedTitle) ||
+ searchFunction(token, tags) ||
+ searchFunction(token, trimmedUrl);
+ }
+ }
+
+ NS_ADDREF(*_result = new IntegerVariant(matches ? 1 : 0));
+ return NS_OK;
+ #undef HAS_BEHAVIOR
+ }
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Frecency Calculation Function
+
+ /* static */
+ nsresult
+ CalculateFrecencyFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<CalculateFrecencyFunction> function =
+ new CalculateFrecencyFunction();
+
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("calculate_frecency"), 1, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMPL_ISUPPORTS(
+ CalculateFrecencyFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ CalculateFrecencyFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+ {
+ // Fetch arguments. Use default values if they were omitted.
+ uint32_t numEntries;
+ nsresult rv = aArguments->GetNumEntries(&numEntries);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(numEntries == 1, "unexpected number of arguments");
+
+ int64_t pageId = aArguments->AsInt64(0);
+ MOZ_ASSERT(pageId > 0, "Should always pass a valid page id");
+ if (pageId <= 0) {
+ NS_ADDREF(*_result = new IntegerVariant(0));
+ return NS_OK;
+ }
+
+ int32_t typed = 0;
+ int32_t visitCount = 0;
+ bool hasBookmark = false;
+ int32_t isQuery = 0;
+ float pointsForSampledVisits = 0.0;
+ int32_t numSampledVisits = 0;
+ int32_t bonus = 0;
+
+ // This is a const version of the history object for thread-safety.
+ const nsNavHistory* history = nsNavHistory::GetConstHistoryService();
+ NS_ENSURE_STATE(history);
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+
+
+ // Fetch the page stats from the database.
+ {
+ RefPtr<mozIStorageStatement> getPageInfo = DB->GetStatement(
+ "SELECT typed, visit_count, foreign_count, "
+ "(substr(url, 0, 7) = 'place:') "
+ "FROM moz_places "
+ "WHERE id = :page_id "
+ );
+ NS_ENSURE_STATE(getPageInfo);
+ mozStorageStatementScoper infoScoper(getPageInfo);
+
+ rv = getPageInfo->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult = false;
+ rv = getPageInfo->ExecuteStep(&hasResult);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_UNEXPECTED);
+
+ rv = getPageInfo->GetInt32(0, &typed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = getPageInfo->GetInt32(1, &visitCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t foreignCount = 0;
+ rv = getPageInfo->GetInt32(2, &foreignCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ hasBookmark = foreignCount > 0;
+ rv = getPageInfo->GetInt32(3, &isQuery);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (visitCount > 0) {
+ // Get a sample of the last visits to the page, to calculate its weight.
+ // In case of a temporary or permanent redirect, calculate the frecency
+ // as if the original page was visited.
+ nsCOMPtr<mozIStorageStatement> getVisits = DB->GetStatement(
+ NS_LITERAL_CSTRING(
+ "/* do not warn (bug 659740 - SQLite may ignore index if few visits exist) */"
+ "SELECT "
+ "ROUND((strftime('%s','now','localtime','utc') - v.visit_date/1000000)/86400), "
+ "IFNULL(r.visit_type, v.visit_type), "
+ "v.visit_date "
+ "FROM moz_historyvisits v "
+ "LEFT JOIN moz_historyvisits r ON r.id = v.from_visit AND v.visit_type BETWEEN "
+ ) + nsPrintfCString("%d AND %d ", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
+ nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) +
+ NS_LITERAL_CSTRING(
+ "WHERE v.place_id = :page_id "
+ "ORDER BY v.visit_date DESC "
+ )
+ );
+ NS_ENSURE_STATE(getVisits);
+ mozStorageStatementScoper visitsScoper(getVisits);
+ rv = getVisits->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Fetch only a limited number of recent visits.
+ bool hasResult = false;
+ for (int32_t maxVisits = history->GetNumVisitsForFrecency();
+ numSampledVisits < maxVisits &&
+ NS_SUCCEEDED(getVisits->ExecuteStep(&hasResult)) && hasResult;
+ numSampledVisits++) {
+ int32_t visitType;
+ rv = getVisits->GetInt32(1, &visitType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bonus = history->GetFrecencyTransitionBonus(visitType, true);
+
+ // Add the bookmark visit bonus.
+ if (hasBookmark) {
+ bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, true);
+ }
+
+ // If bonus was zero, we can skip the work to determine the weight.
+ if (bonus) {
+ int32_t ageInDays = getVisits->AsInt32(0);
+ int32_t weight = history->GetFrecencyAgedWeight(ageInDays);
+ pointsForSampledVisits += (float)(weight * (bonus / 100.0));
+ }
+ }
+ }
+
+ // If we sampled some visits for this page, use the calculated weight.
+ if (numSampledVisits) {
+ // We were unable to calculate points, maybe cause all the visits in the
+ // sample had a zero bonus. Though, we know the page has some past valid
+ // visit, or visit_count would be zero. Thus we set the frecency to
+ // -1, so they are still shown in autocomplete.
+ if (!pointsForSampledVisits) {
+ NS_ADDREF(*_result = new IntegerVariant(-1));
+ }
+ else {
+ // Estimate frecency using the sampled visits.
+ // Use ceilf() so that we don't round down to 0, which
+ // would cause us to completely ignore the place during autocomplete.
+ NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(visitCount * ceilf(pointsForSampledVisits) / numSampledVisits)));
+ }
+ return NS_OK;
+ }
+
+ // Otherwise this page has no visits, it may be bookmarked.
+ if (!hasBookmark || isQuery) {
+ NS_ADDREF(*_result = new IntegerVariant(0));
+ return NS_OK;
+ }
+
+ // For unvisited bookmarks, produce a non-zero frecency, so that they show
+ // up in URL bar autocomplete.
+ visitCount = 1;
+
+ // Make it so something bookmarked and typed will have a higher frecency
+ // than something just typed or just bookmarked.
+ bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, false);
+ if (typed) {
+ bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_TYPED, false);
+ }
+
+ // Assume "now" as our ageInDays, so use the first bucket.
+ pointsForSampledVisits = history->GetFrecencyBucketWeight(1) * (bonus / (float)100.0);
+
+ // use ceilf() so that we don't round down to 0, which
+ // would cause us to completely ignore the place during autocomplete
+ NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(visitCount * ceilf(pointsForSampledVisits))));
+
+ return NS_OK;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+//// GUID Creation Function
+
+ /* static */
+ nsresult
+ GenerateGUIDFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<GenerateGUIDFunction> function = new GenerateGUIDFunction();
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("generate_guid"), 0, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMPL_ISUPPORTS(
+ GenerateGUIDFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ GenerateGUIDFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+ {
+ nsAutoCString guid;
+ nsresult rv = GenerateGUID(guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*_result = new UTF8TextVariant(guid));
+ return NS_OK;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Get Unreversed Host Function
+
+ /* static */
+ nsresult
+ GetUnreversedHostFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<GetUnreversedHostFunction> function = new GetUnreversedHostFunction();
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("get_unreversed_host"), 1, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMPL_ISUPPORTS(
+ GetUnreversedHostFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ GetUnreversedHostFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+ {
+ // Must have non-null function arguments.
+ MOZ_ASSERT(aArguments);
+
+ nsAutoString src;
+ aArguments->GetString(0, src);
+
+ RefPtr<nsVariant> result = new nsVariant();
+
+ if (src.Length()>1) {
+ src.Truncate(src.Length() - 1);
+ nsAutoString dest;
+ ReverseString(src, dest);
+ result->SetAsAString(dest);
+ }
+ else {
+ result->SetAsAString(EmptyString());
+ }
+ result.forget(_result);
+ return NS_OK;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Fixup URL Function
+
+ /* static */
+ nsresult
+ FixupURLFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<FixupURLFunction> function = new FixupURLFunction();
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("fixup_url"), 1, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMPL_ISUPPORTS(
+ FixupURLFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ FixupURLFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+ {
+ // Must have non-null function arguments.
+ MOZ_ASSERT(aArguments);
+
+ nsAutoString src;
+ aArguments->GetString(0, src);
+
+ RefPtr<nsVariant> result = new nsVariant();
+
+ if (StringBeginsWith(src, NS_LITERAL_STRING("http://")))
+ src.Cut(0, 7);
+ else if (StringBeginsWith(src, NS_LITERAL_STRING("https://")))
+ src.Cut(0, 8);
+ else if (StringBeginsWith(src, NS_LITERAL_STRING("ftp://")))
+ src.Cut(0, 6);
+
+ // Remove common URL hostname prefixes
+ if (StringBeginsWith(src, NS_LITERAL_STRING("www."))) {
+ src.Cut(0, 4);
+ }
+
+ result->SetAsAString(src);
+ result.forget(_result);
+ return NS_OK;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Frecency Changed Notification Function
+
+ /* static */
+ nsresult
+ FrecencyNotificationFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<FrecencyNotificationFunction> function =
+ new FrecencyNotificationFunction();
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("notify_frecency"), 5, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMPL_ISUPPORTS(
+ FrecencyNotificationFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ FrecencyNotificationFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
+ nsIVariant **_result)
+ {
+ uint32_t numArgs;
+ nsresult rv = aArgs->GetNumEntries(&numArgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(numArgs == 5);
+
+ int32_t newFrecency = aArgs->AsInt32(0);
+
+ nsAutoCString spec;
+ rv = aArgs->GetUTF8String(1, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString guid;
+ rv = aArgs->GetUTF8String(2, guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hidden = static_cast<bool>(aArgs->AsInt32(3));
+ PRTime lastVisitDate = static_cast<PRTime>(aArgs->AsInt64(4));
+
+ const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
+ NS_ENSURE_STATE(navHistory);
+ navHistory->DispatchFrecencyChangedNotification(spec, newFrecency, guid,
+ hidden, lastVisitDate);
+
+ RefPtr<nsVariant> result = new nsVariant();
+ rv = result->SetAsInt32(newFrecency);
+ NS_ENSURE_SUCCESS(rv, rv);
+ result.forget(_result);
+ return NS_OK;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Store Last Inserted Id Function
+
+ /* static */
+ nsresult
+ StoreLastInsertedIdFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<StoreLastInsertedIdFunction> function =
+ new StoreLastInsertedIdFunction();
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("store_last_inserted_id"), 2, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMPL_ISUPPORTS(
+ StoreLastInsertedIdFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ StoreLastInsertedIdFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
+ nsIVariant **_result)
+ {
+ uint32_t numArgs;
+ nsresult rv = aArgs->GetNumEntries(&numArgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(numArgs == 2);
+
+ nsAutoCString table;
+ rv = aArgs->GetUTF8String(0, table);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t lastInsertedId = aArgs->AsInt64(1);
+
+ MOZ_ASSERT(table.EqualsLiteral("moz_places") ||
+ table.EqualsLiteral("moz_historyvisits") ||
+ table.EqualsLiteral("moz_bookmarks"));
+
+ if (table.EqualsLiteral("moz_bookmarks")) {
+ nsNavBookmarks::StoreLastInsertedId(table, lastInsertedId);
+ } else {
+ nsNavHistory::StoreLastInsertedId(table, lastInsertedId);
+ }
+
+ RefPtr<nsVariant> result = new nsVariant();
+ rv = result->SetAsInt64(lastInsertedId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ result.forget(_result);
+ return NS_OK;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Hash Function
+
+ /* static */
+ nsresult
+ HashFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<HashFunction> function = new HashFunction();
+ return aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("hash"), -1, function
+ );
+ }
+
+ NS_IMPL_ISUPPORTS(
+ HashFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ HashFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+ {
+ // Must have non-null function arguments.
+ MOZ_ASSERT(aArguments);
+
+ // Fetch arguments. Use default values if they were omitted.
+ uint32_t numEntries;
+ nsresult rv = aArguments->GetNumEntries(&numEntries);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(numEntries >= 1 && numEntries <= 2, NS_ERROR_FAILURE);
+
+ nsString str;
+ aArguments->GetString(0, str);
+ nsAutoCString mode;
+ if (numEntries > 1) {
+ aArguments->GetUTF8String(1, mode);
+ }
+
+ RefPtr<nsVariant> result = new nsVariant();
+ if (mode.IsEmpty()) {
+ // URI-like strings (having a prefix before a colon), are handled specially,
+ // as a 48 bit hash, where first 16 bits are the prefix hash, while the
+ // other 32 are the string hash.
+ // The 16 bits have been decided based on the fact hashing all of the IANA
+ // known schemes, plus "places", does not generate collisions.
+ nsAString::const_iterator start, tip, end;
+ str.BeginReading(tip);
+ start = tip;
+ str.EndReading(end);
+ if (FindInReadable(NS_LITERAL_STRING(":"), tip, end)) {
+ const nsDependentSubstring& prefix = Substring(start, tip);
+ uint64_t prefixHash = static_cast<uint64_t>(HashString(prefix) & 0x0000FFFF);
+ // The second half of the url is more likely to be unique, so we add it.
+ uint32_t srcHash = HashString(str);
+ uint64_t hash = (prefixHash << 32) + srcHash;
+ result->SetAsInt64(hash);
+ } else {
+ uint32_t hash = HashString(str);
+ result->SetAsInt64(hash);
+ }
+ } else if (mode.Equals(NS_LITERAL_CSTRING("prefix_lo"))) {
+ // Keep only 16 bits.
+ uint64_t hash = static_cast<uint64_t>(HashString(str) & 0x0000FFFF) << 32;
+ result->SetAsInt64(hash);
+ } else if (mode.Equals(NS_LITERAL_CSTRING("prefix_hi"))) {
+ // Keep only 16 bits.
+ uint64_t hash = static_cast<uint64_t>(HashString(str) & 0x0000FFFF) << 32;
+ // Make this a prefix upper bound by filling the lowest 32 bits.
+ hash += 0xFFFFFFFF;
+ result->SetAsInt64(hash);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ result.forget(_result);
+ return NS_OK;
+ }
+
+} // namespace places
+} // namespace mozilla