/* 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 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 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 DB = Database::GetDatabase(); NS_ENSURE_STATE(DB); // Fetch the page stats from the database. { RefPtr 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 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 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 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 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 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 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 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(aArgs->AsInt32(3)); PRTime lastVisitDate = static_cast(aArgs->AsInt64(4)); const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService(); NS_ENSURE_STATE(navHistory); navHistory->DispatchFrecencyChangedNotification(spec, newFrecency, guid, hidden, lastVisitDate); RefPtr 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 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 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 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 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(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(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(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