LCOV - code coverage report
Current view: directory - toolkit/components/places - nsNavHistory.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 2227 2029 91.1 %
Date: 2012-06-02 Functions: 138 128 92.8 %

       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                 : }

Generated by: LCOV version 1.7