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