michael@0: /* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/storage.h" michael@0: #include "nsString.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsWhitespaceTokenizer.h" michael@0: #include "nsEscape.h" michael@0: #include "mozIPlacesAutoComplete.h" michael@0: #include "SQLFunctions.h" michael@0: #include "nsMathUtils.h" michael@0: #include "nsUTF8Utils.h" michael@0: #include "nsINavHistoryService.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsNavHistory.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/Likely.h" michael@0: michael@0: using namespace mozilla::storage; michael@0: michael@0: // Keep the GUID-related parts of this file in sync with toolkit/downloads/SQLFunctions.cpp! michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Anonymous Helpers michael@0: michael@0: namespace { michael@0: michael@0: typedef nsACString::const_char_iterator const_char_iterator; michael@0: michael@0: /** michael@0: * Get a pointer to the word boundary after aStart if aStart points to an michael@0: * ASCII letter (i.e. [a-zA-Z]). Otherwise, return aNext, which we assume michael@0: * points to the next character in the UTF-8 sequence. michael@0: * michael@0: * We define a word boundary as anything that's not [a-z] -- this lets us michael@0: * match CamelCase words. michael@0: * michael@0: * @param aStart the beginning of the UTF-8 sequence michael@0: * @param aNext the next character in the sequence michael@0: * @param aEnd the first byte which is not part of the sequence michael@0: * michael@0: * @return a pointer to the next word boundary after aStart michael@0: */ michael@0: static michael@0: MOZ_ALWAYS_INLINE const_char_iterator michael@0: nextWordBoundary(const_char_iterator const aStart, michael@0: const_char_iterator const aNext, michael@0: const_char_iterator const aEnd) { michael@0: michael@0: const_char_iterator cur = aStart; michael@0: if (('a' <= *cur && *cur <= 'z') || michael@0: ('A' <= *cur && *cur <= 'Z')) { michael@0: michael@0: // Since we'll halt as soon as we see a non-ASCII letter, we can do a michael@0: // simple byte-by-byte comparison here and avoid the overhead of a michael@0: // UTF8CharEnumerator. michael@0: do { michael@0: cur++; michael@0: } while (cur < aEnd && 'a' <= *cur && *cur <= 'z'); michael@0: } michael@0: else { michael@0: cur = aNext; michael@0: } michael@0: michael@0: return cur; michael@0: } michael@0: michael@0: enum FindInStringBehavior { michael@0: eFindOnBoundary, michael@0: eFindAnywhere michael@0: }; michael@0: michael@0: /** michael@0: * findAnywhere and findOnBoundary do almost the same thing, so it's natural michael@0: * to implement them in terms of a single function. They're both michael@0: * performance-critical functions, however, and checking aBehavior makes them michael@0: * a bit slower. Our solution is to define findInString as MOZ_ALWAYS_INLINE michael@0: * and rely on the compiler to optimize out the aBehavior check. michael@0: * michael@0: * @param aToken michael@0: * The token we're searching for michael@0: * @param aSourceString michael@0: * The string in which we're searching michael@0: * @param aBehavior michael@0: * eFindOnBoundary if we should only consider matchines which occur on michael@0: * word boundaries, or eFindAnywhere if we should consider matches michael@0: * which appear anywhere. michael@0: * michael@0: * @return true if aToken was found in aSourceString, false otherwise. michael@0: */ michael@0: static michael@0: MOZ_ALWAYS_INLINE bool michael@0: findInString(const nsDependentCSubstring &aToken, michael@0: const nsACString &aSourceString, michael@0: FindInStringBehavior aBehavior) michael@0: { michael@0: // CaseInsensitiveUTF8CharsEqual assumes that there's at least one byte in michael@0: // the both strings, so don't pass an empty token here. michael@0: NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!"); michael@0: michael@0: // We cannot match anything if there is nothing to search. michael@0: if (aSourceString.IsEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: const_char_iterator tokenStart(aToken.BeginReading()), michael@0: tokenEnd(aToken.EndReading()), michael@0: sourceStart(aSourceString.BeginReading()), michael@0: sourceEnd(aSourceString.EndReading()); michael@0: michael@0: do { michael@0: // We are on a word boundary (if aBehavior == eFindOnBoundary). See if michael@0: // aToken matches sourceStart. michael@0: michael@0: // Check whether the first character in the token matches the character michael@0: // at sourceStart. At the same time, get a pointer to the next character michael@0: // in both the token and the source. michael@0: const_char_iterator sourceNext, tokenCur; michael@0: bool error; michael@0: if (CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart, michael@0: sourceEnd, tokenEnd, michael@0: &sourceNext, &tokenCur, &error)) { michael@0: michael@0: // We don't need to check |error| here -- if michael@0: // CaseInsensitiveUTF8CharCompare encounters an error, it'll also michael@0: // return false and we'll catch the error outside the if. michael@0: michael@0: const_char_iterator sourceCur = sourceNext; michael@0: while (true) { michael@0: if (tokenCur >= tokenEnd) { michael@0: // We matched the whole token! michael@0: return true; michael@0: } michael@0: michael@0: if (sourceCur >= sourceEnd) { michael@0: // We ran into the end of source while matching a token. This michael@0: // means we'll never find the token we're looking for. michael@0: return false; michael@0: } michael@0: michael@0: if (!CaseInsensitiveUTF8CharsEqual(sourceCur, tokenCur, michael@0: sourceEnd, tokenEnd, michael@0: &sourceCur, &tokenCur, &error)) { michael@0: // sourceCur doesn't match tokenCur (or there's an error), so break michael@0: // out of this loop. michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If something went wrong above, get out of here! michael@0: if (MOZ_UNLIKELY(error)) { michael@0: return false; michael@0: } michael@0: michael@0: // We didn't match the token. If we're searching for matches on word michael@0: // boundaries, skip to the next word boundary. Otherwise, advance michael@0: // forward one character, using the sourceNext pointer we saved earlier. michael@0: michael@0: if (aBehavior == eFindOnBoundary) { michael@0: sourceStart = nextWordBoundary(sourceStart, sourceNext, sourceEnd); michael@0: } michael@0: else { michael@0: sourceStart = sourceNext; michael@0: } michael@0: michael@0: } while (sourceStart < sourceEnd); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: } // End anonymous namespace michael@0: michael@0: namespace mozilla { michael@0: namespace places { michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// AutoComplete Matching Function michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// MatchAutoCompleteFunction michael@0: michael@0: /* static */ michael@0: nsresult michael@0: MatchAutoCompleteFunction::create(mozIStorageConnection *aDBConn) michael@0: { michael@0: nsRefPtr function = michael@0: new MatchAutoCompleteFunction(); michael@0: michael@0: nsresult rv = aDBConn->CreateFunction( michael@0: NS_LITERAL_CSTRING("autocomplete_match"), kArgIndexLength, function michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ michael@0: void michael@0: MatchAutoCompleteFunction::fixupURISpec(const nsCString &aURISpec, michael@0: int32_t aMatchBehavior, michael@0: nsCString &_fixedSpec) michael@0: { michael@0: nsCString unescapedSpec; michael@0: (void)NS_UnescapeURL(aURISpec, esc_SkipControl | esc_AlwaysCopy, michael@0: unescapedSpec); michael@0: michael@0: // If this unescaped string is valid UTF-8, we'll use it. Otherwise, michael@0: // we will simply use our original string. michael@0: NS_ASSERTION(_fixedSpec.IsEmpty(), michael@0: "Passing a non-empty string as an out parameter!"); michael@0: if (IsUTF8(unescapedSpec)) michael@0: _fixedSpec.Assign(unescapedSpec); michael@0: else michael@0: _fixedSpec.Assign(aURISpec); michael@0: michael@0: if (aMatchBehavior == mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED) michael@0: return; michael@0: michael@0: if (StringBeginsWith(_fixedSpec, NS_LITERAL_CSTRING("http://"))) michael@0: _fixedSpec.Cut(0, 7); michael@0: else if (StringBeginsWith(_fixedSpec, NS_LITERAL_CSTRING("https://"))) michael@0: _fixedSpec.Cut(0, 8); michael@0: else if (StringBeginsWith(_fixedSpec, NS_LITERAL_CSTRING("ftp://"))) michael@0: _fixedSpec.Cut(0, 6); michael@0: michael@0: if (StringBeginsWith(_fixedSpec, NS_LITERAL_CSTRING("www."))) michael@0: _fixedSpec.Cut(0, 4); michael@0: } michael@0: michael@0: /* static */ michael@0: bool michael@0: MatchAutoCompleteFunction::findAnywhere(const nsDependentCSubstring &aToken, michael@0: const nsACString &aSourceString) michael@0: { michael@0: // We can't use FindInReadable here; it works only for ASCII. michael@0: michael@0: return findInString(aToken, aSourceString, eFindAnywhere); michael@0: } michael@0: michael@0: /* static */ michael@0: bool michael@0: MatchAutoCompleteFunction::findOnBoundary(const nsDependentCSubstring &aToken, michael@0: const nsACString &aSourceString) michael@0: { michael@0: return findInString(aToken, aSourceString, eFindOnBoundary); michael@0: } michael@0: michael@0: /* static */ michael@0: bool michael@0: MatchAutoCompleteFunction::findBeginning(const nsDependentCSubstring &aToken, michael@0: const nsACString &aSourceString) michael@0: { michael@0: NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!"); michael@0: michael@0: // We can't use StringBeginsWith here, unfortunately. Although it will michael@0: // happily take a case-insensitive UTF8 comparator, it eventually calls michael@0: // nsACString::Equals, which checks that the two strings contain the same michael@0: // number of bytes before calling the comparator. Two characters may be michael@0: // case-insensitively equal while taking up different numbers of bytes, so michael@0: // this is not what we want. michael@0: michael@0: const_char_iterator tokenStart(aToken.BeginReading()), michael@0: tokenEnd(aToken.EndReading()), michael@0: sourceStart(aSourceString.BeginReading()), michael@0: sourceEnd(aSourceString.EndReading()); michael@0: michael@0: bool dummy; michael@0: while (sourceStart < sourceEnd && michael@0: CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart, michael@0: sourceEnd, tokenEnd, michael@0: &sourceStart, &tokenStart, &dummy)) { michael@0: michael@0: // We found the token! michael@0: if (tokenStart >= tokenEnd) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // We don't need to check CaseInsensitiveUTF8CharsEqual's error condition michael@0: // (stored in |dummy|), since the function will return false if it michael@0: // encounters an error. michael@0: michael@0: return false; michael@0: } michael@0: michael@0: /* static */ michael@0: bool michael@0: MatchAutoCompleteFunction::findBeginningCaseSensitive( michael@0: const nsDependentCSubstring &aToken, michael@0: const nsACString &aSourceString) michael@0: { michael@0: NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!"); michael@0: michael@0: return StringBeginsWith(aSourceString, aToken); michael@0: } michael@0: michael@0: /* static */ michael@0: MatchAutoCompleteFunction::searchFunctionPtr michael@0: MatchAutoCompleteFunction::getSearchFunction(int32_t aBehavior) michael@0: { michael@0: switch (aBehavior) { michael@0: case mozIPlacesAutoComplete::MATCH_ANYWHERE: michael@0: case mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED: michael@0: return findAnywhere; michael@0: case mozIPlacesAutoComplete::MATCH_BEGINNING: michael@0: return findBeginning; michael@0: case mozIPlacesAutoComplete::MATCH_BEGINNING_CASE_SENSITIVE: michael@0: return findBeginningCaseSensitive; michael@0: case mozIPlacesAutoComplete::MATCH_BOUNDARY: michael@0: default: michael@0: return findOnBoundary; michael@0: }; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS( michael@0: MatchAutoCompleteFunction, michael@0: mozIStorageFunction michael@0: ) michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// mozIStorageFunction michael@0: michael@0: NS_IMETHODIMP michael@0: MatchAutoCompleteFunction::OnFunctionCall(mozIStorageValueArray *aArguments, michael@0: nsIVariant **_result) michael@0: { michael@0: // Macro to make the code a bit cleaner and easier to read. Operates on michael@0: // searchBehavior. michael@0: int32_t searchBehavior = aArguments->AsInt32(kArgIndexSearchBehavior); michael@0: #define HAS_BEHAVIOR(aBitName) \ michael@0: (searchBehavior & mozIPlacesAutoComplete::BEHAVIOR_##aBitName) michael@0: michael@0: nsAutoCString searchString; michael@0: (void)aArguments->GetUTF8String(kArgSearchString, searchString); michael@0: nsCString url; michael@0: (void)aArguments->GetUTF8String(kArgIndexURL, url); michael@0: michael@0: int32_t matchBehavior = aArguments->AsInt32(kArgIndexMatchBehavior); michael@0: michael@0: // We only want to filter javascript: URLs if we are not supposed to search michael@0: // for them, and the search does not start with "javascript:". michael@0: if (matchBehavior != mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED && michael@0: !HAS_BEHAVIOR(JAVASCRIPT) && michael@0: !StringBeginsWith(searchString, NS_LITERAL_CSTRING("javascript:")) && michael@0: StringBeginsWith(url, NS_LITERAL_CSTRING("javascript:"))) { michael@0: NS_ADDREF(*_result = new IntegerVariant(0)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t visitCount = aArguments->AsInt32(kArgIndexVisitCount); michael@0: bool typed = aArguments->AsInt32(kArgIndexTyped) ? true : false; michael@0: bool bookmark = aArguments->AsInt32(kArgIndexBookmark) ? true : false; michael@0: nsAutoCString tags; michael@0: (void)aArguments->GetUTF8String(kArgIndexTags, tags); michael@0: int32_t openPageCount = aArguments->AsInt32(kArgIndexOpenPageCount); michael@0: michael@0: // Make sure we match all the filter requirements. If a given restriction michael@0: // is active, make sure the corresponding condition is not true. michael@0: bool matches = !( michael@0: (HAS_BEHAVIOR(HISTORY) && visitCount == 0) || michael@0: (HAS_BEHAVIOR(TYPED) && !typed) || michael@0: (HAS_BEHAVIOR(BOOKMARK) && !bookmark) || michael@0: (HAS_BEHAVIOR(TAG) && tags.IsVoid()) || michael@0: (HAS_BEHAVIOR(OPENPAGE) && openPageCount == 0) michael@0: ); michael@0: if (!matches) { michael@0: NS_ADDREF(*_result = new IntegerVariant(0)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Obtain our search function. michael@0: searchFunctionPtr searchFunction = getSearchFunction(matchBehavior); michael@0: michael@0: // Clean up our URI spec and prepare it for searching. michael@0: nsCString fixedURI; michael@0: fixupURISpec(url, matchBehavior, fixedURI); michael@0: michael@0: nsAutoCString title; michael@0: (void)aArguments->GetUTF8String(kArgIndexTitle, title); michael@0: michael@0: // Determine if every token matches either the bookmark title, tags, page michael@0: // title, or page URL. michael@0: nsCWhitespaceTokenizer tokenizer(searchString); michael@0: while (matches && tokenizer.hasMoreTokens()) { michael@0: const nsDependentCSubstring &token = tokenizer.nextToken(); michael@0: michael@0: if (HAS_BEHAVIOR(TITLE) && HAS_BEHAVIOR(URL)) { michael@0: matches = (searchFunction(token, title) || searchFunction(token, tags)) && michael@0: searchFunction(token, fixedURI); michael@0: } michael@0: else if (HAS_BEHAVIOR(TITLE)) { michael@0: matches = searchFunction(token, title) || searchFunction(token, tags); michael@0: } michael@0: else if (HAS_BEHAVIOR(URL)) { michael@0: matches = searchFunction(token, fixedURI); michael@0: } michael@0: else { michael@0: matches = searchFunction(token, title) || michael@0: searchFunction(token, tags) || michael@0: searchFunction(token, fixedURI); michael@0: } michael@0: } michael@0: michael@0: NS_ADDREF(*_result = new IntegerVariant(matches ? 1 : 0)); michael@0: return NS_OK; michael@0: #undef HAS_BEHAVIOR michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Frecency Calculation Function michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// CalculateFrecencyFunction michael@0: michael@0: /* static */ michael@0: nsresult michael@0: CalculateFrecencyFunction::create(mozIStorageConnection *aDBConn) michael@0: { michael@0: nsRefPtr function = michael@0: new CalculateFrecencyFunction(); michael@0: michael@0: nsresult rv = aDBConn->CreateFunction( michael@0: NS_LITERAL_CSTRING("calculate_frecency"), 1, function michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS( michael@0: CalculateFrecencyFunction, michael@0: mozIStorageFunction michael@0: ) michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// mozIStorageFunction michael@0: michael@0: NS_IMETHODIMP michael@0: CalculateFrecencyFunction::OnFunctionCall(mozIStorageValueArray *aArguments, michael@0: nsIVariant **_result) michael@0: { michael@0: // Fetch arguments. Use default values if they were omitted. michael@0: uint32_t numEntries; michael@0: nsresult rv = aArguments->GetNumEntries(&numEntries); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ASSERTION(numEntries > 0, "unexpected number of arguments"); michael@0: michael@0: Telemetry::AutoTimer timer; michael@0: michael@0: int64_t pageId = aArguments->AsInt64(0); michael@0: int32_t typed = numEntries > 1 ? aArguments->AsInt32(1) : 0; michael@0: int32_t fullVisitCount = numEntries > 2 ? aArguments->AsInt32(2) : 0; michael@0: int64_t bookmarkId = numEntries > 3 ? aArguments->AsInt64(3) : 0; michael@0: int32_t visitCount = 0; michael@0: int32_t hidden = 0; michael@0: int32_t isQuery = 0; michael@0: float pointsForSampledVisits = 0.0; michael@0: michael@0: // This is a const version of the history object for thread-safety. michael@0: const nsNavHistory* history = nsNavHistory::GetConstHistoryService(); michael@0: NS_ENSURE_STATE(history); michael@0: nsRefPtr DB = Database::GetDatabase(); michael@0: NS_ENSURE_STATE(DB); michael@0: michael@0: if (pageId > 0) { michael@0: // The page is already in the database, and we can fetch current michael@0: // params from the database. michael@0: nsRefPtr getPageInfo = DB->GetStatement( michael@0: "SELECT typed, hidden, visit_count, " michael@0: "(SELECT count(*) FROM moz_historyvisits WHERE place_id = :page_id), " michael@0: "EXISTS (SELECT 1 FROM moz_bookmarks WHERE fk = :page_id), " michael@0: "(url > 'place:' AND url < 'place;') " michael@0: "FROM moz_places " michael@0: "WHERE id = :page_id " michael@0: ); michael@0: NS_ENSURE_STATE(getPageInfo); michael@0: mozStorageStatementScoper infoScoper(getPageInfo); michael@0: michael@0: rv = getPageInfo->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasResult; michael@0: rv = getPageInfo->ExecuteStep(&hasResult); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED); michael@0: rv = getPageInfo->GetInt32(0, &typed); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = getPageInfo->GetInt32(1, &hidden); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = getPageInfo->GetInt32(2, &visitCount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = getPageInfo->GetInt32(3, &fullVisitCount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = getPageInfo->GetInt64(4, &bookmarkId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = getPageInfo->GetInt32(5, &isQuery); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // NOTE: This is not limited to visits with "visit_type NOT IN (0,4,7,8)" michael@0: // because otherwise it would not return any visit for those transitions michael@0: // causing an incorrect frecency, see CalculateFrecencyInternal(). michael@0: // In case of a temporary or permanent redirect, calculate the frecency michael@0: // as if the original page was visited. michael@0: // Get a sample of the last visits to the page, to calculate its weight. michael@0: nsCOMPtr getVisits = DB->GetStatement( michael@0: NS_LITERAL_CSTRING( michael@0: "/* do not warn (bug 659740 - SQLite may ignore index if few visits exist) */" michael@0: "SELECT " michael@0: "ROUND((strftime('%s','now','localtime','utc') - v.visit_date/1000000)/86400), " michael@0: "IFNULL(r.visit_type, v.visit_type), " michael@0: "v.visit_date " michael@0: "FROM moz_historyvisits v " michael@0: "LEFT JOIN moz_historyvisits r ON r.id = v.from_visit AND v.visit_type BETWEEN " michael@0: ) + nsPrintfCString("%d AND %d ", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, michael@0: nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) + michael@0: NS_LITERAL_CSTRING( michael@0: "WHERE v.place_id = :page_id " michael@0: "ORDER BY v.visit_date DESC " michael@0: ) michael@0: ); michael@0: NS_ENSURE_STATE(getVisits); michael@0: mozStorageStatementScoper visitsScoper(getVisits); michael@0: michael@0: rv = getVisits->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Fetch only a limited number of recent visits. michael@0: int32_t numSampledVisits = 0; michael@0: for (int32_t maxVisits = history->GetNumVisitsForFrecency(); michael@0: numSampledVisits < maxVisits && michael@0: NS_SUCCEEDED(getVisits->ExecuteStep(&hasResult)) && hasResult; michael@0: numSampledVisits++) { michael@0: int32_t visitType; michael@0: rv = getVisits->GetInt32(1, &visitType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: int32_t bonus = history->GetFrecencyTransitionBonus(visitType, true); michael@0: michael@0: // Always add the bookmark visit bonus. michael@0: if (bookmarkId) { michael@0: bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, true); michael@0: } michael@0: michael@0: // If bonus was zero, we can skip the work to determine the weight. michael@0: if (bonus) { michael@0: int32_t ageInDays = getVisits->AsInt32(0); michael@0: int32_t weight = history->GetFrecencyAgedWeight(ageInDays); michael@0: pointsForSampledVisits += (float)(weight * (bonus / 100.0)); michael@0: } michael@0: } michael@0: michael@0: // If we found some visits for this page, use the calculated weight. michael@0: if (numSampledVisits) { michael@0: // fix for bug #412219 michael@0: if (!pointsForSampledVisits) { michael@0: // For URIs with zero points in the sampled recent visits michael@0: // but "browsing" type visits outside the sampling range, set michael@0: // frecency to -visit_count, so they're still shown in autocomplete. michael@0: NS_ADDREF(*_result = new IntegerVariant(-visitCount)); michael@0: } michael@0: else { michael@0: // Estimate frecency using the last few visits. michael@0: // Use ceilf() so that we don't round down to 0, which michael@0: // would cause us to completely ignore the place during autocomplete. michael@0: NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(fullVisitCount * ceilf(pointsForSampledVisits) / numSampledVisits))); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // This page is unknown or has no visits. It could have just been added, so michael@0: // use passed in or default values. michael@0: michael@0: // The code below works well for guessing the frecency on import, and we'll michael@0: // correct later once we have visits. michael@0: // TODO: What if we don't have visits and we never visit? We could end up michael@0: // with a really high value that keeps coming up in ac results? Should we michael@0: // only do this on import? Have to figure it out. michael@0: int32_t bonus = 0; michael@0: michael@0: // Make it so something bookmarked and typed will have a higher frecency michael@0: // than something just typed or just bookmarked. michael@0: if (bookmarkId && !isQuery) { michael@0: bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, false);; michael@0: // For unvisited bookmarks, produce a non-zero frecency, so that they show michael@0: // up in URL bar autocomplete. michael@0: fullVisitCount = 1; michael@0: } michael@0: michael@0: if (typed) { michael@0: bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_TYPED, false); michael@0: } michael@0: michael@0: // Assume "now" as our ageInDays, so use the first bucket. michael@0: pointsForSampledVisits = history->GetFrecencyBucketWeight(1) * (bonus / (float)100.0); michael@0: michael@0: // use ceilf() so that we don't round down to 0, which michael@0: // would cause us to completely ignore the place during autocomplete michael@0: NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(fullVisitCount * ceilf(pointsForSampledVisits)))); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// GUID Creation Function michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// GenerateGUIDFunction michael@0: michael@0: /* static */ michael@0: nsresult michael@0: GenerateGUIDFunction::create(mozIStorageConnection *aDBConn) michael@0: { michael@0: nsRefPtr function = new GenerateGUIDFunction(); michael@0: nsresult rv = aDBConn->CreateFunction( michael@0: NS_LITERAL_CSTRING("generate_guid"), 0, function michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS( michael@0: GenerateGUIDFunction, michael@0: mozIStorageFunction michael@0: ) michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// mozIStorageFunction michael@0: michael@0: NS_IMETHODIMP michael@0: GenerateGUIDFunction::OnFunctionCall(mozIStorageValueArray *aArguments, michael@0: nsIVariant **_result) michael@0: { michael@0: nsAutoCString guid; michael@0: nsresult rv = GenerateGUID(guid); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ADDREF(*_result = new UTF8TextVariant(guid)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Get Unreversed Host Function michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// GetUnreversedHostFunction michael@0: michael@0: /* static */ michael@0: nsresult michael@0: GetUnreversedHostFunction::create(mozIStorageConnection *aDBConn) michael@0: { michael@0: nsRefPtr function = new GetUnreversedHostFunction(); michael@0: nsresult rv = aDBConn->CreateFunction( michael@0: NS_LITERAL_CSTRING("get_unreversed_host"), 1, function michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS( michael@0: GetUnreversedHostFunction, michael@0: mozIStorageFunction michael@0: ) michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// mozIStorageFunction michael@0: michael@0: NS_IMETHODIMP michael@0: GetUnreversedHostFunction::OnFunctionCall(mozIStorageValueArray *aArguments, michael@0: nsIVariant **_result) michael@0: { michael@0: // Must have non-null function arguments. michael@0: MOZ_ASSERT(aArguments); michael@0: michael@0: nsAutoString src; michael@0: aArguments->GetString(0, src); michael@0: michael@0: nsCOMPtr result = michael@0: do_CreateInstance("@mozilla.org/variant;1"); michael@0: NS_ENSURE_STATE(result); michael@0: michael@0: if (src.Length()>1) { michael@0: src.Truncate(src.Length() - 1); michael@0: nsAutoString dest; michael@0: ReverseString(src, dest); michael@0: result->SetAsAString(dest); michael@0: } michael@0: else { michael@0: result->SetAsAString(EmptyString()); michael@0: } michael@0: NS_ADDREF(*_result = result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Fixup URL Function michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// FixupURLFunction michael@0: michael@0: /* static */ michael@0: nsresult michael@0: FixupURLFunction::create(mozIStorageConnection *aDBConn) michael@0: { michael@0: nsRefPtr function = new FixupURLFunction(); michael@0: nsresult rv = aDBConn->CreateFunction( michael@0: NS_LITERAL_CSTRING("fixup_url"), 1, function michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS( michael@0: FixupURLFunction, michael@0: mozIStorageFunction michael@0: ) michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// mozIStorageFunction michael@0: michael@0: NS_IMETHODIMP michael@0: FixupURLFunction::OnFunctionCall(mozIStorageValueArray *aArguments, michael@0: nsIVariant **_result) michael@0: { michael@0: // Must have non-null function arguments. michael@0: MOZ_ASSERT(aArguments); michael@0: michael@0: nsAutoString src; michael@0: aArguments->GetString(0, src); michael@0: michael@0: nsCOMPtr result = michael@0: do_CreateInstance("@mozilla.org/variant;1"); michael@0: NS_ENSURE_STATE(result); michael@0: michael@0: // Remove common URL hostname prefixes michael@0: if (StringBeginsWith(src, NS_LITERAL_STRING("www."))) { michael@0: src.Cut(0, 4); michael@0: } michael@0: michael@0: result->SetAsAString(src); michael@0: NS_ADDREF(*_result = result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Frecency Changed Notification Function michael@0: michael@0: /* static */ michael@0: nsresult michael@0: FrecencyNotificationFunction::create(mozIStorageConnection *aDBConn) michael@0: { michael@0: nsRefPtr function = michael@0: new FrecencyNotificationFunction(); michael@0: nsresult rv = aDBConn->CreateFunction( michael@0: NS_LITERAL_CSTRING("notify_frecency"), 5, function michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS( michael@0: FrecencyNotificationFunction, michael@0: mozIStorageFunction michael@0: ) michael@0: michael@0: NS_IMETHODIMP michael@0: FrecencyNotificationFunction::OnFunctionCall(mozIStorageValueArray *aArgs, michael@0: nsIVariant **_result) michael@0: { michael@0: uint32_t numArgs; michael@0: nsresult rv = aArgs->GetNumEntries(&numArgs); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: MOZ_ASSERT(numArgs == 5); michael@0: michael@0: int32_t newFrecency = aArgs->AsInt32(0); michael@0: michael@0: nsAutoCString spec; michael@0: rv = aArgs->GetUTF8String(1, spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString guid; michael@0: rv = aArgs->GetUTF8String(2, guid); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hidden = static_cast(aArgs->AsInt32(3)); michael@0: PRTime lastVisitDate = static_cast(aArgs->AsInt64(4)); michael@0: michael@0: const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService(); michael@0: NS_ENSURE_STATE(navHistory); michael@0: navHistory->DispatchFrecencyChangedNotification(spec, newFrecency, guid, michael@0: hidden, lastVisitDate); michael@0: michael@0: nsCOMPtr result = michael@0: do_CreateInstance("@mozilla.org/variant;1"); michael@0: NS_ENSURE_STATE(result); michael@0: rv = result->SetAsInt32(newFrecency); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ADDREF(*_result = result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace places michael@0: } // namespace mozilla