LCOV - code coverage report
Current view: top level - toolkit/components/places - SQLFunctions.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 223 399 55.9 %
Date: 2017-07-14 16:53:18 Functions: 30 49 61.2 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
       2             :  * This Source Code Form is subject to the terms of the Mozilla Public
       3             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       4             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       5             : 
       6             : #include "mozilla/storage.h"
       7             : #include "nsString.h"
       8             : #include "nsUnicharUtils.h"
       9             : #include "nsWhitespaceTokenizer.h"
      10             : #include "nsEscape.h"
      11             : #include "mozIPlacesAutoComplete.h"
      12             : #include "SQLFunctions.h"
      13             : #include "nsMathUtils.h"
      14             : #include "nsUTF8Utils.h"
      15             : #include "nsINavHistoryService.h"
      16             : #include "nsPrintfCString.h"
      17             : #include "nsNavHistory.h"
      18             : #include "mozilla/Likely.h"
      19             : #include "nsVariant.h"
      20             : #include "mozilla/HashFunctions.h"
      21             : 
      22             : // Maximum number of chars to search through.
      23             : // MatchAutoCompleteFunction won't look for matches over this threshold.
      24             : #define MAX_CHARS_TO_SEARCH_THROUGH 255
      25             : 
      26             : using namespace mozilla::storage;
      27             : 
      28             : ////////////////////////////////////////////////////////////////////////////////
      29             : //// Anonymous Helpers
      30             : 
      31             : namespace {
      32             : 
      33             :   typedef nsACString::const_char_iterator const_char_iterator;
      34             : 
      35             :   /**
      36             :    * Get a pointer to the word boundary after aStart if aStart points to an
      37             :    * ASCII letter (i.e. [a-zA-Z]).  Otherwise, return aNext, which we assume
      38             :    * points to the next character in the UTF-8 sequence.
      39             :    *
      40             :    * We define a word boundary as anything that's not [a-z] -- this lets us
      41             :    * match CamelCase words.
      42             :    *
      43             :    * @param aStart the beginning of the UTF-8 sequence
      44             :    * @param aNext the next character in the sequence
      45             :    * @param aEnd the first byte which is not part of the sequence
      46             :    *
      47             :    * @return a pointer to the next word boundary after aStart
      48             :    */
      49             :   static
      50             :   MOZ_ALWAYS_INLINE const_char_iterator
      51           0 :   nextWordBoundary(const_char_iterator const aStart,
      52             :                    const_char_iterator const aNext,
      53             :                    const_char_iterator const aEnd) {
      54             : 
      55           0 :     const_char_iterator cur = aStart;
      56           0 :     if (('a' <= *cur && *cur <= 'z') ||
      57           0 :         ('A' <= *cur && *cur <= 'Z')) {
      58             : 
      59             :       // Since we'll halt as soon as we see a non-ASCII letter, we can do a
      60             :       // simple byte-by-byte comparison here and avoid the overhead of a
      61             :       // UTF8CharEnumerator.
      62           0 :       do {
      63           0 :         cur++;
      64           0 :       } while (cur < aEnd && 'a' <= *cur && *cur <= 'z');
      65             :     }
      66             :     else {
      67           0 :       cur = aNext;
      68             :     }
      69             : 
      70           0 :     return cur;
      71             :   }
      72             : 
      73             :   enum FindInStringBehavior {
      74             :     eFindOnBoundary,
      75             :     eFindAnywhere
      76             :   };
      77             : 
      78             :   /**
      79             :    * findAnywhere and findOnBoundary do almost the same thing, so it's natural
      80             :    * to implement them in terms of a single function.  They're both
      81             :    * performance-critical functions, however, and checking aBehavior makes them
      82             :    * a bit slower.  Our solution is to define findInString as MOZ_ALWAYS_INLINE
      83             :    * and rely on the compiler to optimize out the aBehavior check.
      84             :    *
      85             :    * @param aToken
      86             :    *        The token we're searching for
      87             :    * @param aSourceString
      88             :    *        The string in which we're searching
      89             :    * @param aBehavior
      90             :    *        eFindOnBoundary if we should only consider matchines which occur on
      91             :    *        word boundaries, or eFindAnywhere if we should consider matches
      92             :    *        which appear anywhere.
      93             :    *
      94             :    * @return true if aToken was found in aSourceString, false otherwise.
      95             :    */
      96             :   static
      97             :   MOZ_ALWAYS_INLINE bool
      98           0 :   findInString(const nsDependentCSubstring &aToken,
      99             :                const nsACString &aSourceString,
     100             :                FindInStringBehavior aBehavior)
     101             :   {
     102             :     // CaseInsensitiveUTF8CharsEqual assumes that there's at least one byte in
     103             :     // the both strings, so don't pass an empty token here.
     104           0 :     NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!");
     105             : 
     106             :     // We cannot match anything if there is nothing to search.
     107           0 :     if (aSourceString.IsEmpty()) {
     108           0 :       return false;
     109             :     }
     110             : 
     111           0 :     const_char_iterator tokenStart(aToken.BeginReading()),
     112           0 :                         tokenEnd(aToken.EndReading()),
     113           0 :                         sourceStart(aSourceString.BeginReading()),
     114           0 :                         sourceEnd(aSourceString.EndReading());
     115             : 
     116           0 :     do {
     117             :       // We are on a word boundary (if aBehavior == eFindOnBoundary).  See if
     118             :       // aToken matches sourceStart.
     119             : 
     120             :       // Check whether the first character in the token matches the character
     121             :       // at sourceStart.  At the same time, get a pointer to the next character
     122             :       // in both the token and the source.
     123             :       const_char_iterator sourceNext, tokenCur;
     124             :       bool error;
     125           0 :       if (CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart,
     126             :                                         sourceEnd, tokenEnd,
     127             :                                         &sourceNext, &tokenCur, &error)) {
     128             : 
     129             :         // We don't need to check |error| here -- if
     130             :         // CaseInsensitiveUTF8CharCompare encounters an error, it'll also
     131             :         // return false and we'll catch the error outside the if.
     132             : 
     133           0 :         const_char_iterator sourceCur = sourceNext;
     134             :         while (true) {
     135           0 :           if (tokenCur >= tokenEnd) {
     136             :             // We matched the whole token!
     137           0 :             return true;
     138             :           }
     139             : 
     140           0 :           if (sourceCur >= sourceEnd) {
     141             :             // We ran into the end of source while matching a token.  This
     142             :             // means we'll never find the token we're looking for.
     143           0 :             return false;
     144             :           }
     145             : 
     146           0 :           if (!CaseInsensitiveUTF8CharsEqual(sourceCur, tokenCur,
     147             :                                              sourceEnd, tokenEnd,
     148             :                                              &sourceCur, &tokenCur, &error)) {
     149             :             // sourceCur doesn't match tokenCur (or there's an error), so break
     150             :             // out of this loop.
     151           0 :             break;
     152             :           }
     153             :         }
     154             :       }
     155             : 
     156             :       // If something went wrong above, get out of here!
     157           0 :       if (MOZ_UNLIKELY(error)) {
     158           0 :         return false;
     159             :       }
     160             : 
     161             :       // We didn't match the token.  If we're searching for matches on word
     162             :       // boundaries, skip to the next word boundary.  Otherwise, advance
     163             :       // forward one character, using the sourceNext pointer we saved earlier.
     164             : 
     165           0 :       if (aBehavior == eFindOnBoundary) {
     166           0 :         sourceStart = nextWordBoundary(sourceStart, sourceNext, sourceEnd);
     167             :       }
     168             :       else {
     169           0 :         sourceStart = sourceNext;
     170             :       }
     171             : 
     172           0 :     } while (sourceStart < sourceEnd);
     173             : 
     174           0 :     return false;
     175             :   }
     176             : 
     177             :   static
     178             :   MOZ_ALWAYS_INLINE nsDependentCString
     179           0 :   getSharedString(mozIStorageValueArray* aValues, uint32_t aIndex) {
     180             :     uint32_t len;
     181           0 :     const char* str = aValues->AsSharedUTF8String(aIndex, &len);
     182           0 :     if (!str) {
     183           0 :       return nsDependentCString("", (uint32_t)0);
     184             :     }
     185           0 :     return nsDependentCString(str, len);
     186             :   }
     187             : 
     188             : } // End anonymous namespace
     189             : 
     190             : namespace mozilla {
     191             : namespace places {
     192             : 
     193             : ////////////////////////////////////////////////////////////////////////////////
     194             : //// AutoComplete Matching Function
     195             : 
     196             :   /* static */
     197             :   nsresult
     198           1 :   MatchAutoCompleteFunction::create(mozIStorageConnection *aDBConn)
     199             :   {
     200             :     RefPtr<MatchAutoCompleteFunction> function =
     201           2 :       new MatchAutoCompleteFunction();
     202             : 
     203           2 :     nsresult rv = aDBConn->CreateFunction(
     204           2 :       NS_LITERAL_CSTRING("autocomplete_match"), kArgIndexLength, function
     205           3 :     );
     206           1 :     NS_ENSURE_SUCCESS(rv, rv);
     207             : 
     208           1 :     return NS_OK;
     209             :   }
     210             : 
     211             :   /* static */
     212             :   nsDependentCSubstring
     213           0 :   MatchAutoCompleteFunction::fixupURISpec(const nsACString &aURISpec,
     214             :                                           int32_t aMatchBehavior,
     215             :                                           nsACString &aSpecBuf)
     216             :   {
     217           0 :     nsDependentCSubstring fixedSpec;
     218             : 
     219             :     // Try to unescape the string.  If that succeeds and yields a different
     220             :     // string which is also valid UTF-8, we'll use it.
     221             :     // Otherwise, we will simply use our original string.
     222           0 :     bool unescaped = NS_UnescapeURL(aURISpec.BeginReading(),
     223           0 :       aURISpec.Length(), esc_SkipControl, aSpecBuf);
     224           0 :     if (unescaped && IsUTF8(aSpecBuf)) {
     225           0 :       fixedSpec.Rebind(aSpecBuf, 0);
     226             :     } else {
     227           0 :       fixedSpec.Rebind(aURISpec, 0);
     228             :     }
     229             : 
     230           0 :     if (aMatchBehavior == mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED)
     231           0 :       return fixedSpec;
     232             : 
     233           0 :     if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("http://"))) {
     234           0 :       fixedSpec.Rebind(fixedSpec, 7);
     235           0 :     } else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("https://"))) {
     236           0 :       fixedSpec.Rebind(fixedSpec, 8);
     237           0 :     } else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("ftp://"))) {
     238           0 :       fixedSpec.Rebind(fixedSpec, 6);
     239             :     }
     240             : 
     241           0 :     if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("www."))) {
     242           0 :       fixedSpec.Rebind(fixedSpec, 4);
     243             :     }
     244             : 
     245           0 :     return fixedSpec;
     246             :   }
     247             : 
     248             :   /* static */
     249             :   bool
     250           0 :   MatchAutoCompleteFunction::findAnywhere(const nsDependentCSubstring &aToken,
     251             :                                           const nsACString &aSourceString)
     252             :   {
     253             :     // We can't use FindInReadable here; it works only for ASCII.
     254             : 
     255           0 :     return findInString(aToken, aSourceString, eFindAnywhere);
     256             :   }
     257             : 
     258             :   /* static */
     259             :   bool
     260           0 :   MatchAutoCompleteFunction::findOnBoundary(const nsDependentCSubstring &aToken,
     261             :                                             const nsACString &aSourceString)
     262             :   {
     263           0 :     return findInString(aToken, aSourceString, eFindOnBoundary);
     264             :   }
     265             : 
     266             :   /* static */
     267             :   bool
     268           0 :   MatchAutoCompleteFunction::findBeginning(const nsDependentCSubstring &aToken,
     269             :                                            const nsACString &aSourceString)
     270             :   {
     271           0 :     NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!");
     272             : 
     273             :     // We can't use StringBeginsWith here, unfortunately.  Although it will
     274             :     // happily take a case-insensitive UTF8 comparator, it eventually calls
     275             :     // nsACString::Equals, which checks that the two strings contain the same
     276             :     // number of bytes before calling the comparator.  Two characters may be
     277             :     // case-insensitively equal while taking up different numbers of bytes, so
     278             :     // this is not what we want.
     279             : 
     280           0 :     const_char_iterator tokenStart(aToken.BeginReading()),
     281           0 :                         tokenEnd(aToken.EndReading()),
     282           0 :                         sourceStart(aSourceString.BeginReading()),
     283           0 :                         sourceEnd(aSourceString.EndReading());
     284             : 
     285             :     bool dummy;
     286           0 :     while (sourceStart < sourceEnd &&
     287           0 :            CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart,
     288             :                                          sourceEnd, tokenEnd,
     289             :                                          &sourceStart, &tokenStart, &dummy)) {
     290             : 
     291             :       // We found the token!
     292           0 :       if (tokenStart >= tokenEnd) {
     293           0 :         return true;
     294             :       }
     295             :     }
     296             : 
     297             :     // We don't need to check CaseInsensitiveUTF8CharsEqual's error condition
     298             :     // (stored in |dummy|), since the function will return false if it
     299             :     // encounters an error.
     300             : 
     301           0 :     return false;
     302             :   }
     303             : 
     304             :   /* static */
     305             :   bool
     306           0 :   MatchAutoCompleteFunction::findBeginningCaseSensitive(
     307             :     const nsDependentCSubstring &aToken,
     308             :     const nsACString &aSourceString)
     309             :   {
     310           0 :     NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!");
     311             : 
     312           0 :     return StringBeginsWith(aSourceString, aToken);
     313             :   }
     314             : 
     315             :   /* static */
     316             :   MatchAutoCompleteFunction::searchFunctionPtr
     317           0 :   MatchAutoCompleteFunction::getSearchFunction(int32_t aBehavior)
     318             :   {
     319           0 :     switch (aBehavior) {
     320             :       case mozIPlacesAutoComplete::MATCH_ANYWHERE:
     321             :       case mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED:
     322           0 :         return findAnywhere;
     323             :       case mozIPlacesAutoComplete::MATCH_BEGINNING:
     324           0 :         return findBeginning;
     325             :       case mozIPlacesAutoComplete::MATCH_BEGINNING_CASE_SENSITIVE:
     326           0 :         return findBeginningCaseSensitive;
     327             :       case mozIPlacesAutoComplete::MATCH_BOUNDARY:
     328             :       default:
     329           0 :         return findOnBoundary;
     330             :     };
     331             :   }
     332             : 
     333          10 :   NS_IMPL_ISUPPORTS(
     334             :     MatchAutoCompleteFunction,
     335             :     mozIStorageFunction
     336             :   )
     337             : 
     338             :   NS_IMETHODIMP
     339           0 :   MatchAutoCompleteFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
     340             :                                             nsIVariant **_result)
     341             :   {
     342             :     // Macro to make the code a bit cleaner and easier to read.  Operates on
     343             :     // searchBehavior.
     344           0 :     int32_t searchBehavior = aArguments->AsInt32(kArgIndexSearchBehavior);
     345             :     #define HAS_BEHAVIOR(aBitName) \
     346             :       (searchBehavior & mozIPlacesAutoComplete::BEHAVIOR_##aBitName)
     347             : 
     348             :     nsDependentCString searchString =
     349           0 :       getSharedString(aArguments, kArgSearchString);
     350             :     nsDependentCString url =
     351           0 :       getSharedString(aArguments, kArgIndexURL);
     352             : 
     353           0 :     int32_t matchBehavior = aArguments->AsInt32(kArgIndexMatchBehavior);
     354             : 
     355             :     // We only want to filter javascript: URLs if we are not supposed to search
     356             :     // for them, and the search does not start with "javascript:".
     357           0 :     if (matchBehavior != mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED &&
     358           0 :         StringBeginsWith(url, NS_LITERAL_CSTRING("javascript:")) &&
     359           0 :         !HAS_BEHAVIOR(JAVASCRIPT) &&
     360           0 :         !StringBeginsWith(searchString, NS_LITERAL_CSTRING("javascript:"))) {
     361           0 :       NS_ADDREF(*_result = new IntegerVariant(0));
     362           0 :       return NS_OK;
     363             :     }
     364             : 
     365           0 :     int32_t visitCount = aArguments->AsInt32(kArgIndexVisitCount);
     366           0 :     bool typed = aArguments->AsInt32(kArgIndexTyped) ? true : false;
     367           0 :     bool bookmark = aArguments->AsInt32(kArgIndexBookmark) ? true : false;
     368           0 :     nsDependentCString tags = getSharedString(aArguments, kArgIndexTags);
     369           0 :     int32_t openPageCount = aArguments->AsInt32(kArgIndexOpenPageCount);
     370           0 :     bool matches = false;
     371           0 :     if (HAS_BEHAVIOR(RESTRICT)) {
     372             :       // Make sure we match all the filter requirements.  If a given restriction
     373             :       // is active, make sure the corresponding condition is not true.
     374           0 :       matches = (!HAS_BEHAVIOR(HISTORY) || visitCount > 0) &&
     375           0 :                 (!HAS_BEHAVIOR(TYPED) || typed) &&
     376           0 :                 (!HAS_BEHAVIOR(BOOKMARK) || bookmark) &&
     377           0 :                 (!HAS_BEHAVIOR(TAG) || !tags.IsVoid()) &&
     378           0 :                 (!HAS_BEHAVIOR(OPENPAGE) || openPageCount > 0);
     379             :     } else {
     380             :       // Make sure that we match all the filter requirements and that the
     381             :       // corresponding condition is true if at least a given restriction is active.
     382           0 :       matches = (HAS_BEHAVIOR(HISTORY) && visitCount > 0) ||
     383           0 :                 (HAS_BEHAVIOR(TYPED) && typed) ||
     384           0 :                 (HAS_BEHAVIOR(BOOKMARK) && bookmark) ||
     385           0 :                 (HAS_BEHAVIOR(TAG) && !tags.IsVoid()) ||
     386           0 :                 (HAS_BEHAVIOR(OPENPAGE) && openPageCount > 0);
     387             :     }
     388             : 
     389           0 :     if (!matches) {
     390           0 :       NS_ADDREF(*_result = new IntegerVariant(0));
     391           0 :       return NS_OK;
     392             :     }
     393             : 
     394             :     // Obtain our search function.
     395           0 :     searchFunctionPtr searchFunction = getSearchFunction(matchBehavior);
     396             : 
     397             :     // Clean up our URI spec and prepare it for searching.
     398           0 :     nsCString fixedUrlBuf;
     399             :     nsDependentCSubstring fixedUrl =
     400           0 :       fixupURISpec(url, matchBehavior, fixedUrlBuf);
     401             :     // Limit the number of chars we search through.
     402             :     const nsDependentCSubstring& trimmedUrl =
     403           0 :       Substring(fixedUrl, 0, MAX_CHARS_TO_SEARCH_THROUGH);
     404             : 
     405           0 :     nsDependentCString title = getSharedString(aArguments, kArgIndexTitle);
     406             :     // Limit the number of chars we search through.
     407             :     const nsDependentCSubstring& trimmedTitle =
     408           0 :       Substring(title, 0, MAX_CHARS_TO_SEARCH_THROUGH);
     409             : 
     410             :     // Determine if every token matches either the bookmark title, tags, page
     411             :     // title, or page URL.
     412           0 :     nsCWhitespaceTokenizer tokenizer(searchString);
     413           0 :     while (matches && tokenizer.hasMoreTokens()) {
     414           0 :       const nsDependentCSubstring &token = tokenizer.nextToken();
     415             : 
     416           0 :       if (HAS_BEHAVIOR(TITLE) && HAS_BEHAVIOR(URL)) {
     417           0 :         matches = (searchFunction(token, trimmedTitle) ||
     418           0 :                    searchFunction(token, tags)) &&
     419           0 :                   searchFunction(token, trimmedUrl);
     420             :       }
     421           0 :       else if (HAS_BEHAVIOR(TITLE)) {
     422           0 :         matches = searchFunction(token, trimmedTitle) ||
     423           0 :                   searchFunction(token, tags);
     424             :       }
     425           0 :       else if (HAS_BEHAVIOR(URL)) {
     426           0 :         matches = searchFunction(token, trimmedUrl);
     427             :       }
     428             :       else {
     429           0 :         matches = searchFunction(token, trimmedTitle) ||
     430           0 :                   searchFunction(token, tags) ||
     431           0 :                   searchFunction(token, trimmedUrl);
     432             :       }
     433             :     }
     434             : 
     435           0 :     NS_ADDREF(*_result = new IntegerVariant(matches ? 1 : 0));
     436           0 :     return NS_OK;
     437             :     #undef HAS_BEHAVIOR
     438             :   }
     439             : 
     440             : 
     441             : ////////////////////////////////////////////////////////////////////////////////
     442             : //// Frecency Calculation Function
     443             : 
     444             :   /* static */
     445             :   nsresult
     446           1 :   CalculateFrecencyFunction::create(mozIStorageConnection *aDBConn)
     447             :   {
     448             :     RefPtr<CalculateFrecencyFunction> function =
     449           2 :       new CalculateFrecencyFunction();
     450             : 
     451           2 :     nsresult rv = aDBConn->CreateFunction(
     452           2 :       NS_LITERAL_CSTRING("calculate_frecency"), -1, function
     453           3 :     );
     454           1 :     NS_ENSURE_SUCCESS(rv, rv);
     455             : 
     456           1 :     return NS_OK;
     457             :   }
     458             : 
     459          10 :   NS_IMPL_ISUPPORTS(
     460             :     CalculateFrecencyFunction,
     461             :     mozIStorageFunction
     462             :   )
     463             : 
     464             :   NS_IMETHODIMP
     465           1 :   CalculateFrecencyFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
     466             :                                             nsIVariant **_result)
     467             :   {
     468             :     // Fetch arguments.  Use default values if they were omitted.
     469             :     uint32_t numEntries;
     470           1 :     nsresult rv = aArguments->GetNumEntries(&numEntries);
     471           1 :     NS_ENSURE_SUCCESS(rv, rv);
     472           1 :     MOZ_ASSERT(numEntries <= 2, "unexpected number of arguments");
     473             : 
     474           1 :     int64_t pageId = aArguments->AsInt64(0);
     475           1 :     MOZ_ASSERT(pageId > 0, "Should always pass a valid page id");
     476           1 :     if (pageId <= 0) {
     477           0 :       NS_ADDREF(*_result = new IntegerVariant(0));
     478           0 :       return NS_OK;
     479             :     }
     480             : 
     481             :     enum RedirectState {
     482             :       eRedirectUnknown,
     483             :       eIsRedirect,
     484             :       eIsNotRedirect
     485             :     };
     486             : 
     487           1 :     RedirectState isRedirect = eRedirectUnknown;
     488             : 
     489           1 :     if (numEntries > 1) {
     490           1 :       isRedirect = aArguments->AsInt32(1) ? eIsRedirect : eIsNotRedirect;
     491             :     }
     492             : 
     493           1 :     int32_t typed = 0;
     494           1 :     int32_t visitCount = 0;
     495           1 :     bool hasBookmark = false;
     496           1 :     int32_t isQuery = 0;
     497           1 :     float pointsForSampledVisits = 0.0;
     498           1 :     int32_t numSampledVisits = 0;
     499           1 :     int32_t bonus = 0;
     500             : 
     501             :     // This is a const version of the history object for thread-safety.
     502           1 :     const nsNavHistory* history = nsNavHistory::GetConstHistoryService();
     503           1 :     NS_ENSURE_STATE(history);
     504           2 :     RefPtr<Database> DB = Database::GetDatabase();
     505           1 :     NS_ENSURE_STATE(DB);
     506             : 
     507             : 
     508             :     // Fetch the page stats from the database.
     509             :     {
     510           2 :       RefPtr<mozIStorageStatement> getPageInfo = DB->GetStatement(
     511             :         "SELECT typed, visit_count, foreign_count, "
     512             :                "(substr(url, 0, 7) = 'place:') "
     513             :         "FROM moz_places "
     514             :         "WHERE id = :page_id "
     515           2 :       );
     516           1 :       NS_ENSURE_STATE(getPageInfo);
     517           2 :       mozStorageStatementScoper infoScoper(getPageInfo);
     518             : 
     519           1 :       rv = getPageInfo->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId);
     520           1 :       NS_ENSURE_SUCCESS(rv, rv);
     521             : 
     522           1 :       bool hasResult = false;
     523           1 :       rv = getPageInfo->ExecuteStep(&hasResult);
     524           1 :       NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_UNEXPECTED);
     525             : 
     526           1 :       rv = getPageInfo->GetInt32(0, &typed);
     527           1 :       NS_ENSURE_SUCCESS(rv, rv);
     528           1 :       rv = getPageInfo->GetInt32(1, &visitCount);
     529           1 :       NS_ENSURE_SUCCESS(rv, rv);
     530           1 :       int32_t foreignCount = 0;
     531           1 :       rv = getPageInfo->GetInt32(2, &foreignCount);
     532           1 :       NS_ENSURE_SUCCESS(rv, rv);
     533           1 :       hasBookmark = foreignCount > 0;
     534           1 :       rv = getPageInfo->GetInt32(3, &isQuery);
     535           1 :       NS_ENSURE_SUCCESS(rv, rv);
     536             :     }
     537             : 
     538           1 :     if (visitCount > 0) {
     539             :       // Get a sample of the last visits to the page, to calculate its weight.
     540             :       // In case of a temporary or permanent redirect, calculate the frecency
     541             :       // as if the original page was visited.
     542             :       nsCString redirectsTransitionFragment =
     543           2 :         nsPrintfCString("%d AND %d ", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
     544           2 :                                       nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY);
     545           2 :       nsCOMPtr<mozIStorageStatement> getVisits = DB->GetStatement(
     546           2 :         NS_LITERAL_CSTRING(
     547             :           "/* do not warn (bug 659740 - SQLite may ignore index if few visits exist) */"
     548             :           "SELECT "
     549             :             "ROUND((strftime('%s','now','localtime','utc') - v.visit_date/1000000)/86400), "
     550             :             "origin.visit_type, "
     551             :             "v.visit_type, "
     552             :             "target.id NOTNULL "
     553             :           "FROM moz_historyvisits v "
     554             :           "LEFT JOIN moz_historyvisits origin ON origin.id = v.from_visit "
     555             :                                             "AND v.visit_type BETWEEN "
     556           4 :             ) + redirectsTransitionFragment + NS_LITERAL_CSTRING(
     557             :           "LEFT JOIN moz_historyvisits target ON v.id = target.from_visit "
     558             :                                             "AND target.visit_type BETWEEN "
     559           4 :             ) + redirectsTransitionFragment + NS_LITERAL_CSTRING(
     560             :           "WHERE v.place_id = :page_id "
     561             :           "ORDER BY v.visit_date DESC "
     562             :         )
     563           2 :       );
     564           1 :       NS_ENSURE_STATE(getVisits);
     565           2 :       mozStorageStatementScoper visitsScoper(getVisits);
     566           1 :       rv = getVisits->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId);
     567           1 :       NS_ENSURE_SUCCESS(rv, rv);
     568             : 
     569             :       // Fetch only a limited number of recent visits.
     570           1 :       bool hasResult = false;
     571           4 :       for (int32_t maxVisits = history->GetNumVisitsForFrecency();
     572           2 :            numSampledVisits < maxVisits &&
     573           4 :            NS_SUCCEEDED(getVisits->ExecuteStep(&hasResult)) && hasResult;
     574             :            numSampledVisits++) {
     575             : 
     576             :         int32_t visitType;
     577           1 :         bool isNull = false;
     578           1 :         rv = getVisits->GetIsNull(1, &isNull);
     579           1 :         NS_ENSURE_SUCCESS(rv, rv);
     580             : 
     581           1 :         if (isRedirect == eIsRedirect || isNull) {
     582             :           // Use the main visit_type.
     583           1 :           rv = getVisits->GetInt32(2, &visitType);
     584           1 :           NS_ENSURE_SUCCESS(rv, rv);
     585             :         } else {
     586             :           // This is a redirect target, so use the origin visit_type.
     587           0 :           rv = getVisits->GetInt32(1, &visitType);
     588           0 :           NS_ENSURE_SUCCESS(rv, rv);
     589             :         }
     590             : 
     591           1 :         RedirectState visitIsRedirect = isRedirect;
     592             : 
     593             :         // If we don't know if this is a redirect or not, or this is not the
     594             :         // most recent visit that we're looking at, then we use the redirect
     595             :         // value from the database.
     596           1 :         if (visitIsRedirect == eRedirectUnknown || numSampledVisits >= 1) {
     597             :           int32_t redirect;
     598           0 :           rv = getVisits->GetInt32(3, &redirect);
     599           0 :           NS_ENSURE_SUCCESS(rv, rv);
     600           0 :           visitIsRedirect = !!redirect ? eIsRedirect : eIsNotRedirect;
     601             :         }
     602             : 
     603           1 :         bonus = history->GetFrecencyTransitionBonus(visitType, true, visitIsRedirect == eIsRedirect);
     604             : 
     605             :         // Add the bookmark visit bonus.
     606           1 :         if (hasBookmark) {
     607           0 :           bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, true);
     608             :         }
     609             : 
     610             :         // If bonus was zero, we can skip the work to determine the weight.
     611           1 :         if (bonus) {
     612           1 :           int32_t ageInDays = getVisits->AsInt32(0);
     613           1 :           int32_t weight = history->GetFrecencyAgedWeight(ageInDays);
     614           1 :           pointsForSampledVisits += (float)(weight * (bonus / 100.0));
     615             :         }
     616             :       }
     617             :     }
     618             : 
     619             :     // If we sampled some visits for this page, use the calculated weight.
     620           1 :     if (numSampledVisits) {
     621             :       // We were unable to calculate points, maybe cause all the visits in the
     622             :       // sample had a zero bonus. Though, we know the page has some past valid
     623             :       // visit, or visit_count would be zero. Thus we set the frecency to
     624             :       // -1, so they are still shown in autocomplete.
     625           1 :       if (!pointsForSampledVisits) {
     626           0 :         NS_ADDREF(*_result = new IntegerVariant(-1));
     627             :       }
     628             :       else {
     629             :         // Estimate frecency using the sampled visits.
     630             :         // Use ceilf() so that we don't round down to 0, which
     631             :         // would cause us to completely ignore the place during autocomplete.
     632           1 :         NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(visitCount * ceilf(pointsForSampledVisits) / numSampledVisits)));
     633             :       }
     634           1 :       return NS_OK;
     635             :     }
     636             : 
     637             :     // Otherwise this page has no visits, it may be bookmarked.
     638           0 :     if (!hasBookmark || isQuery) {
     639           0 :       NS_ADDREF(*_result = new IntegerVariant(0));
     640           0 :       return NS_OK;
     641             :     }
     642             : 
     643             :     // For unvisited bookmarks, produce a non-zero frecency, so that they show
     644             :     // up in URL bar autocomplete.
     645           0 :     visitCount = 1;
     646             : 
     647             :     // Make it so something bookmarked and typed will have a higher frecency
     648             :     // than something just typed or just bookmarked.
     649           0 :     bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, false);
     650           0 :     if (typed) {
     651           0 :       bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_TYPED, false);
     652             :     }
     653             : 
     654             :     // Assume "now" as our ageInDays, so use the first bucket.
     655           0 :     pointsForSampledVisits = history->GetFrecencyBucketWeight(1) * (bonus / (float)100.0);
     656             : 
     657             :     // use ceilf() so that we don't round down to 0, which
     658             :     // would cause us to completely ignore the place during autocomplete
     659           0 :     NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(visitCount * ceilf(pointsForSampledVisits))));
     660             : 
     661           0 :     return NS_OK;
     662             :   }
     663             : 
     664             : ////////////////////////////////////////////////////////////////////////////////
     665             : //// GUID Creation Function
     666             : 
     667             :   /* static */
     668             :   nsresult
     669           1 :   GenerateGUIDFunction::create(mozIStorageConnection *aDBConn)
     670             :   {
     671           2 :     RefPtr<GenerateGUIDFunction> function = new GenerateGUIDFunction();
     672           2 :     nsresult rv = aDBConn->CreateFunction(
     673           2 :       NS_LITERAL_CSTRING("generate_guid"), 0, function
     674           3 :     );
     675           1 :     NS_ENSURE_SUCCESS(rv, rv);
     676             : 
     677           1 :     return NS_OK;
     678             :   }
     679             : 
     680          10 :   NS_IMPL_ISUPPORTS(
     681             :     GenerateGUIDFunction,
     682             :     mozIStorageFunction
     683             :   )
     684             : 
     685             :   NS_IMETHODIMP
     686           0 :   GenerateGUIDFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
     687             :                                        nsIVariant **_result)
     688             :   {
     689           0 :     nsAutoCString guid;
     690           0 :     nsresult rv = GenerateGUID(guid);
     691           0 :     NS_ENSURE_SUCCESS(rv, rv);
     692             : 
     693           0 :     NS_ADDREF(*_result = new UTF8TextVariant(guid));
     694           0 :     return NS_OK;
     695             :   }
     696             : 
     697             : ////////////////////////////////////////////////////////////////////////////////
     698             : //// Get Unreversed Host Function
     699             : 
     700             :   /* static */
     701             :   nsresult
     702           1 :   GetUnreversedHostFunction::create(mozIStorageConnection *aDBConn)
     703             :   {
     704           2 :     RefPtr<GetUnreversedHostFunction> function = new GetUnreversedHostFunction();
     705           2 :     nsresult rv = aDBConn->CreateFunction(
     706           2 :       NS_LITERAL_CSTRING("get_unreversed_host"), 1, function
     707           3 :     );
     708           1 :     NS_ENSURE_SUCCESS(rv, rv);
     709             : 
     710           1 :     return NS_OK;
     711             :   }
     712             : 
     713          10 :   NS_IMPL_ISUPPORTS(
     714             :     GetUnreversedHostFunction,
     715             :     mozIStorageFunction
     716             :   )
     717             : 
     718             :   NS_IMETHODIMP
     719          16 :   GetUnreversedHostFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
     720             :                                             nsIVariant **_result)
     721             :   {
     722             :     // Must have non-null function arguments.
     723          16 :     MOZ_ASSERT(aArguments);
     724             : 
     725          32 :     nsAutoString src;
     726          16 :     aArguments->GetString(0, src);
     727             : 
     728          32 :     RefPtr<nsVariant> result = new nsVariant();
     729             : 
     730          16 :     if (src.Length()>1) {
     731          16 :       src.Truncate(src.Length() - 1);
     732          32 :       nsAutoString dest;
     733          16 :       ReverseString(src, dest);
     734          16 :       result->SetAsAString(dest);
     735             :     }
     736             :     else {
     737           0 :       result->SetAsAString(EmptyString());
     738             :     }
     739          16 :     result.forget(_result);
     740          32 :     return NS_OK;
     741             :   }
     742             : 
     743             : ////////////////////////////////////////////////////////////////////////////////
     744             : //// Fixup URL Function
     745             : 
     746             :   /* static */
     747             :   nsresult
     748           1 :   FixupURLFunction::create(mozIStorageConnection *aDBConn)
     749             :   {
     750           2 :     RefPtr<FixupURLFunction> function = new FixupURLFunction();
     751           2 :     nsresult rv = aDBConn->CreateFunction(
     752           2 :       NS_LITERAL_CSTRING("fixup_url"), 1, function
     753           3 :     );
     754           1 :     NS_ENSURE_SUCCESS(rv, rv);
     755             : 
     756           1 :     return NS_OK;
     757             :   }
     758             : 
     759          10 :   NS_IMPL_ISUPPORTS(
     760             :     FixupURLFunction,
     761             :     mozIStorageFunction
     762             :   )
     763             : 
     764             :   NS_IMETHODIMP
     765           7 :   FixupURLFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
     766             :                                    nsIVariant **_result)
     767             :   {
     768             :     // Must have non-null function arguments.
     769           7 :     MOZ_ASSERT(aArguments);
     770             : 
     771          14 :     nsAutoString src;
     772           7 :     aArguments->GetString(0, src);
     773             : 
     774          14 :     RefPtr<nsVariant> result = new nsVariant();
     775             : 
     776           7 :     if (StringBeginsWith(src, NS_LITERAL_STRING("http://")))
     777           1 :       src.Cut(0, 7);
     778           6 :     else if (StringBeginsWith(src, NS_LITERAL_STRING("https://")))
     779           0 :       src.Cut(0, 8);
     780           6 :     else if (StringBeginsWith(src, NS_LITERAL_STRING("ftp://")))
     781           0 :       src.Cut(0, 6);
     782             : 
     783             :     // Remove common URL hostname prefixes
     784           7 :     if (StringBeginsWith(src, NS_LITERAL_STRING("www."))) {
     785           0 :       src.Cut(0, 4);
     786             :     }
     787             : 
     788           7 :     result->SetAsAString(src);
     789           7 :     result.forget(_result);
     790          14 :     return NS_OK;
     791             :   }
     792             : 
     793             : ////////////////////////////////////////////////////////////////////////////////
     794             : //// Frecency Changed Notification Function
     795             : 
     796             :   /* static */
     797             :   nsresult
     798           1 :   FrecencyNotificationFunction::create(mozIStorageConnection *aDBConn)
     799             :   {
     800             :     RefPtr<FrecencyNotificationFunction> function =
     801           2 :       new FrecencyNotificationFunction();
     802           2 :     nsresult rv = aDBConn->CreateFunction(
     803           2 :       NS_LITERAL_CSTRING("notify_frecency"), 5, function
     804           3 :     );
     805           1 :     NS_ENSURE_SUCCESS(rv, rv);
     806             : 
     807           1 :     return NS_OK;
     808             :   }
     809             : 
     810          10 :   NS_IMPL_ISUPPORTS(
     811             :     FrecencyNotificationFunction,
     812             :     mozIStorageFunction
     813             :   )
     814             : 
     815             :   NS_IMETHODIMP
     816           1 :   FrecencyNotificationFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
     817             :                                                nsIVariant **_result)
     818             :   {
     819             :     uint32_t numArgs;
     820           1 :     nsresult rv = aArgs->GetNumEntries(&numArgs);
     821           1 :     NS_ENSURE_SUCCESS(rv, rv);
     822           1 :     MOZ_ASSERT(numArgs == 5);
     823             : 
     824           1 :     int32_t newFrecency = aArgs->AsInt32(0);
     825             : 
     826           2 :     nsAutoCString spec;
     827           1 :     rv = aArgs->GetUTF8String(1, spec);
     828           1 :     NS_ENSURE_SUCCESS(rv, rv);
     829             : 
     830           2 :     nsAutoCString guid;
     831           1 :     rv = aArgs->GetUTF8String(2, guid);
     832           1 :     NS_ENSURE_SUCCESS(rv, rv);
     833             : 
     834           1 :     bool hidden = static_cast<bool>(aArgs->AsInt32(3));
     835           1 :     PRTime lastVisitDate = static_cast<PRTime>(aArgs->AsInt64(4));
     836             : 
     837           1 :     const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
     838           1 :     NS_ENSURE_STATE(navHistory);
     839           1 :     navHistory->DispatchFrecencyChangedNotification(spec, newFrecency, guid,
     840           1 :                                                     hidden, lastVisitDate);
     841             : 
     842           2 :     RefPtr<nsVariant> result = new nsVariant();
     843           1 :     rv = result->SetAsInt32(newFrecency);
     844           1 :     NS_ENSURE_SUCCESS(rv, rv);
     845           1 :     result.forget(_result);
     846           1 :     return NS_OK;
     847             :   }
     848             : 
     849             : ////////////////////////////////////////////////////////////////////////////////
     850             : //// Store Last Inserted Id Function
     851             : 
     852             :   /* static */
     853             :   nsresult
     854           1 :   StoreLastInsertedIdFunction::create(mozIStorageConnection *aDBConn)
     855             :   {
     856             :     RefPtr<StoreLastInsertedIdFunction> function =
     857           2 :       new StoreLastInsertedIdFunction();
     858           2 :     nsresult rv = aDBConn->CreateFunction(
     859           2 :       NS_LITERAL_CSTRING("store_last_inserted_id"), 2, function
     860           3 :     );
     861           1 :     NS_ENSURE_SUCCESS(rv, rv);
     862             : 
     863           1 :     return NS_OK;
     864             :   }
     865             : 
     866          10 :   NS_IMPL_ISUPPORTS(
     867             :     StoreLastInsertedIdFunction,
     868             :     mozIStorageFunction
     869             :   )
     870             : 
     871             :   NS_IMETHODIMP
     872           2 :   StoreLastInsertedIdFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
     873             :                                               nsIVariant **_result)
     874             :   {
     875             :     uint32_t numArgs;
     876           2 :     nsresult rv = aArgs->GetNumEntries(&numArgs);
     877           2 :     NS_ENSURE_SUCCESS(rv, rv);
     878           2 :     MOZ_ASSERT(numArgs == 2);
     879             : 
     880           4 :     nsAutoCString table;
     881           2 :     rv = aArgs->GetUTF8String(0, table);
     882           2 :     NS_ENSURE_SUCCESS(rv, rv);
     883             : 
     884           2 :     int64_t lastInsertedId = aArgs->AsInt64(1);
     885             : 
     886           2 :     MOZ_ASSERT(table.EqualsLiteral("moz_places") ||
     887             :                table.EqualsLiteral("moz_historyvisits") ||
     888             :                table.EqualsLiteral("moz_bookmarks") ||
     889             :                table.EqualsLiteral("moz_icons"));
     890             : 
     891           2 :     if (table.EqualsLiteral("moz_bookmarks")) {
     892           0 :       nsNavBookmarks::StoreLastInsertedId(table, lastInsertedId);
     893           2 :     } else if (table.EqualsLiteral("moz_icons")) {
     894           0 :       nsFaviconService::StoreLastInsertedId(table, lastInsertedId);
     895             :     } else {
     896           2 :       nsNavHistory::StoreLastInsertedId(table, lastInsertedId);
     897             :     }
     898             : 
     899           4 :     RefPtr<nsVariant> result = new nsVariant();
     900           2 :     rv = result->SetAsInt64(lastInsertedId);
     901           2 :     NS_ENSURE_SUCCESS(rv, rv);
     902           2 :     result.forget(_result);
     903           2 :     return NS_OK;
     904             :   }
     905             : 
     906             : ////////////////////////////////////////////////////////////////////////////////
     907             : //// Hash Function
     908             : 
     909             :   /* static */
     910             :   nsresult
     911           1 :   HashFunction::create(mozIStorageConnection *aDBConn)
     912             :   {
     913           2 :     RefPtr<HashFunction> function = new HashFunction();
     914           2 :     return aDBConn->CreateFunction(
     915           2 :       NS_LITERAL_CSTRING("hash"), -1, function
     916           4 :     );
     917             :   }
     918             : 
     919          10 :   NS_IMPL_ISUPPORTS(
     920             :     HashFunction,
     921             :     mozIStorageFunction
     922             :   )
     923             : 
     924             :   NS_IMETHODIMP
     925          15 :   HashFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
     926             :                                nsIVariant **_result)
     927             :   {
     928             :     // Must have non-null function arguments.
     929          15 :     MOZ_ASSERT(aArguments);
     930             : 
     931             :     // Fetch arguments.  Use default values if they were omitted.
     932             :     uint32_t numEntries;
     933          15 :     nsresult rv = aArguments->GetNumEntries(&numEntries);
     934          15 :     NS_ENSURE_SUCCESS(rv, rv);
     935          15 :     NS_ENSURE_TRUE(numEntries >= 1  && numEntries <= 2, NS_ERROR_FAILURE);
     936             : 
     937          30 :     nsString str;
     938          15 :     aArguments->GetString(0, str);
     939          30 :     nsAutoCString mode;
     940          15 :     if (numEntries > 1) {
     941           0 :       aArguments->GetUTF8String(1, mode);
     942             :     }
     943             : 
     944          30 :     RefPtr<nsVariant> result = new nsVariant();
     945          15 :     if (mode.IsEmpty()) {
     946             :       // URI-like strings (having a prefix before a colon), are handled specially,
     947             :       // as a 48 bit hash, where first 16 bits are the prefix hash, while the
     948             :       // other 32 are the string hash.
     949             :       // The 16 bits have been decided based on the fact hashing all of the IANA
     950             :       // known schemes, plus "places", does not generate collisions.
     951          15 :       nsAString::const_iterator start, tip, end;
     952          15 :       str.BeginReading(tip);
     953          15 :       start = tip;
     954          15 :       str.EndReading(end);
     955          15 :       if (FindInReadable(NS_LITERAL_STRING(":"), tip, end)) {
     956          30 :         const nsDependentSubstring& prefix = Substring(start, tip);
     957          15 :         uint64_t prefixHash = static_cast<uint64_t>(HashString(prefix) & 0x0000FFFF);
     958             :         // The second half of the url is more likely to be unique, so we add it.
     959          15 :         uint32_t srcHash = HashString(str);
     960          15 :         uint64_t hash = (prefixHash << 32) + srcHash;
     961          15 :         result->SetAsInt64(hash);
     962             :       } else {
     963           0 :         uint32_t hash = HashString(str);
     964           0 :         result->SetAsInt64(hash);
     965             :       }
     966           0 :     } else if (mode.Equals(NS_LITERAL_CSTRING("prefix_lo"))) {
     967             :       // Keep only 16 bits.
     968           0 :       uint64_t hash = static_cast<uint64_t>(HashString(str) & 0x0000FFFF) << 32;
     969           0 :       result->SetAsInt64(hash);
     970           0 :     } else if (mode.Equals(NS_LITERAL_CSTRING("prefix_hi"))) {
     971             :       // Keep only 16 bits.
     972           0 :       uint64_t hash = static_cast<uint64_t>(HashString(str) & 0x0000FFFF) << 32;
     973             :       // Make this a prefix upper bound by filling the lowest 32 bits.
     974           0 :       hash +=  0xFFFFFFFF;
     975           0 :       result->SetAsInt64(hash);
     976             :     } else {
     977           0 :       return NS_ERROR_FAILURE;
     978             :     }
     979             : 
     980          15 :     result.forget(_result);
     981          15 :     return NS_OK;
     982             :   }
     983             : 
     984             : } // namespace places
     985             : } // namespace mozilla

Generated by: LCOV version 1.13