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