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