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 "nsUnicodeProperties.h"
15 #include "nsUTF8Utils.h"
16 #include "nsINavHistoryService.h"
17 #include "nsPrintfCString.h"
18 #include "nsNavHistory.h"
19 #include "mozilla/Likely.h"
20 #include "mozilla/Utf8.h"
21 #include "nsURLHelper.h"
22 #include "nsVariant.h"
23 
24 // Maximum number of chars to search through.
25 // MatchAutoCompleteFunction won't look for matches over this threshold.
26 #define MAX_CHARS_TO_SEARCH_THROUGH 255
27 
28 using namespace mozilla::storage;
29 
30 ////////////////////////////////////////////////////////////////////////////////
31 //// Anonymous Helpers
32 
33 namespace {
34 
35 typedef nsACString::const_char_iterator const_char_iterator;
36 typedef nsACString::size_type size_type;
37 typedef nsACString::char_type char_type;
38 
39 /**
40  * Scan forward through UTF-8 text until the next potential character that
41  * could match a given codepoint when lower-cased (false positives are okay).
42  * This avoids having to actually parse the UTF-8 text, which is slow.
43  *
44  * @param aStart
45  *        An iterator pointing to the first character position considered.
46  *        It will be updated by this function.
47  * @param aEnd
48  *        An interator pointing to past-the-end of the string.
49  */
goToNextSearchCandidate(const_char_iterator & aStart,const const_char_iterator & aEnd,uint32_t aSearchFor)50 static MOZ_ALWAYS_INLINE void goToNextSearchCandidate(
51     const_char_iterator& aStart, const const_char_iterator& aEnd,
52     uint32_t aSearchFor) {
53   // If the character we search for is ASCII, then we can scan until we find
54   // it or its ASCII uppercase character, modulo the special cases
55   // U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE and U+212A KELVIN SIGN
56   // (which are the only non-ASCII characters that lower-case to ASCII ones).
57   // Since false positives are okay, we approximate ASCII lower-casing by
58   // bit-ORing with 0x20, for increased performance.
59   //
60   // If the character we search for is *not* ASCII, we can ignore everything
61   // that is, since all ASCII characters lower-case to ASCII.
62   //
63   // Because of how UTF-8 uses high-order bits, this will never land us
64   // in the middle of a codepoint.
65   //
66   // The assumptions about Unicode made here are verified in the test_casing
67   // gtest.
68   if (aSearchFor < 128) {
69     // When searching for I or K, we pick out the first byte of the UTF-8
70     // encoding of the corresponding special case character, and look for it
71     // in the loop below.  For other characters we fall back to 0xff, which
72     // is not a valid UTF-8 byte.
73     unsigned char target = (unsigned char)(aSearchFor | 0x20);
74     unsigned char special = 0xff;
75     if (target == 'i' || target == 'k') {
76       special = (target == 'i' ? 0xc4 : 0xe2);
77     }
78 
79     while (aStart < aEnd && (unsigned char)(*aStart | 0x20) != target &&
80            (unsigned char)*aStart != special) {
81       aStart++;
82     }
83   } else {
84     while (aStart < aEnd && (unsigned char)(*aStart) < 128) {
85       aStart++;
86     }
87   }
88 }
89 
90 /**
91  * Check whether a character position is on a word boundary of a UTF-8 string
92  * (rather than within a word).  We define "within word" to be any position
93  * between [a-zA-Z] and [a-z] -- this lets us match CamelCase words.
94  * TODO: support non-latin alphabets.
95  *
96  * @param aPos
97  *        An iterator pointing to the character position considered.  It must
98  *        *not* be the first byte of a string.
99  *
100  * @return true if boundary, false otherwise.
101  */
isOnBoundary(const_char_iterator aPos)102 static MOZ_ALWAYS_INLINE bool isOnBoundary(const_char_iterator aPos) {
103   if ('a' <= *aPos && *aPos <= 'z') {
104     char prev = *(aPos - 1) | 0x20;
105     return !('a' <= prev && prev <= 'z');
106   }
107   return true;
108 }
109 
110 /**
111  * Check whether a token string matches a particular position of a source
112  * string, case insensitively (or optionally, case and diacritic insensitively).
113  *
114  * @param aTokenStart
115  *        An iterator pointing to the start of the token string.
116  * @param aTokenEnd
117  *        An iterator pointing past-the-end of the token string.
118  * @param aSourceStart
119  *        An iterator pointing to the position of source string to start
120  *        matching at.
121  * @param aSourceEnd
122  *        An iterator pointing past-the-end of the source string.
123  * @param aMatchDiacritics
124  *        Whether or not the match is diacritic-sensitive.
125  *
126  * @return true if the string [aTokenStart, aTokenEnd) matches the start of
127  *         the string [aSourceStart, aSourceEnd, false otherwise.
128  */
stringMatch(const_char_iterator aTokenStart,const_char_iterator aTokenEnd,const_char_iterator aSourceStart,const_char_iterator aSourceEnd,bool aMatchDiacritics)129 static MOZ_ALWAYS_INLINE bool stringMatch(const_char_iterator aTokenStart,
130                                           const_char_iterator aTokenEnd,
131                                           const_char_iterator aSourceStart,
132                                           const_char_iterator aSourceEnd,
133                                           bool aMatchDiacritics) {
134   const_char_iterator tokenCur = aTokenStart, sourceCur = aSourceStart;
135 
136   while (tokenCur < aTokenEnd) {
137     if (sourceCur >= aSourceEnd) {
138       return false;
139     }
140 
141     bool error;
142     if (!CaseInsensitiveUTF8CharsEqual(sourceCur, tokenCur, aSourceEnd,
143                                        aTokenEnd, &sourceCur, &tokenCur, &error,
144                                        aMatchDiacritics)) {
145       return false;
146     }
147   }
148 
149   return true;
150 }
151 
152 enum FindInStringBehavior { eFindOnBoundary, eFindAnywhere };
153 
154 /**
155  * Common implementation for findAnywhere and findOnBoundary.
156  *
157  * @param aToken
158  *        The token we're searching for
159  * @param aSourceString
160  *        The string in which we're searching
161  * @param aBehavior
162  *        eFindOnBoundary if we should only consider matchines which occur on
163  *        word boundaries, or eFindAnywhere if we should consider matches
164  *        which appear anywhere.
165  *
166  * @return true if aToken was found in aSourceString, false otherwise.
167  */
findInString(const nsDependentCSubstring & aToken,const nsACString & aSourceString,FindInStringBehavior aBehavior)168 static bool findInString(const nsDependentCSubstring& aToken,
169                          const nsACString& aSourceString,
170                          FindInStringBehavior aBehavior) {
171   // GetLowerUTF8Codepoint assumes that there's at least one byte in
172   // the string, so don't pass an empty token here.
173   MOZ_ASSERT(!aToken.IsEmpty(), "Don't search for an empty token!");
174 
175   // We cannot match anything if there is nothing to search.
176   if (aSourceString.IsEmpty()) {
177     return false;
178   }
179 
180   const nsNavHistory* history = nsNavHistory::GetConstHistoryService();
181   bool matchDiacritics = history && history->MatchDiacritics();
182 
183   const_char_iterator tokenStart(aToken.BeginReading()),
184       tokenEnd(aToken.EndReading()), tokenNext,
185       sourceStart(aSourceString.BeginReading()),
186       sourceEnd(aSourceString.EndReading()), sourceCur(sourceStart), sourceNext;
187 
188   uint32_t tokenFirstChar =
189       GetLowerUTF8Codepoint(tokenStart, tokenEnd, &tokenNext);
190   if (tokenFirstChar == uint32_t(-1)) {
191     return false;
192   }
193   if (!matchDiacritics) {
194     tokenFirstChar = ToNaked(tokenFirstChar);
195   }
196 
197   for (;;) {
198     if (matchDiacritics) {
199       // Scan forward to the next viable candidate (if any).
200       goToNextSearchCandidate(sourceCur, sourceEnd, tokenFirstChar);
201     }
202     if (sourceCur == sourceEnd) {
203       break;
204     }
205 
206     // Check whether the first character in the token matches the character
207     // at sourceCur.  At the same time, get a pointer to the next character
208     // in the source.
209     uint32_t sourceFirstChar =
210         GetLowerUTF8Codepoint(sourceCur, sourceEnd, &sourceNext);
211     if (sourceFirstChar == uint32_t(-1)) {
212       return false;
213     }
214     if (!matchDiacritics) {
215       sourceFirstChar = ToNaked(sourceFirstChar);
216     }
217 
218     if (sourceFirstChar == tokenFirstChar &&
219         (aBehavior != eFindOnBoundary || sourceCur == sourceStart ||
220          isOnBoundary(sourceCur)) &&
221         stringMatch(tokenNext, tokenEnd, sourceNext, sourceEnd,
222                     matchDiacritics)) {
223       return true;
224     }
225 
226     sourceCur = sourceNext;
227   }
228 
229   return false;
230 }
231 
232 static MOZ_ALWAYS_INLINE nsDependentCString
getSharedUTF8String(mozIStorageValueArray * aValues,uint32_t aIndex)233 getSharedUTF8String(mozIStorageValueArray* aValues, uint32_t aIndex) {
234   uint32_t len;
235   const char* str = aValues->AsSharedUTF8String(aIndex, &len);
236   if (!str) {
237     return nsDependentCString("", (size_t)0);
238   }
239   return nsDependentCString(str, len);
240 }
241 
242 /**
243  * Gets the length of the prefix in a URI spec.  "Prefix" is defined to be the
244  * scheme, colon, and, if present, two slashes.
245  *
246  * Examples:
247  *
248  *   http://example.com
249  *   ~~~~~~~
250  *   => length == 7
251  *
252  *   foo:example
253  *   ~~~~
254  *   => length == 4
255  *
256  *   not a spec
257  *   => length == 0
258  *
259  * @param  aSpec
260  *         A URI spec, or a string that may be a URI spec.
261  * @return The length of the prefix in the spec.  If there isn't a prefix,
262  *         returns 0.
263  */
getPrefixLength(const nsACString & aSpec)264 static MOZ_ALWAYS_INLINE size_type getPrefixLength(const nsACString& aSpec) {
265   // To keep the search bounded, look at 64 characters at most.  The longest
266   // IANA schemes are ~30, so double that and round up to a nice number.
267   size_type length = std::min(static_cast<size_type>(64), aSpec.Length());
268   for (size_type i = 0; i < length; ++i) {
269     if (aSpec[i] == static_cast<char_type>(':')) {
270       // Found the ':'.  Now skip past "//", if present.
271       if (i + 2 < aSpec.Length() &&
272           aSpec[i + 1] == static_cast<char_type>('/') &&
273           aSpec[i + 2] == static_cast<char_type>('/')) {
274         i += 2;
275       }
276       return i + 1;
277     }
278   }
279   return 0;
280 }
281 
282 /**
283  * Gets the index in a URI spec of the host and port substring and optionally
284  * its length.
285  *
286  * Examples:
287  *
288  *   http://example.com/
289  *          ~~~~~~~~~~~
290  *   => index == 7, length == 11
291  *
292  *   http://example.com:8888/
293  *          ~~~~~~~~~~~~~~~~
294  *   => index == 7, length == 16
295  *
296  *   http://user:pass@example.com/
297  *                    ~~~~~~~~~~~
298  *   => index == 17, length == 11
299  *
300  *   foo:example
301  *       ~~~~~~~
302  *   => index == 4, length == 7
303  *
304  *   not a spec
305  *   ~~~~~~~~~~
306  *   => index == 0, length == 10
307  *
308  * @param  aSpec
309  *         A URI spec, or a string that may be a URI spec.
310  * @param  _hostAndPortLength
311  *         The length of the host and port substring is returned through this
312  *         param.  Pass null if you don't care.
313  * @return The length of the host and port substring in the spec.  If aSpec
314  *         doesn't look like a URI, then the entire aSpec is assumed to be a
315  *         "host and port", and this returns 0, and _hostAndPortLength will be
316  *         the length of aSpec.
317  */
318 static MOZ_ALWAYS_INLINE size_type
indexOfHostAndPort(const nsACString & aSpec,size_type * _hostAndPortLength)319 indexOfHostAndPort(const nsACString& aSpec, size_type* _hostAndPortLength) {
320   size_type index = getPrefixLength(aSpec);
321   size_type i = index;
322   for (; i < aSpec.Length(); ++i) {
323     // RFC 3986 (URIs): The origin ("authority") is terminated by '/', '?', or
324     // '#' (or the end of the URI).
325     if (aSpec[i] == static_cast<char_type>('/') ||
326         aSpec[i] == static_cast<char_type>('?') ||
327         aSpec[i] == static_cast<char_type>('#')) {
328       break;
329     }
330     // RFC 3986: '@' marks the end of the userinfo component.
331     if (aSpec[i] == static_cast<char_type>('@')) {
332       index = i + 1;
333     }
334   }
335   if (_hostAndPortLength) {
336     *_hostAndPortLength = i - index;
337   }
338   return index;
339 }
340 
341 }  // End anonymous namespace
342 
343 namespace mozilla {
344 namespace places {
345 
346 ////////////////////////////////////////////////////////////////////////////////
347 //// AutoComplete Matching Function
348 
349 /* static */
create(mozIStorageConnection * aDBConn)350 nsresult MatchAutoCompleteFunction::create(mozIStorageConnection* aDBConn) {
351   RefPtr<MatchAutoCompleteFunction> function = new MatchAutoCompleteFunction();
352 
353   nsresult rv = aDBConn->CreateFunction("autocomplete_match"_ns,
354                                         kArgIndexLength, function);
355   NS_ENSURE_SUCCESS(rv, rv);
356 
357   return NS_OK;
358 }
359 
360 /* static */
fixupURISpec(const nsACString & aURISpec,int32_t aMatchBehavior,nsACString & aSpecBuf)361 nsDependentCSubstring MatchAutoCompleteFunction::fixupURISpec(
362     const nsACString& aURISpec, int32_t aMatchBehavior, nsACString& aSpecBuf) {
363   nsDependentCSubstring fixedSpec;
364 
365   // Try to unescape the string.  If that succeeds and yields a different
366   // string which is also valid UTF-8, we'll use it.
367   // Otherwise, we will simply use our original string.
368   bool unescaped = NS_UnescapeURL(aURISpec.BeginReading(), aURISpec.Length(),
369                                   esc_SkipControl, aSpecBuf);
370   if (unescaped && IsUtf8(aSpecBuf)) {
371     fixedSpec.Rebind(aSpecBuf, 0);
372   } else {
373     fixedSpec.Rebind(aURISpec, 0);
374   }
375 
376   if (aMatchBehavior == mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED)
377     return fixedSpec;
378 
379   if (StringBeginsWith(fixedSpec, "http://"_ns)) {
380     fixedSpec.Rebind(fixedSpec, 7);
381   } else if (StringBeginsWith(fixedSpec, "https://"_ns)) {
382     fixedSpec.Rebind(fixedSpec, 8);
383   } else if (StringBeginsWith(fixedSpec, "ftp://"_ns)) {
384     fixedSpec.Rebind(fixedSpec, 6);
385   }
386 
387   return fixedSpec;
388 }
389 
390 /* static */
findAnywhere(const nsDependentCSubstring & aToken,const nsACString & aSourceString)391 bool MatchAutoCompleteFunction::findAnywhere(
392     const nsDependentCSubstring& aToken, const nsACString& aSourceString) {
393   // We can't use FindInReadable here; it works only for ASCII.
394 
395   return findInString(aToken, aSourceString, eFindAnywhere);
396 }
397 
398 /* static */
findOnBoundary(const nsDependentCSubstring & aToken,const nsACString & aSourceString)399 bool MatchAutoCompleteFunction::findOnBoundary(
400     const nsDependentCSubstring& aToken, const nsACString& aSourceString) {
401   return findInString(aToken, aSourceString, eFindOnBoundary);
402 }
403 
404 /* static */
405 MatchAutoCompleteFunction::searchFunctionPtr
getSearchFunction(int32_t aBehavior)406 MatchAutoCompleteFunction::getSearchFunction(int32_t aBehavior) {
407   switch (aBehavior) {
408     case mozIPlacesAutoComplete::MATCH_ANYWHERE:
409     case mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED:
410       return findAnywhere;
411     case mozIPlacesAutoComplete::MATCH_BOUNDARY:
412     default:
413       return findOnBoundary;
414   };
415 }
416 
NS_IMPL_ISUPPORTS(MatchAutoCompleteFunction,mozIStorageFunction)417 NS_IMPL_ISUPPORTS(MatchAutoCompleteFunction, mozIStorageFunction)
418 
419 MatchAutoCompleteFunction::MatchAutoCompleteFunction()
420     : mCachedZero(new IntegerVariant(0)), mCachedOne(new IntegerVariant(1)) {
421   static_assert(IntegerVariant::HasThreadSafeRefCnt::value,
422                 "Caching assumes that variants have thread-safe refcounting");
423 }
424 
425 NS_IMETHODIMP
OnFunctionCall(mozIStorageValueArray * aArguments,nsIVariant ** _result)426 MatchAutoCompleteFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
427                                           nsIVariant** _result) {
428   // Macro to make the code a bit cleaner and easier to read.  Operates on
429   // searchBehavior.
430   int32_t searchBehavior = aArguments->AsInt32(kArgIndexSearchBehavior);
431 #define HAS_BEHAVIOR(aBitName) \
432   (searchBehavior & mozIPlacesAutoComplete::BEHAVIOR_##aBitName)
433 
434   nsDependentCString searchString =
435       getSharedUTF8String(aArguments, kArgSearchString);
436   nsDependentCString url = getSharedUTF8String(aArguments, kArgIndexURL);
437 
438   int32_t matchBehavior = aArguments->AsInt32(kArgIndexMatchBehavior);
439 
440   // We only want to filter javascript: URLs if we are not supposed to search
441   // for them, and the search does not start with "javascript:".
442   if (matchBehavior != mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED &&
443       StringBeginsWith(url, "javascript:"_ns) && !HAS_BEHAVIOR(JAVASCRIPT) &&
444       !StringBeginsWith(searchString, "javascript:"_ns)) {
445     NS_ADDREF(*_result = mCachedZero);
446     return NS_OK;
447   }
448 
449   int32_t visitCount = aArguments->AsInt32(kArgIndexVisitCount);
450   // Filtering on typed is no more used by Firefox, it is still being used by
451   // comm-central clients.
452   bool typed = aArguments->AsInt32(kArgIndexTyped) ? true : false;
453   bool bookmark = aArguments->AsInt32(kArgIndexBookmark) ? true : false;
454   nsDependentCString tags = getSharedUTF8String(aArguments, kArgIndexTags);
455   int32_t openPageCount = aArguments->AsInt32(kArgIndexOpenPageCount);
456   bool matches = false;
457   if (HAS_BEHAVIOR(RESTRICT)) {
458     // Make sure we match all the filter requirements.  If a given restriction
459     // is active, make sure the corresponding condition is not true.
460     matches = (!HAS_BEHAVIOR(HISTORY) || visitCount > 0) &&
461               (!HAS_BEHAVIOR(TYPED) || typed) &&
462               (!HAS_BEHAVIOR(BOOKMARK) || bookmark) &&
463               (!HAS_BEHAVIOR(TAG) || !tags.IsVoid()) &&
464               (!HAS_BEHAVIOR(OPENPAGE) || openPageCount > 0);
465   } else {
466     // Make sure that we match all the filter requirements and that the
467     // corresponding condition is true if at least a given restriction is
468     // active.
469     matches = (HAS_BEHAVIOR(HISTORY) && visitCount > 0) ||
470               (HAS_BEHAVIOR(TYPED) && typed) ||
471               (HAS_BEHAVIOR(BOOKMARK) && bookmark) ||
472               (HAS_BEHAVIOR(TAG) && !tags.IsVoid()) ||
473               (HAS_BEHAVIOR(OPENPAGE) && openPageCount > 0);
474   }
475 
476   if (!matches) {
477     NS_ADDREF(*_result = mCachedZero);
478     return NS_OK;
479   }
480 
481   // Obtain our search function.
482   searchFunctionPtr searchFunction = getSearchFunction(matchBehavior);
483 
484   // Clean up our URI spec and prepare it for searching.
485   nsCString fixedUrlBuf;
486   nsDependentCSubstring fixedUrl =
487       fixupURISpec(url, matchBehavior, fixedUrlBuf);
488   // Limit the number of chars we search through.
489   const nsDependentCSubstring& trimmedUrl =
490       Substring(fixedUrl, 0, MAX_CHARS_TO_SEARCH_THROUGH);
491 
492   nsDependentCString title = getSharedUTF8String(aArguments, kArgIndexTitle);
493   // Limit the number of chars we search through.
494   const nsDependentCSubstring& trimmedTitle =
495       Substring(title, 0, MAX_CHARS_TO_SEARCH_THROUGH);
496 
497   // Determine if every token matches either the bookmark title, tags, page
498   // title, or page URL.
499   nsCWhitespaceTokenizer tokenizer(searchString);
500   while (matches && tokenizer.hasMoreTokens()) {
501     const nsDependentCSubstring& token = tokenizer.nextToken();
502 
503     if (HAS_BEHAVIOR(TITLE) && HAS_BEHAVIOR(URL)) {
504       matches = (searchFunction(token, trimmedTitle) ||
505                  searchFunction(token, tags)) &&
506                 searchFunction(token, trimmedUrl);
507     } else if (HAS_BEHAVIOR(TITLE)) {
508       matches =
509           searchFunction(token, trimmedTitle) || searchFunction(token, tags);
510     } else if (HAS_BEHAVIOR(URL)) {
511       matches = searchFunction(token, trimmedUrl);
512     } else {
513       matches = searchFunction(token, trimmedTitle) ||
514                 searchFunction(token, tags) ||
515                 searchFunction(token, trimmedUrl);
516     }
517   }
518 
519   NS_ADDREF(*_result = (matches ? mCachedOne : mCachedZero));
520   return NS_OK;
521 #undef HAS_BEHAVIOR
522 }
523 
524 ////////////////////////////////////////////////////////////////////////////////
525 //// Frecency Calculation Function
526 
527 /* static */
create(mozIStorageConnection * aDBConn)528 nsresult CalculateFrecencyFunction::create(mozIStorageConnection* aDBConn) {
529   RefPtr<CalculateFrecencyFunction> function = new CalculateFrecencyFunction();
530 
531   nsresult rv = aDBConn->CreateFunction("calculate_frecency"_ns, -1, function);
532   NS_ENSURE_SUCCESS(rv, rv);
533 
534   return NS_OK;
535 }
536 
NS_IMPL_ISUPPORTS(CalculateFrecencyFunction,mozIStorageFunction)537 NS_IMPL_ISUPPORTS(CalculateFrecencyFunction, mozIStorageFunction)
538 
539 NS_IMETHODIMP
540 CalculateFrecencyFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
541                                           nsIVariant** _result) {
542   // Fetch arguments.  Use default values if they were omitted.
543   uint32_t numEntries;
544   nsresult rv = aArguments->GetNumEntries(&numEntries);
545   NS_ENSURE_SUCCESS(rv, rv);
546   MOZ_ASSERT(numEntries <= 2, "unexpected number of arguments");
547 
548   int64_t pageId = aArguments->AsInt64(0);
549   MOZ_ASSERT(pageId > 0, "Should always pass a valid page id");
550   if (pageId <= 0) {
551     NS_ADDREF(*_result = new IntegerVariant(0));
552     return NS_OK;
553   }
554 
555   enum RedirectBonus { eUnknown, eRedirect, eNormal };
556 
557   RedirectBonus mostRecentVisitBonus = eUnknown;
558 
559   if (numEntries > 1) {
560     mostRecentVisitBonus = aArguments->AsInt32(1) ? eRedirect : eNormal;
561   }
562 
563   int32_t typed = 0;
564   int32_t visitCount = 0;
565   bool hasBookmark = false;
566   int32_t isQuery = 0;
567   float pointsForSampledVisits = 0.0;
568   int32_t numSampledVisits = 0;
569   int32_t bonus = 0;
570 
571   // This is a const version of the history object for thread-safety.
572   const nsNavHistory* history = nsNavHistory::GetConstHistoryService();
573   NS_ENSURE_STATE(history);
574   RefPtr<Database> DB = Database::GetDatabase();
575   NS_ENSURE_STATE(DB);
576 
577   // Fetch the page stats from the database.
578   {
579     nsCOMPtr<mozIStorageStatement> getPageInfo = DB->GetStatement(
580         "SELECT typed, visit_count, foreign_count, "
581         "(substr(url, 0, 7) = 'place:') "
582         "FROM moz_places "
583         "WHERE id = :page_id ");
584     NS_ENSURE_STATE(getPageInfo);
585     mozStorageStatementScoper infoScoper(getPageInfo);
586 
587     rv = getPageInfo->BindInt64ByName("page_id"_ns, pageId);
588     NS_ENSURE_SUCCESS(rv, rv);
589 
590     bool hasResult = false;
591     rv = getPageInfo->ExecuteStep(&hasResult);
592     NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_UNEXPECTED);
593 
594     rv = getPageInfo->GetInt32(0, &typed);
595     NS_ENSURE_SUCCESS(rv, rv);
596     rv = getPageInfo->GetInt32(1, &visitCount);
597     NS_ENSURE_SUCCESS(rv, rv);
598     int32_t foreignCount = 0;
599     rv = getPageInfo->GetInt32(2, &foreignCount);
600     NS_ENSURE_SUCCESS(rv, rv);
601     hasBookmark = foreignCount > 0;
602     rv = getPageInfo->GetInt32(3, &isQuery);
603     NS_ENSURE_SUCCESS(rv, rv);
604   }
605 
606   if (visitCount > 0) {
607     // Get a sample of the last visits to the page, to calculate its weight.
608     // In case the visit is a redirect target, calculate the frecency
609     // as if the original page was visited.
610     // If it's a redirect source, we may want to use a lower bonus.
611     nsCString redirectsTransitionFragment = nsPrintfCString(
612         "%d AND %d ", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
613         nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY);
614     nsCOMPtr<mozIStorageStatement> getVisits = DB->GetStatement(
615         nsLiteralCString(
616             "/* do not warn (bug 659740 - SQLite may ignore index if few "
617             "visits exist) */"
618             "SELECT "
619             "IFNULL(origin.visit_type, v.visit_type) AS visit_type, "
620             "target.visit_type AS target_visit_type, "
621             "ROUND((strftime('%s','now','localtime','utc') - "
622             "v.visit_date/1000000)/86400) AS age_in_days "
623             "FROM moz_historyvisits v "
624             "LEFT JOIN moz_historyvisits origin ON origin.id = v.from_visit "
625             "AND v.visit_type BETWEEN ") +
626         redirectsTransitionFragment +
627         nsLiteralCString(
628             "LEFT JOIN moz_historyvisits target ON v.id = target.from_visit "
629             "AND target.visit_type BETWEEN ") +
630         redirectsTransitionFragment +
631         nsLiteralCString("WHERE v.place_id = :page_id "
632                          "ORDER BY v.visit_date DESC "
633                          "LIMIT :max_visits "));
634     NS_ENSURE_STATE(getVisits);
635     mozStorageStatementScoper visitsScoper(getVisits);
636     rv = getVisits->BindInt64ByName("page_id"_ns, pageId);
637     NS_ENSURE_SUCCESS(rv, rv);
638     rv = getVisits->BindInt32ByName("max_visits"_ns,
639                                     history->GetNumVisitsForFrecency());
640     NS_ENSURE_SUCCESS(rv, rv);
641 
642     // Fetch only a limited number of recent visits.
643     bool hasResult = false;
644     while (NS_SUCCEEDED(getVisits->ExecuteStep(&hasResult)) && hasResult) {
645       // If this is a redirect target, we'll use the visitType of the source,
646       // otherwise the actual visitType.
647       int32_t visitType = getVisits->AsInt32(0);
648 
649       // When adding a new visit, we should haved passed-in whether we should
650       // use the redirect bonus. We can't fetch this information from the
651       // database, because we only store redirect targets.
652       // For older visits we extract the value from the database.
653       bool useRedirectBonus = mostRecentVisitBonus == eRedirect;
654       if (mostRecentVisitBonus == eUnknown || numSampledVisits > 0) {
655         int32_t targetVisitType = getVisits->AsInt32(1);
656         useRedirectBonus =
657             targetVisitType ==
658                 nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT ||
659             (targetVisitType ==
660                  nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY &&
661              visitType != nsINavHistoryService::TRANSITION_TYPED);
662       }
663 
664       bonus = history->GetFrecencyTransitionBonus(visitType, true,
665                                                   useRedirectBonus);
666 
667       // Add the bookmark visit bonus.
668       if (hasBookmark) {
669         bonus += history->GetFrecencyTransitionBonus(
670             nsINavHistoryService::TRANSITION_BOOKMARK, true);
671       }
672 
673       // If bonus was zero, we can skip the work to determine the weight.
674       if (bonus) {
675         int32_t ageInDays = getVisits->AsInt32(2);
676         int32_t weight = history->GetFrecencyAgedWeight(ageInDays);
677         pointsForSampledVisits += (float)(weight * (bonus / 100.0));
678       }
679 
680       numSampledVisits++;
681     }
682   }
683 
684   // If we sampled some visits for this page, use the calculated weight.
685   if (numSampledVisits) {
686     // We were unable to calculate points, maybe cause all the visits in the
687     // sample had a zero bonus. Though, we know the page has some past valid
688     // visit, or visit_count would be zero. Thus we set the frecency to
689     // -1, so they are still shown in autocomplete.
690     if (!pointsForSampledVisits) {
691       NS_ADDREF(*_result = new IntegerVariant(-1));
692     } else {
693       // Estimate frecency using the sampled visits.
694       // Use ceilf() so that we don't round down to 0, which
695       // would cause us to completely ignore the place during autocomplete.
696       NS_ADDREF(*_result = new IntegerVariant(
697                     (int32_t)ceilf(visitCount * ceilf(pointsForSampledVisits) /
698                                    numSampledVisits)));
699     }
700     return NS_OK;
701   }
702 
703   // Otherwise this page has no visits, it may be bookmarked.
704   if (!hasBookmark || isQuery) {
705     NS_ADDREF(*_result = new IntegerVariant(0));
706     return NS_OK;
707   }
708 
709   // For unvisited bookmarks, produce a non-zero frecency, so that they show
710   // up in URL bar autocomplete.
711   visitCount = 1;
712 
713   // Make it so something bookmarked and typed will have a higher frecency
714   // than something just typed or just bookmarked.
715   bonus += history->GetFrecencyTransitionBonus(
716       nsINavHistoryService::TRANSITION_BOOKMARK, false);
717   if (typed) {
718     bonus += history->GetFrecencyTransitionBonus(
719         nsINavHistoryService::TRANSITION_TYPED, false);
720   }
721 
722   // Assume "now" as our ageInDays, so use the first bucket.
723   pointsForSampledVisits =
724       history->GetFrecencyBucketWeight(1) * (bonus / (float)100.0);
725 
726   // use ceilf() so that we don't round down to 0, which
727   // would cause us to completely ignore the place during autocomplete
728   NS_ADDREF(*_result = new IntegerVariant(
729                 (int32_t)ceilf(visitCount * ceilf(pointsForSampledVisits))));
730 
731   return NS_OK;
732 }
733 
734 ////////////////////////////////////////////////////////////////////////////////
735 //// GUID Creation Function
736 
737 /* static */
create(mozIStorageConnection * aDBConn)738 nsresult GenerateGUIDFunction::create(mozIStorageConnection* aDBConn) {
739   RefPtr<GenerateGUIDFunction> function = new GenerateGUIDFunction();
740   nsresult rv = aDBConn->CreateFunction("generate_guid"_ns, 0, function);
741   NS_ENSURE_SUCCESS(rv, rv);
742 
743   return NS_OK;
744 }
745 
NS_IMPL_ISUPPORTS(GenerateGUIDFunction,mozIStorageFunction)746 NS_IMPL_ISUPPORTS(GenerateGUIDFunction, mozIStorageFunction)
747 
748 NS_IMETHODIMP
749 GenerateGUIDFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
750                                      nsIVariant** _result) {
751   nsAutoCString guid;
752   nsresult rv = GenerateGUID(guid);
753   NS_ENSURE_SUCCESS(rv, rv);
754 
755   NS_ADDREF(*_result = new UTF8TextVariant(guid));
756   return NS_OK;
757 }
758 
759 ////////////////////////////////////////////////////////////////////////////////
760 //// GUID Validation Function
761 
762 /* static */
create(mozIStorageConnection * aDBConn)763 nsresult IsValidGUIDFunction::create(mozIStorageConnection* aDBConn) {
764   RefPtr<IsValidGUIDFunction> function = new IsValidGUIDFunction();
765   return aDBConn->CreateFunction("is_valid_guid"_ns, 1, function);
766 }
767 
NS_IMPL_ISUPPORTS(IsValidGUIDFunction,mozIStorageFunction)768 NS_IMPL_ISUPPORTS(IsValidGUIDFunction, mozIStorageFunction)
769 
770 NS_IMETHODIMP
771 IsValidGUIDFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
772                                     nsIVariant** _result) {
773   // Must have non-null function arguments.
774   MOZ_ASSERT(aArguments);
775 
776   nsAutoCString guid;
777   aArguments->GetUTF8String(0, guid);
778 
779   RefPtr<nsVariant> result = new nsVariant();
780   result->SetAsBool(IsValidGUID(guid));
781   result.forget(_result);
782   return NS_OK;
783 }
784 
785 ////////////////////////////////////////////////////////////////////////////////
786 //// Get Unreversed Host Function
787 
788 /* static */
create(mozIStorageConnection * aDBConn)789 nsresult GetUnreversedHostFunction::create(mozIStorageConnection* aDBConn) {
790   RefPtr<GetUnreversedHostFunction> function = new GetUnreversedHostFunction();
791   nsresult rv = aDBConn->CreateFunction("get_unreversed_host"_ns, 1, function);
792   NS_ENSURE_SUCCESS(rv, rv);
793 
794   return NS_OK;
795 }
796 
NS_IMPL_ISUPPORTS(GetUnreversedHostFunction,mozIStorageFunction)797 NS_IMPL_ISUPPORTS(GetUnreversedHostFunction, mozIStorageFunction)
798 
799 NS_IMETHODIMP
800 GetUnreversedHostFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
801                                           nsIVariant** _result) {
802   // Must have non-null function arguments.
803   MOZ_ASSERT(aArguments);
804 
805   nsAutoString src;
806   aArguments->GetString(0, src);
807 
808   RefPtr<nsVariant> result = new nsVariant();
809 
810   if (src.Length() > 1) {
811     src.Truncate(src.Length() - 1);
812     nsAutoString dest;
813     ReverseString(src, dest);
814     result->SetAsAString(dest);
815   } else {
816     result->SetAsAString(u""_ns);
817   }
818   result.forget(_result);
819   return NS_OK;
820 }
821 
822 ////////////////////////////////////////////////////////////////////////////////
823 //// Fixup URL Function
824 
825 /* static */
create(mozIStorageConnection * aDBConn)826 nsresult FixupURLFunction::create(mozIStorageConnection* aDBConn) {
827   RefPtr<FixupURLFunction> function = new FixupURLFunction();
828   nsresult rv = aDBConn->CreateFunction("fixup_url"_ns, 1, function);
829   NS_ENSURE_SUCCESS(rv, rv);
830 
831   return NS_OK;
832 }
833 
NS_IMPL_ISUPPORTS(FixupURLFunction,mozIStorageFunction)834 NS_IMPL_ISUPPORTS(FixupURLFunction, mozIStorageFunction)
835 
836 NS_IMETHODIMP
837 FixupURLFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
838                                  nsIVariant** _result) {
839   // Must have non-null function arguments.
840   MOZ_ASSERT(aArguments);
841 
842   nsAutoString src;
843   aArguments->GetString(0, src);
844 
845   RefPtr<nsVariant> result = new nsVariant();
846 
847   if (StringBeginsWith(src, u"http://"_ns))
848     src.Cut(0, 7);
849   else if (StringBeginsWith(src, u"https://"_ns))
850     src.Cut(0, 8);
851   else if (StringBeginsWith(src, u"ftp://"_ns))
852     src.Cut(0, 6);
853 
854   // Remove common URL hostname prefixes
855   if (StringBeginsWith(src, u"www."_ns)) {
856     src.Cut(0, 4);
857   }
858 
859   result->SetAsAString(src);
860   result.forget(_result);
861   return NS_OK;
862 }
863 
864 ////////////////////////////////////////////////////////////////////////////////
865 //// Store Last Inserted Id Function
866 
867 /* static */
create(mozIStorageConnection * aDBConn)868 nsresult StoreLastInsertedIdFunction::create(mozIStorageConnection* aDBConn) {
869   RefPtr<StoreLastInsertedIdFunction> function =
870       new StoreLastInsertedIdFunction();
871   nsresult rv =
872       aDBConn->CreateFunction("store_last_inserted_id"_ns, 2, function);
873   NS_ENSURE_SUCCESS(rv, rv);
874 
875   return NS_OK;
876 }
877 
NS_IMPL_ISUPPORTS(StoreLastInsertedIdFunction,mozIStorageFunction)878 NS_IMPL_ISUPPORTS(StoreLastInsertedIdFunction, mozIStorageFunction)
879 
880 NS_IMETHODIMP
881 StoreLastInsertedIdFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
882                                             nsIVariant** _result) {
883   uint32_t numArgs;
884   nsresult rv = aArgs->GetNumEntries(&numArgs);
885   NS_ENSURE_SUCCESS(rv, rv);
886   MOZ_ASSERT(numArgs == 2);
887 
888   nsAutoCString table;
889   rv = aArgs->GetUTF8String(0, table);
890   NS_ENSURE_SUCCESS(rv, rv);
891 
892   int64_t lastInsertedId = aArgs->AsInt64(1);
893 
894   MOZ_ASSERT(table.EqualsLiteral("moz_places") ||
895              table.EqualsLiteral("moz_historyvisits") ||
896              table.EqualsLiteral("moz_bookmarks") ||
897              table.EqualsLiteral("moz_icons"));
898 
899   if (table.EqualsLiteral("moz_bookmarks")) {
900     nsNavBookmarks::StoreLastInsertedId(table, lastInsertedId);
901   } else if (table.EqualsLiteral("moz_icons")) {
902     nsFaviconService::StoreLastInsertedId(table, lastInsertedId);
903   } else {
904     nsNavHistory::StoreLastInsertedId(table, lastInsertedId);
905   }
906 
907   RefPtr<nsVariant> result = new nsVariant();
908   rv = result->SetAsInt64(lastInsertedId);
909   NS_ENSURE_SUCCESS(rv, rv);
910   result.forget(_result);
911   return NS_OK;
912 }
913 
914 ////////////////////////////////////////////////////////////////////////////////
915 //// Get Query Param Function
916 
917 /* static */
create(mozIStorageConnection * aDBConn)918 nsresult GetQueryParamFunction::create(mozIStorageConnection* aDBConn) {
919   RefPtr<GetQueryParamFunction> function = new GetQueryParamFunction();
920   return aDBConn->CreateFunction("get_query_param"_ns, 2, function);
921 }
922 
NS_IMPL_ISUPPORTS(GetQueryParamFunction,mozIStorageFunction)923 NS_IMPL_ISUPPORTS(GetQueryParamFunction, mozIStorageFunction)
924 
925 NS_IMETHODIMP
926 GetQueryParamFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
927                                       nsIVariant** _result) {
928   // Must have non-null function arguments.
929   MOZ_ASSERT(aArguments);
930 
931   nsDependentCString queryString = getSharedUTF8String(aArguments, 0);
932   nsDependentCString paramName = getSharedUTF8String(aArguments, 1);
933 
934   RefPtr<nsVariant> result = new nsVariant();
935   if (!queryString.IsEmpty() && !paramName.IsEmpty()) {
936     URLParams::Parse(
937         queryString,
938         [&paramName, &result](const nsAString& aName, const nsAString& aValue) {
939           NS_ConvertUTF16toUTF8 name(aName);
940           if (!paramName.Equals(name)) {
941             return true;
942           }
943           result->SetAsAString(aValue);
944           return false;
945         });
946   }
947 
948   result.forget(_result);
949   return NS_OK;
950 }
951 
952 ////////////////////////////////////////////////////////////////////////////////
953 //// Hash Function
954 
955 /* static */
create(mozIStorageConnection * aDBConn)956 nsresult HashFunction::create(mozIStorageConnection* aDBConn) {
957   RefPtr<HashFunction> function = new HashFunction();
958   return aDBConn->CreateFunction("hash"_ns, -1, function);
959 }
960 
NS_IMPL_ISUPPORTS(HashFunction,mozIStorageFunction)961 NS_IMPL_ISUPPORTS(HashFunction, mozIStorageFunction)
962 
963 NS_IMETHODIMP
964 HashFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
965                              nsIVariant** _result) {
966   // Must have non-null function arguments.
967   MOZ_ASSERT(aArguments);
968 
969   // Fetch arguments.  Use default values if they were omitted.
970   uint32_t numEntries;
971   nsresult rv = aArguments->GetNumEntries(&numEntries);
972   NS_ENSURE_SUCCESS(rv, rv);
973   NS_ENSURE_TRUE(numEntries >= 1 && numEntries <= 2, NS_ERROR_FAILURE);
974 
975   nsDependentCString str = getSharedUTF8String(aArguments, 0);
976   nsAutoCString mode;
977   if (numEntries > 1) {
978     aArguments->GetUTF8String(1, mode);
979   }
980 
981   RefPtr<nsVariant> result = new nsVariant();
982   uint64_t hash;
983   rv = HashURL(str, mode, &hash);
984   NS_ENSURE_SUCCESS(rv, rv);
985   rv = result->SetAsInt64(hash);
986   NS_ENSURE_SUCCESS(rv, rv);
987 
988   result.forget(_result);
989   return NS_OK;
990 }
991 
992 ////////////////////////////////////////////////////////////////////////////////
993 //// Get prefix function
994 
995 /* static */
create(mozIStorageConnection * aDBConn)996 nsresult GetPrefixFunction::create(mozIStorageConnection* aDBConn) {
997   RefPtr<GetPrefixFunction> function = new GetPrefixFunction();
998   nsresult rv = aDBConn->CreateFunction("get_prefix"_ns, 1, function);
999   NS_ENSURE_SUCCESS(rv, rv);
1000 
1001   return NS_OK;
1002 }
1003 
NS_IMPL_ISUPPORTS(GetPrefixFunction,mozIStorageFunction)1004 NS_IMPL_ISUPPORTS(GetPrefixFunction, mozIStorageFunction)
1005 
1006 NS_IMETHODIMP
1007 GetPrefixFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
1008                                   nsIVariant** _result) {
1009   MOZ_ASSERT(aArgs);
1010 
1011   uint32_t numArgs;
1012   nsresult rv = aArgs->GetNumEntries(&numArgs);
1013   NS_ENSURE_SUCCESS(rv, rv);
1014   MOZ_ASSERT(numArgs == 1);
1015 
1016   nsDependentCString spec(getSharedUTF8String(aArgs, 0));
1017 
1018   RefPtr<nsVariant> result = new nsVariant();
1019   result->SetAsACString(Substring(spec, 0, getPrefixLength(spec)));
1020   result.forget(_result);
1021   return NS_OK;
1022 }
1023 
1024 ////////////////////////////////////////////////////////////////////////////////
1025 //// Get host and port function
1026 
1027 /* static */
create(mozIStorageConnection * aDBConn)1028 nsresult GetHostAndPortFunction::create(mozIStorageConnection* aDBConn) {
1029   RefPtr<GetHostAndPortFunction> function = new GetHostAndPortFunction();
1030   nsresult rv = aDBConn->CreateFunction("get_host_and_port"_ns, 1, function);
1031   NS_ENSURE_SUCCESS(rv, rv);
1032 
1033   return NS_OK;
1034 }
1035 
NS_IMPL_ISUPPORTS(GetHostAndPortFunction,mozIStorageFunction)1036 NS_IMPL_ISUPPORTS(GetHostAndPortFunction, mozIStorageFunction)
1037 
1038 NS_IMETHODIMP
1039 GetHostAndPortFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
1040                                        nsIVariant** _result) {
1041   MOZ_ASSERT(aArgs);
1042 
1043   uint32_t numArgs;
1044   nsresult rv = aArgs->GetNumEntries(&numArgs);
1045   NS_ENSURE_SUCCESS(rv, rv);
1046   MOZ_ASSERT(numArgs == 1);
1047 
1048   nsDependentCString spec(getSharedUTF8String(aArgs, 0));
1049 
1050   RefPtr<nsVariant> result = new nsVariant();
1051 
1052   size_type length;
1053   size_type index = indexOfHostAndPort(spec, &length);
1054   result->SetAsACString(Substring(spec, index, length));
1055   result.forget(_result);
1056   return NS_OK;
1057 }
1058 
1059 ////////////////////////////////////////////////////////////////////////////////
1060 //// Strip prefix and userinfo function
1061 
1062 /* static */
create(mozIStorageConnection * aDBConn)1063 nsresult StripPrefixAndUserinfoFunction::create(
1064     mozIStorageConnection* aDBConn) {
1065   RefPtr<StripPrefixAndUserinfoFunction> function =
1066       new StripPrefixAndUserinfoFunction();
1067   nsresult rv =
1068       aDBConn->CreateFunction("strip_prefix_and_userinfo"_ns, 1, function);
1069   NS_ENSURE_SUCCESS(rv, rv);
1070 
1071   return NS_OK;
1072 }
1073 
NS_IMPL_ISUPPORTS(StripPrefixAndUserinfoFunction,mozIStorageFunction)1074 NS_IMPL_ISUPPORTS(StripPrefixAndUserinfoFunction, mozIStorageFunction)
1075 
1076 NS_IMETHODIMP
1077 StripPrefixAndUserinfoFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
1078                                                nsIVariant** _result) {
1079   MOZ_ASSERT(aArgs);
1080 
1081   uint32_t numArgs;
1082   nsresult rv = aArgs->GetNumEntries(&numArgs);
1083   NS_ENSURE_SUCCESS(rv, rv);
1084   MOZ_ASSERT(numArgs == 1);
1085 
1086   nsDependentCString spec(getSharedUTF8String(aArgs, 0));
1087 
1088   RefPtr<nsVariant> result = new nsVariant();
1089 
1090   size_type index = indexOfHostAndPort(spec, NULL);
1091   result->SetAsACString(Substring(spec, index, spec.Length() - index));
1092   result.forget(_result);
1093   return NS_OK;
1094 }
1095 
1096 ////////////////////////////////////////////////////////////////////////////////
1097 //// Is frecency decaying function
1098 
1099 /* static */
create(mozIStorageConnection * aDBConn)1100 nsresult IsFrecencyDecayingFunction::create(mozIStorageConnection* aDBConn) {
1101   RefPtr<IsFrecencyDecayingFunction> function =
1102       new IsFrecencyDecayingFunction();
1103   nsresult rv = aDBConn->CreateFunction("is_frecency_decaying"_ns, 0, function);
1104   NS_ENSURE_SUCCESS(rv, rv);
1105 
1106   return NS_OK;
1107 }
1108 
NS_IMPL_ISUPPORTS(IsFrecencyDecayingFunction,mozIStorageFunction)1109 NS_IMPL_ISUPPORTS(IsFrecencyDecayingFunction, mozIStorageFunction)
1110 
1111 NS_IMETHODIMP
1112 IsFrecencyDecayingFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
1113                                            nsIVariant** _result) {
1114   MOZ_ASSERT(aArgs);
1115 
1116   uint32_t numArgs;
1117   nsresult rv = aArgs->GetNumEntries(&numArgs);
1118   NS_ENSURE_SUCCESS(rv, rv);
1119   MOZ_ASSERT(numArgs == 0);
1120 
1121   const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
1122   NS_ENSURE_STATE(navHistory);
1123 
1124   RefPtr<nsVariant> result = new nsVariant();
1125   rv = result->SetAsBool(navHistory->IsFrecencyDecaying());
1126   NS_ENSURE_SUCCESS(rv, rv);
1127   result.forget(_result);
1128   return NS_OK;
1129 }
1130 
1131 ////////////////////////////////////////////////////////////////////////////////
1132 //// Note Sync Change Function
1133 
1134 /* static */
create(mozIStorageConnection * aDBConn)1135 nsresult NoteSyncChangeFunction::create(mozIStorageConnection* aDBConn) {
1136   RefPtr<NoteSyncChangeFunction> function = new NoteSyncChangeFunction();
1137   nsresult rv = aDBConn->CreateFunction("note_sync_change"_ns, 0, function);
1138   NS_ENSURE_SUCCESS(rv, rv);
1139 
1140   return NS_OK;
1141 }
1142 
NS_IMPL_ISUPPORTS(NoteSyncChangeFunction,mozIStorageFunction)1143 NS_IMPL_ISUPPORTS(NoteSyncChangeFunction, mozIStorageFunction)
1144 
1145 NS_IMETHODIMP
1146 NoteSyncChangeFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
1147                                        nsIVariant** _result) {
1148   nsNavBookmarks::NoteSyncChange();
1149   *_result = nullptr;
1150   return NS_OK;
1151 }
1152 
1153 ////////////////////////////////////////////////////////////////////////////////
1154 //// Invalidate days of history Function
1155 
1156 /* static */
create(mozIStorageConnection * aDBConn)1157 nsresult InvalidateDaysOfHistoryFunction::create(
1158     mozIStorageConnection* aDBConn) {
1159   RefPtr<InvalidateDaysOfHistoryFunction> function =
1160       new InvalidateDaysOfHistoryFunction();
1161   nsresult rv =
1162       aDBConn->CreateFunction("invalidate_days_of_history"_ns, 0, function);
1163   NS_ENSURE_SUCCESS(rv, rv);
1164 
1165   return NS_OK;
1166 }
1167 
NS_IMPL_ISUPPORTS(InvalidateDaysOfHistoryFunction,mozIStorageFunction)1168 NS_IMPL_ISUPPORTS(InvalidateDaysOfHistoryFunction, mozIStorageFunction)
1169 
1170 NS_IMETHODIMP
1171 InvalidateDaysOfHistoryFunction::OnFunctionCall(mozIStorageValueArray* aArgs,
1172                                                 nsIVariant** _result) {
1173   nsNavHistory::InvalidateDaysOfHistory();
1174   return NS_OK;
1175 }
1176 
1177 }  // namespace places
1178 }  // namespace mozilla
1179