toolkit/components/places/SQLFunctions.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial