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