|
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/Telemetry.h" |
|
19 #include "mozilla/Likely.h" |
|
20 |
|
21 using namespace mozilla::storage; |
|
22 |
|
23 // Keep the GUID-related parts of this file in sync with toolkit/downloads/SQLFunctions.cpp! |
|
24 |
|
25 //////////////////////////////////////////////////////////////////////////////// |
|
26 //// Anonymous Helpers |
|
27 |
|
28 namespace { |
|
29 |
|
30 typedef nsACString::const_char_iterator const_char_iterator; |
|
31 |
|
32 /** |
|
33 * Get a pointer to the word boundary after aStart if aStart points to an |
|
34 * ASCII letter (i.e. [a-zA-Z]). Otherwise, return aNext, which we assume |
|
35 * points to the next character in the UTF-8 sequence. |
|
36 * |
|
37 * We define a word boundary as anything that's not [a-z] -- this lets us |
|
38 * match CamelCase words. |
|
39 * |
|
40 * @param aStart the beginning of the UTF-8 sequence |
|
41 * @param aNext the next character in the sequence |
|
42 * @param aEnd the first byte which is not part of the sequence |
|
43 * |
|
44 * @return a pointer to the next word boundary after aStart |
|
45 */ |
|
46 static |
|
47 MOZ_ALWAYS_INLINE const_char_iterator |
|
48 nextWordBoundary(const_char_iterator const aStart, |
|
49 const_char_iterator const aNext, |
|
50 const_char_iterator const aEnd) { |
|
51 |
|
52 const_char_iterator cur = aStart; |
|
53 if (('a' <= *cur && *cur <= 'z') || |
|
54 ('A' <= *cur && *cur <= 'Z')) { |
|
55 |
|
56 // Since we'll halt as soon as we see a non-ASCII letter, we can do a |
|
57 // simple byte-by-byte comparison here and avoid the overhead of a |
|
58 // UTF8CharEnumerator. |
|
59 do { |
|
60 cur++; |
|
61 } while (cur < aEnd && 'a' <= *cur && *cur <= 'z'); |
|
62 } |
|
63 else { |
|
64 cur = aNext; |
|
65 } |
|
66 |
|
67 return cur; |
|
68 } |
|
69 |
|
70 enum FindInStringBehavior { |
|
71 eFindOnBoundary, |
|
72 eFindAnywhere |
|
73 }; |
|
74 |
|
75 /** |
|
76 * findAnywhere and findOnBoundary do almost the same thing, so it's natural |
|
77 * to implement them in terms of a single function. They're both |
|
78 * performance-critical functions, however, and checking aBehavior makes them |
|
79 * a bit slower. Our solution is to define findInString as MOZ_ALWAYS_INLINE |
|
80 * and rely on the compiler to optimize out the aBehavior check. |
|
81 * |
|
82 * @param aToken |
|
83 * The token we're searching for |
|
84 * @param aSourceString |
|
85 * The string in which we're searching |
|
86 * @param aBehavior |
|
87 * eFindOnBoundary if we should only consider matchines which occur on |
|
88 * word boundaries, or eFindAnywhere if we should consider matches |
|
89 * which appear anywhere. |
|
90 * |
|
91 * @return true if aToken was found in aSourceString, false otherwise. |
|
92 */ |
|
93 static |
|
94 MOZ_ALWAYS_INLINE bool |
|
95 findInString(const nsDependentCSubstring &aToken, |
|
96 const nsACString &aSourceString, |
|
97 FindInStringBehavior aBehavior) |
|
98 { |
|
99 // CaseInsensitiveUTF8CharsEqual assumes that there's at least one byte in |
|
100 // the both strings, so don't pass an empty token here. |
|
101 NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!"); |
|
102 |
|
103 // We cannot match anything if there is nothing to search. |
|
104 if (aSourceString.IsEmpty()) { |
|
105 return false; |
|
106 } |
|
107 |
|
108 const_char_iterator tokenStart(aToken.BeginReading()), |
|
109 tokenEnd(aToken.EndReading()), |
|
110 sourceStart(aSourceString.BeginReading()), |
|
111 sourceEnd(aSourceString.EndReading()); |
|
112 |
|
113 do { |
|
114 // We are on a word boundary (if aBehavior == eFindOnBoundary). See if |
|
115 // aToken matches sourceStart. |
|
116 |
|
117 // Check whether the first character in the token matches the character |
|
118 // at sourceStart. At the same time, get a pointer to the next character |
|
119 // in both the token and the source. |
|
120 const_char_iterator sourceNext, tokenCur; |
|
121 bool error; |
|
122 if (CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart, |
|
123 sourceEnd, tokenEnd, |
|
124 &sourceNext, &tokenCur, &error)) { |
|
125 |
|
126 // We don't need to check |error| here -- if |
|
127 // CaseInsensitiveUTF8CharCompare encounters an error, it'll also |
|
128 // return false and we'll catch the error outside the if. |
|
129 |
|
130 const_char_iterator sourceCur = sourceNext; |
|
131 while (true) { |
|
132 if (tokenCur >= tokenEnd) { |
|
133 // We matched the whole token! |
|
134 return true; |
|
135 } |
|
136 |
|
137 if (sourceCur >= sourceEnd) { |
|
138 // We ran into the end of source while matching a token. This |
|
139 // means we'll never find the token we're looking for. |
|
140 return false; |
|
141 } |
|
142 |
|
143 if (!CaseInsensitiveUTF8CharsEqual(sourceCur, tokenCur, |
|
144 sourceEnd, tokenEnd, |
|
145 &sourceCur, &tokenCur, &error)) { |
|
146 // sourceCur doesn't match tokenCur (or there's an error), so break |
|
147 // out of this loop. |
|
148 break; |
|
149 } |
|
150 } |
|
151 } |
|
152 |
|
153 // If something went wrong above, get out of here! |
|
154 if (MOZ_UNLIKELY(error)) { |
|
155 return false; |
|
156 } |
|
157 |
|
158 // We didn't match the token. If we're searching for matches on word |
|
159 // boundaries, skip to the next word boundary. Otherwise, advance |
|
160 // forward one character, using the sourceNext pointer we saved earlier. |
|
161 |
|
162 if (aBehavior == eFindOnBoundary) { |
|
163 sourceStart = nextWordBoundary(sourceStart, sourceNext, sourceEnd); |
|
164 } |
|
165 else { |
|
166 sourceStart = sourceNext; |
|
167 } |
|
168 |
|
169 } while (sourceStart < sourceEnd); |
|
170 |
|
171 return false; |
|
172 } |
|
173 |
|
174 } // End anonymous namespace |
|
175 |
|
176 namespace mozilla { |
|
177 namespace places { |
|
178 |
|
179 //////////////////////////////////////////////////////////////////////////////// |
|
180 //// AutoComplete Matching Function |
|
181 |
|
182 ////////////////////////////////////////////////////////////////////////////// |
|
183 //// MatchAutoCompleteFunction |
|
184 |
|
185 /* static */ |
|
186 nsresult |
|
187 MatchAutoCompleteFunction::create(mozIStorageConnection *aDBConn) |
|
188 { |
|
189 nsRefPtr<MatchAutoCompleteFunction> function = |
|
190 new MatchAutoCompleteFunction(); |
|
191 |
|
192 nsresult rv = aDBConn->CreateFunction( |
|
193 NS_LITERAL_CSTRING("autocomplete_match"), kArgIndexLength, function |
|
194 ); |
|
195 NS_ENSURE_SUCCESS(rv, rv); |
|
196 |
|
197 return NS_OK; |
|
198 } |
|
199 |
|
200 /* static */ |
|
201 void |
|
202 MatchAutoCompleteFunction::fixupURISpec(const nsCString &aURISpec, |
|
203 int32_t aMatchBehavior, |
|
204 nsCString &_fixedSpec) |
|
205 { |
|
206 nsCString unescapedSpec; |
|
207 (void)NS_UnescapeURL(aURISpec, esc_SkipControl | esc_AlwaysCopy, |
|
208 unescapedSpec); |
|
209 |
|
210 // If this unescaped string is valid UTF-8, we'll use it. Otherwise, |
|
211 // we will simply use our original string. |
|
212 NS_ASSERTION(_fixedSpec.IsEmpty(), |
|
213 "Passing a non-empty string as an out parameter!"); |
|
214 if (IsUTF8(unescapedSpec)) |
|
215 _fixedSpec.Assign(unescapedSpec); |
|
216 else |
|
217 _fixedSpec.Assign(aURISpec); |
|
218 |
|
219 if (aMatchBehavior == mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED) |
|
220 return; |
|
221 |
|
222 if (StringBeginsWith(_fixedSpec, NS_LITERAL_CSTRING("http://"))) |
|
223 _fixedSpec.Cut(0, 7); |
|
224 else if (StringBeginsWith(_fixedSpec, NS_LITERAL_CSTRING("https://"))) |
|
225 _fixedSpec.Cut(0, 8); |
|
226 else if (StringBeginsWith(_fixedSpec, NS_LITERAL_CSTRING("ftp://"))) |
|
227 _fixedSpec.Cut(0, 6); |
|
228 |
|
229 if (StringBeginsWith(_fixedSpec, NS_LITERAL_CSTRING("www."))) |
|
230 _fixedSpec.Cut(0, 4); |
|
231 } |
|
232 |
|
233 /* static */ |
|
234 bool |
|
235 MatchAutoCompleteFunction::findAnywhere(const nsDependentCSubstring &aToken, |
|
236 const nsACString &aSourceString) |
|
237 { |
|
238 // We can't use FindInReadable here; it works only for ASCII. |
|
239 |
|
240 return findInString(aToken, aSourceString, eFindAnywhere); |
|
241 } |
|
242 |
|
243 /* static */ |
|
244 bool |
|
245 MatchAutoCompleteFunction::findOnBoundary(const nsDependentCSubstring &aToken, |
|
246 const nsACString &aSourceString) |
|
247 { |
|
248 return findInString(aToken, aSourceString, eFindOnBoundary); |
|
249 } |
|
250 |
|
251 /* static */ |
|
252 bool |
|
253 MatchAutoCompleteFunction::findBeginning(const nsDependentCSubstring &aToken, |
|
254 const nsACString &aSourceString) |
|
255 { |
|
256 NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!"); |
|
257 |
|
258 // We can't use StringBeginsWith here, unfortunately. Although it will |
|
259 // happily take a case-insensitive UTF8 comparator, it eventually calls |
|
260 // nsACString::Equals, which checks that the two strings contain the same |
|
261 // number of bytes before calling the comparator. Two characters may be |
|
262 // case-insensitively equal while taking up different numbers of bytes, so |
|
263 // this is not what we want. |
|
264 |
|
265 const_char_iterator tokenStart(aToken.BeginReading()), |
|
266 tokenEnd(aToken.EndReading()), |
|
267 sourceStart(aSourceString.BeginReading()), |
|
268 sourceEnd(aSourceString.EndReading()); |
|
269 |
|
270 bool dummy; |
|
271 while (sourceStart < sourceEnd && |
|
272 CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart, |
|
273 sourceEnd, tokenEnd, |
|
274 &sourceStart, &tokenStart, &dummy)) { |
|
275 |
|
276 // We found the token! |
|
277 if (tokenStart >= tokenEnd) { |
|
278 return true; |
|
279 } |
|
280 } |
|
281 |
|
282 // We don't need to check CaseInsensitiveUTF8CharsEqual's error condition |
|
283 // (stored in |dummy|), since the function will return false if it |
|
284 // encounters an error. |
|
285 |
|
286 return false; |
|
287 } |
|
288 |
|
289 /* static */ |
|
290 bool |
|
291 MatchAutoCompleteFunction::findBeginningCaseSensitive( |
|
292 const nsDependentCSubstring &aToken, |
|
293 const nsACString &aSourceString) |
|
294 { |
|
295 NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!"); |
|
296 |
|
297 return StringBeginsWith(aSourceString, aToken); |
|
298 } |
|
299 |
|
300 /* static */ |
|
301 MatchAutoCompleteFunction::searchFunctionPtr |
|
302 MatchAutoCompleteFunction::getSearchFunction(int32_t aBehavior) |
|
303 { |
|
304 switch (aBehavior) { |
|
305 case mozIPlacesAutoComplete::MATCH_ANYWHERE: |
|
306 case mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED: |
|
307 return findAnywhere; |
|
308 case mozIPlacesAutoComplete::MATCH_BEGINNING: |
|
309 return findBeginning; |
|
310 case mozIPlacesAutoComplete::MATCH_BEGINNING_CASE_SENSITIVE: |
|
311 return findBeginningCaseSensitive; |
|
312 case mozIPlacesAutoComplete::MATCH_BOUNDARY: |
|
313 default: |
|
314 return findOnBoundary; |
|
315 }; |
|
316 } |
|
317 |
|
318 NS_IMPL_ISUPPORTS( |
|
319 MatchAutoCompleteFunction, |
|
320 mozIStorageFunction |
|
321 ) |
|
322 |
|
323 ////////////////////////////////////////////////////////////////////////////// |
|
324 //// mozIStorageFunction |
|
325 |
|
326 NS_IMETHODIMP |
|
327 MatchAutoCompleteFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
|
328 nsIVariant **_result) |
|
329 { |
|
330 // Macro to make the code a bit cleaner and easier to read. Operates on |
|
331 // searchBehavior. |
|
332 int32_t searchBehavior = aArguments->AsInt32(kArgIndexSearchBehavior); |
|
333 #define HAS_BEHAVIOR(aBitName) \ |
|
334 (searchBehavior & mozIPlacesAutoComplete::BEHAVIOR_##aBitName) |
|
335 |
|
336 nsAutoCString searchString; |
|
337 (void)aArguments->GetUTF8String(kArgSearchString, searchString); |
|
338 nsCString url; |
|
339 (void)aArguments->GetUTF8String(kArgIndexURL, url); |
|
340 |
|
341 int32_t matchBehavior = aArguments->AsInt32(kArgIndexMatchBehavior); |
|
342 |
|
343 // We only want to filter javascript: URLs if we are not supposed to search |
|
344 // for them, and the search does not start with "javascript:". |
|
345 if (matchBehavior != mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED && |
|
346 !HAS_BEHAVIOR(JAVASCRIPT) && |
|
347 !StringBeginsWith(searchString, NS_LITERAL_CSTRING("javascript:")) && |
|
348 StringBeginsWith(url, NS_LITERAL_CSTRING("javascript:"))) { |
|
349 NS_ADDREF(*_result = new IntegerVariant(0)); |
|
350 return NS_OK; |
|
351 } |
|
352 |
|
353 int32_t visitCount = aArguments->AsInt32(kArgIndexVisitCount); |
|
354 bool typed = aArguments->AsInt32(kArgIndexTyped) ? true : false; |
|
355 bool bookmark = aArguments->AsInt32(kArgIndexBookmark) ? true : false; |
|
356 nsAutoCString tags; |
|
357 (void)aArguments->GetUTF8String(kArgIndexTags, tags); |
|
358 int32_t openPageCount = aArguments->AsInt32(kArgIndexOpenPageCount); |
|
359 |
|
360 // Make sure we match all the filter requirements. If a given restriction |
|
361 // is active, make sure the corresponding condition is not true. |
|
362 bool matches = !( |
|
363 (HAS_BEHAVIOR(HISTORY) && visitCount == 0) || |
|
364 (HAS_BEHAVIOR(TYPED) && !typed) || |
|
365 (HAS_BEHAVIOR(BOOKMARK) && !bookmark) || |
|
366 (HAS_BEHAVIOR(TAG) && tags.IsVoid()) || |
|
367 (HAS_BEHAVIOR(OPENPAGE) && openPageCount == 0) |
|
368 ); |
|
369 if (!matches) { |
|
370 NS_ADDREF(*_result = new IntegerVariant(0)); |
|
371 return NS_OK; |
|
372 } |
|
373 |
|
374 // Obtain our search function. |
|
375 searchFunctionPtr searchFunction = getSearchFunction(matchBehavior); |
|
376 |
|
377 // Clean up our URI spec and prepare it for searching. |
|
378 nsCString fixedURI; |
|
379 fixupURISpec(url, matchBehavior, fixedURI); |
|
380 |
|
381 nsAutoCString title; |
|
382 (void)aArguments->GetUTF8String(kArgIndexTitle, title); |
|
383 |
|
384 // Determine if every token matches either the bookmark title, tags, page |
|
385 // title, or page URL. |
|
386 nsCWhitespaceTokenizer tokenizer(searchString); |
|
387 while (matches && tokenizer.hasMoreTokens()) { |
|
388 const nsDependentCSubstring &token = tokenizer.nextToken(); |
|
389 |
|
390 if (HAS_BEHAVIOR(TITLE) && HAS_BEHAVIOR(URL)) { |
|
391 matches = (searchFunction(token, title) || searchFunction(token, tags)) && |
|
392 searchFunction(token, fixedURI); |
|
393 } |
|
394 else if (HAS_BEHAVIOR(TITLE)) { |
|
395 matches = searchFunction(token, title) || searchFunction(token, tags); |
|
396 } |
|
397 else if (HAS_BEHAVIOR(URL)) { |
|
398 matches = searchFunction(token, fixedURI); |
|
399 } |
|
400 else { |
|
401 matches = searchFunction(token, title) || |
|
402 searchFunction(token, tags) || |
|
403 searchFunction(token, fixedURI); |
|
404 } |
|
405 } |
|
406 |
|
407 NS_ADDREF(*_result = new IntegerVariant(matches ? 1 : 0)); |
|
408 return NS_OK; |
|
409 #undef HAS_BEHAVIOR |
|
410 } |
|
411 |
|
412 |
|
413 //////////////////////////////////////////////////////////////////////////////// |
|
414 //// Frecency Calculation Function |
|
415 |
|
416 ////////////////////////////////////////////////////////////////////////////// |
|
417 //// CalculateFrecencyFunction |
|
418 |
|
419 /* static */ |
|
420 nsresult |
|
421 CalculateFrecencyFunction::create(mozIStorageConnection *aDBConn) |
|
422 { |
|
423 nsRefPtr<CalculateFrecencyFunction> function = |
|
424 new CalculateFrecencyFunction(); |
|
425 |
|
426 nsresult rv = aDBConn->CreateFunction( |
|
427 NS_LITERAL_CSTRING("calculate_frecency"), 1, function |
|
428 ); |
|
429 NS_ENSURE_SUCCESS(rv, rv); |
|
430 |
|
431 return NS_OK; |
|
432 } |
|
433 |
|
434 NS_IMPL_ISUPPORTS( |
|
435 CalculateFrecencyFunction, |
|
436 mozIStorageFunction |
|
437 ) |
|
438 |
|
439 ////////////////////////////////////////////////////////////////////////////// |
|
440 //// mozIStorageFunction |
|
441 |
|
442 NS_IMETHODIMP |
|
443 CalculateFrecencyFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
|
444 nsIVariant **_result) |
|
445 { |
|
446 // Fetch arguments. Use default values if they were omitted. |
|
447 uint32_t numEntries; |
|
448 nsresult rv = aArguments->GetNumEntries(&numEntries); |
|
449 NS_ENSURE_SUCCESS(rv, rv); |
|
450 NS_ASSERTION(numEntries > 0, "unexpected number of arguments"); |
|
451 |
|
452 Telemetry::AutoTimer<Telemetry::PLACES_FRECENCY_CALC_TIME_MS> timer; |
|
453 |
|
454 int64_t pageId = aArguments->AsInt64(0); |
|
455 int32_t typed = numEntries > 1 ? aArguments->AsInt32(1) : 0; |
|
456 int32_t fullVisitCount = numEntries > 2 ? aArguments->AsInt32(2) : 0; |
|
457 int64_t bookmarkId = numEntries > 3 ? aArguments->AsInt64(3) : 0; |
|
458 int32_t visitCount = 0; |
|
459 int32_t hidden = 0; |
|
460 int32_t isQuery = 0; |
|
461 float pointsForSampledVisits = 0.0; |
|
462 |
|
463 // This is a const version of the history object for thread-safety. |
|
464 const nsNavHistory* history = nsNavHistory::GetConstHistoryService(); |
|
465 NS_ENSURE_STATE(history); |
|
466 nsRefPtr<Database> DB = Database::GetDatabase(); |
|
467 NS_ENSURE_STATE(DB); |
|
468 |
|
469 if (pageId > 0) { |
|
470 // The page is already in the database, and we can fetch current |
|
471 // params from the database. |
|
472 nsRefPtr<mozIStorageStatement> getPageInfo = DB->GetStatement( |
|
473 "SELECT typed, hidden, visit_count, " |
|
474 "(SELECT count(*) FROM moz_historyvisits WHERE place_id = :page_id), " |
|
475 "EXISTS (SELECT 1 FROM moz_bookmarks WHERE fk = :page_id), " |
|
476 "(url > 'place:' AND url < 'place;') " |
|
477 "FROM moz_places " |
|
478 "WHERE id = :page_id " |
|
479 ); |
|
480 NS_ENSURE_STATE(getPageInfo); |
|
481 mozStorageStatementScoper infoScoper(getPageInfo); |
|
482 |
|
483 rv = getPageInfo->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId); |
|
484 NS_ENSURE_SUCCESS(rv, rv); |
|
485 |
|
486 bool hasResult; |
|
487 rv = getPageInfo->ExecuteStep(&hasResult); |
|
488 NS_ENSURE_SUCCESS(rv, rv); |
|
489 NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED); |
|
490 rv = getPageInfo->GetInt32(0, &typed); |
|
491 NS_ENSURE_SUCCESS(rv, rv); |
|
492 rv = getPageInfo->GetInt32(1, &hidden); |
|
493 NS_ENSURE_SUCCESS(rv, rv); |
|
494 rv = getPageInfo->GetInt32(2, &visitCount); |
|
495 NS_ENSURE_SUCCESS(rv, rv); |
|
496 rv = getPageInfo->GetInt32(3, &fullVisitCount); |
|
497 NS_ENSURE_SUCCESS(rv, rv); |
|
498 rv = getPageInfo->GetInt64(4, &bookmarkId); |
|
499 NS_ENSURE_SUCCESS(rv, rv); |
|
500 rv = getPageInfo->GetInt32(5, &isQuery); |
|
501 NS_ENSURE_SUCCESS(rv, rv); |
|
502 |
|
503 // NOTE: This is not limited to visits with "visit_type NOT IN (0,4,7,8)" |
|
504 // because otherwise it would not return any visit for those transitions |
|
505 // causing an incorrect frecency, see CalculateFrecencyInternal(). |
|
506 // In case of a temporary or permanent redirect, calculate the frecency |
|
507 // as if the original page was visited. |
|
508 // Get a sample of the last visits to the page, to calculate its weight. |
|
509 nsCOMPtr<mozIStorageStatement> getVisits = DB->GetStatement( |
|
510 NS_LITERAL_CSTRING( |
|
511 "/* do not warn (bug 659740 - SQLite may ignore index if few visits exist) */" |
|
512 "SELECT " |
|
513 "ROUND((strftime('%s','now','localtime','utc') - v.visit_date/1000000)/86400), " |
|
514 "IFNULL(r.visit_type, v.visit_type), " |
|
515 "v.visit_date " |
|
516 "FROM moz_historyvisits v " |
|
517 "LEFT JOIN moz_historyvisits r ON r.id = v.from_visit AND v.visit_type BETWEEN " |
|
518 ) + nsPrintfCString("%d AND %d ", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, |
|
519 nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) + |
|
520 NS_LITERAL_CSTRING( |
|
521 "WHERE v.place_id = :page_id " |
|
522 "ORDER BY v.visit_date DESC " |
|
523 ) |
|
524 ); |
|
525 NS_ENSURE_STATE(getVisits); |
|
526 mozStorageStatementScoper visitsScoper(getVisits); |
|
527 |
|
528 rv = getVisits->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId); |
|
529 NS_ENSURE_SUCCESS(rv, rv); |
|
530 |
|
531 // Fetch only a limited number of recent visits. |
|
532 int32_t numSampledVisits = 0; |
|
533 for (int32_t maxVisits = history->GetNumVisitsForFrecency(); |
|
534 numSampledVisits < maxVisits && |
|
535 NS_SUCCEEDED(getVisits->ExecuteStep(&hasResult)) && hasResult; |
|
536 numSampledVisits++) { |
|
537 int32_t visitType; |
|
538 rv = getVisits->GetInt32(1, &visitType); |
|
539 NS_ENSURE_SUCCESS(rv, rv); |
|
540 int32_t bonus = history->GetFrecencyTransitionBonus(visitType, true); |
|
541 |
|
542 // Always add the bookmark visit bonus. |
|
543 if (bookmarkId) { |
|
544 bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, true); |
|
545 } |
|
546 |
|
547 // If bonus was zero, we can skip the work to determine the weight. |
|
548 if (bonus) { |
|
549 int32_t ageInDays = getVisits->AsInt32(0); |
|
550 int32_t weight = history->GetFrecencyAgedWeight(ageInDays); |
|
551 pointsForSampledVisits += (float)(weight * (bonus / 100.0)); |
|
552 } |
|
553 } |
|
554 |
|
555 // If we found some visits for this page, use the calculated weight. |
|
556 if (numSampledVisits) { |
|
557 // fix for bug #412219 |
|
558 if (!pointsForSampledVisits) { |
|
559 // For URIs with zero points in the sampled recent visits |
|
560 // but "browsing" type visits outside the sampling range, set |
|
561 // frecency to -visit_count, so they're still shown in autocomplete. |
|
562 NS_ADDREF(*_result = new IntegerVariant(-visitCount)); |
|
563 } |
|
564 else { |
|
565 // Estimate frecency using the last few visits. |
|
566 // Use ceilf() so that we don't round down to 0, which |
|
567 // would cause us to completely ignore the place during autocomplete. |
|
568 NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(fullVisitCount * ceilf(pointsForSampledVisits) / numSampledVisits))); |
|
569 } |
|
570 |
|
571 return NS_OK; |
|
572 } |
|
573 } |
|
574 |
|
575 // This page is unknown or has no visits. It could have just been added, so |
|
576 // use passed in or default values. |
|
577 |
|
578 // The code below works well for guessing the frecency on import, and we'll |
|
579 // correct later once we have visits. |
|
580 // TODO: What if we don't have visits and we never visit? We could end up |
|
581 // with a really high value that keeps coming up in ac results? Should we |
|
582 // only do this on import? Have to figure it out. |
|
583 int32_t bonus = 0; |
|
584 |
|
585 // Make it so something bookmarked and typed will have a higher frecency |
|
586 // than something just typed or just bookmarked. |
|
587 if (bookmarkId && !isQuery) { |
|
588 bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, false);; |
|
589 // For unvisited bookmarks, produce a non-zero frecency, so that they show |
|
590 // up in URL bar autocomplete. |
|
591 fullVisitCount = 1; |
|
592 } |
|
593 |
|
594 if (typed) { |
|
595 bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_TYPED, false); |
|
596 } |
|
597 |
|
598 // Assume "now" as our ageInDays, so use the first bucket. |
|
599 pointsForSampledVisits = history->GetFrecencyBucketWeight(1) * (bonus / (float)100.0); |
|
600 |
|
601 // use ceilf() so that we don't round down to 0, which |
|
602 // would cause us to completely ignore the place during autocomplete |
|
603 NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(fullVisitCount * ceilf(pointsForSampledVisits)))); |
|
604 |
|
605 return NS_OK; |
|
606 } |
|
607 |
|
608 //////////////////////////////////////////////////////////////////////////////// |
|
609 //// GUID Creation Function |
|
610 |
|
611 ////////////////////////////////////////////////////////////////////////////// |
|
612 //// GenerateGUIDFunction |
|
613 |
|
614 /* static */ |
|
615 nsresult |
|
616 GenerateGUIDFunction::create(mozIStorageConnection *aDBConn) |
|
617 { |
|
618 nsRefPtr<GenerateGUIDFunction> function = new GenerateGUIDFunction(); |
|
619 nsresult rv = aDBConn->CreateFunction( |
|
620 NS_LITERAL_CSTRING("generate_guid"), 0, function |
|
621 ); |
|
622 NS_ENSURE_SUCCESS(rv, rv); |
|
623 |
|
624 return NS_OK; |
|
625 } |
|
626 |
|
627 NS_IMPL_ISUPPORTS( |
|
628 GenerateGUIDFunction, |
|
629 mozIStorageFunction |
|
630 ) |
|
631 |
|
632 ////////////////////////////////////////////////////////////////////////////// |
|
633 //// mozIStorageFunction |
|
634 |
|
635 NS_IMETHODIMP |
|
636 GenerateGUIDFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
|
637 nsIVariant **_result) |
|
638 { |
|
639 nsAutoCString guid; |
|
640 nsresult rv = GenerateGUID(guid); |
|
641 NS_ENSURE_SUCCESS(rv, rv); |
|
642 |
|
643 NS_ADDREF(*_result = new UTF8TextVariant(guid)); |
|
644 return NS_OK; |
|
645 } |
|
646 |
|
647 //////////////////////////////////////////////////////////////////////////////// |
|
648 //// Get Unreversed Host Function |
|
649 |
|
650 ////////////////////////////////////////////////////////////////////////////// |
|
651 //// GetUnreversedHostFunction |
|
652 |
|
653 /* static */ |
|
654 nsresult |
|
655 GetUnreversedHostFunction::create(mozIStorageConnection *aDBConn) |
|
656 { |
|
657 nsRefPtr<GetUnreversedHostFunction> function = new GetUnreversedHostFunction(); |
|
658 nsresult rv = aDBConn->CreateFunction( |
|
659 NS_LITERAL_CSTRING("get_unreversed_host"), 1, function |
|
660 ); |
|
661 NS_ENSURE_SUCCESS(rv, rv); |
|
662 |
|
663 return NS_OK; |
|
664 } |
|
665 |
|
666 NS_IMPL_ISUPPORTS( |
|
667 GetUnreversedHostFunction, |
|
668 mozIStorageFunction |
|
669 ) |
|
670 |
|
671 ////////////////////////////////////////////////////////////////////////////// |
|
672 //// mozIStorageFunction |
|
673 |
|
674 NS_IMETHODIMP |
|
675 GetUnreversedHostFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
|
676 nsIVariant **_result) |
|
677 { |
|
678 // Must have non-null function arguments. |
|
679 MOZ_ASSERT(aArguments); |
|
680 |
|
681 nsAutoString src; |
|
682 aArguments->GetString(0, src); |
|
683 |
|
684 nsCOMPtr<nsIWritableVariant> result = |
|
685 do_CreateInstance("@mozilla.org/variant;1"); |
|
686 NS_ENSURE_STATE(result); |
|
687 |
|
688 if (src.Length()>1) { |
|
689 src.Truncate(src.Length() - 1); |
|
690 nsAutoString dest; |
|
691 ReverseString(src, dest); |
|
692 result->SetAsAString(dest); |
|
693 } |
|
694 else { |
|
695 result->SetAsAString(EmptyString()); |
|
696 } |
|
697 NS_ADDREF(*_result = result); |
|
698 return NS_OK; |
|
699 } |
|
700 |
|
701 //////////////////////////////////////////////////////////////////////////////// |
|
702 //// Fixup URL Function |
|
703 |
|
704 ////////////////////////////////////////////////////////////////////////////// |
|
705 //// FixupURLFunction |
|
706 |
|
707 /* static */ |
|
708 nsresult |
|
709 FixupURLFunction::create(mozIStorageConnection *aDBConn) |
|
710 { |
|
711 nsRefPtr<FixupURLFunction> function = new FixupURLFunction(); |
|
712 nsresult rv = aDBConn->CreateFunction( |
|
713 NS_LITERAL_CSTRING("fixup_url"), 1, function |
|
714 ); |
|
715 NS_ENSURE_SUCCESS(rv, rv); |
|
716 |
|
717 return NS_OK; |
|
718 } |
|
719 |
|
720 NS_IMPL_ISUPPORTS( |
|
721 FixupURLFunction, |
|
722 mozIStorageFunction |
|
723 ) |
|
724 |
|
725 ////////////////////////////////////////////////////////////////////////////// |
|
726 //// mozIStorageFunction |
|
727 |
|
728 NS_IMETHODIMP |
|
729 FixupURLFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
|
730 nsIVariant **_result) |
|
731 { |
|
732 // Must have non-null function arguments. |
|
733 MOZ_ASSERT(aArguments); |
|
734 |
|
735 nsAutoString src; |
|
736 aArguments->GetString(0, src); |
|
737 |
|
738 nsCOMPtr<nsIWritableVariant> result = |
|
739 do_CreateInstance("@mozilla.org/variant;1"); |
|
740 NS_ENSURE_STATE(result); |
|
741 |
|
742 // Remove common URL hostname prefixes |
|
743 if (StringBeginsWith(src, NS_LITERAL_STRING("www."))) { |
|
744 src.Cut(0, 4); |
|
745 } |
|
746 |
|
747 result->SetAsAString(src); |
|
748 NS_ADDREF(*_result = result); |
|
749 return NS_OK; |
|
750 } |
|
751 |
|
752 //////////////////////////////////////////////////////////////////////////////// |
|
753 //// Frecency Changed Notification Function |
|
754 |
|
755 /* static */ |
|
756 nsresult |
|
757 FrecencyNotificationFunction::create(mozIStorageConnection *aDBConn) |
|
758 { |
|
759 nsRefPtr<FrecencyNotificationFunction> function = |
|
760 new FrecencyNotificationFunction(); |
|
761 nsresult rv = aDBConn->CreateFunction( |
|
762 NS_LITERAL_CSTRING("notify_frecency"), 5, function |
|
763 ); |
|
764 NS_ENSURE_SUCCESS(rv, rv); |
|
765 |
|
766 return NS_OK; |
|
767 } |
|
768 |
|
769 NS_IMPL_ISUPPORTS( |
|
770 FrecencyNotificationFunction, |
|
771 mozIStorageFunction |
|
772 ) |
|
773 |
|
774 NS_IMETHODIMP |
|
775 FrecencyNotificationFunction::OnFunctionCall(mozIStorageValueArray *aArgs, |
|
776 nsIVariant **_result) |
|
777 { |
|
778 uint32_t numArgs; |
|
779 nsresult rv = aArgs->GetNumEntries(&numArgs); |
|
780 NS_ENSURE_SUCCESS(rv, rv); |
|
781 MOZ_ASSERT(numArgs == 5); |
|
782 |
|
783 int32_t newFrecency = aArgs->AsInt32(0); |
|
784 |
|
785 nsAutoCString spec; |
|
786 rv = aArgs->GetUTF8String(1, spec); |
|
787 NS_ENSURE_SUCCESS(rv, rv); |
|
788 |
|
789 nsAutoCString guid; |
|
790 rv = aArgs->GetUTF8String(2, guid); |
|
791 NS_ENSURE_SUCCESS(rv, rv); |
|
792 |
|
793 bool hidden = static_cast<bool>(aArgs->AsInt32(3)); |
|
794 PRTime lastVisitDate = static_cast<PRTime>(aArgs->AsInt64(4)); |
|
795 |
|
796 const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService(); |
|
797 NS_ENSURE_STATE(navHistory); |
|
798 navHistory->DispatchFrecencyChangedNotification(spec, newFrecency, guid, |
|
799 hidden, lastVisitDate); |
|
800 |
|
801 nsCOMPtr<nsIWritableVariant> result = |
|
802 do_CreateInstance("@mozilla.org/variant;1"); |
|
803 NS_ENSURE_STATE(result); |
|
804 rv = result->SetAsInt32(newFrecency); |
|
805 NS_ENSURE_SUCCESS(rv, rv); |
|
806 NS_ADDREF(*_result = result); |
|
807 return NS_OK; |
|
808 } |
|
809 |
|
810 } // namespace places |
|
811 } // namespace mozilla |