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
|