Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/toolkit/components/places/nsNavHistory.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include <stdio.h>
8
9
#include "mozilla/DebugOnly.h"
10
#include "mozilla/IntegerPrintfMacros.h"
11
12
#include "nsNavHistory.h"
13
14
#include "mozIPlacesAutoComplete.h"
15
#include "nsNavBookmarks.h"
16
#include "nsAnnotationService.h"
17
#include "nsFaviconService.h"
18
#include "nsPlacesMacros.h"
19
#include "nsPlacesTriggers.h"
20
#include "DateTimeFormat.h"
21
#include "History.h"
22
#include "Helpers.h"
23
24
#include "nsTArray.h"
25
#include "nsCollationCID.h"
26
#include "nsNetUtil.h"
27
#include "nsPrintfCString.h"
28
#include "nsPromiseFlatString.h"
29
#include "nsString.h"
30
#include "nsUnicharUtils.h"
31
#include "prsystem.h"
32
#include "prtime.h"
33
#include "nsEscape.h"
34
#include "nsIEffectiveTLDService.h"
35
#include "nsIClassInfoImpl.h"
36
#include "nsIIDNService.h"
37
#include "nsQueryObject.h"
38
#include "nsThreadUtils.h"
39
#include "nsAppDirectoryServiceDefs.h"
40
#include "nsMathUtils.h"
41
#include "mozilla/storage.h"
42
#include "mozilla/Preferences.h"
43
#include <algorithm>
44
45
#ifdef MOZ_XUL
46
#include "nsIAutoCompleteInput.h"
47
#include "nsIAutoCompletePopup.h"
48
#endif
49
50
using namespace mozilla;
51
using namespace mozilla::places;
52
53
// The maximum number of things that we will store in the recent events list
54
// before calling ExpireNonrecentEvents. This number should be big enough so it
55
// is very difficult to get that many unconsumed events (for example, typed but
56
// never visited) in the RECENT_EVENT_THRESHOLD. Otherwise, we'll start
57
// checking each one for every page visit, which will be somewhat slower.
58
0
#define RECENT_EVENT_QUEUE_MAX_LENGTH 128
59
60
// preference ID strings
61
0
#define PREF_HISTORY_ENABLED                    "places.history.enabled"
62
63
#define PREF_FREC_NUM_VISITS                    "places.frecency.numVisits"
64
0
#define PREF_FREC_NUM_VISITS_DEF                10
65
#define PREF_FREC_FIRST_BUCKET_CUTOFF           "places.frecency.firstBucketCutoff"
66
0
#define PREF_FREC_FIRST_BUCKET_CUTOFF_DEF       4
67
#define PREF_FREC_SECOND_BUCKET_CUTOFF          "places.frecency.secondBucketCutoff"
68
0
#define PREF_FREC_SECOND_BUCKET_CUTOFF_DEF      14
69
#define PREF_FREC_THIRD_BUCKET_CUTOFF           "places.frecency.thirdBucketCutoff"
70
0
#define PREF_FREC_THIRD_BUCKET_CUTOFF_DEF       31
71
#define PREF_FREC_FOURTH_BUCKET_CUTOFF          "places.frecency.fourthBucketCutoff"
72
0
#define PREF_FREC_FOURTH_BUCKET_CUTOFF_DEF      90
73
#define PREF_FREC_FIRST_BUCKET_WEIGHT           "places.frecency.firstBucketWeight"
74
0
#define PREF_FREC_FIRST_BUCKET_WEIGHT_DEF       100
75
#define PREF_FREC_SECOND_BUCKET_WEIGHT          "places.frecency.secondBucketWeight"
76
0
#define PREF_FREC_SECOND_BUCKET_WEIGHT_DEF      70
77
#define PREF_FREC_THIRD_BUCKET_WEIGHT           "places.frecency.thirdBucketWeight"
78
0
#define PREF_FREC_THIRD_BUCKET_WEIGHT_DEF       50
79
#define PREF_FREC_FOURTH_BUCKET_WEIGHT          "places.frecency.fourthBucketWeight"
80
0
#define PREF_FREC_FOURTH_BUCKET_WEIGHT_DEF      30
81
#define PREF_FREC_DEFAULT_BUCKET_WEIGHT         "places.frecency.defaultBucketWeight"
82
0
#define PREF_FREC_DEFAULT_BUCKET_WEIGHT_DEF     10
83
#define PREF_FREC_EMBED_VISIT_BONUS             "places.frecency.embedVisitBonus"
84
0
#define PREF_FREC_EMBED_VISIT_BONUS_DEF         0
85
#define PREF_FREC_FRAMED_LINK_VISIT_BONUS       "places.frecency.framedLinkVisitBonus"
86
0
#define PREF_FREC_FRAMED_LINK_VISIT_BONUS_DEF   0
87
#define PREF_FREC_LINK_VISIT_BONUS              "places.frecency.linkVisitBonus"
88
0
#define PREF_FREC_LINK_VISIT_BONUS_DEF          100
89
#define PREF_FREC_TYPED_VISIT_BONUS             "places.frecency.typedVisitBonus"
90
0
#define PREF_FREC_TYPED_VISIT_BONUS_DEF         2000
91
#define PREF_FREC_BOOKMARK_VISIT_BONUS          "places.frecency.bookmarkVisitBonus"
92
0
#define PREF_FREC_BOOKMARK_VISIT_BONUS_DEF      75
93
#define PREF_FREC_DOWNLOAD_VISIT_BONUS          "places.frecency.downloadVisitBonus"
94
0
#define PREF_FREC_DOWNLOAD_VISIT_BONUS_DEF      0
95
#define PREF_FREC_PERM_REDIRECT_VISIT_BONUS     "places.frecency.permRedirectVisitBonus"
96
0
#define PREF_FREC_PERM_REDIRECT_VISIT_BONUS_DEF 0
97
#define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS     "places.frecency.tempRedirectVisitBonus"
98
0
#define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS_DEF 0
99
#define PREF_FREC_REDIR_SOURCE_VISIT_BONUS      "places.frecency.redirectSourceVisitBonus"
100
0
#define PREF_FREC_REDIR_SOURCE_VISIT_BONUS_DEF  25
101
#define PREF_FREC_DEFAULT_VISIT_BONUS           "places.frecency.defaultVisitBonus"
102
0
#define PREF_FREC_DEFAULT_VISIT_BONUS_DEF       0
103
#define PREF_FREC_UNVISITED_BOOKMARK_BONUS      "places.frecency.unvisitedBookmarkBonus"
104
0
#define PREF_FREC_UNVISITED_BOOKMARK_BONUS_DEF  140
105
#define PREF_FREC_UNVISITED_TYPED_BONUS         "places.frecency.unvisitedTypedBonus"
106
0
#define PREF_FREC_UNVISITED_TYPED_BONUS_DEF     200
107
#define PREF_FREC_RELOAD_VISIT_BONUS            "places.frecency.reloadVisitBonus"
108
0
#define PREF_FREC_RELOAD_VISIT_BONUS_DEF        0
109
110
// This is a 'hidden' pref for the purposes of unit tests.
111
0
#define PREF_FREC_DECAY_RATE     "places.frecency.decayRate"
112
0
#define PREF_FREC_DECAY_RATE_DEF 0.975f
113
// An adaptive history entry is removed if unused for these many days.
114
0
#define ADAPTIVE_HISTORY_EXPIRE_DAYS 90
115
116
// In order to avoid calling PR_now() too often we use a cached "now" value
117
// for repeating stuff.  These are milliseconds between "now" cache refreshes.
118
0
#define RENEW_CACHED_NOW_TIMEOUT ((int32_t)3 * PR_MSEC_PER_SEC)
119
120
// character-set annotation
121
#define CHARSET_ANNO NS_LITERAL_CSTRING("URIProperties/characterSet")
122
123
// These macros are used when splitting history by date.
124
// These are the day containers and catch-all final container.
125
0
#define HISTORY_ADDITIONAL_DATE_CONT_NUM 3
126
// We use a guess of the number of months considering all of them 30 days
127
// long, but we split only the last 6 months.
128
#define HISTORY_DATE_CONT_NUM(_daysFromOldestVisit) \
129
0
  (HISTORY_ADDITIONAL_DATE_CONT_NUM + \
130
0
   std::min(6, (int32_t)ceilf((float)_daysFromOldestVisit/30)))
131
// Max number of containers, used to initialize the params hash.
132
0
#define HISTORY_DATE_CONT_LENGTH 8
133
134
// Initial length of the embed visits cache.
135
#define EMBED_VISITS_INITIAL_CACHE_LENGTH 64
136
137
// Initial length of the recent events cache.
138
#define RECENT_EVENTS_INITIAL_CACHE_LENGTH 64
139
140
// Observed topics.
141
#ifdef MOZ_XUL
142
0
#define TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING "autocomplete-will-enter-text"
143
#endif
144
0
#define TOPIC_IDLE_DAILY "idle-daily"
145
0
#define TOPIC_PREF_CHANGED "nsPref:changed"
146
0
#define TOPIC_PROFILE_TEARDOWN "profile-change-teardown"
147
0
#define TOPIC_PROFILE_CHANGE "profile-before-change"
148
149
static const char* kObservedPrefs[] = {
150
  PREF_HISTORY_ENABLED
151
, PREF_FREC_NUM_VISITS
152
, PREF_FREC_FIRST_BUCKET_CUTOFF
153
, PREF_FREC_SECOND_BUCKET_CUTOFF
154
, PREF_FREC_THIRD_BUCKET_CUTOFF
155
, PREF_FREC_FOURTH_BUCKET_CUTOFF
156
, PREF_FREC_FIRST_BUCKET_WEIGHT
157
, PREF_FREC_SECOND_BUCKET_WEIGHT
158
, PREF_FREC_THIRD_BUCKET_WEIGHT
159
, PREF_FREC_FOURTH_BUCKET_WEIGHT
160
, PREF_FREC_DEFAULT_BUCKET_WEIGHT
161
, PREF_FREC_EMBED_VISIT_BONUS
162
, PREF_FREC_FRAMED_LINK_VISIT_BONUS
163
, PREF_FREC_LINK_VISIT_BONUS
164
, PREF_FREC_TYPED_VISIT_BONUS
165
, PREF_FREC_BOOKMARK_VISIT_BONUS
166
, PREF_FREC_DOWNLOAD_VISIT_BONUS
167
, PREF_FREC_PERM_REDIRECT_VISIT_BONUS
168
, PREF_FREC_TEMP_REDIRECT_VISIT_BONUS
169
, PREF_FREC_REDIR_SOURCE_VISIT_BONUS
170
, PREF_FREC_DEFAULT_VISIT_BONUS
171
, PREF_FREC_UNVISITED_BOOKMARK_BONUS
172
, PREF_FREC_UNVISITED_TYPED_BONUS
173
, nullptr
174
};
175
176
NS_IMPL_ADDREF(nsNavHistory)
177
NS_IMPL_RELEASE(nsNavHistory)
178
179
NS_IMPL_CLASSINFO(nsNavHistory, nullptr, nsIClassInfo::SINGLETON,
180
                  NS_NAVHISTORYSERVICE_CID)
181
0
NS_INTERFACE_MAP_BEGIN(nsNavHistory)
182
0
  NS_INTERFACE_MAP_ENTRY(nsINavHistoryService)
183
0
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
184
0
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
185
0
  NS_INTERFACE_MAP_ENTRY(mozIStorageVacuumParticipant)
186
0
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryService)
187
0
  NS_IMPL_QUERY_CLASSINFO(nsNavHistory)
188
0
NS_INTERFACE_MAP_END
189
190
// We don't care about flattening everything
191
NS_IMPL_CI_INTERFACE_GETTER(nsNavHistory,
192
                            nsINavHistoryService)
193
194
namespace {
195
196
static nsCString GetSimpleBookmarksQueryParent(const RefPtr<nsNavHistoryQuery>& aQuery,
197
                                               const RefPtr<nsNavHistoryQueryOptions>& aOptions);
198
static void ParseSearchTermsFromQuery(const RefPtr<nsNavHistoryQuery>& aQuery,
199
                                      nsTArray<nsString>* aTerms);
200
201
void GetTagsSqlFragment(int64_t aTagsFolder,
202
                        const nsACString& aRelation,
203
                        bool aHasSearchTerms,
204
0
                        nsACString& _sqlFragment) {
205
0
  if (!aHasSearchTerms)
206
0
    _sqlFragment.AssignLiteral("null");
207
0
  else {
208
0
    // This subquery DOES NOT order tags for performance reasons.
209
0
    _sqlFragment.Assign(NS_LITERAL_CSTRING(
210
0
         "(SELECT GROUP_CONCAT(t_t.title, ',') "
211
0
           "FROM moz_bookmarks b_t "
212
0
           "JOIN moz_bookmarks t_t ON t_t.id = +b_t.parent  "
213
0
           "WHERE b_t.fk = ") + aRelation + NS_LITERAL_CSTRING(" "
214
0
           "AND t_t.parent = ") +
215
0
           nsPrintfCString("%" PRId64, aTagsFolder) + NS_LITERAL_CSTRING(" "
216
0
         ")"));
217
0
  }
218
0
219
0
  _sqlFragment.AppendLiteral(" AS tags ");
220
0
}
221
222
/**
223
 * Recalculates invalid frecencies in chunks on the storage thread, optionally
224
 * decays frecencies, and notifies history observers on the main thread.
225
 */
226
class FixAndDecayFrecencyRunnable final : public Runnable
227
{
228
public:
229
  explicit FixAndDecayFrecencyRunnable(Database* aDB, float aDecayRate)
230
    : Runnable("places::FixAndDecayFrecencyRunnable")
231
    , mDB(aDB)
232
    , mDecayRate(aDecayRate)
233
    , mDecayReason(mozIStorageStatementCallback::REASON_FINISHED)
234
0
  {}
235
236
  NS_IMETHOD
237
0
  Run() override {
238
0
    if (NS_IsMainThread()) {
239
0
      nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
240
0
      NS_ENSURE_STATE(navHistory);
241
0
242
0
      navHistory->DecayFrecencyCompleted(mDecayReason);
243
0
      return NS_OK;
244
0
    }
245
0
246
0
    MOZ_ASSERT(!NS_IsMainThread(),
247
0
               "Frecencies should be recalculated on async thread");
248
0
249
0
    nsCOMPtr<mozIStorageStatement> updateStmt = mDB->GetStatement(
250
0
      "UPDATE moz_places "
251
0
      "SET frecency = CALCULATE_FRECENCY(id) "
252
0
      "WHERE id IN ("
253
0
        "SELECT id FROM moz_places "
254
0
        "WHERE frecency < 0 "
255
0
        "ORDER BY frecency ASC "
256
0
        "LIMIT 400"
257
0
      ")"
258
0
    );
259
0
    NS_ENSURE_STATE(updateStmt);
260
0
    nsresult rv = updateStmt->Execute();
261
0
    NS_ENSURE_SUCCESS(rv, rv);
262
0
263
0
    nsCOMPtr<mozIStorageStatement> selectStmt = mDB->GetStatement(
264
0
      "SELECT id FROM moz_places WHERE frecency < 0 "
265
0
      "LIMIT 1"
266
0
    );
267
0
    NS_ENSURE_STATE(selectStmt);
268
0
    bool hasResult = false;
269
0
    rv = selectStmt->ExecuteStep(&hasResult);
270
0
    NS_ENSURE_SUCCESS(rv, rv);
271
0
    if (hasResult) {
272
0
      // There are more invalid frecencies to fix. Re-dispatch to the async
273
0
      // storage thread for the next chunk.
274
0
      return NS_DispatchToCurrentThread(this);
275
0
    }
276
0
277
0
    mozStorageTransaction transaction(mDB->MainConn(), false,
278
0
                                      mozIStorageConnection::TRANSACTION_IMMEDIATE);
279
0
280
0
    if (NS_WARN_IF(NS_FAILED(DecayFrecencies()))) {
281
0
      mDecayReason = mozIStorageStatementCallback::REASON_ERROR;
282
0
    }
283
0
284
0
    // We've finished fixing and decaying frecencies. Trigger frecency updates
285
0
    // for all affected origins.
286
0
    nsCOMPtr<mozIStorageStatement> updateOriginFrecenciesStmt =
287
0
      mDB->GetStatement("DELETE FROM moz_updateoriginsupdate_temp");
288
0
    NS_ENSURE_STATE(updateOriginFrecenciesStmt);
289
0
    rv = updateOriginFrecenciesStmt->Execute();
290
0
    NS_ENSURE_SUCCESS(rv, rv);
291
0
292
0
    rv = transaction.Commit();
293
0
    NS_ENSURE_SUCCESS(rv, rv);
294
0
295
0
    // Re-dispatch to the main thread to notify observers.
296
0
    return NS_DispatchToMainThread(this);
297
0
  }
298
299
private:
300
  nsresult
301
  DecayFrecencies()
302
0
  {
303
0
    TimeStamp start = TimeStamp::Now();
304
0
305
0
    // Globally decay places frecency rankings to estimate reduced frecency
306
0
    // values of pages that haven't been visited for a while, i.e., they do
307
0
    // not get an updated frecency.  A scaling factor of .975 results in .5 the
308
0
    // original value after 28 days.
309
0
    // When changing the scaling factor, ensure that the barrier in
310
0
    // moz_places_afterupdate_frecency_trigger still ignores these changes.
311
0
    nsCOMPtr<mozIStorageStatement> decayFrecency = mDB->GetStatement(
312
0
      "UPDATE moz_places SET frecency = ROUND(frecency * :decay_rate) "
313
0
      "WHERE frecency > 0"
314
0
    );
315
0
    NS_ENSURE_STATE(decayFrecency);
316
0
    nsresult rv = decayFrecency->BindDoubleByName(NS_LITERAL_CSTRING("decay_rate"),
317
0
                                                  static_cast<double>(mDecayRate));
318
0
    NS_ENSURE_SUCCESS(rv, rv);
319
0
    rv = decayFrecency->Execute();
320
0
    NS_ENSURE_SUCCESS(rv, rv);
321
0
322
0
    // Decay potentially unused adaptive entries (e.g. those that are at 1)
323
0
    // to allow better chances for new entries that will start at 1.
324
0
    nsCOMPtr<mozIStorageStatement> decayAdaptive = mDB->GetStatement(
325
0
      "UPDATE moz_inputhistory SET use_count = use_count * :decay_rate"
326
0
    );
327
0
    NS_ENSURE_STATE(decayAdaptive);
328
0
    rv = decayAdaptive->BindDoubleByName(NS_LITERAL_CSTRING("decay_rate"),
329
0
                                         static_cast<double>(mDecayRate));
330
0
    NS_ENSURE_SUCCESS(rv, rv);
331
0
    rv = decayAdaptive->Execute();
332
0
    NS_ENSURE_SUCCESS(rv, rv);
333
0
334
0
    // Delete any adaptive entries that won't help in ordering anymore.
335
0
    nsCOMPtr<mozIStorageStatement> deleteAdaptive = mDB->GetStatement(
336
0
      "DELETE FROM moz_inputhistory WHERE use_count < :use_count"
337
0
    );
338
0
    NS_ENSURE_STATE(deleteAdaptive);
339
0
    rv = deleteAdaptive->BindDoubleByName(NS_LITERAL_CSTRING("use_count"),
340
0
                                          std::pow(static_cast<double>(mDecayRate),
341
0
                                                   ADAPTIVE_HISTORY_EXPIRE_DAYS));
342
0
    NS_ENSURE_SUCCESS(rv, rv);
343
0
    rv = deleteAdaptive->Execute();
344
0
    NS_ENSURE_SUCCESS(rv, rv);
345
0
346
0
    Telemetry::AccumulateTimeDelta(Telemetry::PLACES_IDLE_FRECENCY_DECAY_TIME_MS, start);
347
0
348
0
    return NS_OK;
349
0
  }
350
351
  RefPtr<Database> mDB;
352
  float mDecayRate;
353
  uint16_t mDecayReason;
354
};
355
356
} // namespace
357
358
359
// Queries rows indexes to bind or get values, if adding a new one, be sure to
360
// update nsNavBookmarks statements and its kGetChildrenIndex_* constants
361
const int32_t nsNavHistory::kGetInfoIndex_PageID = 0;
362
const int32_t nsNavHistory::kGetInfoIndex_URL = 1;
363
const int32_t nsNavHistory::kGetInfoIndex_Title = 2;
364
const int32_t nsNavHistory::kGetInfoIndex_RevHost = 3;
365
const int32_t nsNavHistory::kGetInfoIndex_VisitCount = 4;
366
const int32_t nsNavHistory::kGetInfoIndex_VisitDate = 5;
367
const int32_t nsNavHistory::kGetInfoIndex_FaviconURL = 6;
368
const int32_t nsNavHistory::kGetInfoIndex_ItemId = 7;
369
const int32_t nsNavHistory::kGetInfoIndex_ItemDateAdded = 8;
370
const int32_t nsNavHistory::kGetInfoIndex_ItemLastModified = 9;
371
const int32_t nsNavHistory::kGetInfoIndex_ItemParentId = 10;
372
const int32_t nsNavHistory::kGetInfoIndex_ItemTags = 11;
373
const int32_t nsNavHistory::kGetInfoIndex_Frecency = 12;
374
const int32_t nsNavHistory::kGetInfoIndex_Hidden = 13;
375
const int32_t nsNavHistory::kGetInfoIndex_Guid = 14;
376
const int32_t nsNavHistory::kGetInfoIndex_VisitId = 15;
377
const int32_t nsNavHistory::kGetInfoIndex_FromVisitId = 16;
378
const int32_t nsNavHistory::kGetInfoIndex_VisitType = 17;
379
// These columns are followed by corresponding constants in nsNavBookmarks.cpp,
380
// which must be kept in sync:
381
// nsNavBookmarks::kGetChildrenIndex_Guid = 18;
382
// nsNavBookmarks::kGetChildrenIndex_Position = 19;
383
// nsNavBookmarks::kGetChildrenIndex_Type = 20;
384
// nsNavBookmarks::kGetChildrenIndex_PlaceID = 21;
385
386
387
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavHistory, gHistoryService)
388
389
nsNavHistory::nsNavHistory()
390
  : mCachedNow(0)
391
  , mRecentTyped(RECENT_EVENTS_INITIAL_CACHE_LENGTH)
392
  , mRecentLink(RECENT_EVENTS_INITIAL_CACHE_LENGTH)
393
  , mRecentBookmark(RECENT_EVENTS_INITIAL_CACHE_LENGTH)
394
  , mEmbedVisits(EMBED_VISITS_INITIAL_CACHE_LENGTH)
395
  , mHistoryEnabled(true)
396
  , mNumVisitsForFrecency(10)
397
  , mDecayFrecencyPendingCount(0)
398
  , mTagsFolder(-1)
399
  , mDaysOfHistory(-1)
400
  , mLastCachedStartOfDay(INT64_MAX)
401
  , mLastCachedEndOfDay(0)
402
  , mCanNotify(true)
403
#ifdef XP_WIN
404
  , mCryptoProviderInitialized(false)
405
#endif
406
0
{
407
0
  NS_ASSERTION(!gHistoryService,
408
0
               "Attempting to create two instances of the service!");
409
#ifdef XP_WIN
410
  BOOL cryptoAcquired = CryptAcquireContext(&mCryptoProvider, 0, 0, PROV_RSA_FULL,
411
                                            CRYPT_VERIFYCONTEXT | CRYPT_SILENT);
412
  if (cryptoAcquired) {
413
    mCryptoProviderInitialized = true;
414
  }
415
#endif
416
  gHistoryService = this;
417
0
}
418
419
420
nsNavHistory::~nsNavHistory()
421
0
{
422
0
  MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
423
0
424
0
  // remove the static reference to the service. Check to make sure its us
425
0
  // in case somebody creates an extra instance of the service.
426
0
  NS_ASSERTION(gHistoryService == this,
427
0
               "Deleting a non-singleton instance of the service");
428
0
429
0
  if (gHistoryService == this)
430
0
    gHistoryService = nullptr;
431
0
432
#ifdef XP_WIN
433
  if (mCryptoProviderInitialized) {
434
    Unused << CryptReleaseContext(mCryptoProvider, 0);
435
  }
436
#endif
437
}
438
439
440
nsresult
441
nsNavHistory::Init()
442
0
{
443
0
  LoadPrefs();
444
0
445
0
  mDB = Database::GetDatabase();
446
0
  NS_ENSURE_STATE(mDB);
447
0
448
0
  /*****************************************************************************
449
0
   *** IMPORTANT NOTICE!
450
0
   ***
451
0
   *** Nothing after these add observer calls should return anything but NS_OK.
452
0
   *** If a failure code is returned, this nsNavHistory object will be held onto
453
0
   *** by the observer service and the preference service.
454
0
   ****************************************************************************/
455
0
456
0
  // Observe preferences changes.
457
0
  Preferences::AddWeakObservers(this, kObservedPrefs);
458
0
459
0
  nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
460
0
  if (obsSvc) {
461
0
    (void)obsSvc->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
462
0
    (void)obsSvc->AddObserver(this, TOPIC_IDLE_DAILY, true);
463
0
#ifdef MOZ_XUL
464
0
    (void)obsSvc->AddObserver(this, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING, true);
465
0
#endif
466
0
  }
467
0
468
0
  // Don't add code that can fail here! Do it up above, before we add our
469
0
  // observers.
470
0
471
0
  return NS_OK;
472
0
}
473
474
NS_IMETHODIMP
475
nsNavHistory::GetDatabaseStatus(uint16_t *aDatabaseStatus)
476
0
{
477
0
  NS_ENSURE_ARG_POINTER(aDatabaseStatus);
478
0
  *aDatabaseStatus = mDB->GetDatabaseStatus();
479
0
  return NS_OK;
480
0
}
481
482
uint32_t
483
nsNavHistory::GetRecentFlags(nsIURI *aURI)
484
0
{
485
0
  uint32_t result = 0;
486
0
  nsAutoCString spec;
487
0
  nsresult rv = aURI->GetSpec(spec);
488
0
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to get aURI's spec");
489
0
490
0
  if (NS_SUCCEEDED(rv)) {
491
0
    if (CheckIsRecentEvent(&mRecentTyped, spec))
492
0
      result |= RECENT_TYPED;
493
0
    if (CheckIsRecentEvent(&mRecentLink, spec))
494
0
      result |= RECENT_ACTIVATED;
495
0
    if (CheckIsRecentEvent(&mRecentBookmark, spec))
496
0
      result |= RECENT_BOOKMARKED;
497
0
  }
498
0
499
0
  return result;
500
0
}
501
502
nsresult
503
nsNavHistory::GetIdForPage(nsIURI* aURI,
504
                           int64_t* _pageId,
505
                           nsCString& _GUID)
506
0
{
507
0
  *_pageId = 0;
508
0
509
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
510
0
    "SELECT id, url, title, rev_host, visit_count, guid "
511
0
    "FROM moz_places "
512
0
    "WHERE url_hash = hash(:page_url) AND url = :page_url "
513
0
  );
514
0
  NS_ENSURE_STATE(stmt);
515
0
  mozStorageStatementScoper scoper(stmt);
516
0
517
0
  nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
518
0
  NS_ENSURE_SUCCESS(rv, rv);
519
0
520
0
  bool hasEntry = false;
521
0
  rv = stmt->ExecuteStep(&hasEntry);
522
0
  NS_ENSURE_SUCCESS(rv, rv);
523
0
524
0
  if (hasEntry) {
525
0
    rv = stmt->GetInt64(0, _pageId);
526
0
    NS_ENSURE_SUCCESS(rv, rv);
527
0
    rv = stmt->GetUTF8String(5, _GUID);
528
0
    NS_ENSURE_SUCCESS(rv, rv);
529
0
  }
530
0
531
0
  return NS_OK;
532
0
}
533
534
nsresult
535
nsNavHistory::GetOrCreateIdForPage(nsIURI* aURI,
536
                                   int64_t* _pageId,
537
                                   nsCString& _GUID)
538
0
{
539
0
  nsresult rv = GetIdForPage(aURI, _pageId, _GUID);
540
0
  NS_ENSURE_SUCCESS(rv, rv);
541
0
542
0
  if (*_pageId != 0) {
543
0
    return NS_OK;
544
0
  }
545
0
546
0
  {
547
0
    // Create a new hidden, untyped and unvisited entry.
548
0
    nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
549
0
      "INSERT INTO moz_places (url, url_hash, rev_host, hidden, frecency, guid) "
550
0
      "VALUES (:page_url, hash(:page_url), :rev_host, :hidden, :frecency, :guid) "
551
0
    );
552
0
    NS_ENSURE_STATE(stmt);
553
0
    mozStorageStatementScoper scoper(stmt);
554
0
555
0
    rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
556
0
    NS_ENSURE_SUCCESS(rv, rv);
557
0
    // host (reversed with trailing period)
558
0
    nsAutoString revHost;
559
0
    rv = GetReversedHostname(aURI, revHost);
560
0
    // Not all URI types have hostnames, so this is optional.
561
0
    if (NS_SUCCEEDED(rv)) {
562
0
      rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"), revHost);
563
0
    } else {
564
0
      rv = stmt->BindNullByName(NS_LITERAL_CSTRING("rev_host"));
565
0
    }
566
0
    NS_ENSURE_SUCCESS(rv, rv);
567
0
    rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), 1);
568
0
    NS_ENSURE_SUCCESS(rv, rv);
569
0
    nsAutoCString spec;
570
0
    rv = aURI->GetSpec(spec);
571
0
    NS_ENSURE_SUCCESS(rv, rv);
572
0
    rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"),
573
0
                               IsQueryURI(spec) ? 0 : -1);
574
0
    NS_ENSURE_SUCCESS(rv, rv);
575
0
    rv = GenerateGUID(_GUID);
576
0
    NS_ENSURE_SUCCESS(rv, rv);
577
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _GUID);
578
0
    NS_ENSURE_SUCCESS(rv, rv);
579
0
580
0
    rv = stmt->Execute();
581
0
    NS_ENSURE_SUCCESS(rv, rv);
582
0
583
0
    *_pageId = sLastInsertedPlaceId;
584
0
  }
585
0
586
0
  {
587
0
    // Trigger the updates to the moz_origins tables
588
0
    nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
589
0
      "DELETE FROM moz_updateoriginsinsert_temp"
590
0
    );
591
0
    NS_ENSURE_STATE(stmt);
592
0
    mozStorageStatementScoper scoper(stmt);
593
0
  }
594
0
595
0
  return NS_OK;
596
0
}
597
598
599
void
600
nsNavHistory::LoadPrefs()
601
0
{
602
0
  // History preferences.
603
0
  mHistoryEnabled = Preferences::GetBool(PREF_HISTORY_ENABLED, true);
604
0
605
0
  // Frecency preferences.
606
0
#define FRECENCY_PREF(_prop, _pref) \
607
0
  _prop = Preferences::GetInt(_pref, _pref##_DEF)
608
0
609
0
  FRECENCY_PREF(mNumVisitsForFrecency,     PREF_FREC_NUM_VISITS);
610
0
  FRECENCY_PREF(mFirstBucketCutoffInDays,  PREF_FREC_FIRST_BUCKET_CUTOFF);
611
0
  FRECENCY_PREF(mSecondBucketCutoffInDays, PREF_FREC_SECOND_BUCKET_CUTOFF);
612
0
  FRECENCY_PREF(mThirdBucketCutoffInDays,  PREF_FREC_THIRD_BUCKET_CUTOFF);
613
0
  FRECENCY_PREF(mFourthBucketCutoffInDays, PREF_FREC_FOURTH_BUCKET_CUTOFF);
614
0
  FRECENCY_PREF(mEmbedVisitBonus,          PREF_FREC_EMBED_VISIT_BONUS);
615
0
  FRECENCY_PREF(mFramedLinkVisitBonus,     PREF_FREC_FRAMED_LINK_VISIT_BONUS);
616
0
  FRECENCY_PREF(mLinkVisitBonus,           PREF_FREC_LINK_VISIT_BONUS);
617
0
  FRECENCY_PREF(mTypedVisitBonus,          PREF_FREC_TYPED_VISIT_BONUS);
618
0
  FRECENCY_PREF(mBookmarkVisitBonus,       PREF_FREC_BOOKMARK_VISIT_BONUS);
619
0
  FRECENCY_PREF(mDownloadVisitBonus,       PREF_FREC_DOWNLOAD_VISIT_BONUS);
620
0
  FRECENCY_PREF(mPermRedirectVisitBonus,   PREF_FREC_PERM_REDIRECT_VISIT_BONUS);
621
0
  FRECENCY_PREF(mTempRedirectVisitBonus,   PREF_FREC_TEMP_REDIRECT_VISIT_BONUS);
622
0
  FRECENCY_PREF(mRedirectSourceVisitBonus, PREF_FREC_REDIR_SOURCE_VISIT_BONUS);
623
0
  FRECENCY_PREF(mDefaultVisitBonus,        PREF_FREC_DEFAULT_VISIT_BONUS);
624
0
  FRECENCY_PREF(mUnvisitedBookmarkBonus,   PREF_FREC_UNVISITED_BOOKMARK_BONUS);
625
0
  FRECENCY_PREF(mUnvisitedTypedBonus,      PREF_FREC_UNVISITED_TYPED_BONUS);
626
0
  FRECENCY_PREF(mReloadVisitBonus,         PREF_FREC_RELOAD_VISIT_BONUS);
627
0
  FRECENCY_PREF(mFirstBucketWeight,        PREF_FREC_FIRST_BUCKET_WEIGHT);
628
0
  FRECENCY_PREF(mSecondBucketWeight,       PREF_FREC_SECOND_BUCKET_WEIGHT);
629
0
  FRECENCY_PREF(mThirdBucketWeight,        PREF_FREC_THIRD_BUCKET_WEIGHT);
630
0
  FRECENCY_PREF(mFourthBucketWeight,       PREF_FREC_FOURTH_BUCKET_WEIGHT);
631
0
  FRECENCY_PREF(mDefaultWeight,            PREF_FREC_DEFAULT_BUCKET_WEIGHT);
632
0
633
0
#undef FRECENCY_PREF
634
0
}
635
636
void
637
nsNavHistory::UpdateDaysOfHistory(PRTime visitTime)
638
0
{
639
0
  if (mDaysOfHistory == 0) {
640
0
    mDaysOfHistory = 1;
641
0
  }
642
0
643
0
  if (visitTime > mLastCachedEndOfDay || visitTime < mLastCachedStartOfDay) {
644
0
    mDaysOfHistory = -1;
645
0
  }
646
0
}
647
648
void
649
nsNavHistory::NotifyTitleChange(nsIURI* aURI,
650
                                const nsString& aTitle,
651
                                const nsACString& aGUID)
652
0
{
653
0
  MOZ_ASSERT(!aGUID.IsEmpty());
654
0
  NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
655
0
                   OnTitleChanged(aURI, aTitle, aGUID));
656
0
}
657
658
void
659
nsNavHistory::NotifyFrecencyChanged(const nsACString& aSpec,
660
                                    int32_t aNewFrecency,
661
                                    const nsACString& aGUID,
662
                                    bool aHidden,
663
                                    PRTime aLastVisitDate)
664
0
{
665
0
  MOZ_ASSERT(!aGUID.IsEmpty());
666
0
667
0
  nsCOMPtr<nsIURI> uri;
668
0
  Unused << NS_NewURI(getter_AddRefs(uri), aSpec);
669
0
  // We cannot assert since some automated tests are checking this path.
670
0
  NS_WARNING_ASSERTION(uri, "Invalid URI in nsNavHistory::NotifyFrecencyChanged");
671
0
  // Notify a frecency change only if we have a valid uri, otherwise
672
0
  // the observer couldn't gather any useful data from the notification.
673
0
  if (!uri) {
674
0
    return;
675
0
  }
676
0
  NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
677
0
                   OnFrecencyChanged(uri, aNewFrecency, aGUID, aHidden,
678
0
                                     aLastVisitDate));
679
0
}
680
681
void
682
nsNavHistory::NotifyManyFrecenciesChanged()
683
0
{
684
0
  NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
685
0
                   OnManyFrecenciesChanged());
686
0
}
687
688
void
689
nsNavHistory::DispatchFrecencyChangedNotification(const nsACString& aSpec,
690
                                                  int32_t aNewFrecency,
691
                                                  const nsACString& aGUID,
692
                                                  bool aHidden,
693
                                                  PRTime aLastVisitDate) const
694
0
{
695
0
  Unused << NS_DispatchToMainThread(
696
0
    NewRunnableMethod<nsCString, int32_t, nsCString, bool, PRTime>(
697
0
      "nsNavHistory::NotifyFrecencyChanged",
698
0
      const_cast<nsNavHistory*>(this),
699
0
      &nsNavHistory::NotifyFrecencyChanged,
700
0
      aSpec, aNewFrecency, aGUID, aHidden, aLastVisitDate
701
0
    )
702
0
  );
703
0
}
704
705
NS_IMETHODIMP
706
nsNavHistory::RecalculateOriginFrecencyStats(nsIObserver *aCallback)
707
0
{
708
0
  RefPtr<nsNavHistory> self(this);
709
0
  nsMainThreadPtrHandle<nsIObserver> callback(
710
0
    !aCallback ? nullptr :
711
0
    new nsMainThreadPtrHolder<nsIObserver>(
712
0
      "nsNavHistory::RecalculateOriginFrecencyStats callback",
713
0
      aCallback
714
0
    )
715
0
  );
716
0
717
0
  nsCOMPtr<nsIEventTarget> target(do_GetInterface(mDB->MainConn()));
718
0
  NS_ENSURE_STATE(target);
719
0
  nsresult rv = target->Dispatch(NS_NewRunnableFunction(
720
0
    "nsNavHistory::RecalculateOriginFrecencyStats",
721
0
    [self, callback] {
722
0
      Unused << self->RecalculateOriginFrecencyStatsInternal();
723
0
      Unused << NS_DispatchToMainThread(NS_NewRunnableFunction(
724
0
        "nsNavHistory::RecalculateOriginFrecencyStats callback",
725
0
        [callback] {
726
0
          if (callback) {
727
0
            Unused << callback->Observe(nullptr, "", nullptr);
728
0
          }
729
0
        }
730
0
      ));
731
0
    }
732
0
  ));
733
0
  NS_ENSURE_SUCCESS(rv, rv);
734
0
735
0
  return NS_OK;
736
0
}
737
738
nsresult
739
nsNavHistory::RecalculateOriginFrecencyStatsInternal()
740
0
{
741
0
  nsCOMPtr<mozIStorageConnection> conn(mDB->MainConn());
742
0
  NS_ENSURE_STATE(conn);
743
0
744
0
  nsresult rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
745
0
    "INSERT OR REPLACE INTO moz_meta(key, value) VALUES "
746
0
    "( "
747
0
      "'" MOZ_META_KEY_ORIGIN_FRECENCY_COUNT "' , "
748
0
      "(SELECT COUNT(*) FROM moz_origins WHERE frecency > 0) "
749
0
    "), "
750
0
    "( "
751
0
      "'" MOZ_META_KEY_ORIGIN_FRECENCY_SUM "', "
752
0
      "(SELECT TOTAL(frecency) FROM moz_origins WHERE frecency > 0) "
753
0
    "), "
754
0
    "( "
755
0
      "'" MOZ_META_KEY_ORIGIN_FRECENCY_SUM_OF_SQUARES "' , "
756
0
      "(SELECT TOTAL(frecency * frecency) FROM moz_origins WHERE frecency > 0) "
757
0
    ") "
758
0
  ));
759
0
  NS_ENSURE_SUCCESS(rv, rv);
760
0
761
0
  return NS_OK;
762
0
}
763
764
Atomic<int64_t> nsNavHistory::sLastInsertedPlaceId(0);
765
Atomic<int64_t> nsNavHistory::sLastInsertedVisitId(0);
766
767
void // static
768
nsNavHistory::StoreLastInsertedId(const nsACString& aTable,
769
0
                                  const int64_t aLastInsertedId) {
770
0
  if (aTable.EqualsLiteral("moz_places")) {
771
0
    nsNavHistory::sLastInsertedPlaceId = aLastInsertedId;
772
0
  } else if (aTable.EqualsLiteral("moz_historyvisits")) {
773
0
    nsNavHistory::sLastInsertedVisitId = aLastInsertedId;
774
0
  } else {
775
0
    MOZ_ASSERT(false, "Trying to store the insert id for an unknown table?");
776
0
  }
777
0
}
778
779
int32_t
780
0
nsNavHistory::GetDaysOfHistory() {
781
0
  MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread");
782
0
783
0
  if (mDaysOfHistory != -1)
784
0
    return mDaysOfHistory;
785
0
786
0
  // SQLite doesn't have a CEIL() function, so we must do that later.
787
0
  // We should also take into account timers resolution, that may be as bad as
788
0
  // 16ms on Windows, so in some cases the difference may be 0, if the
789
0
  // check is done near the visit.  Thus remember to check for NULL separately.
790
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
791
0
    "SELECT CAST(( "
792
0
        "strftime('%s','now','localtime','utc') - "
793
0
        "(SELECT MIN(visit_date)/1000000 FROM moz_historyvisits) "
794
0
      ") AS DOUBLE) "
795
0
    "/86400, "
796
0
    "strftime('%s','now','localtime','+1 day','start of day','utc') * 1000000"
797
0
  );
798
0
  NS_ENSURE_TRUE(stmt, 0);
799
0
  mozStorageStatementScoper scoper(stmt);
800
0
801
0
  bool hasResult;
802
0
  if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
803
0
    // If we get NULL, then there are no visits, otherwise there must always be
804
0
    // at least 1 day of history.
805
0
    bool hasNoVisits;
806
0
    (void)stmt->GetIsNull(0, &hasNoVisits);
807
0
    mDaysOfHistory = hasNoVisits ?
808
0
      0 : std::max(1, static_cast<int32_t>(ceil(stmt->AsDouble(0))));
809
0
    mLastCachedStartOfDay =
810
0
      NormalizeTime(nsINavHistoryQuery::TIME_RELATIVE_TODAY, 0);
811
0
    mLastCachedEndOfDay = stmt->AsInt64(1) - 1; // Start of tomorrow - 1.
812
0
  }
813
0
814
0
  return mDaysOfHistory;
815
0
}
816
817
PRTime
818
nsNavHistory::GetNow()
819
0
{
820
0
  if (!mCachedNow) {
821
0
    mCachedNow = PR_Now();
822
0
    if (!mExpireNowTimer)
823
0
      mExpireNowTimer = NS_NewTimer();
824
0
    if (mExpireNowTimer)
825
0
      mExpireNowTimer->InitWithNamedFuncCallback(expireNowTimerCallback,
826
0
                                                 this,
827
0
                                                 RENEW_CACHED_NOW_TIMEOUT,
828
0
                                                 nsITimer::TYPE_ONE_SHOT,
829
0
                                                 "nsNavHistory::GetNow");
830
0
  }
831
0
  return mCachedNow;
832
0
}
833
834
835
void nsNavHistory::expireNowTimerCallback(nsITimer* aTimer, void* aClosure)
836
0
{
837
0
  nsNavHistory *history = static_cast<nsNavHistory *>(aClosure);
838
0
  if (history) {
839
0
    history->mCachedNow = 0;
840
0
    history->mExpireNowTimer = nullptr;
841
0
  }
842
0
}
843
844
845
/**
846
 * Code borrowed from mozilla/xpfe/components/history/src/nsGlobalHistory.cpp
847
 * Pass in a pre-normalized now and a date, and we'll find the difference since
848
 * midnight on each of the days.
849
 */
850
static PRTime
851
NormalizeTimeRelativeToday(PRTime aTime)
852
0
{
853
0
  // round to midnight this morning
854
0
  PRExplodedTime explodedTime;
855
0
  PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime);
856
0
857
0
  // set to midnight (0:00)
858
0
  explodedTime.tm_min =
859
0
    explodedTime.tm_hour =
860
0
    explodedTime.tm_sec =
861
0
    explodedTime.tm_usec = 0;
862
0
863
0
  return PR_ImplodeTime(&explodedTime);
864
0
}
865
866
// nsNavHistory::NormalizeTime
867
//
868
//    Converts a nsINavHistoryQuery reference+offset time into a PRTime
869
//    relative to the epoch.
870
//
871
//    It is important that this function NOT use the current time optimization.
872
//    It is called to update queries, and we really need to know what right
873
//    now is because those incoming values will also have current times that
874
//    we will have to compare against.
875
876
PRTime // static
877
nsNavHistory::NormalizeTime(uint32_t aRelative, PRTime aOffset)
878
0
{
879
0
  PRTime ref;
880
0
  switch (aRelative)
881
0
  {
882
0
    case nsINavHistoryQuery::TIME_RELATIVE_EPOCH:
883
0
      return aOffset;
884
0
    case nsINavHistoryQuery::TIME_RELATIVE_TODAY:
885
0
      ref = NormalizeTimeRelativeToday(PR_Now());
886
0
      break;
887
0
    case nsINavHistoryQuery::TIME_RELATIVE_NOW:
888
0
      ref = PR_Now();
889
0
      break;
890
0
    default:
891
0
      MOZ_ASSERT_UNREACHABLE("Invalid relative time");
892
0
      return 0;
893
0
  }
894
0
  return ref + aOffset;
895
0
}
896
897
// nsNavHistory::DomainNameFromURI
898
//
899
//    This does the www.mozilla.org -> mozilla.org and
900
//    foo.theregister.co.uk -> theregister.co.uk conversion
901
void
902
nsNavHistory::DomainNameFromURI(nsIURI *aURI,
903
                                nsACString& aDomainName)
904
0
{
905
0
  // lazily get the effective tld service
906
0
  if (!mTLDService)
907
0
    mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
908
0
909
0
  if (mTLDService) {
910
0
    // get the base domain for a given hostname.
911
0
    // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk".
912
0
    nsresult rv = mTLDService->GetBaseDomain(aURI, 0, aDomainName);
913
0
    if (NS_SUCCEEDED(rv))
914
0
      return;
915
0
  }
916
0
917
0
  // just return the original hostname
918
0
  // (it's also possible the host is an IP address)
919
0
  aURI->GetAsciiHost(aDomainName);
920
0
}
921
922
923
bool
924
nsNavHistory::hasHistoryEntries()
925
0
{
926
0
  return GetDaysOfHistory() > 0;
927
0
}
928
929
930
// Call this method before visiting a URL in order to help determine the
931
// transition type of the visit.
932
//
933
// @see MarkPageAsTyped
934
935
NS_IMETHODIMP
936
nsNavHistory::MarkPageAsFollowedBookmark(nsIURI* aURI)
937
0
{
938
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
939
0
  NS_ENSURE_ARG(aURI);
940
0
941
0
  // don't add when history is disabled
942
0
  if (IsHistoryDisabled())
943
0
    return NS_OK;
944
0
945
0
  nsAutoCString uriString;
946
0
  nsresult rv = aURI->GetSpec(uriString);
947
0
  NS_ENSURE_SUCCESS(rv, rv);
948
0
949
0
  mRecentBookmark.Put(uriString, GetNow());
950
0
951
0
  if (mRecentBookmark.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH)
952
0
    ExpireNonrecentEvents(&mRecentBookmark);
953
0
954
0
  return NS_OK;
955
0
}
956
957
958
// nsNavHistory::CanAddURI
959
//
960
//    Filter out unwanted URIs such as "chrome:", "mailbox:", etc.
961
//
962
//    The model is if we don't know differently then add which basically means
963
//    we are suppose to try all the things we know not to allow in and then if
964
//    we don't bail go on and allow it in.
965
966
NS_IMETHODIMP
967
nsNavHistory::CanAddURI(nsIURI* aURI, bool* canAdd)
968
0
{
969
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
970
0
  NS_ENSURE_ARG(aURI);
971
0
  NS_ENSURE_ARG_POINTER(canAdd);
972
0
973
0
  // Default to false.
974
0
  *canAdd = false;
975
0
976
0
  // If history is disabled, don't add any entry.
977
0
  if (IsHistoryDisabled()) {
978
0
    return NS_OK;
979
0
  }
980
0
981
0
  // If the url length is over a threshold, don't add it.
982
0
  nsCString spec;
983
0
  nsresult rv = aURI->GetSpec(spec);
984
0
  NS_ENSURE_SUCCESS(rv, rv);
985
0
  if (!mDB || spec.Length() > mDB->MaxUrlLength()) {
986
0
    return NS_OK;
987
0
  }
988
0
989
0
  nsAutoCString scheme;
990
0
  rv = aURI->GetScheme(scheme);
991
0
  NS_ENSURE_SUCCESS(rv, rv);
992
0
993
0
  // first check the most common cases (HTTP, HTTPS) to allow in to avoid most
994
0
  // of the work
995
0
  if (scheme.EqualsLiteral("http")) {
996
0
    *canAdd = true;
997
0
    return NS_OK;
998
0
  }
999
0
  if (scheme.EqualsLiteral("https")) {
1000
0
    *canAdd = true;
1001
0
    return NS_OK;
1002
0
  }
1003
0
1004
0
  // now check for all bad things
1005
0
  if (scheme.EqualsLiteral("about") ||
1006
0
      scheme.EqualsLiteral("blob") ||
1007
0
      scheme.EqualsLiteral("chrome") ||
1008
0
      scheme.EqualsLiteral("data") ||
1009
0
      scheme.EqualsLiteral("imap") ||
1010
0
      scheme.EqualsLiteral("javascript") ||
1011
0
      scheme.EqualsLiteral("mailbox") ||
1012
0
      scheme.EqualsLiteral("moz-anno") ||
1013
0
      scheme.EqualsLiteral("news") ||
1014
0
      scheme.EqualsLiteral("page-icon") ||
1015
0
      scheme.EqualsLiteral("resource") ||
1016
0
      scheme.EqualsLiteral("view-source") ||
1017
0
      scheme.EqualsLiteral("wyciwyg")) {
1018
0
    return NS_OK;
1019
0
  }
1020
0
  *canAdd = true;
1021
0
  return NS_OK;
1022
0
}
1023
1024
// nsNavHistory::GetNewQuery
1025
1026
NS_IMETHODIMP
1027
nsNavHistory::GetNewQuery(nsINavHistoryQuery **_retval)
1028
0
{
1029
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
1030
0
  NS_ENSURE_ARG_POINTER(_retval);
1031
0
1032
0
  RefPtr<nsNavHistoryQuery> query = new nsNavHistoryQuery();
1033
0
  query.forget(_retval);
1034
0
  return NS_OK;
1035
0
}
1036
1037
// nsNavHistory::GetNewQueryOptions
1038
1039
NS_IMETHODIMP
1040
nsNavHistory::GetNewQueryOptions(nsINavHistoryQueryOptions **_retval)
1041
0
{
1042
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
1043
0
  NS_ENSURE_ARG_POINTER(_retval);
1044
0
1045
0
  RefPtr<nsNavHistoryQueryOptions> queryOptions = new nsNavHistoryQueryOptions();
1046
0
  queryOptions.forget(_retval);
1047
0
  return NS_OK;
1048
0
}
1049
1050
1051
NS_IMETHODIMP
1052
nsNavHistory::ExecuteQuery(nsINavHistoryQuery *aQuery,
1053
                           nsINavHistoryQueryOptions *aOptions,
1054
                           nsINavHistoryResult** _retval)
1055
0
{
1056
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
1057
0
  NS_ENSURE_ARG(aQuery);
1058
0
  NS_ENSURE_ARG(aOptions);
1059
0
  NS_ENSURE_ARG_POINTER(_retval);
1060
0
1061
0
  // Clone the input query and options, because the caller might change the
1062
0
  // objects, but we always want to reflect the original parameters.
1063
0
  nsCOMPtr<nsINavHistoryQuery> queryClone;
1064
0
  aQuery->Clone(getter_AddRefs(queryClone));
1065
0
  NS_ENSURE_STATE(queryClone);
1066
0
  RefPtr<nsNavHistoryQuery> query = do_QueryObject(queryClone);
1067
0
  NS_ENSURE_STATE(query);
1068
0
  nsCOMPtr<nsINavHistoryQueryOptions> optionsClone;
1069
0
  aOptions->Clone(getter_AddRefs(optionsClone));
1070
0
  NS_ENSURE_STATE(optionsClone);
1071
0
  RefPtr<nsNavHistoryQueryOptions> options = do_QueryObject(optionsClone);
1072
0
  NS_ENSURE_STATE(options);
1073
0
1074
0
  // Create the root node.
1075
0
  RefPtr<nsNavHistoryContainerResultNode> rootNode;
1076
0
1077
0
  nsCString folderGuid = GetSimpleBookmarksQueryParent(query, options);
1078
0
  if (!folderGuid.IsEmpty()) {
1079
0
    // In the simple case where we're just querying children of a single
1080
0
    // bookmark folder, we can more efficiently generate results.
1081
0
    nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
1082
0
    NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
1083
0
    RefPtr<nsNavHistoryResultNode> tempRootNode;
1084
0
    nsresult rv = bookmarks->ResultNodeForContainer(folderGuid, options,
1085
0
                                                    getter_AddRefs(tempRootNode));
1086
0
    if (NS_SUCCEEDED(rv)) {
1087
0
      rootNode = tempRootNode->GetAsContainer();
1088
0
    }
1089
0
    else {
1090
0
      NS_WARNING("Generating a generic empty node for a broken query!");
1091
0
      // This is a perf hack to generate an empty query that skips filtering.
1092
0
      options->SetExcludeItems(true);
1093
0
    }
1094
0
  }
1095
0
1096
0
  if (!rootNode) {
1097
0
    // Either this is not a folder shortcut, or is a broken one.  In both cases
1098
0
    // just generate a query node.
1099
0
    nsAutoCString queryUri;
1100
0
    nsresult rv = QueryToQueryString(query, options, queryUri);
1101
0
    NS_ENSURE_SUCCESS(rv, rv);
1102
0
    rootNode = new nsNavHistoryQueryResultNode(EmptyCString(), 0, queryUri,
1103
0
                                               query, options);
1104
0
  }
1105
0
1106
0
  // Create the result that will hold nodes.  Inject batching status into it.
1107
0
  RefPtr<nsNavHistoryResult> result = new nsNavHistoryResult(rootNode,
1108
0
                                                             query, options);
1109
0
  result.forget(_retval);
1110
0
  return NS_OK;
1111
0
}
1112
1113
// determine from our nsNavHistoryQuery array and nsNavHistoryQueryOptions
1114
// if this is the place query from the history menu.
1115
// from browser-menubar.inc, our history menu query is:
1116
// place:sort=4&maxResults=10
1117
// note, any maxResult > 0 will still be considered a history menu query
1118
// or if this is the place query from the old "Most Visited" item in some profiles:
1119
// folder: place:sort=8&maxResults=10
1120
// note, any maxResult > 0 will still be considered a Most Visited menu query
1121
static
1122
bool IsOptimizableHistoryQuery(const RefPtr<nsNavHistoryQuery>& aQuery,
1123
                               const RefPtr<nsNavHistoryQueryOptions>& aOptions,
1124
                               uint16_t aSortMode)
1125
0
{
1126
0
  if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
1127
0
    return false;
1128
0
1129
0
  if (aOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_URI)
1130
0
    return false;
1131
0
1132
0
  if (aOptions->SortingMode() != aSortMode)
1133
0
    return false;
1134
0
1135
0
  if (aOptions->MaxResults() <= 0)
1136
0
    return false;
1137
0
1138
0
  if (aOptions->ExcludeItems())
1139
0
    return false;
1140
0
1141
0
  if (aOptions->IncludeHidden())
1142
0
    return false;
1143
0
1144
0
  if (aQuery->MinVisits() != -1 || aQuery->MaxVisits() != -1)
1145
0
    return false;
1146
0
1147
0
  if (aQuery->BeginTime() || aQuery->BeginTimeReference())
1148
0
    return false;
1149
0
1150
0
  if (aQuery->EndTime() || aQuery->EndTimeReference())
1151
0
    return false;
1152
0
1153
0
  if (!aQuery->SearchTerms().IsEmpty())
1154
0
    return false;
1155
0
1156
0
  if (aQuery->OnlyBookmarked())
1157
0
    return false;
1158
0
1159
0
  if (aQuery->DomainIsHost() || !aQuery->Domain().IsEmpty())
1160
0
    return false;
1161
0
1162
0
  if (aQuery->AnnotationIsNot() || !aQuery->Annotation().IsEmpty())
1163
0
    return false;
1164
0
1165
0
  if (aQuery->Parents().Length() > 0)
1166
0
    return false;
1167
0
1168
0
  if (aQuery->Tags().Length() > 0)
1169
0
    return false;
1170
0
1171
0
  if (aQuery->Transitions().Length() > 0)
1172
0
    return false;
1173
0
1174
0
  return true;
1175
0
}
1176
1177
static
1178
bool NeedToFilterResultSet(const RefPtr<nsNavHistoryQuery>& aQuery,
1179
                           nsNavHistoryQueryOptions *aOptions)
1180
0
{
1181
0
  return aOptions->ExcludeQueries();
1182
0
}
1183
1184
// ** Helper class for ConstructQueryString **/
1185
1186
class PlacesSQLQueryBuilder
1187
{
1188
public:
1189
  PlacesSQLQueryBuilder(const nsCString& aConditions,
1190
                        const RefPtr<nsNavHistoryQuery>& aQuery,
1191
                        const RefPtr<nsNavHistoryQueryOptions>& aOptions,
1192
                        bool aUseLimit,
1193
                        nsNavHistory::StringHash& aAddParams,
1194
                        bool aHasSearchTerms);
1195
1196
  nsresult GetQueryString(nsCString& aQueryString);
1197
1198
private:
1199
  nsresult Select();
1200
1201
  nsresult SelectAsURI();
1202
  nsresult SelectAsVisit();
1203
  nsresult SelectAsDay();
1204
  nsresult SelectAsSite();
1205
  nsresult SelectAsTag();
1206
  nsresult SelectAsRoots();
1207
  nsresult SelectAsLeftPane();
1208
1209
  nsresult Where();
1210
  nsresult GroupBy();
1211
  nsresult OrderBy();
1212
  nsresult Limit();
1213
1214
  void OrderByColumnIndexAsc(int32_t aIndex);
1215
  void OrderByColumnIndexDesc(int32_t aIndex);
1216
  // Use these if you want a case insensitive sorting.
1217
  void OrderByTextColumnIndexAsc(int32_t aIndex);
1218
  void OrderByTextColumnIndexDesc(int32_t aIndex);
1219
1220
  const nsCString& mConditions;
1221
  bool mUseLimit;
1222
  bool mHasSearchTerms;
1223
1224
  uint16_t mResultType;
1225
  uint16_t mQueryType;
1226
  bool mIncludeHidden;
1227
  uint16_t mSortingMode;
1228
  uint32_t mMaxResults;
1229
1230
  nsCString mQueryString;
1231
  nsCString mGroupBy;
1232
  bool mHasDateColumns;
1233
  bool mSkipOrderBy;
1234
1235
  nsNavHistory::StringHash& mAddParams;
1236
};
1237
1238
PlacesSQLQueryBuilder::PlacesSQLQueryBuilder(
1239
    const nsCString& aConditions,
1240
    const RefPtr<nsNavHistoryQuery>& aQuery,
1241
    const RefPtr<nsNavHistoryQueryOptions>& aOptions,
1242
    bool aUseLimit,
1243
    nsNavHistory::StringHash& aAddParams,
1244
    bool aHasSearchTerms)
1245
: mConditions(aConditions)
1246
, mUseLimit(aUseLimit)
1247
, mHasSearchTerms(aHasSearchTerms)
1248
, mResultType(aOptions->ResultType())
1249
, mQueryType(aOptions->QueryType())
1250
, mIncludeHidden(aOptions->IncludeHidden())
1251
, mSortingMode(aOptions->SortingMode())
1252
, mMaxResults(aOptions->MaxResults())
1253
, mSkipOrderBy(false)
1254
, mAddParams(aAddParams)
1255
0
{
1256
0
  mHasDateColumns = (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS);
1257
0
  // Force the default sorting mode for tag queries.
1258
0
  if (mSortingMode == nsINavHistoryQueryOptions::SORT_BY_NONE &&
1259
0
      aQuery->Tags().Length() > 0) {
1260
0
    mSortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING;
1261
0
  }
1262
0
}
1263
1264
nsresult
1265
PlacesSQLQueryBuilder::GetQueryString(nsCString& aQueryString)
1266
0
{
1267
0
  nsresult rv = Select();
1268
0
  NS_ENSURE_SUCCESS(rv, rv);
1269
0
  rv = Where();
1270
0
  NS_ENSURE_SUCCESS(rv, rv);
1271
0
  rv = GroupBy();
1272
0
  NS_ENSURE_SUCCESS(rv, rv);
1273
0
  rv = OrderBy();
1274
0
  NS_ENSURE_SUCCESS(rv, rv);
1275
0
  rv = Limit();
1276
0
  NS_ENSURE_SUCCESS(rv, rv);
1277
0
1278
0
  aQueryString = mQueryString;
1279
0
  return NS_OK;
1280
0
}
1281
1282
nsresult
1283
PlacesSQLQueryBuilder::Select()
1284
0
{
1285
0
  nsresult rv;
1286
0
1287
0
  switch (mResultType)
1288
0
  {
1289
0
    case nsINavHistoryQueryOptions::RESULTS_AS_URI:
1290
0
      rv = SelectAsURI();
1291
0
      NS_ENSURE_SUCCESS(rv, rv);
1292
0
      break;
1293
0
1294
0
    case nsINavHistoryQueryOptions::RESULTS_AS_VISIT:
1295
0
      rv = SelectAsVisit();
1296
0
      NS_ENSURE_SUCCESS(rv, rv);
1297
0
      break;
1298
0
1299
0
    case nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY:
1300
0
    case nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY:
1301
0
      rv = SelectAsDay();
1302
0
      NS_ENSURE_SUCCESS(rv, rv);
1303
0
      break;
1304
0
1305
0
    case nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY:
1306
0
      rv = SelectAsSite();
1307
0
      NS_ENSURE_SUCCESS(rv, rv);
1308
0
      break;
1309
0
1310
0
    case nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT:
1311
0
      rv = SelectAsTag();
1312
0
      NS_ENSURE_SUCCESS(rv, rv);
1313
0
      break;
1314
0
1315
0
    case nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY:
1316
0
      rv = SelectAsRoots();
1317
0
      NS_ENSURE_SUCCESS(rv, rv);
1318
0
      break;
1319
0
1320
0
    case nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY:
1321
0
      rv = SelectAsLeftPane();
1322
0
      NS_ENSURE_SUCCESS(rv, rv);
1323
0
      break;
1324
0
1325
0
    default:
1326
0
      MOZ_ASSERT_UNREACHABLE("Invalid result type");
1327
0
  }
1328
0
  return NS_OK;
1329
0
}
1330
1331
nsresult
1332
PlacesSQLQueryBuilder::SelectAsURI()
1333
0
{
1334
0
  nsNavHistory *history = nsNavHistory::GetHistoryService();
1335
0
  NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1336
0
  nsAutoCString tagsSqlFragment;
1337
0
1338
0
  switch (mQueryType) {
1339
0
    case nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY:
1340
0
      GetTagsSqlFragment(history->GetTagsFolder(),
1341
0
                         NS_LITERAL_CSTRING("h.id"),
1342
0
                         mHasSearchTerms,
1343
0
                         tagsSqlFragment);
1344
0
1345
0
      mQueryString = NS_LITERAL_CSTRING(
1346
0
        "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, "
1347
0
        "h.last_visit_date, null, null, null, null, null, ") +
1348
0
        tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
1349
0
        "null, null, null "
1350
0
        "FROM moz_places h "
1351
0
        // WHERE 1 is a no-op since additonal conditions will start with AND.
1352
0
        "WHERE 1 "
1353
0
          "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
1354
0
          "{ADDITIONAL_CONDITIONS} ");
1355
0
      break;
1356
0
1357
0
    case nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS:
1358
0
1359
0
      GetTagsSqlFragment(history->GetTagsFolder(),
1360
0
                          NS_LITERAL_CSTRING("b.fk"),
1361
0
                          mHasSearchTerms,
1362
0
                          tagsSqlFragment);
1363
0
      mQueryString = NS_LITERAL_CSTRING(
1364
0
        "SELECT b.fk, h.url, b.title AS page_title, "
1365
0
          "h.rev_host, h.visit_count, h.last_visit_date, null, b.id, "
1366
0
          "b.dateAdded, b.lastModified, b.parent, ") +
1367
0
          tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid,"
1368
0
          "null, null, null, b.guid, b.position, b.type, b.fk "
1369
0
        "FROM moz_bookmarks b "
1370
0
        "JOIN moz_places h ON b.fk = h.id "
1371
0
        "WHERE NOT EXISTS "
1372
0
            "(SELECT id FROM moz_bookmarks "
1373
0
              "WHERE id = b.parent AND parent = ") +
1374
0
                nsPrintfCString("%" PRId64, history->GetTagsFolder()) +
1375
0
            NS_LITERAL_CSTRING(") "
1376
0
            "AND NOT h.url_hash BETWEEN hash('place', 'prefix_lo') AND "
1377
0
                                        "hash('place', 'prefix_hi') "
1378
0
          "{ADDITIONAL_CONDITIONS}");
1379
0
      break;
1380
0
1381
0
    default:
1382
0
      return NS_ERROR_NOT_IMPLEMENTED;
1383
0
  }
1384
0
  return NS_OK;
1385
0
}
1386
1387
nsresult
1388
PlacesSQLQueryBuilder::SelectAsVisit()
1389
0
{
1390
0
  nsNavHistory *history = nsNavHistory::GetHistoryService();
1391
0
  NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1392
0
  nsAutoCString tagsSqlFragment;
1393
0
  GetTagsSqlFragment(history->GetTagsFolder(),
1394
0
                     NS_LITERAL_CSTRING("h.id"),
1395
0
                     mHasSearchTerms,
1396
0
                     tagsSqlFragment);
1397
0
  mQueryString = NS_LITERAL_CSTRING(
1398
0
    "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, "
1399
0
      "v.visit_date, null, null, null, null, null, ") +
1400
0
      tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
1401
0
      "v.id, v.from_visit, v.visit_type "
1402
0
    "FROM moz_places h "
1403
0
    "JOIN moz_historyvisits v ON h.id = v.place_id "
1404
0
    // WHERE 1 is a no-op since additonal conditions will start with AND.
1405
0
    "WHERE 1 "
1406
0
      "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
1407
0
      "{ADDITIONAL_CONDITIONS} ");
1408
0
1409
0
  return NS_OK;
1410
0
}
1411
1412
nsresult
1413
PlacesSQLQueryBuilder::SelectAsDay()
1414
0
{
1415
0
  mSkipOrderBy = true;
1416
0
1417
0
  // Sort child queries based on sorting mode if it's provided, otherwise
1418
0
  // fallback to default sort by title ascending.
1419
0
  uint16_t sortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING;
1420
0
  if (mSortingMode != nsINavHistoryQueryOptions::SORT_BY_NONE &&
1421
0
      mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY)
1422
0
    sortingMode = mSortingMode;
1423
0
1424
0
  uint16_t resultType =
1425
0
    mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ?
1426
0
      (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_URI :
1427
0
      (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY;
1428
0
1429
0
  // beginTime will become the node's time property, we don't use endTime
1430
0
  // because it could overlap, and we use time to sort containers and find
1431
0
  // insert position in a result.
1432
0
  mQueryString = nsPrintfCString(
1433
0
     "SELECT null, "
1434
0
       "'place:type=%d&sort=%d&beginTime='||beginTime||'&endTime='||endTime, "
1435
0
      "dayTitle, null, null, beginTime, null, null, null, null, null, null, "
1436
0
      "null, null, null "
1437
0
     "FROM (", // TOUTER BEGIN
1438
0
     resultType,
1439
0
     sortingMode);
1440
0
1441
0
  nsNavHistory *history = nsNavHistory::GetHistoryService();
1442
0
  NS_ENSURE_STATE(history);
1443
0
1444
0
  int32_t daysOfHistory = history->GetDaysOfHistory();
1445
0
  for (int32_t i = 0; i <= HISTORY_DATE_CONT_NUM(daysOfHistory); i++) {
1446
0
    nsAutoCString dateName;
1447
0
    // Timeframes are calculated as BeginTime <= container < EndTime.
1448
0
    // Notice times can't be relative to now, since to recognize a query we
1449
0
    // must ensure it won't change based on the time it is built.
1450
0
    // So, to select till now, we really select till start of tomorrow, that is
1451
0
    // a fixed timestamp.
1452
0
    // These are used as limits for the inside containers.
1453
0
    nsAutoCString sqlFragmentContainerBeginTime, sqlFragmentContainerEndTime;
1454
0
    // These are used to query if the container should be visible.
1455
0
    nsAutoCString sqlFragmentSearchBeginTime, sqlFragmentSearchEndTime;
1456
0
    switch(i) {
1457
0
       case 0:
1458
0
        // Today
1459
0
        history->GetStringFromName(
1460
0
          "finduri-AgeInDays-is-0", dateName);
1461
0
        // From start of today
1462
0
        sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1463
0
          "(strftime('%s','now','localtime','start of day','utc')*1000000)");
1464
0
        // To now (tomorrow)
1465
0
        sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1466
0
          "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)");
1467
0
        // Search for the same timeframe.
1468
0
        sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1469
0
        sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
1470
0
         break;
1471
0
       case 1:
1472
0
        // Yesterday
1473
0
        history->GetStringFromName(
1474
0
          "finduri-AgeInDays-is-1", dateName);
1475
0
        // From start of yesterday
1476
0
        sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1477
0
          "(strftime('%s','now','localtime','start of day','-1 day','utc')*1000000)");
1478
0
        // To start of today
1479
0
        sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1480
0
          "(strftime('%s','now','localtime','start of day','utc')*1000000)");
1481
0
        // Search for the same timeframe.
1482
0
        sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1483
0
        sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
1484
0
        break;
1485
0
      case 2:
1486
0
        // Last 7 days
1487
0
        history->GetAgeInDaysString(7,
1488
0
          "finduri-AgeInDays-last-is", dateName);
1489
0
        // From start of 7 days ago
1490
0
        sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1491
0
          "(strftime('%s','now','localtime','start of day','-7 days','utc')*1000000)");
1492
0
        // To now (tomorrow)
1493
0
        sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1494
0
          "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)");
1495
0
        // This is an overlapped container, but we show it only if there are
1496
0
        // visits older than yesterday.
1497
0
        sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1498
0
        sqlFragmentSearchEndTime = NS_LITERAL_CSTRING(
1499
0
          "(strftime('%s','now','localtime','start of day','-1 day','utc')*1000000)");
1500
0
        break;
1501
0
      case 3:
1502
0
        // This month
1503
0
        history->GetStringFromName(
1504
0
          "finduri-AgeInMonths-is-0", dateName);
1505
0
        // From start of this month
1506
0
        sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1507
0
          "(strftime('%s','now','localtime','start of month','utc')*1000000)");
1508
0
        // To now (tomorrow)
1509
0
        sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1510
0
          "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)");
1511
0
        // This is an overlapped container, but we show it only if there are
1512
0
        // visits older than 7 days ago.
1513
0
        sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1514
0
        sqlFragmentSearchEndTime = NS_LITERAL_CSTRING(
1515
0
          "(strftime('%s','now','localtime','start of day','-7 days','utc')*1000000)");
1516
0
         break;
1517
0
       default:
1518
0
        if (i == HISTORY_ADDITIONAL_DATE_CONT_NUM + 6) {
1519
0
          // Older than 6 months
1520
0
          history->GetAgeInDaysString(6,
1521
0
            "finduri-AgeInMonths-isgreater", dateName);
1522
0
          // From start of epoch
1523
0
          sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1524
0
            "(datetime(0, 'unixepoch')*1000000)");
1525
0
          // To start of 6 months ago ( 5 months + this month).
1526
0
          sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1527
0
            "(strftime('%s','now','localtime','start of month','-5 months','utc')*1000000)");
1528
0
          // Search for the same timeframe.
1529
0
          sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1530
0
          sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
1531
0
          break;
1532
0
        }
1533
0
        int32_t MonthIndex = i - HISTORY_ADDITIONAL_DATE_CONT_NUM;
1534
0
        // Previous months' titles are month's name if inside this year,
1535
0
        // month's name and year for previous years.
1536
0
        PRExplodedTime tm;
1537
0
        PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &tm);
1538
0
        uint16_t currentYear = tm.tm_year;
1539
0
        // Set day before month, setting month without day could cause issues.
1540
0
        // For example setting month to February when today is 30, since
1541
0
        // February has not 30 days, will return March instead.
1542
0
        // Also, we use day 2 instead of day 1, so that the GMT month is always
1543
0
        // the same as the local month. (Bug 603002)
1544
0
        tm.tm_mday = 2;
1545
0
        tm.tm_month -= MonthIndex;
1546
0
        // Notice we use GMTParameters because we just want to get the first
1547
0
        // day of each month.  Using LocalTimeParameters would instead force us
1548
0
        // to apply a DST correction that we don't really need here.
1549
0
        PR_NormalizeTime(&tm, PR_GMTParameters);
1550
0
        // If the container is for a past year, add the year to its title,
1551
0
        // otherwise just show the month name.
1552
0
        if (tm.tm_year < currentYear) {
1553
0
          nsNavHistory::GetMonthYear(tm, dateName);
1554
0
        }
1555
0
        else {
1556
0
          nsNavHistory::GetMonthName(tm, dateName);
1557
0
        }
1558
0
1559
0
        // From start of MonthIndex + 1 months ago
1560
0
        sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1561
0
          "(strftime('%s','now','localtime','start of month','-");
1562
0
        sqlFragmentContainerBeginTime.AppendInt(MonthIndex);
1563
0
        sqlFragmentContainerBeginTime.AppendLiteral(" months','utc')*1000000)");
1564
0
        // To start of MonthIndex months ago
1565
0
        sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1566
0
          "(strftime('%s','now','localtime','start of month','-");
1567
0
        sqlFragmentContainerEndTime.AppendInt(MonthIndex - 1);
1568
0
        sqlFragmentContainerEndTime.AppendLiteral(" months','utc')*1000000)");
1569
0
        // Search for the same timeframe.
1570
0
        sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1571
0
        sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
1572
0
        break;
1573
0
    }
1574
0
1575
0
    nsPrintfCString dateParam("dayTitle%d", i);
1576
0
    mAddParams.Put(dateParam, dateName);
1577
0
1578
0
    nsPrintfCString dayRange(
1579
0
      "SELECT :%s AS dayTitle, "
1580
0
             "%s AS beginTime, "
1581
0
             "%s AS endTime "
1582
0
       "WHERE EXISTS ( "
1583
0
        "SELECT id FROM moz_historyvisits "
1584
0
        "WHERE visit_date >= %s "
1585
0
          "AND visit_date < %s "
1586
0
           "AND visit_type NOT IN (0,%d,%d) "
1587
0
           "{QUERY_OPTIONS_VISITS} "
1588
0
         "LIMIT 1 "
1589
0
      ") ",
1590
0
      dateParam.get(),
1591
0
      sqlFragmentContainerBeginTime.get(),
1592
0
      sqlFragmentContainerEndTime.get(),
1593
0
      sqlFragmentSearchBeginTime.get(),
1594
0
      sqlFragmentSearchEndTime.get(),
1595
0
      nsINavHistoryService::TRANSITION_EMBED,
1596
0
      nsINavHistoryService::TRANSITION_FRAMED_LINK
1597
0
    );
1598
0
1599
0
    mQueryString.Append(dayRange);
1600
0
1601
0
    if (i < HISTORY_DATE_CONT_NUM(daysOfHistory))
1602
0
      mQueryString.AppendLiteral(" UNION ALL ");
1603
0
  }
1604
0
1605
0
  mQueryString.AppendLiteral(") "); // TOUTER END
1606
0
1607
0
  return NS_OK;
1608
0
}
1609
1610
nsresult
1611
PlacesSQLQueryBuilder::SelectAsSite()
1612
0
{
1613
0
  nsAutoCString localFiles;
1614
0
1615
0
  nsNavHistory *history = nsNavHistory::GetHistoryService();
1616
0
  NS_ENSURE_STATE(history);
1617
0
1618
0
  history->GetStringFromName("localhost", localFiles);
1619
0
  mAddParams.Put(NS_LITERAL_CSTRING("localhost"), localFiles);
1620
0
1621
0
  // If there are additional conditions the query has to join on visits too.
1622
0
  nsAutoCString visitsJoin;
1623
0
  nsAutoCString additionalConditions;
1624
0
  nsAutoCString timeConstraints;
1625
0
  if (!mConditions.IsEmpty()) {
1626
0
    visitsJoin.AssignLiteral("JOIN moz_historyvisits v ON v.place_id = h.id ");
1627
0
    additionalConditions.AssignLiteral("{QUERY_OPTIONS_VISITS} "
1628
0
                                       "{QUERY_OPTIONS_PLACES} "
1629
0
                                       "{ADDITIONAL_CONDITIONS} ");
1630
0
    timeConstraints.AssignLiteral("||'&beginTime='||:begin_time||"
1631
0
                                    "'&endTime='||:end_time");
1632
0
  }
1633
0
1634
0
  mQueryString = nsPrintfCString(
1635
0
    "SELECT null, 'place:type=%d&sort=%d&domain=&domainIsHost=true'%s, "
1636
0
           ":localhost, :localhost, null, null, null, null, null, null, null, "
1637
0
           "null, null, null "
1638
0
    "WHERE EXISTS ( "
1639
0
      "SELECT h.id FROM moz_places h "
1640
0
      "%s "
1641
0
      "WHERE h.hidden = 0 "
1642
0
        "AND h.visit_count > 0 "
1643
0
        "AND h.url_hash BETWEEN hash('file', 'prefix_lo') AND "
1644
0
                               "hash('file', 'prefix_hi') "
1645
0
      "%s "
1646
0
      "LIMIT 1 "
1647
0
    ") "
1648
0
    "UNION ALL "
1649
0
    "SELECT null, "
1650
0
           "'place:type=%d&sort=%d&domain='||host||'&domainIsHost=true'%s, "
1651
0
           "host, host, null, null, null, null, null, null, null, "
1652
0
           "null, null, null "
1653
0
    "FROM ( "
1654
0
      "SELECT get_unreversed_host(h.rev_host) AS host "
1655
0
      "FROM moz_places h "
1656
0
      "%s "
1657
0
      "WHERE h.hidden = 0 "
1658
0
        "AND h.rev_host <> '.' "
1659
0
        "AND h.visit_count > 0 "
1660
0
        "%s "
1661
0
      "GROUP BY h.rev_host "
1662
0
      "ORDER BY host ASC "
1663
0
    ") ",
1664
0
    nsINavHistoryQueryOptions::RESULTS_AS_URI,
1665
0
    mSortingMode,
1666
0
    timeConstraints.get(),
1667
0
    visitsJoin.get(),
1668
0
    additionalConditions.get(),
1669
0
    nsINavHistoryQueryOptions::RESULTS_AS_URI,
1670
0
    mSortingMode,
1671
0
    timeConstraints.get(),
1672
0
    visitsJoin.get(),
1673
0
    additionalConditions.get()
1674
0
  );
1675
0
1676
0
  return NS_OK;
1677
0
}
1678
1679
nsresult
1680
PlacesSQLQueryBuilder::SelectAsTag()
1681
0
{
1682
0
  nsNavHistory *history = nsNavHistory::GetHistoryService();
1683
0
  NS_ENSURE_STATE(history);
1684
0
1685
0
  // This allows sorting by date fields what is not possible with
1686
0
  // other history queries.
1687
0
  mHasDateColumns = true;
1688
0
1689
0
  // TODO (Bug 1449939): This is likely wrong, since the tag name should
1690
0
  // probably be urlencoded, and we have no util for that in SQL, yet.
1691
0
  // We could encode the tag when the user sets it though.
1692
0
  mQueryString = nsPrintfCString(
1693
0
    "SELECT null, 'place:tag=' || title, "
1694
0
           "title, null, null, null, null, null, dateAdded, "
1695
0
           "lastModified, null, null, null, null, null, null "
1696
0
    "FROM moz_bookmarks "
1697
0
    "WHERE parent = %" PRId64,
1698
0
    history->GetTagsFolder()
1699
0
  );
1700
0
1701
0
  return NS_OK;
1702
0
}
1703
1704
nsresult
1705
PlacesSQLQueryBuilder::SelectAsRoots()
1706
0
{
1707
0
  nsNavHistory *history = nsNavHistory::GetHistoryService();
1708
0
  NS_ENSURE_STATE(history);
1709
0
1710
0
  nsAutoCString toolbarTitle;
1711
0
  nsAutoCString menuTitle;
1712
0
  nsAutoCString unfiledTitle;
1713
0
1714
0
  history->GetStringFromName("BookmarksToolbarFolderTitle", toolbarTitle);
1715
0
  mAddParams.Put(NS_LITERAL_CSTRING("BookmarksToolbarFolderTitle"), toolbarTitle);
1716
0
  history->GetStringFromName("BookmarksMenuFolderTitle", menuTitle);
1717
0
  mAddParams.Put(NS_LITERAL_CSTRING("BookmarksMenuFolderTitle"), menuTitle);
1718
0
  history->GetStringFromName("OtherBookmarksFolderTitle", unfiledTitle);
1719
0
  mAddParams.Put(NS_LITERAL_CSTRING("OtherBookmarksFolderTitle"), unfiledTitle);
1720
0
1721
0
  nsAutoCString mobileString;
1722
0
1723
0
  if (Preferences::GetBool(MOBILE_BOOKMARKS_PREF, false)) {
1724
0
    nsAutoCString mobileTitle;
1725
0
    history->GetStringFromName("MobileBookmarksFolderTitle", mobileTitle);
1726
0
    mAddParams.Put(NS_LITERAL_CSTRING("MobileBookmarksFolderTitle"), mobileTitle);
1727
0
1728
0
    mobileString = NS_LITERAL_CSTRING(","
1729
0
      "(null, 'place:parent=" MOBILE_ROOT_GUID "', :MobileBookmarksFolderTitle, null, null, null, "
1730
0
       "null, null, 0, 0, null, null, null, null, '" MOBILE_BOOKMARKS_VIRTUAL_GUID "', null) ");
1731
0
  }
1732
0
1733
0
  mQueryString = NS_LITERAL_CSTRING(
1734
0
    "SELECT * FROM ("
1735
0
        "VALUES(null, 'place:parent=" TOOLBAR_ROOT_GUID "', :BookmarksToolbarFolderTitle, null, null, null, "
1736
0
               "null, null, 0, 0, null, null, null, null, 'toolbar____v', null), "
1737
0
              "(null, 'place:parent=" MENU_ROOT_GUID "', :BookmarksMenuFolderTitle, null, null, null, "
1738
0
               "null, null, 0, 0, null, null, null, null, 'menu_______v', null), "
1739
0
              "(null, 'place:parent=" UNFILED_ROOT_GUID "', :OtherBookmarksFolderTitle, null, null, null, "
1740
0
               "null, null, 0, 0, null, null, null, null, 'unfiled___v', null) ") +
1741
0
    mobileString + NS_LITERAL_CSTRING(")");
1742
0
1743
0
  return NS_OK;
1744
0
}
1745
1746
nsresult
1747
PlacesSQLQueryBuilder::SelectAsLeftPane()
1748
0
{
1749
0
  nsNavHistory *history = nsNavHistory::GetHistoryService();
1750
0
  NS_ENSURE_STATE(history);
1751
0
1752
0
  nsAutoCString historyTitle;
1753
0
  nsAutoCString downloadsTitle;
1754
0
  nsAutoCString tagsTitle;
1755
0
  nsAutoCString allBookmarksTitle;
1756
0
1757
0
  history->GetStringFromName("OrganizerQueryHistory", historyTitle);
1758
0
  mAddParams.Put(NS_LITERAL_CSTRING("OrganizerQueryHistory"), historyTitle);
1759
0
  history->GetStringFromName("OrganizerQueryDownloads", downloadsTitle);
1760
0
  mAddParams.Put(NS_LITERAL_CSTRING("OrganizerQueryDownloads"), downloadsTitle);
1761
0
  history->GetStringFromName("TagsFolderTitle", tagsTitle);
1762
0
  mAddParams.Put(NS_LITERAL_CSTRING("TagsFolderTitle"), tagsTitle);
1763
0
  history->GetStringFromName("OrganizerQueryAllBookmarks", allBookmarksTitle);
1764
0
  mAddParams.Put(NS_LITERAL_CSTRING("OrganizerQueryAllBookmarks"), allBookmarksTitle);
1765
0
1766
0
  mQueryString = nsPrintfCString(
1767
0
    "SELECT * FROM ("
1768
0
        "VALUES"
1769
0
              "(null, 'place:type=%d&sort=%d', :OrganizerQueryHistory, null, null, null, "
1770
0
               "null, null, 0, 0, null, null, null, null, 'history____v', null), "
1771
0
              "(null, 'place:transition=%d&sort=%d', :OrganizerQueryDownloads, null, null, null, "
1772
0
               "null, null, 0, 0, null, null, null, null, 'downloads__v', null), "
1773
0
              "(null, 'place:type=%d&sort=%d', :TagsFolderTitle, null, null, null, "
1774
0
               "null, null, 0, 0, null, null, null, null, 'tags_______v', null), "
1775
0
              "(null, 'place:type=%d', :OrganizerQueryAllBookmarks, null, null, null, "
1776
0
               "null, null, 0, 0, null, null, null, null, 'allbms_____v', null) "
1777
0
    ")",
1778
0
    nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY,
1779
0
    nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING,
1780
0
    nsINavHistoryService::TRANSITION_DOWNLOAD,
1781
0
    nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING,
1782
0
    nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT,
1783
0
    nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING,
1784
0
    nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY);
1785
0
  return NS_OK;
1786
0
}
1787
1788
nsresult
1789
PlacesSQLQueryBuilder::Where()
1790
0
{
1791
0
1792
0
  // Set query options
1793
0
  nsAutoCString additionalVisitsConditions;
1794
0
  nsAutoCString additionalPlacesConditions;
1795
0
1796
0
  if (!mIncludeHidden) {
1797
0
    additionalPlacesConditions += NS_LITERAL_CSTRING("AND hidden = 0 ");
1798
0
  }
1799
0
1800
0
  if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) {
1801
0
    // last_visit_date is updated for any kind of visit, so it's a good
1802
0
    // indicator whether the page has visits.
1803
0
    additionalPlacesConditions += NS_LITERAL_CSTRING(
1804
0
      "AND last_visit_date NOTNULL "
1805
0
    );
1806
0
  }
1807
0
1808
0
  if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI &&
1809
0
      !additionalVisitsConditions.IsEmpty()) {
1810
0
    // URI results don't join on visits.
1811
0
    nsAutoCString tmp = additionalVisitsConditions;
1812
0
    additionalVisitsConditions = "AND EXISTS (SELECT 1 FROM moz_historyvisits WHERE place_id = h.id ";
1813
0
    additionalVisitsConditions.Append(tmp);
1814
0
    additionalVisitsConditions.AppendLiteral("LIMIT 1)");
1815
0
  }
1816
0
1817
0
  mQueryString.ReplaceSubstring("{QUERY_OPTIONS_VISITS}",
1818
0
                                additionalVisitsConditions.get());
1819
0
  mQueryString.ReplaceSubstring("{QUERY_OPTIONS_PLACES}",
1820
0
                                additionalPlacesConditions.get());
1821
0
1822
0
  // If we used WHERE already, we inject the conditions
1823
0
  // in place of {ADDITIONAL_CONDITIONS}
1824
0
  if (mQueryString.Find("{ADDITIONAL_CONDITIONS}") != kNotFound) {
1825
0
    nsAutoCString innerCondition;
1826
0
    // If we have condition AND it
1827
0
    if (!mConditions.IsEmpty()) {
1828
0
      innerCondition = " AND (";
1829
0
      innerCondition += mConditions;
1830
0
      innerCondition += ")";
1831
0
    }
1832
0
    mQueryString.ReplaceSubstring("{ADDITIONAL_CONDITIONS}",
1833
0
                                  innerCondition.get());
1834
0
1835
0
  } else if (!mConditions.IsEmpty()) {
1836
0
1837
0
    mQueryString += "WHERE ";
1838
0
    mQueryString += mConditions;
1839
0
1840
0
  }
1841
0
  return NS_OK;
1842
0
}
1843
1844
nsresult
1845
PlacesSQLQueryBuilder::GroupBy()
1846
0
{
1847
0
  mQueryString += mGroupBy;
1848
0
  return NS_OK;
1849
0
}
1850
1851
nsresult
1852
PlacesSQLQueryBuilder::OrderBy()
1853
0
{
1854
0
  if (mSkipOrderBy)
1855
0
    return NS_OK;
1856
0
1857
0
  // Sort clause: we will sort later, but if it comes out of the DB sorted,
1858
0
  // our later sort will be basically free. The DB can sort these for free
1859
0
  // most of the time anyway, because it has indices over these items.
1860
0
  switch(mSortingMode)
1861
0
  {
1862
0
    case nsINavHistoryQueryOptions::SORT_BY_NONE:
1863
0
      // Ensure sorting does not change based on tables status.
1864
0
      if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI) {
1865
0
        if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS)
1866
0
          mQueryString += NS_LITERAL_CSTRING(" ORDER BY b.id ASC ");
1867
0
        else if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
1868
0
          mQueryString += NS_LITERAL_CSTRING(" ORDER BY h.id ASC ");
1869
0
      }
1870
0
      break;
1871
0
    case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING:
1872
0
    case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING:
1873
0
      // If the user wants few results, we limit them by date, necessitating
1874
0
      // a sort by date here (see the IDL definition for maxResults).
1875
0
      // Otherwise we will do actual sorting by title, but since we could need
1876
0
      // to special sort for some locale we will repeat a second sorting at the
1877
0
      // end in nsNavHistoryResult, that should be faster since the list will be
1878
0
      // almost ordered.
1879
0
      if (mMaxResults > 0)
1880
0
        OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate);
1881
0
      else if (mSortingMode == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING)
1882
0
        OrderByTextColumnIndexAsc(nsNavHistory::kGetInfoIndex_Title);
1883
0
      else
1884
0
        OrderByTextColumnIndexDesc(nsNavHistory::kGetInfoIndex_Title);
1885
0
      break;
1886
0
    case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING:
1887
0
      OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitDate);
1888
0
      break;
1889
0
    case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING:
1890
0
      OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate);
1891
0
      break;
1892
0
    case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING:
1893
0
      OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_URL);
1894
0
      break;
1895
0
    case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING:
1896
0
      OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_URL);
1897
0
      break;
1898
0
    case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING:
1899
0
      OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitCount);
1900
0
      break;
1901
0
    case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING:
1902
0
      OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitCount);
1903
0
      break;
1904
0
    case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING:
1905
0
      if (mHasDateColumns)
1906
0
        OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemDateAdded);
1907
0
      break;
1908
0
    case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING:
1909
0
      if (mHasDateColumns)
1910
0
        OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemDateAdded);
1911
0
      break;
1912
0
    case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING:
1913
0
      if (mHasDateColumns)
1914
0
        OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemLastModified);
1915
0
      break;
1916
0
    case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING:
1917
0
      if (mHasDateColumns)
1918
0
        OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemLastModified);
1919
0
      break;
1920
0
    case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING:
1921
0
    case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING:
1922
0
      break; // Sort later in nsNavHistoryQueryResultNode::FillChildren()
1923
0
    case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING:
1924
0
        OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_Frecency);
1925
0
      break;
1926
0
    case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING:
1927
0
        OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_Frecency);
1928
0
      break;
1929
0
    default:
1930
0
      MOZ_ASSERT_UNREACHABLE("Invalid sorting mode");
1931
0
  }
1932
0
  return NS_OK;
1933
0
}
1934
1935
void PlacesSQLQueryBuilder::OrderByColumnIndexAsc(int32_t aIndex)
1936
0
{
1937
0
  mQueryString += nsPrintfCString(" ORDER BY %d ASC", aIndex+1);
1938
0
}
1939
1940
void PlacesSQLQueryBuilder::OrderByColumnIndexDesc(int32_t aIndex)
1941
0
{
1942
0
  mQueryString += nsPrintfCString(" ORDER BY %d DESC", aIndex+1);
1943
0
}
1944
1945
void PlacesSQLQueryBuilder::OrderByTextColumnIndexAsc(int32_t aIndex)
1946
0
{
1947
0
  mQueryString += nsPrintfCString(" ORDER BY %d COLLATE NOCASE ASC",
1948
0
                                  aIndex+1);
1949
0
}
1950
1951
void PlacesSQLQueryBuilder::OrderByTextColumnIndexDesc(int32_t aIndex)
1952
0
{
1953
0
  mQueryString += nsPrintfCString(" ORDER BY %d COLLATE NOCASE DESC",
1954
0
                                  aIndex+1);
1955
0
}
1956
1957
nsresult
1958
PlacesSQLQueryBuilder::Limit()
1959
0
{
1960
0
  if (mUseLimit && mMaxResults > 0) {
1961
0
    mQueryString += NS_LITERAL_CSTRING(" LIMIT ");
1962
0
    mQueryString.AppendInt(mMaxResults);
1963
0
    mQueryString.Append(' ');
1964
0
  }
1965
0
  return NS_OK;
1966
0
}
1967
1968
nsresult
1969
nsNavHistory::ConstructQueryString(
1970
    const RefPtr<nsNavHistoryQuery>& aQuery,
1971
    const RefPtr<nsNavHistoryQueryOptions>& aOptions,
1972
    nsCString& queryString,
1973
    bool& aParamsPresent,
1974
    nsNavHistory::StringHash& aAddParams)
1975
0
{
1976
0
  // For information about visit_type see nsINavHistoryService.idl.
1977
0
  // visitType == 0 is undefined (see bug #375777 for details).
1978
0
  // Some sites, especially Javascript-heavy ones, load things in frames to
1979
0
  // display them, resulting in a lot of these entries. This is the reason
1980
0
  // why such visits are filtered out.
1981
0
  nsresult rv;
1982
0
  aParamsPresent = false;
1983
0
1984
0
  int32_t sortingMode = aOptions->SortingMode();
1985
0
  NS_ASSERTION(sortingMode >= nsINavHistoryQueryOptions::SORT_BY_NONE &&
1986
0
               sortingMode <= nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING,
1987
0
               "Invalid sortingMode found while building query!");
1988
0
1989
0
  bool hasSearchTerms = !aQuery->SearchTerms().IsEmpty();
1990
0
1991
0
  nsAutoCString tagsSqlFragment;
1992
0
  GetTagsSqlFragment(GetTagsFolder(),
1993
0
                     NS_LITERAL_CSTRING("h.id"),
1994
0
                     hasSearchTerms,
1995
0
                     tagsSqlFragment);
1996
0
1997
0
  if (IsOptimizableHistoryQuery(aQuery, aOptions,
1998
0
        nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) ||
1999
0
      IsOptimizableHistoryQuery(aQuery, aOptions,
2000
0
        nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING)) {
2001
0
    // Generate an optimized query for the history menu and the old most visited
2002
0
    // bookmark that was inserted into profiles.
2003
0
    queryString = NS_LITERAL_CSTRING(
2004
0
      "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, h.last_visit_date, "
2005
0
          "null, null, null, null, null, ") +
2006
0
          tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
2007
0
          "null, null, null "
2008
0
        "FROM moz_places h "
2009
0
        "WHERE h.hidden = 0 "
2010
0
          "AND EXISTS (SELECT id FROM moz_historyvisits WHERE place_id = h.id "
2011
0
                       "AND visit_type NOT IN ") +
2012
0
                       nsPrintfCString("(0,%d,%d) ",
2013
0
                                       nsINavHistoryService::TRANSITION_EMBED,
2014
0
                                       nsINavHistoryService::TRANSITION_FRAMED_LINK) +
2015
0
                       NS_LITERAL_CSTRING("LIMIT 1) "
2016
0
          "{QUERY_OPTIONS} "
2017
0
        );
2018
0
2019
0
    queryString.AppendLiteral("ORDER BY ");
2020
0
    if (sortingMode == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING)
2021
0
      queryString.AppendLiteral("last_visit_date DESC ");
2022
0
    else
2023
0
      queryString.AppendLiteral("visit_count DESC ");
2024
0
2025
0
    queryString.AppendLiteral("LIMIT ");
2026
0
    queryString.AppendInt(aOptions->MaxResults());
2027
0
2028
0
    nsAutoCString additionalQueryOptions;
2029
0
2030
0
    queryString.ReplaceSubstring("{QUERY_OPTIONS}",
2031
0
                                  additionalQueryOptions.get());
2032
0
    return NS_OK;
2033
0
  }
2034
0
2035
0
  // If the query is a tag query, the type is bookmarks.
2036
0
  if (!aQuery->Tags().IsEmpty()) {
2037
0
    aOptions->SetQueryType(nsNavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS);
2038
0
  }
2039
0
2040
0
  nsAutoCString conditions;
2041
0
  nsCString queryClause;
2042
0
  rv = QueryToSelectClause(aQuery, aOptions, &queryClause);
2043
0
  NS_ENSURE_SUCCESS(rv, rv);
2044
0
  if (!queryClause.IsEmpty()) {
2045
0
    // TODO: This should be set on a case basis, not blindly.
2046
0
    aParamsPresent = true;
2047
0
    conditions += queryClause;
2048
0
  }
2049
0
2050
0
  // Determine whether we can push maxResults constraints into the query
2051
0
  // as LIMIT, or if we need to do result count clamping later
2052
0
  // using FilterResultSet()
2053
0
  bool useLimitClause = !NeedToFilterResultSet(aQuery, aOptions);
2054
0
2055
0
  PlacesSQLQueryBuilder queryStringBuilder(conditions, aQuery, aOptions,
2056
0
                                           useLimitClause, aAddParams,
2057
0
                                           hasSearchTerms);
2058
0
  rv = queryStringBuilder.GetQueryString(queryString);
2059
0
  NS_ENSURE_SUCCESS(rv, rv);
2060
0
2061
0
  return NS_OK;
2062
0
}
2063
2064
// nsNavHistory::GetQueryResults
2065
//
2066
//    Call this to get the results from a complex query. This is used by
2067
//    nsNavHistoryQueryResultNode to populate its children. For simple bookmark
2068
//    queries, use nsNavBookmarks::QueryFolderChildren.
2069
//
2070
//    THIS DOES NOT DO SORTING. You will need to sort the container yourself
2071
//    when you get the results. This is because sorting depends on tree
2072
//    statistics that will be built from the perspective of the tree. See
2073
//    nsNavHistoryQueryResultNode::FillChildren
2074
//
2075
//    FIXME: This only does keyword searching for the first query, and does
2076
//    it ANDed with the all the rest of the queries.
2077
2078
nsresult
2079
nsNavHistory::GetQueryResults(nsNavHistoryQueryResultNode *aResultNode,
2080
                              const RefPtr<nsNavHistoryQuery>& aQuery,
2081
                              const RefPtr<nsNavHistoryQueryOptions>& aOptions,
2082
                              nsCOMArray<nsNavHistoryResultNode>* aResults)
2083
0
{
2084
0
  NS_ENSURE_ARG_POINTER(aQuery);
2085
0
  NS_ENSURE_ARG_POINTER(aOptions);
2086
0
  NS_ASSERTION(aResults->Count() == 0, "Initial result array must be empty");
2087
0
2088
0
2089
0
  nsCString queryString;
2090
0
  bool paramsPresent = false;
2091
0
  nsNavHistory::StringHash addParams(HISTORY_DATE_CONT_LENGTH);
2092
0
  nsresult rv = ConstructQueryString(aQuery, aOptions, queryString,
2093
0
                                     paramsPresent, addParams);
2094
0
  NS_ENSURE_SUCCESS(rv,rv);
2095
0
2096
0
  // create statement
2097
0
  nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(queryString);
2098
#ifdef DEBUG
2099
  if (!statement) {
2100
    nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
2101
    if (conn) {
2102
      nsAutoCString lastErrorString;
2103
      (void)conn->GetLastErrorString(lastErrorString);
2104
      int32_t lastError = 0;
2105
      (void)conn->GetLastError(&lastError);
2106
      printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n",
2107
            queryString.get(), lastError, lastErrorString.get());
2108
    }
2109
  }
2110
#endif
2111
0
  NS_ENSURE_STATE(statement);
2112
0
  mozStorageStatementScoper scoper(statement);
2113
0
2114
0
  if (paramsPresent) {
2115
0
    rv = BindQueryClauseParameters(statement, aQuery, aOptions);
2116
0
    NS_ENSURE_SUCCESS(rv, rv);
2117
0
  }
2118
0
2119
0
  for (auto iter = addParams.Iter(); !iter.Done(); iter.Next()) {
2120
0
    nsresult rv = statement->BindUTF8StringByName(iter.Key(), iter.Data());
2121
0
    if (NS_FAILED(rv)) {
2122
0
      break;
2123
0
    }
2124
0
  }
2125
0
2126
0
  // Optimize the case where there is no need for any post-query filtering.
2127
0
  if (NeedToFilterResultSet(aQuery, aOptions)) {
2128
0
    // Generate the top-level results.
2129
0
    nsCOMArray<nsNavHistoryResultNode> toplevel;
2130
0
    rv = ResultsAsList(statement, aOptions, &toplevel);
2131
0
    NS_ENSURE_SUCCESS(rv, rv);
2132
0
2133
0
    FilterResultSet(aResultNode, toplevel, aResults, aQuery, aOptions);
2134
0
  } else {
2135
0
    rv = ResultsAsList(statement, aOptions, aResults);
2136
0
    NS_ENSURE_SUCCESS(rv, rv);
2137
0
  }
2138
0
2139
0
  return NS_OK;
2140
0
}
2141
2142
NS_IMETHODIMP
2143
nsNavHistory::AddObserver(nsINavHistoryObserver* aObserver, bool aOwnsWeak)
2144
0
{
2145
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2146
0
  NS_ENSURE_ARG(aObserver);
2147
0
2148
0
  if (NS_WARN_IF(!mCanNotify))
2149
0
    return NS_ERROR_UNEXPECTED;
2150
0
2151
0
  return mObservers.AppendWeakElement(aObserver, aOwnsWeak);
2152
0
}
2153
2154
NS_IMETHODIMP
2155
nsNavHistory::RemoveObserver(nsINavHistoryObserver* aObserver)
2156
0
{
2157
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2158
0
  NS_ENSURE_ARG(aObserver);
2159
0
2160
0
  return mObservers.RemoveWeakElement(aObserver);
2161
0
}
2162
2163
NS_IMETHODIMP
2164
nsNavHistory::GetObservers(uint32_t* _count,
2165
                           nsINavHistoryObserver*** _observers)
2166
0
{
2167
0
  NS_ENSURE_ARG_POINTER(_count);
2168
0
  NS_ENSURE_ARG_POINTER(_observers);
2169
0
2170
0
  *_count = 0;
2171
0
  *_observers = nullptr;
2172
0
2173
0
  // Clear any cached value, cause it's very likely the consumer has made
2174
0
  // changes to history and is now trying to notify them.
2175
0
  mDaysOfHistory = -1;
2176
0
2177
0
  if (!mCanNotify)
2178
0
    return NS_OK;
2179
0
2180
0
  nsCOMArray<nsINavHistoryObserver> observers;
2181
0
2182
0
  // Then add the other observers.
2183
0
  for (uint32_t i = 0; i < mObservers.Length(); ++i) {
2184
0
    const nsCOMPtr<nsINavHistoryObserver> &observer = mObservers.ElementAt(i).GetValue();
2185
0
    // Skip nullified weak observers.
2186
0
    if (observer)
2187
0
      observers.AppendElement(observer);
2188
0
  }
2189
0
2190
0
  if (observers.Count() == 0)
2191
0
    return NS_OK;
2192
0
2193
0
  *_count = observers.Count();
2194
0
  observers.Forget(_observers);
2195
0
2196
0
  return NS_OK;
2197
0
}
2198
2199
NS_IMETHODIMP
2200
nsNavHistory::GetHistoryDisabled(bool *_retval)
2201
0
{
2202
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2203
0
  NS_ENSURE_ARG_POINTER(_retval);
2204
0
2205
0
  *_retval = IsHistoryDisabled();
2206
0
  return NS_OK;
2207
0
}
2208
2209
// Call this method before visiting a URL in order to help determine the
2210
// transition type of the visit.
2211
//
2212
// @see MarkPageAsFollowedBookmark
2213
2214
NS_IMETHODIMP
2215
nsNavHistory::MarkPageAsTyped(nsIURI *aURI)
2216
0
{
2217
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2218
0
  NS_ENSURE_ARG(aURI);
2219
0
2220
0
  // don't add when history is disabled
2221
0
  if (IsHistoryDisabled())
2222
0
    return NS_OK;
2223
0
2224
0
  nsAutoCString uriString;
2225
0
  nsresult rv = aURI->GetSpec(uriString);
2226
0
  NS_ENSURE_SUCCESS(rv, rv);
2227
0
2228
0
  mRecentTyped.Put(uriString, GetNow());
2229
0
2230
0
  if (mRecentTyped.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH)
2231
0
    ExpireNonrecentEvents(&mRecentTyped);
2232
0
2233
0
  return NS_OK;
2234
0
}
2235
2236
2237
// Call this method before visiting a URL in order to help determine the
2238
// transition type of the visit.
2239
//
2240
// @see MarkPageAsTyped
2241
2242
NS_IMETHODIMP
2243
nsNavHistory::MarkPageAsFollowedLink(nsIURI *aURI)
2244
0
{
2245
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2246
0
  NS_ENSURE_ARG(aURI);
2247
0
2248
0
  // don't add when history is disabled
2249
0
  if (IsHistoryDisabled())
2250
0
    return NS_OK;
2251
0
2252
0
  nsAutoCString uriString;
2253
0
  nsresult rv = aURI->GetSpec(uriString);
2254
0
  NS_ENSURE_SUCCESS(rv, rv);
2255
0
2256
0
  mRecentLink.Put(uriString, GetNow());
2257
0
2258
0
  if (mRecentLink.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH)
2259
0
    ExpireNonrecentEvents(&mRecentLink);
2260
0
2261
0
  return NS_OK;
2262
0
}
2263
2264
2265
////////////////////////////////////////////////////////////////////////////////
2266
//// mozIStorageVacuumParticipant
2267
2268
NS_IMETHODIMP
2269
nsNavHistory::GetDatabaseConnection(mozIStorageConnection** _DBConnection)
2270
0
{
2271
0
  return GetDBConnection(_DBConnection);
2272
0
}
2273
2274
2275
NS_IMETHODIMP
2276
nsNavHistory::GetExpectedDatabasePageSize(int32_t* _expectedPageSize)
2277
0
{
2278
0
  NS_ENSURE_STATE(mDB);
2279
0
  NS_ENSURE_STATE(mDB->MainConn());
2280
0
  return mDB->MainConn()->GetDefaultPageSize(_expectedPageSize);
2281
0
}
2282
2283
2284
NS_IMETHODIMP
2285
nsNavHistory::OnBeginVacuum(bool* _vacuumGranted)
2286
0
{
2287
0
  // TODO: Check if we have to deny the vacuum in some heavy-load case.
2288
0
  // We could maybe want to do that during batches?
2289
0
  *_vacuumGranted = true;
2290
0
  return NS_OK;
2291
0
}
2292
2293
2294
NS_IMETHODIMP
2295
nsNavHistory::OnEndVacuum(bool aSucceeded)
2296
0
{
2297
0
  NS_WARNING_ASSERTION(aSucceeded, "Places.sqlite vacuum failed.");
2298
0
  return NS_OK;
2299
0
}
2300
2301
NS_IMETHODIMP
2302
nsNavHistory::GetDBConnection(mozIStorageConnection **_DBConnection)
2303
0
{
2304
0
  NS_ENSURE_ARG_POINTER(_DBConnection);
2305
0
  nsCOMPtr<mozIStorageConnection> connection = mDB->MainConn();
2306
0
  connection.forget(_DBConnection);
2307
0
2308
0
  return NS_OK;
2309
0
}
2310
2311
NS_IMETHODIMP
2312
nsNavHistory::GetShutdownClient(nsIAsyncShutdownClient **_shutdownClient)
2313
0
{
2314
0
  NS_ENSURE_ARG_POINTER(_shutdownClient);
2315
0
  nsCOMPtr<nsIAsyncShutdownClient> client = mDB->GetClientsShutdown();
2316
0
  if (!client) {
2317
0
    return NS_ERROR_UNEXPECTED;
2318
0
  }
2319
0
  client.forget(_shutdownClient);
2320
0
  return NS_OK;
2321
0
}
2322
2323
NS_IMETHODIMP
2324
nsNavHistory::GetConnectionShutdownClient(nsIAsyncShutdownClient **_shutdownClient)
2325
0
{
2326
0
  NS_ENSURE_ARG_POINTER(_shutdownClient);
2327
0
  nsCOMPtr<nsIAsyncShutdownClient> client = mDB->GetConnectionShutdown();
2328
0
  if (!client) {
2329
0
    return NS_ERROR_UNEXPECTED;
2330
0
  }
2331
0
  client.forget(_shutdownClient);
2332
0
  return NS_OK;
2333
0
}
2334
2335
NS_IMETHODIMP
2336
nsNavHistory::AsyncExecuteLegacyQuery(nsINavHistoryQuery* aQuery,
2337
                                      nsINavHistoryQueryOptions* aOptions,
2338
                                      mozIStorageStatementCallback* aCallback,
2339
                                      mozIStoragePendingStatement** _stmt)
2340
0
{
2341
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2342
0
  NS_ENSURE_ARG(aQuery);
2343
0
  NS_ENSURE_ARG(aOptions);
2344
0
  NS_ENSURE_ARG(aCallback);
2345
0
  NS_ENSURE_ARG_POINTER(_stmt);
2346
0
2347
0
  RefPtr<nsNavHistoryQuery> query = do_QueryObject(aQuery);
2348
0
  NS_ENSURE_STATE(query);
2349
0
  RefPtr<nsNavHistoryQueryOptions> options = do_QueryObject(aOptions);
2350
0
  NS_ENSURE_ARG(options);
2351
0
2352
0
  nsCString queryString;
2353
0
  bool paramsPresent = false;
2354
0
  nsNavHistory::StringHash addParams(HISTORY_DATE_CONT_LENGTH);
2355
0
  nsresult rv = ConstructQueryString(query, options, queryString,
2356
0
                                     paramsPresent, addParams);
2357
0
  NS_ENSURE_SUCCESS(rv,rv);
2358
0
2359
0
  nsCOMPtr<mozIStorageAsyncStatement> statement =
2360
0
    mDB->GetAsyncStatement(queryString);
2361
0
  NS_ENSURE_STATE(statement);
2362
0
2363
#ifdef DEBUG
2364
  if (NS_FAILED(rv)) {
2365
    nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
2366
    if (conn) {
2367
      nsAutoCString lastErrorString;
2368
      (void)mDB->MainConn()->GetLastErrorString(lastErrorString);
2369
      int32_t lastError = 0;
2370
      (void)mDB->MainConn()->GetLastError(&lastError);
2371
      printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n",
2372
            queryString.get(), lastError, lastErrorString.get());
2373
    }
2374
  }
2375
#endif
2376
0
  NS_ENSURE_SUCCESS(rv, rv);
2377
0
2378
0
  if (paramsPresent) {
2379
0
    rv = BindQueryClauseParameters(statement, query, options);
2380
0
    NS_ENSURE_SUCCESS(rv, rv);
2381
0
  }
2382
0
2383
0
  for (auto iter = addParams.Iter(); !iter.Done(); iter.Next()) {
2384
0
    nsresult rv = statement->BindUTF8StringByName(iter.Key(), iter.Data());
2385
0
    if (NS_FAILED(rv)) {
2386
0
      break;
2387
0
    }
2388
0
  }
2389
0
2390
0
  rv = statement->ExecuteAsync(aCallback, _stmt);
2391
0
  NS_ENSURE_SUCCESS(rv, rv);
2392
0
2393
0
  return NS_OK;
2394
0
}
2395
2396
////////////////////////////////////////////////////////////////////////////////
2397
//// nsIObserver
2398
2399
NS_IMETHODIMP
2400
nsNavHistory::Observe(nsISupports *aSubject, const char *aTopic,
2401
                    const char16_t *aData)
2402
0
{
2403
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2404
0
  if (strcmp(aTopic, TOPIC_PROFILE_TEARDOWN) == 0 ||
2405
0
      strcmp(aTopic, TOPIC_PROFILE_CHANGE) == 0 ||
2406
0
      strcmp(aTopic, TOPIC_SIMULATE_PLACES_SHUTDOWN) == 0) {
2407
0
    // These notifications are used by tests to simulate a Places shutdown.
2408
0
    // They should just be forwarded to the Database handle.
2409
0
    mDB->Observe(aSubject, aTopic, aData);
2410
0
  }
2411
0
2412
0
  else if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) {
2413
0
    // Don't even try to notify observers from this point on, the category
2414
0
    // cache would init services that could try to use our APIs.
2415
0
    mCanNotify = false;
2416
0
    mObservers.Clear();
2417
0
  }
2418
0
2419
0
#ifdef MOZ_XUL
2420
0
  else if (strcmp(aTopic, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING) == 0) {
2421
0
    nsCOMPtr<nsIAutoCompleteInput> input = do_QueryInterface(aSubject);
2422
0
    if (!input)
2423
0
      return NS_OK;
2424
0
2425
0
    // If the source is a private window, don't add any input history.
2426
0
    bool isPrivate;
2427
0
    nsresult rv = input->GetInPrivateContext(&isPrivate);
2428
0
    NS_ENSURE_SUCCESS(rv, rv);
2429
0
    if (isPrivate)
2430
0
      return NS_OK;
2431
0
2432
0
    nsCOMPtr<nsIAutoCompletePopup> popup;
2433
0
    input->GetPopup(getter_AddRefs(popup));
2434
0
    if (!popup)
2435
0
      return NS_OK;
2436
0
2437
0
    nsCOMPtr<nsIAutoCompleteController> controller;
2438
0
    input->GetController(getter_AddRefs(controller));
2439
0
    if (!controller)
2440
0
      return NS_OK;
2441
0
2442
0
    // Don't bother if the popup is closed
2443
0
    bool open;
2444
0
    rv = popup->GetPopupOpen(&open);
2445
0
    NS_ENSURE_SUCCESS(rv, rv);
2446
0
    if (!open)
2447
0
      return NS_OK;
2448
0
2449
0
    // Ignore if nothing selected from the popup
2450
0
    int32_t selectedIndex;
2451
0
    rv = popup->GetSelectedIndex(&selectedIndex);
2452
0
    NS_ENSURE_SUCCESS(rv, rv);
2453
0
    if (selectedIndex == -1)
2454
0
      return NS_OK;
2455
0
2456
0
    rv = AutoCompleteFeedback(selectedIndex, controller);
2457
0
    NS_ENSURE_SUCCESS(rv, rv);
2458
0
  }
2459
0
2460
0
#endif
2461
0
  else if (strcmp(aTopic, TOPIC_PREF_CHANGED) == 0) {
2462
0
    LoadPrefs();
2463
0
  }
2464
0
2465
0
  else if (strcmp(aTopic, TOPIC_IDLE_DAILY) == 0) {
2466
0
    (void)FixAndDecayFrecency();
2467
0
  }
2468
0
2469
0
  return NS_OK;
2470
0
}
2471
2472
nsresult
2473
nsNavHistory::FixAndDecayFrecency()
2474
0
{
2475
0
  float decayRate = Preferences::GetFloat(PREF_FREC_DECAY_RATE,
2476
0
                                          PREF_FREC_DECAY_RATE_DEF);
2477
0
  if (decayRate > 1.0f) {
2478
0
    MOZ_ASSERT(false, "The frecency decay rate should not be greater than 1.0");
2479
0
    decayRate = PREF_FREC_DECAY_RATE_DEF;
2480
0
  }
2481
0
2482
0
  RefPtr<FixAndDecayFrecencyRunnable> runnable =
2483
0
    new FixAndDecayFrecencyRunnable(mDB, decayRate);
2484
0
  nsCOMPtr<nsIEventTarget> target = do_GetInterface(mDB->MainConn());
2485
0
  NS_ENSURE_STATE(target);
2486
0
2487
0
  mDecayFrecencyPendingCount++;
2488
0
  return target->Dispatch(runnable, NS_DISPATCH_NORMAL);
2489
0
}
2490
2491
void
2492
nsNavHistory::DecayFrecencyCompleted(uint16_t reason)
2493
0
{
2494
0
  MOZ_ASSERT(mDecayFrecencyPendingCount > 0);
2495
0
  mDecayFrecencyPendingCount--;
2496
0
  if (mozIStorageStatementCallback::REASON_FINISHED == reason) {
2497
0
    NotifyManyFrecenciesChanged();
2498
0
  }
2499
0
}
2500
2501
bool
2502
nsNavHistory::IsFrecencyDecaying() const
2503
0
{
2504
0
  return mDecayFrecencyPendingCount > 0;
2505
0
}
2506
2507
2508
// Query stuff *****************************************************************
2509
2510
// Helper class for QueryToSelectClause
2511
//
2512
// This class helps to build part of the WHERE clause.
2513
2514
class ConditionBuilder
2515
{
2516
public:
2517
  ConditionBuilder& Condition(const char* aStr)
2518
0
  {
2519
0
    if (!mClause.IsEmpty())
2520
0
      mClause.AppendLiteral(" AND ");
2521
0
    Str(aStr);
2522
0
    return *this;
2523
0
  }
2524
2525
  ConditionBuilder& Str(const char* aStr)
2526
0
  {
2527
0
    mClause.Append(' ');
2528
0
    mClause.Append(aStr);
2529
0
    mClause.Append(' ');
2530
0
    return *this;
2531
0
  }
2532
2533
  ConditionBuilder& Param(const char* aParam)
2534
0
  {
2535
0
    mClause.Append(' ');
2536
0
    mClause.Append(aParam);
2537
0
    mClause.Append(' ');
2538
0
    return *this;
2539
0
  }
2540
2541
  void GetClauseString(nsCString& aResult)
2542
0
  {
2543
0
    aResult = mClause;
2544
0
  }
2545
2546
private:
2547
2548
  nsCString mClause;
2549
};
2550
2551
2552
// nsNavHistory::QueryToSelectClause
2553
//
2554
//    THE BEHAVIOR SHOULD BE IN SYNC WITH BindQueryClauseParameters
2555
//
2556
//    I don't check return values from the query object getters because there's
2557
//    no way for those to fail.
2558
2559
nsresult
2560
nsNavHistory::QueryToSelectClause(const RefPtr<nsNavHistoryQuery>& aQuery,
2561
                                  const RefPtr<nsNavHistoryQueryOptions>& aOptions,
2562
                                  nsCString* aClause)
2563
0
{
2564
0
  bool hasIt;
2565
0
  // We don't use the value from options here - we post filter if that
2566
0
  // is set.
2567
0
  bool excludeQueries = false;
2568
0
2569
0
  ConditionBuilder clause;
2570
0
2571
0
  if ((NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) ||
2572
0
    (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)) {
2573
0
    clause.Condition("EXISTS (SELECT 1 FROM moz_historyvisits "
2574
0
                              "WHERE place_id = h.id");
2575
0
    // begin time
2576
0
    if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt)
2577
0
      clause.Condition("visit_date >=").Param(":begin_time");
2578
0
    // end time
2579
0
    if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)
2580
0
      clause.Condition("visit_date <=").Param(":end_time");
2581
0
    clause.Str(" LIMIT 1)");
2582
0
  }
2583
0
2584
0
  // search terms
2585
0
  int32_t searchBehavior = mozIPlacesAutoComplete::BEHAVIOR_HISTORY |
2586
0
                           mozIPlacesAutoComplete::BEHAVIOR_BOOKMARK;
2587
0
  if (!aQuery->SearchTerms().IsEmpty()) {
2588
0
    // Re-use the autocomplete_match function.  Setting the behavior to match
2589
0
    // history or typed history or bookmarks or open pages will match almost
2590
0
    // everything.
2591
0
    clause.Condition("AUTOCOMPLETE_MATCH(").Param(":search_string")
2592
0
          .Str(", h.url, page_title, tags, ")
2593
0
          .Str(nsPrintfCString("1, 1, 1, 1, %d, %d)",
2594
0
                               mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED,
2595
0
                               searchBehavior).get());
2596
0
    // Serching by terms implicitly exclude queries.
2597
0
    excludeQueries = true;
2598
0
  }
2599
0
2600
0
  // min and max visit count
2601
0
  if (aQuery->MinVisits() >= 0)
2602
0
    clause.Condition("h.visit_count >=").Param(":min_visits");
2603
0
2604
0
  if (aQuery->MaxVisits() >= 0)
2605
0
    clause.Condition("h.visit_count <=").Param(":max_visits");
2606
0
2607
0
  // only bookmarked, has no affect on bookmarks-only queries
2608
0
  if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS &&
2609
0
      aQuery->OnlyBookmarked())
2610
0
    clause.Condition("EXISTS (SELECT b.fk FROM moz_bookmarks b WHERE b.type = ")
2611
0
          .Str(nsPrintfCString("%d", nsNavBookmarks::TYPE_BOOKMARK).get())
2612
0
          .Str("AND b.fk = h.id)");
2613
0
2614
0
  // domain
2615
0
  if (!aQuery->Domain().IsVoid()) {
2616
0
    bool domainIsHost = false;
2617
0
    aQuery->GetDomainIsHost(&domainIsHost);
2618
0
    if (domainIsHost)
2619
0
      clause.Condition("h.rev_host =").Param(":domain_lower");
2620
0
    else
2621
0
      // see domain setting in BindQueryClauseParameters for why we do this
2622
0
      clause.Condition("h.rev_host >=").Param(":domain_lower")
2623
0
            .Condition("h.rev_host <").Param(":domain_upper");
2624
0
  }
2625
0
2626
0
  // URI
2627
0
  if (aQuery->Uri()) {
2628
0
    clause.Condition("h.url_hash = hash(").Param(":uri").Str(")")
2629
0
          .Condition("h.url =").Param(":uri");
2630
0
  }
2631
0
2632
0
  // annotation
2633
0
  if (!aQuery->Annotation().IsEmpty()) {
2634
0
    clause.Condition("");
2635
0
    if (aQuery->AnnotationIsNot())
2636
0
      clause.Str("NOT");
2637
0
    clause.Str(
2638
0
      "EXISTS "
2639
0
        "(SELECT h.id "
2640
0
         "FROM moz_annos anno "
2641
0
         "JOIN moz_anno_attributes annoname "
2642
0
           "ON anno.anno_attribute_id = annoname.id "
2643
0
         "WHERE anno.place_id = h.id "
2644
0
           "AND annoname.name = ").Param(":anno").Str(")");
2645
0
    // annotation-based queries don't get the common conditions, so you get
2646
0
    // all URLs with that annotation
2647
0
  }
2648
0
2649
0
  // tags
2650
0
  const nsTArray<nsString> &tags = aQuery->Tags();
2651
0
  if (tags.Length() > 0) {
2652
0
    clause.Condition("h.id");
2653
0
    if (aQuery->TagsAreNot())
2654
0
      clause.Str("NOT");
2655
0
    clause.Str(
2656
0
      "IN "
2657
0
        "(SELECT bms.fk "
2658
0
         "FROM moz_bookmarks bms "
2659
0
         "JOIN moz_bookmarks tags ON bms.parent = tags.id "
2660
0
         "WHERE tags.parent =").
2661
0
           Param(":tags_folder").
2662
0
           Str("AND lower(tags.title) IN (");
2663
0
    for (uint32_t i = 0; i < tags.Length(); ++i) {
2664
0
      nsPrintfCString param(":tag%d_", i);
2665
0
      clause.Param(param.get());
2666
0
      if (i < tags.Length() - 1)
2667
0
        clause.Str(",");
2668
0
    }
2669
0
    clause.Str(")");
2670
0
    if (!aQuery->TagsAreNot()) {
2671
0
      clause.Str("GROUP BY bms.fk HAVING count(*) >=").Param(":tag_count");
2672
0
    }
2673
0
    clause.Str(")");
2674
0
  }
2675
0
2676
0
  // transitions
2677
0
  const nsTArray<uint32_t>& transitions = aQuery->Transitions();
2678
0
  for (uint32_t i = 0; i < transitions.Length(); ++i) {
2679
0
    nsPrintfCString param(":transition%d_", i);
2680
0
    clause.Condition("h.id IN (SELECT place_id FROM moz_historyvisits "
2681
0
                             "WHERE visit_type = ")
2682
0
          .Param(param.get())
2683
0
          .Str(")");
2684
0
  }
2685
0
2686
0
  // parents
2687
0
  const nsTArray<nsCString>& parents = aQuery->Parents();
2688
0
  if (parents.Length() > 0) {
2689
0
    aOptions->SetQueryType(nsNavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS);
2690
0
    clause.Condition("b.parent IN( "
2691
0
                       "WITH RECURSIVE parents(id) AS ( "
2692
0
                         "SELECT id FROM moz_bookmarks WHERE GUID IN (");
2693
0
2694
0
    for (uint32_t i = 0; i < parents.Length(); ++i) {
2695
0
      nsPrintfCString param(":parentguid%d_", i);
2696
0
      clause.Param(param.get());
2697
0
      if (i < parents.Length() - 1) {
2698
0
        clause.Str(",");
2699
0
      }
2700
0
    }
2701
0
    clause.Str(          ") "
2702
0
                         "UNION ALL "
2703
0
                         "SELECT b2.id "
2704
0
                         "FROM moz_bookmarks b2 "
2705
0
                         "JOIN parents p ON b2.parent = p.id "
2706
0
                         "WHERE b2.type = 2 "
2707
0
                       ") "
2708
0
                       "SELECT id FROM parents "
2709
0
                     ")");
2710
0
  }
2711
0
2712
0
  if (excludeQueries) {
2713
0
    // Serching by terms implicitly exclude queries and folder shortcuts.
2714
0
    clause.Condition("NOT h.url_hash BETWEEN hash('place', 'prefix_lo') AND "
2715
0
                                            "hash('place', 'prefix_hi')");
2716
0
  }
2717
0
2718
0
  clause.GetClauseString(*aClause);
2719
0
  return NS_OK;
2720
0
}
2721
2722
2723
// nsNavHistory::BindQueryClauseParameters
2724
//
2725
//    THE BEHAVIOR SHOULD BE IN SYNC WITH QueryToSelectClause
2726
2727
nsresult
2728
nsNavHistory::BindQueryClauseParameters(mozIStorageBaseStatement* statement,
2729
                                        const RefPtr<nsNavHistoryQuery>& aQuery,
2730
                                        const RefPtr<nsNavHistoryQueryOptions>& aOptions)
2731
0
{
2732
0
  nsresult rv;
2733
0
2734
0
  bool hasIt;
2735
0
  // begin time
2736
0
  if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) {
2737
0
    PRTime time = NormalizeTime(aQuery->BeginTimeReference(),
2738
0
                                aQuery->BeginTime());
2739
0
    rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("begin_time"), time);
2740
0
    NS_ENSURE_SUCCESS(rv, rv);
2741
0
  }
2742
0
2743
0
  // end time
2744
0
  if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt) {
2745
0
    PRTime time = NormalizeTime(aQuery->EndTimeReference(),
2746
0
                                aQuery->EndTime());
2747
0
    rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("end_time"), time);
2748
0
    NS_ENSURE_SUCCESS(rv, rv);
2749
0
  }
2750
0
2751
0
  // search terms
2752
0
  if (!aQuery->SearchTerms().IsEmpty()) {
2753
0
    rv = statement->BindStringByName(NS_LITERAL_CSTRING("search_string"),
2754
0
                                     aQuery->SearchTerms());
2755
0
    NS_ENSURE_SUCCESS(rv, rv);
2756
0
  }
2757
0
2758
0
  // min and max visit count
2759
0
  int32_t visits = aQuery->MinVisits();
2760
0
  if (visits >= 0) {
2761
0
    rv = statement->BindInt32ByName(NS_LITERAL_CSTRING("min_visits"), visits);
2762
0
    NS_ENSURE_SUCCESS(rv, rv);
2763
0
  }
2764
0
2765
0
  visits = aQuery->MaxVisits();
2766
0
  if (visits >= 0) {
2767
0
    rv = statement->BindInt32ByName(NS_LITERAL_CSTRING("max_visits"), visits);
2768
0
    NS_ENSURE_SUCCESS(rv, rv);
2769
0
  }
2770
0
2771
0
  // domain (see GetReversedHostname for more info on reversed host names)
2772
0
  if (!aQuery->Domain().IsVoid()) {
2773
0
    nsString revDomain;
2774
0
    GetReversedHostname(NS_ConvertUTF8toUTF16(aQuery->Domain()), revDomain);
2775
0
2776
0
    if (aQuery->DomainIsHost()) {
2777
0
      rv = statement->BindStringByName(NS_LITERAL_CSTRING("domain_lower"),
2778
0
                                       revDomain);
2779
0
      NS_ENSURE_SUCCESS(rv, rv);
2780
0
    } else {
2781
0
      // for "mozilla.org" do query >= "gro.allizom." AND < "gro.allizom/"
2782
0
      // which will get everything starting with "gro.allizom." while using the
2783
0
      // index (using SUBSTRING() causes indexes to be discarded).
2784
0
      NS_ASSERTION(revDomain[revDomain.Length() - 1] == '.', "Invalid rev. host");
2785
0
      rv = statement->BindStringByName(NS_LITERAL_CSTRING("domain_lower"),
2786
0
                                       revDomain);
2787
0
      NS_ENSURE_SUCCESS(rv, rv);
2788
0
      revDomain.Truncate(revDomain.Length() - 1);
2789
0
      revDomain.Append(char16_t('/'));
2790
0
      rv = statement->BindStringByName(NS_LITERAL_CSTRING("domain_upper"),
2791
0
                                       revDomain);
2792
0
      NS_ENSURE_SUCCESS(rv, rv);
2793
0
    }
2794
0
  }
2795
0
2796
0
  // URI
2797
0
  if (aQuery->Uri()) {
2798
0
    rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("uri"), aQuery->Uri());
2799
0
    NS_ENSURE_SUCCESS(rv, rv);
2800
0
  }
2801
0
2802
0
  // annotation
2803
0
  if (!aQuery->Annotation().IsEmpty()) {
2804
0
    rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("anno"),
2805
0
                                         aQuery->Annotation());
2806
0
    NS_ENSURE_SUCCESS(rv, rv);
2807
0
  }
2808
0
2809
0
  // tags
2810
0
  const nsTArray<nsString> &tags = aQuery->Tags();
2811
0
  if (tags.Length() > 0) {
2812
0
    for (uint32_t i = 0; i < tags.Length(); ++i) {
2813
0
      nsPrintfCString paramName("tag%d_", i);
2814
0
      NS_ConvertUTF16toUTF8 tag(tags[i]);
2815
0
      ToLowerCase(tag);
2816
0
      rv = statement->BindUTF8StringByName(paramName, tag);
2817
0
      NS_ENSURE_SUCCESS(rv, rv);
2818
0
    }
2819
0
    int64_t tagsFolder = GetTagsFolder();
2820
0
    rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("tags_folder"),
2821
0
                                    tagsFolder);
2822
0
    NS_ENSURE_SUCCESS(rv, rv);
2823
0
    if (!aQuery->TagsAreNot()) {
2824
0
      rv = statement->BindInt32ByName(NS_LITERAL_CSTRING("tag_count"),
2825
0
                                      tags.Length());
2826
0
      NS_ENSURE_SUCCESS(rv, rv);
2827
0
    }
2828
0
  }
2829
0
2830
0
  // transitions
2831
0
  const nsTArray<uint32_t>& transitions = aQuery->Transitions();
2832
0
  for (uint32_t i = 0; i < transitions.Length(); ++i) {
2833
0
    nsPrintfCString paramName("transition%d_", i);
2834
0
    rv = statement->BindInt64ByName(paramName, transitions[i]);
2835
0
    NS_ENSURE_SUCCESS(rv, rv);
2836
0
  }
2837
0
2838
0
  // parents
2839
0
  const nsTArray<nsCString>& parents = aQuery->Parents();
2840
0
  for (uint32_t i = 0; i < parents.Length(); ++i) {
2841
0
    nsPrintfCString paramName("parentguid%d_", i);
2842
0
    rv = statement->BindUTF8StringByName(paramName, parents[i]);
2843
0
    NS_ENSURE_SUCCESS(rv, rv);
2844
0
  }
2845
0
2846
0
  return NS_OK;
2847
0
}
2848
2849
2850
// nsNavHistory::ResultsAsList
2851
//
2852
2853
nsresult
2854
nsNavHistory::ResultsAsList(mozIStorageStatement* statement,
2855
                            nsNavHistoryQueryOptions* aOptions,
2856
                            nsCOMArray<nsNavHistoryResultNode>* aResults)
2857
0
{
2858
0
  nsresult rv;
2859
0
  nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv);
2860
0
  NS_ENSURE_SUCCESS(rv, rv);
2861
0
2862
0
  bool hasMore = false;
2863
0
  while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) {
2864
0
    RefPtr<nsNavHistoryResultNode> result;
2865
0
    rv = RowToResult(row, aOptions, getter_AddRefs(result));
2866
0
    NS_ENSURE_SUCCESS(rv, rv);
2867
0
    aResults->AppendElement(result.forget());
2868
0
  }
2869
0
  return NS_OK;
2870
0
}
2871
2872
const int64_t UNDEFINED_URN_VALUE = -1;
2873
2874
// Create a urn (like
2875
// urn:places-persist:place:group=0&group=1&sort=1&type=1,,%28local%20files%29)
2876
// to be used to persist the open state of this container
2877
nsresult
2878
CreatePlacesPersistURN(nsNavHistoryQueryResultNode *aResultNode,
2879
                       int64_t aValue, const nsCString& aTitle, nsCString& aURN)
2880
0
{
2881
0
  nsAutoCString uri;
2882
0
  nsresult rv = aResultNode->GetUri(uri);
2883
0
  NS_ENSURE_SUCCESS(rv, rv);
2884
0
2885
0
  aURN.AssignLiteral("urn:places-persist:");
2886
0
  aURN.Append(uri);
2887
0
2888
0
  aURN.Append(',');
2889
0
  if (aValue != UNDEFINED_URN_VALUE)
2890
0
    aURN.AppendInt(aValue);
2891
0
2892
0
  aURN.Append(',');
2893
0
  if (!aTitle.IsEmpty()) {
2894
0
    nsAutoCString escapedTitle;
2895
0
    bool success = NS_Escape(aTitle, escapedTitle, url_XAlphas);
2896
0
    NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
2897
0
    aURN.Append(escapedTitle);
2898
0
  }
2899
0
2900
0
  return NS_OK;
2901
0
}
2902
2903
int64_t
2904
nsNavHistory::GetTagsFolder()
2905
0
{
2906
0
  // cache our tags folder
2907
0
  // note, we can't do this in nsNavHistory::Init(),
2908
0
  // as getting the bookmarks service would initialize it.
2909
0
  if (mTagsFolder == -1) {
2910
0
    nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
2911
0
    NS_ENSURE_TRUE(bookmarks, -1);
2912
0
2913
0
    nsresult rv = bookmarks->GetTagsFolder(&mTagsFolder);
2914
0
    NS_ENSURE_SUCCESS(rv, -1);
2915
0
  }
2916
0
  return mTagsFolder;
2917
0
}
2918
2919
// nsNavHistory::FilterResultSet
2920
//
2921
// This does some post-query-execution filtering:
2922
//   - searching on title, url and tags
2923
//   - limit count
2924
//
2925
// Note:  changes to filtering in FilterResultSet()
2926
// may require changes to NeedToFilterResultSet()
2927
2928
// static
2929
nsresult
2930
nsNavHistory::FilterResultSet(nsNavHistoryQueryResultNode* aQueryNode,
2931
                              const nsCOMArray<nsNavHistoryResultNode>& aSet,
2932
                              nsCOMArray<nsNavHistoryResultNode>* aFiltered,
2933
                              const RefPtr<nsNavHistoryQuery>& aQuery,
2934
                              nsNavHistoryQueryOptions *aOptions)
2935
0
{
2936
0
  // parse the search terms
2937
0
  nsTArray<nsString> terms;
2938
0
  ParseSearchTermsFromQuery(aQuery, &terms);
2939
0
2940
0
  bool excludeQueries = aOptions->ExcludeQueries();
2941
0
  for (int32_t nodeIndex = 0; nodeIndex < aSet.Count(); nodeIndex++) {
2942
0
    if (excludeQueries && aSet[nodeIndex]->IsQuery()) {
2943
0
      continue;
2944
0
    }
2945
0
2946
0
    if (aSet[nodeIndex]->mItemId != -1 && aQueryNode &&
2947
0
        aQueryNode->mItemId == aSet[nodeIndex]->mItemId) {
2948
0
      continue;
2949
0
    }
2950
0
2951
0
    // If there are search terms, we are already getting only uri nodes,
2952
0
    // thus we don't need to filter node types. Though, we must check for
2953
0
    // matching terms.
2954
0
    if (terms.Length()) {
2955
0
      // Filter based on search terms.
2956
0
      // Convert title and url for the current node to UTF16 strings.
2957
0
      NS_ConvertUTF8toUTF16 nodeTitle(aSet[nodeIndex]->mTitle);
2958
0
      // Unescape the URL for search terms matching.
2959
0
      nsAutoCString cNodeURL(aSet[nodeIndex]->mURI);
2960
0
      NS_ConvertUTF8toUTF16 nodeURL(NS_UnescapeURL(cNodeURL));
2961
0
2962
0
      // Determine if every search term matches anywhere in the title, url or
2963
0
      // tag.
2964
0
      bool matchAllTerms = true;
2965
0
      for (int32_t termIndex = terms.Length() - 1;
2966
0
            termIndex >= 0 && matchAllTerms;
2967
0
            termIndex--) {
2968
0
        nsString& term = terms.ElementAt(termIndex);
2969
0
        // True if any of them match; false makes us quit the loop
2970
0
        matchAllTerms = CaseInsensitiveFindInReadable(term, nodeTitle) ||
2971
0
                        CaseInsensitiveFindInReadable(term, nodeURL) ||
2972
0
                        CaseInsensitiveFindInReadable(term, aSet[nodeIndex]->mTags);
2973
0
      }
2974
0
      // Skip the node if we don't match all terms in the title, url or tag
2975
0
      if (!matchAllTerms) {
2976
0
        continue;
2977
0
      }
2978
0
    }
2979
0
2980
0
    aFiltered->AppendObject(aSet[nodeIndex]);
2981
0
2982
0
    // Stop once we have reached max results.
2983
0
    if (aOptions->MaxResults() > 0 &&
2984
0
        (uint32_t)aFiltered->Count() >= aOptions->MaxResults())
2985
0
      break;
2986
0
  }
2987
0
2988
0
  return NS_OK;
2989
0
}
2990
2991
void
2992
nsNavHistory::registerEmbedVisit(nsIURI* aURI,
2993
                                 int64_t aTime)
2994
0
{
2995
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2996
0
2997
0
  VisitHashKey* visit = mEmbedVisits.PutEntry(aURI);
2998
0
  if (!visit) {
2999
0
    NS_WARNING("Unable to register a EMBED visit.");
3000
0
    return;
3001
0
  }
3002
0
  visit->visitTime = aTime;
3003
0
}
3004
3005
bool
3006
0
nsNavHistory::hasEmbedVisit(nsIURI* aURI) {
3007
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
3008
0
3009
0
  return !!mEmbedVisits.GetEntry(aURI);
3010
0
}
3011
3012
NS_IMETHODIMP
3013
0
nsNavHistory::ClearEmbedVisits() {
3014
0
  mEmbedVisits.Clear();
3015
0
  return NS_OK;
3016
0
}
3017
3018
NS_IMETHODIMP
3019
0
nsNavHistory::MakeGuid(nsACString& aGuid) {
3020
0
  if (NS_FAILED(GenerateGUID(aGuid))) {
3021
0
    MOZ_ASSERT(false, "Shouldn't fail to create a guid!");
3022
0
    aGuid.SetIsVoid(true);
3023
0
  }
3024
0
  return NS_OK;
3025
0
}
3026
3027
NS_IMETHODIMP
3028
nsNavHistory::HashURL(const nsACString& aSpec, const nsACString& aMode,
3029
0
                      uint64_t* _hash) {
3030
0
  return places::HashURL(aSpec, aMode, _hash);
3031
0
}
3032
3033
// nsNavHistory::CheckIsRecentEvent
3034
//
3035
//    Sees if this URL happened "recently."
3036
//
3037
//    It is always removed from our recent list no matter what. It only counts
3038
//    as "recent" if the event happened more recently than our event
3039
//    threshold ago.
3040
3041
bool
3042
nsNavHistory::CheckIsRecentEvent(RecentEventHash* hashTable,
3043
                                 const nsACString& url)
3044
0
{
3045
0
  PRTime eventTime;
3046
0
  if (hashTable->Get(url, reinterpret_cast<int64_t*>(&eventTime))) {
3047
0
    hashTable->Remove(url);
3048
0
    if (eventTime > GetNow() - RECENT_EVENT_THRESHOLD)
3049
0
      return true;
3050
0
    return false;
3051
0
  }
3052
0
  return false;
3053
0
}
3054
3055
3056
// nsNavHistory::ExpireNonrecentEvents
3057
//
3058
//    This goes through our
3059
3060
void
3061
nsNavHistory::ExpireNonrecentEvents(RecentEventHash* hashTable)
3062
0
{
3063
0
  int64_t threshold = GetNow() - RECENT_EVENT_THRESHOLD;
3064
0
  for (auto iter = hashTable->Iter(); !iter.Done(); iter.Next()) {
3065
0
    if (iter.Data() < threshold) {
3066
0
      iter.Remove();
3067
0
    }
3068
0
  }
3069
0
}
3070
3071
3072
// nsNavHistory::RowToResult
3073
//
3074
//    Here, we just have a generic row. It could be a query, URL, visit,
3075
//    or full visit.
3076
3077
nsresult
3078
nsNavHistory::RowToResult(mozIStorageValueArray* aRow,
3079
                          nsNavHistoryQueryOptions* aOptions,
3080
                          nsNavHistoryResultNode** aResult)
3081
0
{
3082
0
  NS_ASSERTION(aRow && aOptions && aResult, "Null pointer in RowToResult");
3083
0
3084
0
  // URL
3085
0
  nsAutoCString url;
3086
0
  nsresult rv = aRow->GetUTF8String(kGetInfoIndex_URL, url);
3087
0
  NS_ENSURE_SUCCESS(rv, rv);
3088
0
  // In case of data corruption URL may be null, but our UI code prefers an
3089
0
  // empty string.
3090
0
  if (url.IsVoid()) {
3091
0
    MOZ_ASSERT(false, "Found a NULL url in moz_places");
3092
0
    url.SetIsVoid(false);
3093
0
  }
3094
0
3095
0
  // title
3096
0
  nsAutoCString title;
3097
0
  bool isNull;
3098
0
  rv = aRow->GetIsNull(kGetInfoIndex_Title, &isNull);
3099
0
  NS_ENSURE_SUCCESS(rv, rv);
3100
0
  if (!isNull) {
3101
0
    rv = aRow->GetUTF8String(kGetInfoIndex_Title, title);
3102
0
    NS_ENSURE_SUCCESS(rv, rv);
3103
0
  }
3104
0
3105
0
  uint32_t accessCount = aRow->AsInt32(kGetInfoIndex_VisitCount);
3106
0
  PRTime time = aRow->AsInt64(kGetInfoIndex_VisitDate);
3107
0
3108
0
  // itemId
3109
0
  int64_t itemId = aRow->AsInt64(kGetInfoIndex_ItemId);
3110
0
  int64_t parentId = -1;
3111
0
  if (itemId == 0) {
3112
0
    // This is not a bookmark.  For non-bookmarks we use a -1 itemId value.
3113
0
    // Notice ids in sqlite tables start from 1, so itemId cannot ever be 0.
3114
0
    itemId = -1;
3115
0
  }
3116
0
  else {
3117
0
    // This is a bookmark, so it has a parent.
3118
0
    int64_t itemParentId = aRow->AsInt64(kGetInfoIndex_ItemParentId);
3119
0
    if (itemParentId > 0) {
3120
0
      // The Places root has parent == 0, but that item id does not really
3121
0
      // exist. We want to set the parent only if it's a real one.
3122
0
      parentId = itemParentId;
3123
0
    }
3124
0
  }
3125
0
3126
0
  if (IsQueryURI(url)) {
3127
0
    // Special case "place:" URIs: turn them into containers.
3128
0
    if (itemId != -1) {
3129
0
      // We should never expose the history title for query nodes if the
3130
0
      // bookmark-item's title is set to null (the history title may be the
3131
0
      // query string without the place: prefix). Thus we call getItemTitle
3132
0
      // explicitly. Doing this in the SQL query would be less performant since
3133
0
      // it should be done for all results rather than only for queries.
3134
0
      nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
3135
0
      NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
3136
0
3137
0
      rv = bookmarks->GetItemTitle(itemId, title);
3138
0
      NS_ENSURE_SUCCESS(rv, rv);
3139
0
    }
3140
0
3141
0
    nsAutoCString guid;
3142
0
    if (itemId != -1) {
3143
0
      rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid, guid);
3144
0
      NS_ENSURE_SUCCESS(rv, rv);
3145
0
    }
3146
0
3147
0
    if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
3148
0
        aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY) {
3149
0
      rv = aRow->GetUTF8String(kGetInfoIndex_Guid, guid);
3150
0
      NS_ENSURE_SUCCESS(rv, rv);
3151
0
    }
3152
0
3153
0
    RefPtr<nsNavHistoryResultNode> resultNode;
3154
0
    rv = QueryRowToResult(itemId, guid, url, title, accessCount, time,
3155
0
                          getter_AddRefs(resultNode));
3156
0
    NS_ENSURE_SUCCESS(rv, rv);
3157
0
3158
0
    if (itemId != -1 ||
3159
0
        aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT) {
3160
0
      // RESULTS_AS_TAGS_ROOT has date columns
3161
0
      resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded);
3162
0
      resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified);
3163
0
      if (resultNode->IsFolder()) {
3164
0
        // If it's a simple folder node (i.e. a shortcut to another folder), apply
3165
0
        // our options for it. However, if the parent type was tag query, we do not
3166
0
        // apply them, because it would not yield any results.
3167
0
        resultNode->GetAsContainer()->mOptions = aOptions;
3168
0
      }
3169
0
    }
3170
0
3171
0
    resultNode.forget(aResult);
3172
0
    return rv;
3173
0
  } else if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_URI) {
3174
0
    RefPtr<nsNavHistoryResultNode> resultNode =
3175
0
      new nsNavHistoryResultNode(url, title, accessCount, time);
3176
0
3177
0
    if (itemId != -1) {
3178
0
      resultNode->mItemId = itemId;
3179
0
      resultNode->mFolderId = parentId;
3180
0
      resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded);
3181
0
      resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified);
3182
0
3183
0
      rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid,
3184
0
                               resultNode->mBookmarkGuid);
3185
0
      NS_ENSURE_SUCCESS(rv, rv);
3186
0
    }
3187
0
3188
0
    resultNode->mFrecency = aRow->AsInt32(kGetInfoIndex_Frecency);
3189
0
    resultNode->mHidden = !!aRow->AsInt32(kGetInfoIndex_Hidden);
3190
0
3191
0
    nsAutoString tags;
3192
0
    rv = aRow->GetString(kGetInfoIndex_ItemTags, tags);
3193
0
    NS_ENSURE_SUCCESS(rv, rv);
3194
0
    if (!tags.IsVoid()) {
3195
0
      resultNode->mTags.Assign(tags);
3196
0
    }
3197
0
3198
0
    rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid);
3199
0
    NS_ENSURE_SUCCESS(rv, rv);
3200
0
3201
0
    resultNode.forget(aResult);
3202
0
    return NS_OK;
3203
0
  }
3204
0
3205
0
  if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_VISIT) {
3206
0
    RefPtr<nsNavHistoryResultNode> resultNode =
3207
0
      new nsNavHistoryResultNode(url, title, accessCount, time);
3208
0
3209
0
    nsAutoString tags;
3210
0
    rv = aRow->GetString(kGetInfoIndex_ItemTags, tags);
3211
0
    if (!tags.IsVoid())
3212
0
      resultNode->mTags.Assign(tags);
3213
0
3214
0
    rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid);
3215
0
    NS_ENSURE_SUCCESS(rv, rv);
3216
0
3217
0
    rv = aRow->GetInt64(kGetInfoIndex_VisitId, &resultNode->mVisitId);
3218
0
    NS_ENSURE_SUCCESS(rv, rv);
3219
0
3220
0
    int64_t fromVisitId;
3221
0
    rv = aRow->GetInt64(kGetInfoIndex_FromVisitId, &fromVisitId);
3222
0
    NS_ENSURE_SUCCESS(rv, rv);
3223
0
3224
0
    if (fromVisitId > 0) {
3225
0
      resultNode->mFromVisitId = fromVisitId;
3226
0
    }
3227
0
3228
0
    resultNode->mTransitionType = aRow->AsInt32(kGetInfoIndex_VisitType);
3229
0
3230
0
    resultNode.forget(aResult);
3231
0
    return NS_OK;
3232
0
  }
3233
0
3234
0
  return NS_ERROR_FAILURE;
3235
0
}
3236
3237
3238
// nsNavHistory::QueryRowToResult
3239
//
3240
//    Called by RowToResult when the URI is a place: URI to generate the proper
3241
//    folder or query node.
3242
3243
nsresult
3244
nsNavHistory::QueryRowToResult(int64_t itemId,
3245
                               const nsACString& aBookmarkGuid,
3246
                               const nsACString& aURI,
3247
                               const nsACString& aTitle,
3248
                               uint32_t aAccessCount,
3249
                               PRTime aTime,
3250
                               nsNavHistoryResultNode** aNode)
3251
0
{
3252
0
  // Only assert if the itemId is set. In some cases (e.g. virtual queries), we
3253
0
  // have a guid, but not an itemId.
3254
0
  if (itemId != -1) {
3255
0
    MOZ_ASSERT(!aBookmarkGuid.IsEmpty());
3256
0
  }
3257
0
3258
0
  nsCOMPtr<nsINavHistoryQuery> query;
3259
0
  nsCOMPtr<nsINavHistoryQueryOptions> options;
3260
0
  nsresult rv = QueryStringToQuery(aURI, getter_AddRefs(query),
3261
0
                                   getter_AddRefs(options));
3262
0
  RefPtr<nsNavHistoryResultNode> resultNode;
3263
0
  RefPtr<nsNavHistoryQuery> queryObj = do_QueryObject(query);
3264
0
  NS_ENSURE_STATE(queryObj);
3265
0
  RefPtr<nsNavHistoryQueryOptions> optionsObj = do_QueryObject(options);
3266
0
  NS_ENSURE_STATE(optionsObj);
3267
0
  // If this failed the query does not parse correctly, let the error pass and
3268
0
  // handle it later.
3269
0
  if (NS_SUCCEEDED(rv)) {
3270
0
    // Check if this is a folder shortcut, so we can take a faster path.
3271
0
    nsCString targetFolderGuid = GetSimpleBookmarksQueryParent(queryObj, optionsObj);
3272
0
    if (!targetFolderGuid.IsEmpty()) {
3273
0
      nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
3274
0
      NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
3275
0
3276
0
      rv = bookmarks->ResultNodeForContainer(targetFolderGuid, optionsObj,
3277
0
                                             getter_AddRefs(resultNode));
3278
0
      // If this failed the shortcut is pointing to nowhere, let the error pass
3279
0
      // and handle it later.
3280
0
      if (NS_SUCCEEDED(rv)) {
3281
0
        // At this point the node is set up like a regular folder node. Here
3282
0
        // we make the necessary change to make it a folder shortcut.
3283
0
        resultNode->mItemId = itemId;
3284
0
        resultNode->mBookmarkGuid = aBookmarkGuid;
3285
0
        resultNode->GetAsFolder()->mTargetFolderGuid = targetFolderGuid;
3286
0
3287
0
        // Use the query item title, unless it's empty (in that case use the
3288
0
        // concrete folder title).
3289
0
        if (!aTitle.IsEmpty()) {
3290
0
          resultNode->mTitle = aTitle;
3291
0
        }
3292
0
      }
3293
0
    }
3294
0
    else {
3295
0
      // This is a regular query.
3296
0
      resultNode = new nsNavHistoryQueryResultNode(aTitle, aTime, aURI, queryObj, optionsObj);
3297
0
      resultNode->mItemId = itemId;
3298
0
      resultNode->mBookmarkGuid = aBookmarkGuid;
3299
0
    }
3300
0
  }
3301
0
3302
0
  if (NS_FAILED(rv)) {
3303
0
    NS_WARNING("Generating a generic empty node for a broken query!");
3304
0
    // This is a broken query, that either did not parse or points to not
3305
0
    // existing data.  We don't want to return failure since that will kill the
3306
0
    // whole result.  Instead make a generic empty query node.
3307
0
    resultNode = new nsNavHistoryQueryResultNode(aTitle, 0, aURI, queryObj, optionsObj);
3308
0
    resultNode->mItemId = itemId;
3309
0
    resultNode->mBookmarkGuid = aBookmarkGuid;
3310
0
    // This is a perf hack to generate an empty query that skips filtering.
3311
0
    resultNode->GetAsQuery()->Options()->SetExcludeItems(true);
3312
0
  }
3313
0
3314
0
  resultNode.forget(aNode);
3315
0
  return NS_OK;
3316
0
}
3317
3318
3319
// nsNavHistory::VisitIdToResultNode
3320
//
3321
//    Used by the query results to create new nodes on the fly when
3322
//    notifications come in. This just creates a node for the given visit ID.
3323
3324
nsresult
3325
nsNavHistory::VisitIdToResultNode(int64_t visitId,
3326
                                  nsNavHistoryQueryOptions* aOptions,
3327
                                  nsNavHistoryResultNode** aResult)
3328
0
{
3329
0
  nsAutoCString tagsFragment;
3330
0
  GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
3331
0
                     true, tagsFragment);
3332
0
3333
0
  nsCOMPtr<mozIStorageStatement> statement;
3334
0
  switch (aOptions->ResultType())
3335
0
  {
3336
0
    case nsNavHistoryQueryOptions::RESULTS_AS_VISIT:
3337
0
      // visit query - want exact visit time
3338
0
      // Should match kGetInfoIndex_* (see GetQueryResults)
3339
0
      statement = mDB->GetStatement(NS_LITERAL_CSTRING(
3340
0
        "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
3341
0
               "v.visit_date, null, null, null, null, null, "
3342
0
               ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
3343
0
              "v.id, v.from_visit, v.visit_type "
3344
0
        "FROM moz_places h "
3345
0
        "JOIN moz_historyvisits v ON h.id = v.place_id "
3346
0
        "WHERE v.id = :visit_id ")
3347
0
      );
3348
0
      break;
3349
0
3350
0
    case nsNavHistoryQueryOptions::RESULTS_AS_URI:
3351
0
      // URL results - want last visit time
3352
0
      // Should match kGetInfoIndex_* (see GetQueryResults)
3353
0
      statement = mDB->GetStatement(NS_LITERAL_CSTRING(
3354
0
        "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
3355
0
               "h.last_visit_date, null, null, null, null, null, "
3356
0
               ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
3357
0
              "null, null, null "
3358
0
        "FROM moz_places h "
3359
0
        "JOIN moz_historyvisits v ON h.id = v.place_id "
3360
0
        "WHERE v.id = :visit_id ")
3361
0
      );
3362
0
      break;
3363
0
3364
0
    default:
3365
0
      // Query base types like RESULTS_AS_*_QUERY handle additions
3366
0
      // by registering their own observers when they are expanded.
3367
0
      return NS_OK;
3368
0
  }
3369
0
  NS_ENSURE_STATE(statement);
3370
0
  mozStorageStatementScoper scoper(statement);
3371
0
3372
0
  nsresult rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("visit_id"),
3373
0
                                           visitId);
3374
0
  NS_ENSURE_SUCCESS(rv, rv);
3375
0
3376
0
  bool hasMore = false;
3377
0
  rv = statement->ExecuteStep(&hasMore);
3378
0
  NS_ENSURE_SUCCESS(rv, rv);
3379
0
  if (! hasMore) {
3380
0
    MOZ_ASSERT_UNREACHABLE("Trying to get a result node for an invalid visit");
3381
0
    return NS_ERROR_INVALID_ARG;
3382
0
  }
3383
0
3384
0
  nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv);
3385
0
  NS_ENSURE_SUCCESS(rv, rv);
3386
0
3387
0
  return RowToResult(row, aOptions, aResult);
3388
0
}
3389
3390
nsresult
3391
nsNavHistory::BookmarkIdToResultNode(int64_t aBookmarkId, nsNavHistoryQueryOptions* aOptions,
3392
                                     nsNavHistoryResultNode** aResult)
3393
0
{
3394
0
  nsAutoCString tagsFragment;
3395
0
  GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
3396
0
                     true, tagsFragment);
3397
0
  // Should match kGetInfoIndex_*
3398
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
3399
0
      "SELECT b.fk, h.url, b.title, "
3400
0
             "h.rev_host, h.visit_count, h.last_visit_date, null, b.id, "
3401
0
             "b.dateAdded, b.lastModified, b.parent, "
3402
0
             ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
3403
0
             "null, null, null, b.guid, b.position, b.type, b.fk "
3404
0
      "FROM moz_bookmarks b "
3405
0
      "JOIN moz_places h ON b.fk = h.id "
3406
0
      "WHERE b.id = :item_id ")
3407
0
  );
3408
0
  NS_ENSURE_STATE(stmt);
3409
0
  mozStorageStatementScoper scoper(stmt);
3410
0
3411
0
  nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
3412
0
                                      aBookmarkId);
3413
0
  NS_ENSURE_SUCCESS(rv, rv);
3414
0
3415
0
  bool hasMore = false;
3416
0
  rv = stmt->ExecuteStep(&hasMore);
3417
0
  NS_ENSURE_SUCCESS(rv, rv);
3418
0
  if (!hasMore) {
3419
0
    MOZ_ASSERT_UNREACHABLE("Trying to get a result node for an invalid "
3420
0
                           "bookmark identifier");
3421
0
    return NS_ERROR_INVALID_ARG;
3422
0
  }
3423
0
3424
0
  nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
3425
0
  NS_ENSURE_SUCCESS(rv, rv);
3426
0
3427
0
  return RowToResult(row, aOptions, aResult);
3428
0
}
3429
3430
nsresult
3431
nsNavHistory::URIToResultNode(nsIURI* aURI,
3432
                              nsNavHistoryQueryOptions* aOptions,
3433
                              nsNavHistoryResultNode** aResult)
3434
0
{
3435
0
  nsAutoCString tagsFragment;
3436
0
  GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
3437
0
                     true, tagsFragment);
3438
0
  // Should match kGetInfoIndex_*
3439
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
3440
0
    "SELECT h.id, :page_url, COALESCE(b.title, h.title), "
3441
0
           "h.rev_host, h.visit_count, h.last_visit_date, null, "
3442
0
           "b.id, b.dateAdded, b.lastModified, b.parent, "
3443
0
           ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
3444
0
           "null, null, null, b.guid, b.position, b.type, b.fk "
3445
0
    "FROM moz_places h "
3446
0
    "LEFT JOIN moz_bookmarks b ON b.fk = h.id "
3447
0
    "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url ")
3448
0
  );
3449
0
  NS_ENSURE_STATE(stmt);
3450
0
  mozStorageStatementScoper scoper(stmt);
3451
0
3452
0
  nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
3453
0
  NS_ENSURE_SUCCESS(rv, rv);
3454
0
3455
0
  bool hasMore = false;
3456
0
  rv = stmt->ExecuteStep(&hasMore);
3457
0
  NS_ENSURE_SUCCESS(rv, rv);
3458
0
  if (!hasMore) {
3459
0
    MOZ_ASSERT_UNREACHABLE("Trying to get a result node for an invalid url");
3460
0
    return NS_ERROR_INVALID_ARG;
3461
0
  }
3462
0
3463
0
  nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
3464
0
  NS_ENSURE_SUCCESS(rv, rv);
3465
0
3466
0
  return RowToResult(row, aOptions, aResult);
3467
0
}
3468
3469
void
3470
nsNavHistory::SendPageChangedNotification(nsIURI* aURI,
3471
                                          uint32_t aChangedAttribute,
3472
                                          const nsAString& aNewValue,
3473
                                          const nsACString& aGUID)
3474
0
{
3475
0
  MOZ_ASSERT(!aGUID.IsEmpty());
3476
0
  NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
3477
0
                   OnPageChanged(aURI, aChangedAttribute, aNewValue, aGUID));
3478
0
}
3479
3480
void
3481
nsNavHistory::GetAgeInDaysString(int32_t aInt, const char* aName,
3482
                                 nsACString& aResult)
3483
0
{
3484
0
  nsIStringBundle *bundle = GetBundle();
3485
0
  if (bundle) {
3486
0
    nsAutoString intString;
3487
0
    intString.AppendInt(aInt);
3488
0
    const char16_t* strings[1] = { intString.get() };
3489
0
    nsAutoString value;
3490
0
    nsresult rv = bundle->FormatStringFromName(aName, strings, 1, value);
3491
0
    if (NS_SUCCEEDED(rv)) {
3492
0
      CopyUTF16toUTF8(value, aResult);
3493
0
      return;
3494
0
    }
3495
0
  }
3496
0
  aResult.Assign(aName);
3497
0
}
3498
3499
void
3500
nsNavHistory::GetStringFromName(const char* aName, nsACString& aResult)
3501
0
{
3502
0
  nsIStringBundle *bundle = GetBundle();
3503
0
  if (bundle) {
3504
0
    nsAutoString value;
3505
0
    nsresult rv = bundle->GetStringFromName(aName, value);
3506
0
    if (NS_SUCCEEDED(rv)) {
3507
0
      CopyUTF16toUTF8(value, aResult);
3508
0
      return;
3509
0
    }
3510
0
  }
3511
0
  aResult.Assign(aName);
3512
0
}
3513
3514
// static
3515
void
3516
nsNavHistory::GetMonthName(const PRExplodedTime& aTime, nsACString& aResult)
3517
0
{
3518
0
  nsAutoString month;
3519
0
  nsresult rv = DateTimeFormat::FormatPRExplodedTime(kDateFormatMonthLong,
3520
0
                                                     kTimeFormatNone,
3521
0
                                                     &aTime,
3522
0
                                                     month);
3523
0
  if (NS_FAILED(rv)) {
3524
0
    aResult = nsPrintfCString("[%d]", aTime.tm_month + 1);
3525
0
    return;
3526
0
  }
3527
0
  CopyUTF16toUTF8(month, aResult);
3528
0
}
3529
3530
// static
3531
void
3532
nsNavHistory::GetMonthYear(const PRExplodedTime& aTime, nsACString& aResult)
3533
0
{
3534
0
  nsAutoString monthYear;
3535
0
  nsresult rv = DateTimeFormat::FormatPRExplodedTime(kDateFormatYearMonthLong,
3536
0
                                                     kTimeFormatNone,
3537
0
                                                     &aTime,
3538
0
                                                     monthYear);
3539
0
  if (NS_FAILED(rv)) {
3540
0
    aResult = nsPrintfCString("[%d-%d]", aTime.tm_month + 1, aTime.tm_year);
3541
0
    return;
3542
0
  }
3543
0
  CopyUTF16toUTF8(monthYear, aResult);
3544
0
}
3545
3546
3547
namespace {
3548
3549
// GetSimpleBookmarksQueryParent
3550
//
3551
//    Determines if this is a simple bookmarks query for a
3552
//    folder with no other constraints. In these common cases, we can more
3553
//    efficiently compute the results.
3554
//
3555
//    A simple bookmarks query will result in a hierarchical tree of
3556
//    bookmark items, folders and separators.
3557
//
3558
//    Returns the folder ID if it is a simple folder query, 0 if not.
3559
static nsCString
3560
GetSimpleBookmarksQueryParent(const RefPtr<nsNavHistoryQuery>& aQuery,
3561
                              const RefPtr<nsNavHistoryQueryOptions>& aOptions)
3562
0
{
3563
0
  if (aQuery->Parents().Length() != 1)
3564
0
    return EmptyCString();
3565
0
3566
0
  bool hasIt;
3567
0
  if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt)
3568
0
    return EmptyCString();
3569
0
  if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)
3570
0
    return EmptyCString();
3571
0
  if (!aQuery->Domain().IsVoid())
3572
0
    return EmptyCString();
3573
0
  if (aQuery->Uri())
3574
0
    return EmptyCString();
3575
0
  if (!aQuery->SearchTerms().IsEmpty())
3576
0
    return EmptyCString();
3577
0
  if (aQuery->Tags().Length() > 0)
3578
0
    return EmptyCString();
3579
0
  if (aOptions->MaxResults() > 0)
3580
0
    return EmptyCString();
3581
0
3582
0
  // Don't care about onlyBookmarked flag, since specifying a bookmark
3583
0
  // folder is inferring onlyBookmarked.
3584
0
3585
0
  return aQuery->Parents()[0];
3586
0
}
3587
3588
3589
// ParseSearchTermsFromQuery
3590
//
3591
//    Construct an array of search terms from the given query.
3592
//    Within a query, all the terms are ANDed together.
3593
//
3594
//    This just breaks the query up into words. We don't do anything fancy,
3595
//    not even quoting. We do, however, strip quotes, because people might
3596
//    try to input quotes expecting them to do something and get no results
3597
//    back.
3598
3599
inline bool isQueryWhitespace(char16_t ch)
3600
0
{
3601
0
  return ch == ' ';
3602
0
}
3603
3604
void ParseSearchTermsFromQuery(const RefPtr<nsNavHistoryQuery>& aQuery,
3605
                               nsTArray<nsString>* aTerms)
3606
0
{
3607
0
  int32_t lastBegin = -1;
3608
0
  if (!aQuery->SearchTerms().IsEmpty()) {
3609
0
    const nsString& searchTerms = aQuery->SearchTerms();
3610
0
    for (uint32_t j = 0; j < searchTerms.Length(); j++) {
3611
0
      if (isQueryWhitespace(searchTerms[j]) ||
3612
0
          searchTerms[j] == '"') {
3613
0
        if (lastBegin >= 0) {
3614
0
          // found the end of a word
3615
0
          aTerms->AppendElement(Substring(searchTerms, lastBegin, j - lastBegin));
3616
0
          lastBegin = -1;
3617
0
        }
3618
0
      } else {
3619
0
        if (lastBegin < 0) {
3620
0
          // found the beginning of a word
3621
0
          lastBegin = j;
3622
0
        }
3623
0
      }
3624
0
    }
3625
0
    // last word
3626
0
    if (lastBegin >= 0)
3627
0
      aTerms->AppendElement(Substring(searchTerms, lastBegin));
3628
0
  }
3629
0
}
3630
3631
} // namespace
3632
3633
3634
nsresult
3635
nsNavHistory::UpdateFrecency(int64_t aPlaceId)
3636
0
{
3637
0
  nsCOMPtr<mozIStorageAsyncStatement> updateFrecencyStmt = mDB->GetAsyncStatement(
3638
0
    "UPDATE moz_places "
3639
0
    "SET frecency = NOTIFY_FRECENCY("
3640
0
      "CALCULATE_FRECENCY(:page_id), url, guid, hidden, last_visit_date"
3641
0
    ") "
3642
0
    "WHERE id = :page_id"
3643
0
  );
3644
0
  NS_ENSURE_STATE(updateFrecencyStmt);
3645
0
  nsresult rv = updateFrecencyStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
3646
0
                                                    aPlaceId);
3647
0
  NS_ENSURE_SUCCESS(rv, rv);
3648
0
  nsCOMPtr<mozIStorageAsyncStatement> updateHiddenStmt = mDB->GetAsyncStatement(
3649
0
    "UPDATE moz_places "
3650
0
    "SET hidden = 0 "
3651
0
    "WHERE id = :page_id AND frecency <> 0"
3652
0
  );
3653
0
  NS_ENSURE_STATE(updateHiddenStmt);
3654
0
  rv = updateHiddenStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
3655
0
                                         aPlaceId);
3656
0
  NS_ENSURE_SUCCESS(rv, rv);
3657
0
3658
0
  nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
3659
0
  if (!conn) {
3660
0
    return NS_ERROR_UNEXPECTED;
3661
0
  }
3662
0
3663
0
  mozIStorageBaseStatement *stmts[] = {
3664
0
    updateFrecencyStmt.get()
3665
0
  , updateHiddenStmt.get()
3666
0
  };
3667
0
  nsCOMPtr<mozIStoragePendingStatement> ps;
3668
0
  rv = conn->ExecuteAsync(stmts, ArrayLength(stmts), nullptr,
3669
0
                          getter_AddRefs(ps));
3670
0
  NS_ENSURE_SUCCESS(rv, rv);
3671
0
3672
0
  // Trigger frecency updates for all affected origins.
3673
0
  nsCOMPtr<mozIStorageAsyncStatement> updateOriginFrecenciesStmt =
3674
0
    mDB->GetAsyncStatement("DELETE FROM moz_updateoriginsupdate_temp");
3675
0
  NS_ENSURE_STATE(updateOriginFrecenciesStmt);
3676
0
  rv = updateOriginFrecenciesStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
3677
0
  NS_ENSURE_SUCCESS(rv, rv);
3678
0
3679
0
  return NS_OK;
3680
0
}
3681
3682
3683
#ifdef MOZ_XUL
3684
3685
nsresult
3686
nsNavHistory::AutoCompleteFeedback(int32_t aIndex,
3687
                                   nsIAutoCompleteController *aController)
3688
0
{
3689
0
  nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
3690
0
    "INSERT OR REPLACE INTO moz_inputhistory "
3691
0
    // use_count will asymptotically approach the max of 10.
3692
0
    "SELECT h.id, IFNULL(i.input, :input_text), IFNULL(i.use_count, 0) * .9 + 1 "
3693
0
    "FROM moz_places h "
3694
0
    "LEFT JOIN moz_inputhistory i ON i.place_id = h.id AND i.input = :input_text "
3695
0
    "WHERE url_hash = hash(:page_url) AND url = :page_url "
3696
0
  );
3697
0
  NS_ENSURE_STATE(stmt);
3698
0
3699
0
  nsAutoString input;
3700
0
  nsresult rv = aController->GetSearchString(input);
3701
0
  NS_ENSURE_SUCCESS(rv, rv);
3702
0
  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("input_text"), input);
3703
0
  NS_ENSURE_SUCCESS(rv, rv);
3704
0
3705
0
  nsAutoString url;
3706
0
  rv = aController->GetValueAt(aIndex, url);
3707
0
  NS_ENSURE_SUCCESS(rv, rv);
3708
0
  rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
3709
0
                       NS_ConvertUTF16toUTF8(url));
3710
0
  NS_ENSURE_SUCCESS(rv, rv);
3711
0
3712
0
  // We do the update asynchronously and we do not care about failures.
3713
0
  RefPtr<AsyncStatementCallbackNotifier> callback =
3714
0
    new AsyncStatementCallbackNotifier(TOPIC_AUTOCOMPLETE_FEEDBACK_UPDATED);
3715
0
  nsCOMPtr<mozIStoragePendingStatement> canceler;
3716
0
  rv = stmt->ExecuteAsync(callback, getter_AddRefs(canceler));
3717
0
  NS_ENSURE_SUCCESS(rv, rv);
3718
0
3719
0
  return NS_OK;
3720
0
}
3721
3722
#endif
3723
3724
3725
nsICollation *
3726
nsNavHistory::GetCollation()
3727
0
{
3728
0
  if (mCollation)
3729
0
    return mCollation;
3730
0
3731
0
  // collation
3732
0
  nsCOMPtr<nsICollationFactory> cfact =
3733
0
    do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
3734
0
  NS_ENSURE_TRUE(cfact, nullptr);
3735
0
  nsresult rv = cfact->CreateCollation(getter_AddRefs(mCollation));
3736
0
  NS_ENSURE_SUCCESS(rv, nullptr);
3737
0
3738
0
  return mCollation;
3739
0
}
3740
3741
nsIStringBundle *
3742
nsNavHistory::GetBundle()
3743
0
{
3744
0
  if (!mBundle) {
3745
0
    nsCOMPtr<nsIStringBundleService> bundleService =
3746
0
      services::GetStringBundleService();
3747
0
    NS_ENSURE_TRUE(bundleService, nullptr);
3748
0
    nsresult rv = bundleService->CreateBundle(
3749
0
        "chrome://places/locale/places.properties",
3750
0
        getter_AddRefs(mBundle));
3751
0
    NS_ENSURE_SUCCESS(rv, nullptr);
3752
0
  }
3753
0
  return mBundle;
3754
0
}