toolkit/components/places/nsNavHistoryResult.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 //* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include <stdio.h>
     7 #include "nsNavHistory.h"
     8 #include "nsNavBookmarks.h"
     9 #include "nsFaviconService.h"
    10 #include "nsITaggingService.h"
    11 #include "nsAnnotationService.h"
    12 #include "Helpers.h"
    13 #include "mozilla/DebugOnly.h"
    14 #include "nsDebug.h"
    15 #include "nsNetUtil.h"
    16 #include "nsString.h"
    17 #include "nsReadableUtils.h"
    18 #include "nsUnicharUtils.h"
    19 #include "prtime.h"
    20 #include "prprf.h"
    22 #include "nsCycleCollectionParticipant.h"
    24 // Thanks, Windows.h :(
    25 #undef CompareString
    27 #define TO_ICONTAINER(_node)                                                  \
    28     static_cast<nsINavHistoryContainerResultNode*>(_node)                      
    30 #define TO_CONTAINER(_node)                                                   \
    31     static_cast<nsNavHistoryContainerResultNode*>(_node)
    33 #define NOTIFY_RESULT_OBSERVERS_RET(_result, _method, _ret)                   \
    34   PR_BEGIN_MACRO                                                              \
    35   NS_ENSURE_TRUE(_result, _ret);                                              \
    36   if (!_result->mSuppressNotifications) {                                     \
    37     ENUMERATE_WEAKARRAY(_result->mObservers, nsINavHistoryResultObserver,     \
    38                         _method)                                              \
    39   }                                                                           \
    40   PR_END_MACRO
    42 #define NOTIFY_RESULT_OBSERVERS(_result, _method)                             \
    43   NOTIFY_RESULT_OBSERVERS_RET(_result, _method, NS_ERROR_UNEXPECTED)
    45 // What we want is: NS_INTERFACE_MAP_ENTRY(self) for static IID accessors,
    46 // but some of our classes (like nsNavHistoryResult) have an ambiguous base
    47 // class of nsISupports which prevents this from working (the default macro
    48 // converts it to nsISupports, then addrefs it, then returns it). Therefore, we
    49 // expand the macro here and change it so that it works. Yuck.
    50 #define NS_INTERFACE_MAP_STATIC_AMBIGUOUS(_class) \
    51   if (aIID.Equals(NS_GET_IID(_class))) { \
    52     NS_ADDREF(this); \
    53     *aInstancePtr = this; \
    54     return NS_OK; \
    55   } else
    57 // Number of changes to handle separately in a batch.  If more changes are
    58 // requested the node will switch to full refresh mode.
    59 #define MAX_BATCH_CHANGES_BEFORE_REFRESH 5
    61 // Emulate string comparison (used for sorting) for PRTime and int.
    62 inline int32_t ComparePRTime(PRTime a, PRTime b)
    63 {
    64   if (a < b)
    65     return -1;
    66   else if (a > b)
    67     return 1;
    68   return 0;
    69 }
    70 inline int32_t CompareIntegers(uint32_t a, uint32_t b)
    71 {
    72   return a - b;
    73 }
    75 using namespace mozilla;
    76 using namespace mozilla::places;
    78 NS_IMPL_CYCLE_COLLECTION(nsNavHistoryResultNode, mParent)
    80 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNavHistoryResultNode)
    81   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryResultNode)
    82   NS_INTERFACE_MAP_ENTRY(nsINavHistoryResultNode)
    83 NS_INTERFACE_MAP_END
    85 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNavHistoryResultNode)
    86 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNavHistoryResultNode)
    88 nsNavHistoryResultNode::nsNavHistoryResultNode(
    89     const nsACString& aURI, const nsACString& aTitle, uint32_t aAccessCount,
    90     PRTime aTime, const nsACString& aIconURI) :
    91   mParent(nullptr),
    92   mURI(aURI),
    93   mTitle(aTitle),
    94   mAreTagsSorted(false),
    95   mAccessCount(aAccessCount),
    96   mTime(aTime),
    97   mFaviconURI(aIconURI),
    98   mBookmarkIndex(-1),
    99   mItemId(-1),
   100   mFolderId(-1),
   101   mDateAdded(0),
   102   mLastModified(0),
   103   mIndentLevel(-1),
   104   mFrecency(0),
   105   mHidden(false),
   106   mTransitionType(0)
   107 {
   108   mTags.SetIsVoid(true);
   109 }
   112 NS_IMETHODIMP
   113 nsNavHistoryResultNode::GetIcon(nsACString& aIcon)
   114 {
   115   if (mFaviconURI.IsEmpty()) {
   116     aIcon.Truncate();
   117     return NS_OK;
   118   }
   120   nsFaviconService* faviconService = nsFaviconService::GetFaviconService();
   121   NS_ENSURE_TRUE(faviconService, NS_ERROR_OUT_OF_MEMORY);
   122   faviconService->GetFaviconSpecForIconString(mFaviconURI, aIcon);
   123   return NS_OK;
   124 }
   127 NS_IMETHODIMP
   128 nsNavHistoryResultNode::GetParent(nsINavHistoryContainerResultNode** aParent)
   129 {
   130   NS_IF_ADDREF(*aParent = mParent);
   131   return NS_OK;
   132 }
   135 NS_IMETHODIMP
   136 nsNavHistoryResultNode::GetParentResult(nsINavHistoryResult** aResult)
   137 {
   138   *aResult = nullptr;
   139   if (IsContainer())
   140     NS_IF_ADDREF(*aResult = GetAsContainer()->mResult);
   141   else if (mParent)
   142     NS_IF_ADDREF(*aResult = mParent->mResult);
   144   NS_ENSURE_STATE(*aResult);
   145   return NS_OK;
   146 }
   149 NS_IMETHODIMP
   150 nsNavHistoryResultNode::GetTags(nsAString& aTags) {
   151   // Only URI-nodes may be associated with tags
   152   if (!IsURI()) {
   153     aTags.Truncate();
   154     return NS_OK;
   155   }
   157   // Initially, the tags string is set to a void string (see constructor).  We
   158   // then build it the first time this method called is called (and by that,
   159   // implicitly unset the void flag). Result observers may re-set the void flag
   160   // in order to force rebuilding of the tags string.
   161   if (!mTags.IsVoid()) {
   162     // If mTags is assigned by a history query it is unsorted for performance
   163     // reasons, it must be sorted by name on first read access.
   164     if (!mAreTagsSorted) {
   165       nsTArray<nsCString> tags;
   166       ParseString(NS_ConvertUTF16toUTF8(mTags), ',', tags);
   167       tags.Sort();
   168       mTags.SetIsVoid(true);
   169       for (nsTArray<nsCString>::index_type i = 0; i < tags.Length(); ++i) {
   170         AppendUTF8toUTF16(tags[i], mTags);
   171         if (i < tags.Length() - 1 )
   172           mTags.AppendLiteral(", ");
   173       }
   174       mAreTagsSorted = true;
   175     }
   176     aTags.Assign(mTags);
   177     return NS_OK;
   178   }
   180   // Fetch the tags
   181   nsRefPtr<Database> DB = Database::GetDatabase();
   182   NS_ENSURE_STATE(DB);
   183   nsCOMPtr<mozIStorageStatement> stmt = DB->GetStatement(
   184     "/* do not warn (bug 487594) */ "
   185     "SELECT GROUP_CONCAT(tag_title, ', ') "
   186     "FROM ( "
   187       "SELECT t.title AS tag_title "
   188       "FROM moz_bookmarks b "
   189       "JOIN moz_bookmarks t ON t.id = +b.parent "
   190       "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
   191         "AND t.parent = :tags_folder "
   192       "ORDER BY t.title COLLATE NOCASE ASC "
   193     ") "
   194   );
   195   NS_ENSURE_STATE(stmt);
   196   mozStorageStatementScoper scoper(stmt);
   198   nsNavHistory* history = nsNavHistory::GetHistoryService();
   199   NS_ENSURE_STATE(history);
   200   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("tags_folder"),
   201                                       history->GetTagsFolder());
   202   NS_ENSURE_SUCCESS(rv, rv);
   203   rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mURI);
   204   NS_ENSURE_SUCCESS(rv, rv);
   206   bool hasTags = false;
   207   if (NS_SUCCEEDED(stmt->ExecuteStep(&hasTags)) && hasTags) {
   208     rv = stmt->GetString(0, mTags);
   209     NS_ENSURE_SUCCESS(rv, rv);
   210     aTags.Assign(mTags);
   211     mAreTagsSorted = true;
   212   }
   214   // If this node is a child of a history query, we need to make sure changes
   215   // to tags are properly live-updated.
   216   if (mParent && mParent->IsQuery() &&
   217       mParent->mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) {
   218     nsNavHistoryQueryResultNode* query = mParent->GetAsQuery();
   219     nsNavHistoryResult* result = query->GetResult();
   220     NS_ENSURE_STATE(result);
   221     result->AddAllBookmarksObserver(query);
   222   }
   224   return NS_OK;
   225 }
   227 NS_IMETHODIMP
   228 nsNavHistoryResultNode::GetPageGuid(nsACString& aPageGuid) {
   229   aPageGuid = mPageGuid;
   230   return NS_OK;
   231 }
   234 NS_IMETHODIMP
   235 nsNavHistoryResultNode::GetBookmarkGuid(nsACString& aBookmarkGuid) {
   236   aBookmarkGuid = mBookmarkGuid;
   237   return NS_OK;
   238 }
   241 void
   242 nsNavHistoryResultNode::OnRemoving()
   243 {
   244   mParent = nullptr;
   245 }
   248 /**
   249  * This will find the result for this node.  We can ask the nearest container
   250  * for this value (either ourselves or our parents should be a container,
   251  * and all containers have result pointers).
   252  *
   253  * @note The result may be null, if the container is detached from the result
   254  *       who owns it.
   255  */
   256 nsNavHistoryResult*
   257 nsNavHistoryResultNode::GetResult()
   258 {
   259   nsNavHistoryResultNode* node = this;
   260   do {
   261     if (node->IsContainer()) {
   262       nsNavHistoryContainerResultNode* container = TO_CONTAINER(node);
   263       return container->mResult;
   264     }
   265     node = node->mParent;
   266   } while (node);
   267   MOZ_ASSERT(false, "No container node found in hierarchy!");
   268   return nullptr;
   269 }
   272 /**
   273  * Searches up the tree for the closest ancestor node that has an options
   274  * structure.  This will tell us the options that were used to generate this
   275  * node.
   276  *
   277  * Be careful, this function walks up the tree, so it can not be used when
   278  * result nodes are created because they have no parent.  Only call this
   279  * function after the tree has been built.
   280  */
   281 nsNavHistoryQueryOptions*
   282 nsNavHistoryResultNode::GetGeneratingOptions()
   283 {
   284   if (!mParent) {
   285     // When we have no parent, it either means we haven't built the tree yet,
   286     // in which case calling this function is a bug, or this node is the root
   287     // of the tree.  When we are the root of the tree, our own options are the
   288     // generating options.
   289     if (IsContainer())
   290       return GetAsContainer()->mOptions;
   292     NS_NOTREACHED("Can't find a generating node for this container, perhaps FillStats has not been called on this tree yet?");
   293     return nullptr;
   294   }
   296   // Look up the tree.  We want the options that were used to create this node,
   297   // and since it has a parent, it's the options of an ancestor, not of the node
   298   // itself.  So start at the parent.
   299   nsNavHistoryContainerResultNode* cur = mParent;
   300   while (cur) {
   301     if (cur->IsContainer())
   302       return cur->GetAsContainer()->mOptions;
   303     cur = cur->mParent;
   304   }
   306   // We should always find a container node as an ancestor.
   307   NS_NOTREACHED("Can't find a generating node for this container, the tree seemes corrupted.");
   308   return nullptr;
   309 }
   311 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode,
   312                                    mResult,
   313                                    mChildren)
   315 NS_IMPL_ADDREF_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
   316 NS_IMPL_RELEASE_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
   318 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsNavHistoryContainerResultNode)
   319   NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryContainerResultNode)
   320   NS_INTERFACE_MAP_ENTRY(nsINavHistoryContainerResultNode)
   321 NS_INTERFACE_MAP_END_INHERITING(nsNavHistoryResultNode)
   323 nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
   324     const nsACString& aURI, const nsACString& aTitle,
   325     const nsACString& aIconURI, uint32_t aContainerType, bool aReadOnly,
   326     nsNavHistoryQueryOptions* aOptions) :
   327   nsNavHistoryResultNode(aURI, aTitle, 0, 0, aIconURI),
   328   mResult(nullptr),
   329   mContainerType(aContainerType),
   330   mExpanded(false),
   331   mChildrenReadOnly(aReadOnly),
   332   mOptions(aOptions),
   333   mAsyncCanceledState(NOT_CANCELED)
   334 {
   335 }
   337 nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
   338     const nsACString& aURI, const nsACString& aTitle,
   339     PRTime aTime,
   340     const nsACString& aIconURI, uint32_t aContainerType, bool aReadOnly,
   341     nsNavHistoryQueryOptions* aOptions) :
   342   nsNavHistoryResultNode(aURI, aTitle, 0, aTime, aIconURI),
   343   mResult(nullptr),
   344   mContainerType(aContainerType),
   345   mExpanded(false),
   346   mChildrenReadOnly(aReadOnly),
   347   mOptions(aOptions),
   348   mAsyncCanceledState(NOT_CANCELED)
   349 {
   350 }
   353 nsNavHistoryContainerResultNode::~nsNavHistoryContainerResultNode()
   354 {
   355   // Explicitly clean up array of children of this container.  We must ensure
   356   // all references are gone and all of their destructors are called.
   357   mChildren.Clear();
   358 }
   361 /**
   362  * Containers should notify their children that they are being removed when the
   363  * container is being removed.
   364  */
   365 void
   366 nsNavHistoryContainerResultNode::OnRemoving()
   367 {
   368   nsNavHistoryResultNode::OnRemoving();
   369   for (int32_t i = 0; i < mChildren.Count(); ++i)
   370     mChildren[i]->OnRemoving();
   371   mChildren.Clear();
   372   mResult = nullptr;
   373 }
   376 bool
   377 nsNavHistoryContainerResultNode::AreChildrenVisible()
   378 {
   379   nsNavHistoryResult* result = GetResult();
   380   if (!result) {
   381     NS_NOTREACHED("Invalid result");
   382     return false;
   383   }
   385   if (!mExpanded)
   386     return false;
   388   // Now check if any ancestor is closed.
   389   nsNavHistoryContainerResultNode* ancestor = mParent;
   390   while (ancestor) {
   391     if (!ancestor->mExpanded)
   392       return false;
   394     ancestor = ancestor->mParent;
   395   }
   397   return true;
   398 }
   401 NS_IMETHODIMP
   402 nsNavHistoryContainerResultNode::GetContainerOpen(bool *aContainerOpen)
   403 {
   404   *aContainerOpen = mExpanded;
   405   return NS_OK;
   406 }
   409 NS_IMETHODIMP
   410 nsNavHistoryContainerResultNode::SetContainerOpen(bool aContainerOpen)
   411 {
   412   if (aContainerOpen) {
   413     if (!mExpanded) {
   414       nsNavHistoryQueryOptions* options = GetGeneratingOptions();
   415       if (options && options->AsyncEnabled())
   416         OpenContainerAsync();
   417       else
   418         OpenContainer();
   419     }
   420   }
   421   else {
   422     if (mExpanded)
   423       CloseContainer();
   424     else if (mAsyncPendingStmt)
   425       CancelAsyncOpen(false);
   426   }
   428   return NS_OK;
   429 }
   432 /**
   433  * Notifies the result's observers of a change in the container's state.  The
   434  * notification includes both the old and new states:  The old is aOldState, and
   435  * the new is the container's current state.
   436  *
   437  * @param aOldState
   438  *        The state being transitioned out of.
   439  */
   440 nsresult
   441 nsNavHistoryContainerResultNode::NotifyOnStateChange(uint16_t aOldState)
   442 {
   443   nsNavHistoryResult* result = GetResult();
   444   NS_ENSURE_STATE(result);
   446   nsresult rv;
   447   uint16_t currState;
   448   rv = GetState(&currState);
   449   NS_ENSURE_SUCCESS(rv, rv);
   451   // Notify via the new ContainerStateChanged observer method.
   452   NOTIFY_RESULT_OBSERVERS(result,
   453                           ContainerStateChanged(this, aOldState, currState));
   454   return NS_OK;
   455 }
   458 NS_IMETHODIMP
   459 nsNavHistoryContainerResultNode::GetState(uint16_t* _state)
   460 {
   461   NS_ENSURE_ARG_POINTER(_state);
   463   *_state = mExpanded ? (uint16_t)STATE_OPENED
   464                       : mAsyncPendingStmt ? (uint16_t)STATE_LOADING
   465                                           : (uint16_t)STATE_CLOSED;
   467   return NS_OK;
   468 }
   471 /**
   472  * This handles the generic container case.  Other container types should
   473  * override this to do their own handling.
   474  */
   475 nsresult
   476 nsNavHistoryContainerResultNode::OpenContainer()
   477 {
   478   NS_ASSERTION(!mExpanded, "Container must not be expanded to open it");
   479   mExpanded = true;
   481   nsresult rv = NotifyOnStateChange(STATE_CLOSED);
   482   NS_ENSURE_SUCCESS(rv, rv);
   484   return NS_OK;
   485 }
   488 /**
   489  * Unset aSuppressNotifications to notify observers on this change.  That is
   490  * the normal operation.  This is set to false for the recursive calls since the
   491  * root container that is being closed will handle recomputation of the visible
   492  * elements for its entire subtree.
   493  */
   494 nsresult
   495 nsNavHistoryContainerResultNode::CloseContainer(bool aSuppressNotifications)
   496 {
   497   NS_ASSERTION((mExpanded && !mAsyncPendingStmt) ||
   498                (!mExpanded && mAsyncPendingStmt),
   499                "Container must be expanded or loading to close it");
   501   nsresult rv;
   502   uint16_t oldState;
   503   rv = GetState(&oldState);
   504   NS_ENSURE_SUCCESS(rv, rv);
   506   if (mExpanded) {
   507     // Recursively close all child containers.
   508     for (int32_t i = 0; i < mChildren.Count(); ++i) {
   509       if (mChildren[i]->IsContainer() &&
   510           mChildren[i]->GetAsContainer()->mExpanded)
   511         mChildren[i]->GetAsContainer()->CloseContainer(true);
   512     }
   514     mExpanded = false;
   515   }
   517   // Be sure to set this to null before notifying observers.  It signifies that
   518   // the container is no longer loading (if it was in the first place).
   519   mAsyncPendingStmt = nullptr;
   521   if (!aSuppressNotifications) {
   522     rv = NotifyOnStateChange(oldState);
   523     NS_ENSURE_SUCCESS(rv, rv);
   524   }
   526   // If this is the root container of a result, we can tell the result to stop
   527   // observing changes, otherwise the result will stay in memory and updates
   528   // itself till it is cycle collected.
   529   nsNavHistoryResult* result = GetResult();
   530   NS_ENSURE_STATE(result);
   531   if (result->mRootNode == this) {
   532     result->StopObserving();
   533     // When reopening this node its result will be out of sync.
   534     // We must clear our children to ensure we will call FillChildren
   535     // again in such a case.
   536     if (this->IsQuery())
   537       this->GetAsQuery()->ClearChildren(true);
   538     else if (this->IsFolder())
   539       this->GetAsFolder()->ClearChildren(true);
   540   }
   542   return NS_OK;
   543 }
   546 /**
   547  * The async version of OpenContainer.
   548  */
   549 nsresult
   550 nsNavHistoryContainerResultNode::OpenContainerAsync()
   551 {
   552   return NS_ERROR_NOT_IMPLEMENTED;
   553 }
   556 /**
   557  * Cancels the pending asynchronous Storage execution triggered by
   558  * FillChildrenAsync, if it exists.  This method doesn't do much, because after
   559  * cancelation Storage will call this node's HandleCompletion callback, where
   560  * the real work is done.
   561  *
   562  * @param aRestart
   563  *        If true, async execution will be restarted by HandleCompletion.
   564  */
   565 void
   566 nsNavHistoryContainerResultNode::CancelAsyncOpen(bool aRestart)
   567 {
   568   NS_ASSERTION(mAsyncPendingStmt, "Async execution canceled but not pending");
   570   mAsyncCanceledState = aRestart ? CANCELED_RESTART_NEEDED : CANCELED;
   572   // Cancel will fail if the pending statement has already been canceled.
   573   // That's OK since this method may be called multiple times, and multiple
   574   // cancels don't harm anything.
   575   (void)mAsyncPendingStmt->Cancel();
   576 }
   579 /**
   580  * This builds up tree statistics from the bottom up.  Call with a container
   581  * and the indent level of that container.  To init the full tree, call with
   582  * the root container.  The default indent level is -1, which is appropriate
   583  * for the root level.
   584  *
   585  * CALL THIS AFTER FILLING ANY CONTAINER to update the parent and result node
   586  * pointers, even if you don't care about visit counts and last visit dates.
   587  */
   588 void
   589 nsNavHistoryContainerResultNode::FillStats()
   590 {
   591   uint32_t accessCount = 0;
   592   PRTime newTime = 0;
   594   for (int32_t i = 0; i < mChildren.Count(); ++i) {
   595     nsNavHistoryResultNode* node = mChildren[i];
   596     node->mParent = this;
   597     node->mIndentLevel = mIndentLevel + 1;
   598     if (node->IsContainer()) {
   599       nsNavHistoryContainerResultNode* container = node->GetAsContainer();
   600       container->mResult = mResult;
   601       container->FillStats();
   602     }
   603     accessCount += node->mAccessCount;
   604     // this is how container nodes get sorted by date
   605     // The container gets the most recent time of the child nodes.
   606     if (node->mTime > newTime)
   607       newTime = node->mTime;
   608   }
   610   if (mExpanded) {
   611     mAccessCount = accessCount;
   612     if (!IsQuery() || newTime > mTime)
   613       mTime = newTime;
   614   }
   615 }
   618 /**
   619  * This is used when one container changes to do a minimal update of the tree
   620  * structure.  When something changes, you want to call FillStats if necessary
   621  * and update this container completely.  Then call this function which will
   622  * walk up the tree and fill in the previous containers.
   623  *
   624  * Note that you have to tell us by how much our access count changed.  Our
   625  * access count should already be set to the new value; this is used tochange
   626  * the parents without having to re-count all their children.
   627  *
   628  * This does NOT update the last visit date downward.  Therefore, if you are
   629  * deleting a node that has the most recent last visit date, the parents will
   630  * not get their last visit dates downshifted accordingly.  This is a rather
   631  * unusual case: we don't often delete things, and we usually don't even show
   632  * the last visit date for folders.  Updating would be slower because we would
   633  * have to recompute it from scratch.
   634  */
   635 nsresult
   636 nsNavHistoryContainerResultNode::ReverseUpdateStats(int32_t aAccessCountChange)
   637 {
   638   if (mParent) {
   639     nsNavHistoryResult* result = GetResult();
   640     bool shouldNotify = result && mParent->mParent &&
   641                           mParent->mParent->AreChildrenVisible();
   643     mParent->mAccessCount += aAccessCountChange;
   644     bool timeChanged = false;
   645     if (mTime > mParent->mTime) {
   646       timeChanged = true;
   647       mParent->mTime = mTime;
   648     }
   650     if (shouldNotify) {
   651       NOTIFY_RESULT_OBSERVERS(result,
   652                               NodeHistoryDetailsChanged(TO_ICONTAINER(mParent),
   653                                                         mParent->mTime,
   654                                                         mParent->mAccessCount));
   655     }
   657     // check sorting, the stats may have caused this node to move if the
   658     // sorting depended on something we are changing.
   659     uint16_t sortMode = mParent->GetSortType();
   660     bool sortingByVisitCount =
   661       sortMode == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
   662       sortMode == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING;
   663     bool sortingByTime =
   664       sortMode == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
   665       sortMode == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING;
   667     if ((sortingByVisitCount && aAccessCountChange != 0) ||
   668         (sortingByTime && timeChanged)) {
   669       int32_t ourIndex = mParent->FindChild(this);
   670       NS_ASSERTION(ourIndex >= 0, "Could not find self in parent");
   671       if (ourIndex >= 0)
   672         EnsureItemPosition(static_cast<uint32_t>(ourIndex));
   673     }
   675     nsresult rv = mParent->ReverseUpdateStats(aAccessCountChange);
   676     NS_ENSURE_SUCCESS(rv, rv);
   677   }
   679   return NS_OK;
   680 }
   683 /**
   684  * This walks up the tree until we find a query result node or the root to get
   685  * the sorting type.
   686  */
   687 uint16_t
   688 nsNavHistoryContainerResultNode::GetSortType()
   689 {
   690   if (mParent)
   691     return mParent->GetSortType();
   692   if (mResult)
   693     return mResult->mSortingMode;
   695   // This is a detached container, just use natural order.
   696   return nsINavHistoryQueryOptions::SORT_BY_NONE;
   697 }
   700 nsresult nsNavHistoryContainerResultNode::Refresh() {
   701   NS_WARNING("Refresh() is supported by queries or folders, not generic containers.");
   702   return NS_OK;
   703 }
   705 void
   706 nsNavHistoryContainerResultNode::GetSortingAnnotation(nsACString& aAnnotation)
   707 {
   708   if (mParent)
   709     mParent->GetSortingAnnotation(aAnnotation);
   710   else if (mResult)
   711     aAnnotation.Assign(mResult->mSortingAnnotation);
   712 }
   714 /**
   715  * @return the sorting comparator function for the give sort type, or null if
   716  * there is no comparator.
   717  */
   718 nsNavHistoryContainerResultNode::SortComparator
   719 nsNavHistoryContainerResultNode::GetSortingComparator(uint16_t aSortType)
   720 {
   721   switch (aSortType)
   722   {
   723     case nsINavHistoryQueryOptions::SORT_BY_NONE:
   724       return &SortComparison_Bookmark;
   725     case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING:
   726       return &SortComparison_TitleLess;
   727     case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING:
   728       return &SortComparison_TitleGreater;
   729     case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING:
   730       return &SortComparison_DateLess;
   731     case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING:
   732       return &SortComparison_DateGreater;
   733     case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING:
   734       return &SortComparison_URILess;
   735     case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING:
   736       return &SortComparison_URIGreater;
   737     case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING:
   738       return &SortComparison_VisitCountLess;
   739     case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING:
   740       return &SortComparison_VisitCountGreater;
   741     case nsINavHistoryQueryOptions::SORT_BY_KEYWORD_ASCENDING:
   742       return &SortComparison_KeywordLess;
   743     case nsINavHistoryQueryOptions::SORT_BY_KEYWORD_DESCENDING:
   744       return &SortComparison_KeywordGreater;
   745     case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_ASCENDING:
   746       return &SortComparison_AnnotationLess;
   747     case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_DESCENDING:
   748       return &SortComparison_AnnotationGreater;
   749     case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING:
   750       return &SortComparison_DateAddedLess;
   751     case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING:
   752       return &SortComparison_DateAddedGreater;
   753     case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING:
   754       return &SortComparison_LastModifiedLess;
   755     case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING:
   756       return &SortComparison_LastModifiedGreater;
   757     case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING:
   758       return &SortComparison_TagsLess;
   759     case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING:
   760       return &SortComparison_TagsGreater;
   761     case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING:
   762       return &SortComparison_FrecencyLess;
   763     case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING:
   764       return &SortComparison_FrecencyGreater;
   765     default:
   766       NS_NOTREACHED("Bad sorting type");
   767       return nullptr;
   768   }
   769 }
   772 /**
   773  * This is used by Result::SetSortingMode and QueryResultNode::FillChildren to
   774  * sort the child list.
   775  *
   776  * This does NOT update any visibility or tree information.  The caller will
   777  * have to completely rebuild the visible list after this.
   778  */
   779 void
   780 nsNavHistoryContainerResultNode::RecursiveSort(
   781     const char* aData, SortComparator aComparator)
   782 {
   783   void* data = const_cast<void*>(static_cast<const void*>(aData));
   785   mChildren.Sort(aComparator, data);
   786   for (int32_t i = 0; i < mChildren.Count(); ++i) {
   787     if (mChildren[i]->IsContainer())
   788       mChildren[i]->GetAsContainer()->RecursiveSort(aData, aComparator);
   789   }
   790 }
   793 /**
   794  * @return the index that the given item would fall on if it were to be
   795  * inserted using the given sorting.
   796  */
   797 uint32_t
   798 nsNavHistoryContainerResultNode::FindInsertionPoint(
   799     nsNavHistoryResultNode* aNode, SortComparator aComparator,
   800     const char* aData, bool* aItemExists)
   801 {
   802   if (aItemExists)
   803     (*aItemExists) = false;
   805   if (mChildren.Count() == 0)
   806     return 0;
   808   void* data = const_cast<void*>(static_cast<const void*>(aData));
   810   // The common case is the beginning or the end because this is used to insert
   811   // new items that are added to history, which is usually sorted by date.
   812   int32_t res;
   813   res = aComparator(aNode, mChildren[0], data);
   814   if (res <= 0) {
   815     if (aItemExists && res == 0)
   816       (*aItemExists) = true;
   817     return 0;
   818   }
   819   res = aComparator(aNode, mChildren[mChildren.Count() - 1], data);
   820   if (res >= 0) {
   821     if (aItemExists && res == 0)
   822       (*aItemExists) = true;
   823     return mChildren.Count();
   824   }
   826   uint32_t beginRange = 0; // inclusive
   827   uint32_t endRange = mChildren.Count(); // exclusive
   828   while (1) {
   829     if (beginRange == endRange)
   830       return endRange;
   831     uint32_t center = beginRange + (endRange - beginRange) / 2;
   832     int32_t res = aComparator(aNode, mChildren[center], data);
   833     if (res <= 0) {
   834       endRange = center; // left side
   835       if (aItemExists && res == 0)
   836         (*aItemExists) = true;
   837     }
   838     else {
   839       beginRange = center + 1; // right site
   840     }
   841   }
   842 }
   845 /**
   846  * This checks the child node at the given index to see if its sorting is
   847  * correct.  This is called when nodes are updated and we need to see whether
   848  * we need to move it.
   849  *
   850  * @returns true if not and it should be resorted.
   851 */
   852 bool
   853 nsNavHistoryContainerResultNode::DoesChildNeedResorting(uint32_t aIndex,
   854     SortComparator aComparator, const char* aData)
   855 {
   856   NS_ASSERTION(aIndex < uint32_t(mChildren.Count()),
   857                "Input index out of range");
   858   if (mChildren.Count() == 1)
   859     return false;
   861   void* data = const_cast<void*>(static_cast<const void*>(aData));
   863   if (aIndex > 0) {
   864     // compare to previous item
   865     if (aComparator(mChildren[aIndex - 1], mChildren[aIndex], data) > 0)
   866       return true;
   867   }
   868   if (aIndex < uint32_t(mChildren.Count()) - 1) {
   869     // compare to next item
   870     if (aComparator(mChildren[aIndex], mChildren[aIndex + 1], data) > 0)
   871       return true;
   872   }
   873   return false;
   874 }
   877 /* static */
   878 int32_t nsNavHistoryContainerResultNode::SortComparison_StringLess(
   879     const nsAString& a, const nsAString& b) {
   881   nsNavHistory* history = nsNavHistory::GetHistoryService();
   882   NS_ENSURE_TRUE(history, 0);
   883   nsICollation* collation = history->GetCollation();
   884   NS_ENSURE_TRUE(collation, 0);
   886   int32_t res = 0;
   887   collation->CompareString(nsICollation::kCollationCaseInSensitive, a, b, &res);
   888   return res;
   889 }
   892 /**
   893  * When there are bookmark indices, we should never have ties, so we don't
   894  * need to worry about tiebreaking.  When there are no bookmark indices,
   895  * everything will be -1 and we don't worry about sorting.
   896  */
   897 int32_t nsNavHistoryContainerResultNode::SortComparison_Bookmark(
   898     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
   899 {
   900   return a->mBookmarkIndex - b->mBookmarkIndex;
   901 }
   903 /**
   904  * These are a little more complicated because they do a localization
   905  * conversion.  If this is too slow, we can compute the sort keys once in
   906  * advance, sort that array, and then reorder the real array accordingly.
   907  * This would save some key generations.
   908  *
   909  * The collation object must be allocated before sorting on title!
   910  */
   911 int32_t nsNavHistoryContainerResultNode::SortComparison_TitleLess(
   912     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
   913 {
   914   uint32_t aType;
   915   a->GetType(&aType);
   917   int32_t value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
   918                                             NS_ConvertUTF8toUTF16(b->mTitle));
   919   if (value == 0) {
   920     // resolve by URI
   921     if (a->IsURI()) {
   922       value = a->mURI.Compare(b->mURI.get());
   923     }
   924     if (value == 0) {
   925       // resolve by date
   926       value = ComparePRTime(a->mTime, b->mTime);
   927       if (value == 0)
   928         value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
   929     }
   930   }
   931   return value;
   932 }
   933 int32_t nsNavHistoryContainerResultNode::SortComparison_TitleGreater(
   934     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
   935 {
   936   return -SortComparison_TitleLess(a, b, closure);
   937 }
   939 /**
   940  * Equal times will be very unusual, but it is important that there is some
   941  * deterministic ordering of the results so they don't move around.
   942  */
   943 int32_t nsNavHistoryContainerResultNode::SortComparison_DateLess(
   944     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
   945 {
   946   int32_t value = ComparePRTime(a->mTime, b->mTime);
   947   if (value == 0) {
   948     value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
   949                                       NS_ConvertUTF8toUTF16(b->mTitle));
   950     if (value == 0)
   951       value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
   952   }
   953   return value;
   954 }
   955 int32_t nsNavHistoryContainerResultNode::SortComparison_DateGreater(
   956     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
   957 {
   958   return -nsNavHistoryContainerResultNode::SortComparison_DateLess(a, b, closure);
   959 }
   962 int32_t nsNavHistoryContainerResultNode::SortComparison_DateAddedLess(
   963     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
   964 {
   965   int32_t value = ComparePRTime(a->mDateAdded, b->mDateAdded);
   966   if (value == 0) {
   967     value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
   968                                       NS_ConvertUTF8toUTF16(b->mTitle));
   969     if (value == 0)
   970       value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
   971   }
   972   return value;
   973 }
   974 int32_t nsNavHistoryContainerResultNode::SortComparison_DateAddedGreater(
   975     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
   976 {
   977   return -nsNavHistoryContainerResultNode::SortComparison_DateAddedLess(a, b, closure);
   978 }
   981 int32_t nsNavHistoryContainerResultNode::SortComparison_LastModifiedLess(
   982     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
   983 {
   984   int32_t value = ComparePRTime(a->mLastModified, b->mLastModified);
   985   if (value == 0) {
   986     value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
   987                                       NS_ConvertUTF8toUTF16(b->mTitle));
   988     if (value == 0)
   989       value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
   990   }
   991   return value;
   992 }
   993 int32_t nsNavHistoryContainerResultNode::SortComparison_LastModifiedGreater(
   994     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
   995 {
   996   return -nsNavHistoryContainerResultNode::SortComparison_LastModifiedLess(a, b, closure);
   997 }
  1000 /**
  1001  * Certain types of parent nodes are treated specially because URIs are not
  1002  * valid (like days or hosts).
  1003  */
  1004 int32_t nsNavHistoryContainerResultNode::SortComparison_URILess(
  1005     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
  1007   int32_t value;
  1008   if (a->IsURI() && b->IsURI()) {
  1009     // normal URI or visit
  1010     value = a->mURI.Compare(b->mURI.get());
  1011   } else {
  1012     // for everything else, use title (= host name)
  1013     value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
  1014                                       NS_ConvertUTF8toUTF16(b->mTitle));
  1017   if (value == 0) {
  1018     value = ComparePRTime(a->mTime, b->mTime);
  1019     if (value == 0)
  1020       value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
  1022   return value;
  1024 int32_t nsNavHistoryContainerResultNode::SortComparison_URIGreater(
  1025     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
  1027   return -SortComparison_URILess(a, b, closure);
  1031 int32_t nsNavHistoryContainerResultNode::SortComparison_KeywordLess(
  1032     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
  1034   int32_t value = 0;
  1035   if (a->mItemId != -1 || b->mItemId != -1) {
  1036     // compare the keywords
  1037     nsAutoString keywordA, keywordB;
  1038     nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
  1039     NS_ENSURE_TRUE(bookmarks, 0);
  1041     nsresult rv;
  1042     if (a->mItemId != -1) {
  1043       rv = bookmarks->GetKeywordForBookmark(a->mItemId, keywordA);
  1044       NS_ENSURE_SUCCESS(rv, 0);
  1046     if (b->mItemId != -1) {
  1047       rv = bookmarks->GetKeywordForBookmark(b->mItemId, keywordB);
  1048       NS_ENSURE_SUCCESS(rv, 0);
  1051     value = SortComparison_StringLess(keywordA, keywordB);
  1054   // Fall back to title sorting.
  1055   if (value == 0)
  1056     value = SortComparison_TitleLess(a, b, closure);
  1058   return value;
  1061 int32_t nsNavHistoryContainerResultNode::SortComparison_KeywordGreater(
  1062     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
  1064   return -SortComparison_KeywordLess(a, b, closure);
  1067 int32_t nsNavHistoryContainerResultNode::SortComparison_AnnotationLess(
  1068     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
  1070   nsAutoCString annoName(static_cast<char*>(closure));
  1071   NS_ENSURE_TRUE(!annoName.IsEmpty(), 0);
  1073   bool a_itemAnno = false;
  1074   bool b_itemAnno = false;
  1076   // Not used for item annos
  1077   nsCOMPtr<nsIURI> a_uri, b_uri;
  1078   if (a->mItemId != -1) {
  1079     a_itemAnno = true;
  1080   } else {
  1081     nsAutoCString spec;
  1082     if (NS_SUCCEEDED(a->GetUri(spec)))
  1083       NS_NewURI(getter_AddRefs(a_uri), spec);
  1084     NS_ENSURE_TRUE(a_uri, 0);
  1087   if (b->mItemId != -1) {
  1088     b_itemAnno = true;
  1089   } else {
  1090     nsAutoCString spec;
  1091     if (NS_SUCCEEDED(b->GetUri(spec)))
  1092       NS_NewURI(getter_AddRefs(b_uri), spec);
  1093     NS_ENSURE_TRUE(b_uri, 0);
  1096   nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
  1097   NS_ENSURE_TRUE(annosvc, 0);
  1099   bool a_hasAnno, b_hasAnno;
  1100   if (a_itemAnno) {
  1101     NS_ENSURE_SUCCESS(annosvc->ItemHasAnnotation(a->mItemId, annoName,
  1102                                                  &a_hasAnno), 0);
  1103   } else {
  1104     NS_ENSURE_SUCCESS(annosvc->PageHasAnnotation(a_uri, annoName,
  1105                                                  &a_hasAnno), 0);
  1107   if (b_itemAnno) {
  1108     NS_ENSURE_SUCCESS(annosvc->ItemHasAnnotation(b->mItemId, annoName,
  1109                                                  &b_hasAnno), 0);
  1110   } else {
  1111     NS_ENSURE_SUCCESS(annosvc->PageHasAnnotation(b_uri, annoName,
  1112                                                  &b_hasAnno), 0);    
  1115   int32_t value = 0;
  1116   if (a_hasAnno || b_hasAnno) {
  1117     uint16_t annoType;
  1118     if (a_hasAnno) {
  1119       if (a_itemAnno) {
  1120         NS_ENSURE_SUCCESS(annosvc->GetItemAnnotationType(a->mItemId,
  1121                                                          annoName,
  1122                                                          &annoType), 0);
  1123       } else {
  1124         NS_ENSURE_SUCCESS(annosvc->GetPageAnnotationType(a_uri, annoName,
  1125                                                          &annoType), 0);
  1128     if (b_hasAnno) {
  1129       uint16_t b_type;
  1130       if (b_itemAnno) {
  1131         NS_ENSURE_SUCCESS(annosvc->GetItemAnnotationType(b->mItemId,
  1132                                                          annoName,
  1133                                                          &b_type), 0);
  1134       } else {
  1135         NS_ENSURE_SUCCESS(annosvc->GetPageAnnotationType(b_uri, annoName,
  1136                                                          &b_type), 0);
  1138       // We better make the API not support this state, really
  1139       // XXXmano: this is actually wrong for double<->int and int64_t<->int32_t
  1140       if (a_hasAnno && b_type != annoType)
  1141         return 0;
  1142       annoType = b_type;
  1145 #define GET_ANNOTATIONS_VALUES(METHOD_ITEM, METHOD_PAGE, A_VAL, B_VAL)        \
  1146         if (a_hasAnno) {                                                      \
  1147           if (a_itemAnno) {                                                   \
  1148             NS_ENSURE_SUCCESS(annosvc->METHOD_ITEM(a->mItemId, annoName,      \
  1149                                                    A_VAL), 0);                \
  1150           } else {                                                            \
  1151             NS_ENSURE_SUCCESS(annosvc->METHOD_PAGE(a_uri, annoName,           \
  1152                                                    A_VAL), 0);                \
  1153           }                                                                   \
  1154         }                                                                     \
  1155         if (b_hasAnno) {                                                      \
  1156           if (b_itemAnno) {                                                   \
  1157             NS_ENSURE_SUCCESS(annosvc->METHOD_ITEM(b->mItemId, annoName,      \
  1158                                                    B_VAL), 0);                \
  1159           } else {                                                            \
  1160             NS_ENSURE_SUCCESS(annosvc->METHOD_PAGE(b_uri, annoName,           \
  1161                                                    B_VAL), 0);                \
  1162           }                                                                   \
  1165     if (annoType == nsIAnnotationService::TYPE_STRING) {
  1166       nsAutoString a_val, b_val;
  1167       GET_ANNOTATIONS_VALUES(GetItemAnnotationString,
  1168                              GetPageAnnotationString, a_val, b_val);
  1169       value = SortComparison_StringLess(a_val, b_val);
  1171     else if (annoType == nsIAnnotationService::TYPE_INT32) {
  1172       int32_t a_val = 0, b_val = 0;
  1173       GET_ANNOTATIONS_VALUES(GetItemAnnotationInt32,
  1174                              GetPageAnnotationInt32, &a_val, &b_val);
  1175       value = (a_val < b_val) ? -1 : (a_val > b_val) ? 1 : 0;
  1177     else if (annoType == nsIAnnotationService::TYPE_INT64) {
  1178       int64_t a_val = 0, b_val = 0;
  1179       GET_ANNOTATIONS_VALUES(GetItemAnnotationInt64,
  1180                              GetPageAnnotationInt64, &a_val, &b_val);
  1181       value = (a_val < b_val) ? -1 : (a_val > b_val) ? 1 : 0;
  1183     else if (annoType == nsIAnnotationService::TYPE_DOUBLE) {
  1184       double a_val = 0, b_val = 0;
  1185       GET_ANNOTATIONS_VALUES(GetItemAnnotationDouble,
  1186                              GetPageAnnotationDouble, &a_val, &b_val);
  1187       value = (a_val < b_val) ? -1 : (a_val > b_val) ? 1 : 0;
  1191   // Note we also fall back to the title-sorting route one of the items didn't
  1192   // have the annotation set or if both had it set but in a different storage
  1193   // type
  1194   if (value == 0)
  1195     return SortComparison_TitleLess(a, b, nullptr);
  1197   return value;
  1199 int32_t nsNavHistoryContainerResultNode::SortComparison_AnnotationGreater(
  1200     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
  1202   return -SortComparison_AnnotationLess(a, b, closure);
  1205 /**
  1206  * Fall back on dates for conflict resolution
  1207  */
  1208 int32_t nsNavHistoryContainerResultNode::SortComparison_VisitCountLess(
  1209     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
  1211   int32_t value = CompareIntegers(a->mAccessCount, b->mAccessCount);
  1212   if (value == 0) {
  1213     value = ComparePRTime(a->mTime, b->mTime);
  1214     if (value == 0)
  1215       value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
  1217   return value;
  1219 int32_t nsNavHistoryContainerResultNode::SortComparison_VisitCountGreater(
  1220     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
  1222   return -nsNavHistoryContainerResultNode::SortComparison_VisitCountLess(a, b, closure);
  1226 int32_t nsNavHistoryContainerResultNode::SortComparison_TagsLess(
  1227     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
  1229   int32_t value = 0;
  1230   nsAutoString aTags, bTags;
  1232   nsresult rv = a->GetTags(aTags);
  1233   NS_ENSURE_SUCCESS(rv, 0);
  1235   rv = b->GetTags(bTags);
  1236   NS_ENSURE_SUCCESS(rv, 0);
  1238   value = SortComparison_StringLess(aTags, bTags);
  1240   // fall back to title sorting
  1241   if (value == 0)
  1242     value = SortComparison_TitleLess(a, b, closure);
  1244   return value;
  1247 int32_t nsNavHistoryContainerResultNode::SortComparison_TagsGreater(
  1248     nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
  1250   return -SortComparison_TagsLess(a, b, closure);
  1253 /**
  1254  * Fall back on date and bookmarked status, for conflict resolution.
  1255  */
  1256 int32_t
  1257 nsNavHistoryContainerResultNode::SortComparison_FrecencyLess(
  1258   nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure
  1261   int32_t value = CompareIntegers(a->mFrecency, b->mFrecency);
  1262   if (value == 0) {
  1263     value = ComparePRTime(a->mTime, b->mTime);
  1264     if (value == 0) {
  1265       value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
  1268   return value;
  1270 int32_t
  1271 nsNavHistoryContainerResultNode::SortComparison_FrecencyGreater(
  1272   nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure
  1275   return -nsNavHistoryContainerResultNode::SortComparison_FrecencyLess(a, b, closure);
  1278 /**
  1279  * Searches this folder for a node with the given URI.  Returns null if not
  1280  * found.
  1282  * @note Does not addref the node!
  1283  */
  1284 nsNavHistoryResultNode*
  1285 nsNavHistoryContainerResultNode::FindChildURI(const nsACString& aSpec,
  1286     uint32_t* aNodeIndex)
  1288   for (int32_t i = 0; i < mChildren.Count(); ++i) {
  1289     if (mChildren[i]->IsURI()) {
  1290       if (aSpec.Equals(mChildren[i]->mURI)) {
  1291         *aNodeIndex = i;
  1292         return mChildren[i];
  1296   return nullptr;
  1299 /**
  1300  * This does the work of adding a child to the container.  The child can be
  1301  * either a container or or a single item that may even be collapsed with the
  1302  * adjacent ones.
  1304  * Some inserts are "temporary" meaning that they are happening immediately
  1305  * after a temporary remove.  We do this when movings elements when they
  1306  * change to keep them in the proper sorting position.  In these cases, we
  1307  * don't need to recompute any statistics.
  1308  */
  1309 nsresult
  1310 nsNavHistoryContainerResultNode::InsertChildAt(nsNavHistoryResultNode* aNode,
  1311                                                int32_t aIndex,
  1312                                                bool aIsTemporary)
  1314   nsNavHistoryResult* result = GetResult();
  1315   NS_ENSURE_STATE(result);
  1317   aNode->mParent = this;
  1318   aNode->mIndentLevel = mIndentLevel + 1;
  1319   if (!aIsTemporary && aNode->IsContainer()) {
  1320     // need to update all the new item's children
  1321     nsNavHistoryContainerResultNode* container = aNode->GetAsContainer();
  1322     container->mResult = result;
  1323     container->FillStats();
  1326   if (!mChildren.InsertObjectAt(aNode, aIndex))
  1327     return NS_ERROR_OUT_OF_MEMORY;
  1329   // Update our stats and notify the result's observers.
  1330   if (!aIsTemporary) {
  1331     mAccessCount += aNode->mAccessCount;
  1332     if (mTime < aNode->mTime)
  1333       mTime = aNode->mTime;
  1334     if (!mParent || mParent->AreChildrenVisible()) {
  1335       NOTIFY_RESULT_OBSERVERS(result,
  1336                               NodeHistoryDetailsChanged(TO_ICONTAINER(this),
  1337                                                         mTime,
  1338                                                         mAccessCount));
  1341     nsresult rv = ReverseUpdateStats(aNode->mAccessCount);
  1342     NS_ENSURE_SUCCESS(rv, rv);
  1345   // Update tree if we are visible.  Note that we could be here and not
  1346   // expanded, like when there is a bookmark folder being updated because its
  1347   // parent is visible.
  1348   if (AreChildrenVisible())
  1349     NOTIFY_RESULT_OBSERVERS(result, NodeInserted(this, aNode, aIndex));
  1351   return NS_OK;
  1355 /**
  1356  * This locates the proper place for insertion according to the current sort
  1357  * and calls InsertChildAt
  1358  */
  1359 nsresult
  1360 nsNavHistoryContainerResultNode::InsertSortedChild(
  1361     nsNavHistoryResultNode* aNode, 
  1362     bool aIsTemporary, bool aIgnoreDuplicates)
  1365   if (mChildren.Count() == 0)
  1366     return InsertChildAt(aNode, 0, aIsTemporary);
  1368   SortComparator comparator = GetSortingComparator(GetSortType());
  1369   if (comparator) {
  1370     // When inserting a new node, it must have proper statistics because we use
  1371     // them to find the correct insertion point.  The insert function will then
  1372     // recompute these statistics and fill in the proper parents and hierarchy
  1373     // level.  Doing this twice shouldn't be a large performance penalty because
  1374     // when we are inserting new containers, they typically contain only one
  1375     // item (because we've browsed a new page).
  1376     if (!aIsTemporary && aNode->IsContainer()) {
  1377       // need to update all the new item's children
  1378       nsNavHistoryContainerResultNode* container = aNode->GetAsContainer();
  1379       container->mResult = mResult;
  1380       container->FillStats();
  1383     nsAutoCString sortingAnnotation;
  1384     GetSortingAnnotation(sortingAnnotation);
  1385     bool itemExists;
  1386     uint32_t position = FindInsertionPoint(aNode, comparator, 
  1387                                            sortingAnnotation.get(), 
  1388                                            &itemExists);
  1389     if (aIgnoreDuplicates && itemExists)
  1390       return NS_OK;
  1392     return InsertChildAt(aNode, position, aIsTemporary);
  1394   return InsertChildAt(aNode, mChildren.Count(), aIsTemporary);
  1397 /**
  1398  * This checks if the item at aIndex is located correctly given the sorting
  1399  * move.  If it's not, the item is moved, and the result's observers are
  1400  * notified.
  1402  * @return true if the item position has been changed, false otherwise.
  1403  */
  1404 bool
  1405 nsNavHistoryContainerResultNode::EnsureItemPosition(uint32_t aIndex) {
  1406   NS_ASSERTION(aIndex < (uint32_t)mChildren.Count(), "Invalid index");
  1407   if (aIndex >= (uint32_t)mChildren.Count())
  1408     return false;
  1410   SortComparator comparator = GetSortingComparator(GetSortType());
  1411   if (!comparator)
  1412     return false;
  1414   nsAutoCString sortAnno;
  1415   GetSortingAnnotation(sortAnno);
  1416   if (!DoesChildNeedResorting(aIndex, comparator, sortAnno.get()))
  1417     return false;
  1419   nsRefPtr<nsNavHistoryResultNode> node(mChildren[aIndex]);
  1420   mChildren.RemoveObjectAt(aIndex);
  1422   uint32_t newIndex = FindInsertionPoint(
  1423                           node, comparator,sortAnno.get(), nullptr);
  1424   mChildren.InsertObjectAt(node.get(), newIndex);
  1426   if (AreChildrenVisible()) {
  1427     nsNavHistoryResult* result = GetResult();
  1428     NOTIFY_RESULT_OBSERVERS_RET(result,
  1429                                 NodeMoved(node, this, aIndex, this, newIndex),
  1430                                 false);
  1433   return true;
  1436 /**
  1437  * This does all the work of removing a child from this container, including
  1438  * updating the tree if necessary.  Note that we do not need to be open for
  1439  * this to work.
  1441  * Some removes are "temporary" meaning that they'll just get inserted again.
  1442  * We do this for resorting.  In these cases, we don't need to recompute any
  1443  * statistics, and we shouldn't notify those container that they are being
  1444  * removed.
  1445  */
  1446 nsresult
  1447 nsNavHistoryContainerResultNode::RemoveChildAt(int32_t aIndex,
  1448                                                bool aIsTemporary)
  1450   NS_ASSERTION(aIndex >= 0 && aIndex < mChildren.Count(), "Invalid index");
  1452   // Hold an owning reference to keep from expiring while we work with it.
  1453   nsRefPtr<nsNavHistoryResultNode> oldNode = mChildren[aIndex];
  1455   // Update stats.
  1456   uint32_t oldAccessCount = 0;
  1457   if (!aIsTemporary) {
  1458     oldAccessCount = mAccessCount;
  1459     mAccessCount -= mChildren[aIndex]->mAccessCount;
  1460     NS_ASSERTION(mAccessCount >= 0, "Invalid access count while updating!");
  1463   // Remove it from our list and notify the result's observers.
  1464   mChildren.RemoveObjectAt(aIndex);
  1465   if (AreChildrenVisible()) {
  1466     nsNavHistoryResult* result = GetResult();
  1467     NOTIFY_RESULT_OBSERVERS(result,
  1468                             NodeRemoved(this, oldNode, aIndex));
  1471   if (!aIsTemporary) {
  1472     nsresult rv = ReverseUpdateStats(mAccessCount - oldAccessCount);
  1473     NS_ENSURE_SUCCESS(rv, rv);
  1474     oldNode->OnRemoving();
  1476   return NS_OK;
  1480 /**
  1481  * Searches for matches for the given URI.  If aOnlyOne is set, it will
  1482  * terminate as soon as it finds a single match.  This would be used when there
  1483  * are URI results so there will only ever be one copy of any URI.
  1485  * When aOnlyOne is false, it will check all elements.  This is for visit
  1486  * style results that may have multiple copies of any given URI.
  1487  */
  1488 void
  1489 nsNavHistoryContainerResultNode::RecursiveFindURIs(bool aOnlyOne,
  1490     nsNavHistoryContainerResultNode* aContainer, const nsCString& aSpec,
  1491     nsCOMArray<nsNavHistoryResultNode>* aMatches)
  1493   for (int32_t child = 0; child < aContainer->mChildren.Count(); ++child) {
  1494     uint32_t type;
  1495     aContainer->mChildren[child]->GetType(&type);
  1496     if (nsNavHistoryResultNode::IsTypeURI(type)) {
  1497       // compare URIs
  1498       nsNavHistoryResultNode* uriNode = aContainer->mChildren[child];
  1499       if (uriNode->mURI.Equals(aSpec)) {
  1500         // found
  1501         aMatches->AppendObject(uriNode);
  1502         if (aOnlyOne)
  1503           return;
  1510 /**
  1511  * If aUpdateSort is true, we will also update the sorting of this item.
  1512  * Normally you want this to be true, but it can be false if the thing you are
  1513  * changing can not affect sorting (like favicons).
  1515  * You should NOT change any child lists as part of the callback function.
  1516  */
  1517 bool
  1518 nsNavHistoryContainerResultNode::UpdateURIs(bool aRecursive, bool aOnlyOne,
  1519     bool aUpdateSort, const nsCString& aSpec,
  1520     nsresult (*aCallback)(nsNavHistoryResultNode*, const void*, const nsNavHistoryResult*),
  1521     const void* aClosure)
  1523   const nsNavHistoryResult* result = GetResult();
  1524   if (!result) {
  1525     MOZ_ASSERT(false, "Should have a result");
  1526     return false;
  1529   // this needs to be owning since sometimes we remove and re-insert nodes
  1530   // in their parents and we don't want them to go away.
  1531   nsCOMArray<nsNavHistoryResultNode> matches;
  1533   if (aRecursive) {
  1534     RecursiveFindURIs(aOnlyOne, this, aSpec, &matches);
  1535   } else if (aOnlyOne) {
  1536     uint32_t nodeIndex;
  1537     nsNavHistoryResultNode* node = FindChildURI(aSpec, &nodeIndex);
  1538     if (node)
  1539       matches.AppendObject(node);
  1540   } else {
  1541     MOZ_ASSERT(false,
  1542                "UpdateURIs does not handle nonrecursive updates of multiple items.");
  1543     // this case easy to add if you need it, just find all the matching URIs
  1544     // at this level.  However, this isn't currently used. History uses
  1545     // recursive, Bookmarks uses one level and knows that the match is unique.
  1546     return false;
  1549   if (matches.Count() == 0)
  1550     return false;
  1552   // PERFORMANCE: This updates each container for each child in it that
  1553   // changes.  In some cases, many elements have changed inside the same
  1554   // container.  It would be better to compose a list of containers, and
  1555   // update each one only once for all the items that have changed in it.
  1556   for (int32_t i = 0; i < matches.Count(); ++i)
  1558     nsNavHistoryResultNode* node = matches[i];
  1559     nsNavHistoryContainerResultNode* parent = node->mParent;
  1560     if (!parent) {
  1561       MOZ_ASSERT(false, "All URI nodes being updated must have parents");
  1562       continue;
  1565     uint32_t oldAccessCount = node->mAccessCount;
  1566     PRTime oldTime = node->mTime;
  1567     aCallback(node, aClosure, result);
  1569     if (oldAccessCount != node->mAccessCount || oldTime != node->mTime) {
  1570       parent->mAccessCount += node->mAccessCount - oldAccessCount;
  1571       if (node->mTime > parent->mTime)
  1572         parent->mTime = node->mTime;
  1573       if (parent->AreChildrenVisible()) {
  1574         NOTIFY_RESULT_OBSERVERS_RET(result,
  1575                                     NodeHistoryDetailsChanged(
  1576                                       TO_ICONTAINER(parent),
  1577                                       parent->mTime,
  1578                                       parent->mAccessCount),
  1579                                     true);
  1581       DebugOnly<nsresult> rv = parent->ReverseUpdateStats(node->mAccessCount - oldAccessCount);
  1582       MOZ_ASSERT(NS_SUCCEEDED(rv), "should be able to ReverseUpdateStats");
  1585     if (aUpdateSort) {
  1586       int32_t childIndex = parent->FindChild(node);
  1587       MOZ_ASSERT(childIndex >= 0, "Could not find child we just got a reference to");
  1588       if (childIndex >= 0)
  1589         parent->EnsureItemPosition(childIndex);
  1593   return true;
  1597 /**
  1598  * This is used to update the titles in the tree.  This is called from both
  1599  * query and bookmark folder containers to update the tree.  Bookmark folders
  1600  * should be sure to set recursive to false, since child folders will have
  1601  * their own callbacks registered.
  1602  */
  1603 static nsresult setTitleCallback(nsNavHistoryResultNode* aNode,
  1604                                  const void* aClosure,
  1605                                  const nsNavHistoryResult* aResult)
  1607   const nsACString* newTitle = static_cast<const nsACString*>(aClosure);
  1608   aNode->mTitle = *newTitle;
  1610   if (aResult && (!aNode->mParent || aNode->mParent->AreChildrenVisible()))
  1611     NOTIFY_RESULT_OBSERVERS(aResult, NodeTitleChanged(aNode, *newTitle));
  1613   return NS_OK;
  1615 nsresult
  1616 nsNavHistoryContainerResultNode::ChangeTitles(nsIURI* aURI,
  1617                                               const nsACString& aNewTitle,
  1618                                               bool aRecursive,
  1619                                               bool aOnlyOne)
  1621   // uri string
  1622   nsAutoCString uriString;
  1623   nsresult rv = aURI->GetSpec(uriString);
  1624   NS_ENSURE_SUCCESS(rv, rv);
  1626   // The recursive function will update the result's tree nodes, but only if we
  1627   // give it a non-null pointer.  So if there isn't a tree, just pass nullptr
  1628   // so it doesn't bother trying to call the result.
  1629   nsNavHistoryResult* result = GetResult();
  1630   NS_ENSURE_STATE(result);
  1632   uint16_t sortType = GetSortType();
  1633   bool updateSorting =
  1634     (sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING ||
  1635      sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING);
  1637   UpdateURIs(aRecursive, aOnlyOne, updateSorting, uriString,
  1638              setTitleCallback,
  1639              static_cast<const void*>(&aNewTitle));
  1641   return NS_OK;
  1645 /**
  1646  * Complex containers (folders and queries) will override this.  Here, we
  1647  * handle the case of simple containers (like host groups) where the children
  1648  * are always stored.
  1649  */
  1650 NS_IMETHODIMP
  1651 nsNavHistoryContainerResultNode::GetHasChildren(bool *aHasChildren)
  1653   *aHasChildren = (mChildren.Count() > 0);
  1654   return NS_OK;
  1658 /**
  1659  * @throws if this node is closed.
  1660  */
  1661 NS_IMETHODIMP
  1662 nsNavHistoryContainerResultNode::GetChildCount(uint32_t* aChildCount)
  1664   if (!mExpanded)
  1665     return NS_ERROR_NOT_AVAILABLE;
  1666   *aChildCount = mChildren.Count();
  1667   return NS_OK;
  1671 NS_IMETHODIMP
  1672 nsNavHistoryContainerResultNode::GetChild(uint32_t aIndex,
  1673                                           nsINavHistoryResultNode** _retval)
  1675   if (!mExpanded)
  1676     return NS_ERROR_NOT_AVAILABLE;
  1677   if (aIndex >= uint32_t(mChildren.Count()))
  1678     return NS_ERROR_INVALID_ARG;
  1679   NS_ADDREF(*_retval = mChildren[aIndex]);
  1680   return NS_OK;
  1684 NS_IMETHODIMP
  1685 nsNavHistoryContainerResultNode::GetChildIndex(nsINavHistoryResultNode* aNode,
  1686                                                uint32_t* _retval)
  1688   if (!mExpanded)
  1689     return NS_ERROR_NOT_AVAILABLE;
  1691   int32_t nodeIndex = FindChild(static_cast<nsNavHistoryResultNode*>(aNode));
  1692   if (nodeIndex == -1)
  1693     return NS_ERROR_INVALID_ARG;
  1695   *_retval = nodeIndex;
  1696   return NS_OK;
  1700 NS_IMETHODIMP
  1701 nsNavHistoryContainerResultNode::FindNodeByDetails(const nsACString& aURIString,
  1702                                                    PRTime aTime,
  1703                                                    int64_t aItemId,
  1704                                                    bool aRecursive,
  1705                                                    nsINavHistoryResultNode** _retval) {
  1706   if (!mExpanded)
  1707     return NS_ERROR_NOT_AVAILABLE;
  1709   *_retval = nullptr;
  1710   for (int32_t i = 0; i < mChildren.Count(); ++i) {
  1711     if (mChildren[i]->mURI.Equals(aURIString) &&
  1712         mChildren[i]->mTime == aTime &&
  1713         mChildren[i]->mItemId == aItemId) {
  1714       *_retval = mChildren[i];
  1715       break;
  1718     if (aRecursive && mChildren[i]->IsContainer()) {
  1719       nsNavHistoryContainerResultNode* asContainer =
  1720         mChildren[i]->GetAsContainer();
  1721       if (asContainer->mExpanded) {
  1722         nsresult rv = asContainer->FindNodeByDetails(aURIString, aTime,
  1723                                                      aItemId,
  1724                                                      aRecursive,
  1725                                                      _retval);
  1727         if (NS_SUCCEEDED(rv) && _retval)
  1728           break;
  1732   NS_IF_ADDREF(*_retval);
  1733   return NS_OK;
  1736 /**
  1737  * @note Overridden for folders to query the bookmarks service directly.
  1738  */
  1739 NS_IMETHODIMP
  1740 nsNavHistoryContainerResultNode::GetChildrenReadOnly(bool *aChildrenReadOnly)
  1742   *aChildrenReadOnly = mChildrenReadOnly;
  1743   return NS_OK;
  1746 /**
  1747  * HOW QUERY UPDATING WORKS
  1749  * Queries are different than bookmark folders in that we can not always do
  1750  * dynamic updates (easily) and updates are more expensive.  Therefore, we do
  1751  * NOT query if we are not open and want to see if we have any children (for
  1752  * drawing a twisty) and always assume we will.
  1754  * When the container is opened, we execute the query and register the
  1755  * listeners.  Like bookmark folders, we stay registered even when closed, and
  1756  * clear ourselves as soon as a message comes in.  This lets us respond quickly
  1757  * if the user closes and reopens the container.
  1759  * We try to handle the most common notifications for the most common query
  1760  * types dynamically, that is, figuring out what should happen in response to
  1761  * a message without doing a requery.  For complex changes or complex queries,
  1762  * we give up and requery.
  1763  */
  1764 NS_IMPL_ISUPPORTS_INHERITED(nsNavHistoryQueryResultNode,
  1765                             nsNavHistoryContainerResultNode,
  1766                             nsINavHistoryQueryResultNode)
  1768 nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
  1769     const nsACString& aTitle, const nsACString& aIconURI,
  1770     const nsACString& aQueryURI) :
  1771   nsNavHistoryContainerResultNode(aQueryURI, aTitle, aIconURI,
  1772                                   nsNavHistoryResultNode::RESULT_TYPE_QUERY,
  1773                                   true, nullptr),
  1774   mLiveUpdate(QUERYUPDATE_COMPLEX_WITH_BOOKMARKS),
  1775   mHasSearchTerms(false),
  1776   mContentsValid(false),
  1777   mBatchChanges(0)
  1781 nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
  1782     const nsACString& aTitle, const nsACString& aIconURI,
  1783     const nsCOMArray<nsNavHistoryQuery>& aQueries,
  1784     nsNavHistoryQueryOptions* aOptions) :
  1785   nsNavHistoryContainerResultNode(EmptyCString(), aTitle, aIconURI,
  1786                                   nsNavHistoryResultNode::RESULT_TYPE_QUERY,
  1787                                   true, aOptions),
  1788   mQueries(aQueries),
  1789   mContentsValid(false),
  1790   mBatchChanges(0),
  1791   mTransitions(mQueries[0]->Transitions())
  1793   NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query");
  1795   nsNavHistory* history = nsNavHistory::GetHistoryService();
  1796   NS_ASSERTION(history, "History service missing");
  1797   if (history) {
  1798     mLiveUpdate = history->GetUpdateRequirements(mQueries, mOptions,
  1799                                                  &mHasSearchTerms);
  1802   // Collect transitions shared by all queries.
  1803   for (int32_t i = 1; i < mQueries.Count(); ++i) {
  1804     const nsTArray<uint32_t>& queryTransitions = mQueries[i]->Transitions();
  1805     for (uint32_t j = 0; j < mTransitions.Length() ; ++j) {
  1806       uint32_t transition = mTransitions.SafeElementAt(j, 0);
  1807       if (transition && !queryTransitions.Contains(transition))
  1808         mTransitions.RemoveElement(transition);
  1813 nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
  1814     const nsACString& aTitle, const nsACString& aIconURI,
  1815     PRTime aTime,
  1816     const nsCOMArray<nsNavHistoryQuery>& aQueries,
  1817     nsNavHistoryQueryOptions* aOptions) :
  1818   nsNavHistoryContainerResultNode(EmptyCString(), aTitle, aTime, aIconURI,
  1819                                   nsNavHistoryResultNode::RESULT_TYPE_QUERY,
  1820                                   true, aOptions),
  1821   mQueries(aQueries),
  1822   mContentsValid(false),
  1823   mBatchChanges(0),
  1824   mTransitions(mQueries[0]->Transitions())
  1826   NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query");
  1828   nsNavHistory* history = nsNavHistory::GetHistoryService();
  1829   NS_ASSERTION(history, "History service missing");
  1830   if (history) {
  1831     mLiveUpdate = history->GetUpdateRequirements(mQueries, mOptions,
  1832                                                  &mHasSearchTerms);
  1835   // Collect transitions shared by all queries.
  1836   for (int32_t i = 1; i < mQueries.Count(); ++i) {
  1837     const nsTArray<uint32_t>& queryTransitions = mQueries[i]->Transitions();
  1838     for (uint32_t j = 0; j < mTransitions.Length() ; ++j) {
  1839       uint32_t transition = mTransitions.SafeElementAt(j, 0);
  1840       if (transition && !queryTransitions.Contains(transition))
  1841         mTransitions.RemoveElement(transition);
  1846 nsNavHistoryQueryResultNode::~nsNavHistoryQueryResultNode() {
  1847   // Remove this node from result's observers.  We don't need to be notified
  1848   // anymore.
  1849   if (mResult && mResult->mAllBookmarksObservers.IndexOf(this) !=
  1850                    mResult->mAllBookmarksObservers.NoIndex)
  1851     mResult->RemoveAllBookmarksObserver(this);
  1852   if (mResult && mResult->mHistoryObservers.IndexOf(this) !=
  1853                    mResult->mHistoryObservers.NoIndex)
  1854     mResult->RemoveHistoryObserver(this);
  1857 /**
  1858  * Whoever made us may want non-expanding queries. However, we always expand
  1859  * when we are the root node, or else asking for non-expanding queries would be
  1860  * useless.  A query node is not expandable if excludeItems is set or if
  1861  * expandQueries is unset.
  1862  */
  1863 bool
  1864 nsNavHistoryQueryResultNode::CanExpand()
  1866   if (IsContainersQuery())
  1867     return true;
  1869   // If ExcludeItems is set on the root or on the node itself, don't expand.
  1870   if ((mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
  1871       Options()->ExcludeItems())
  1872     return false;
  1874   // Check the ancestor container.
  1875   nsNavHistoryQueryOptions* options = GetGeneratingOptions();
  1876   if (options) {
  1877     if (options->ExcludeItems())
  1878       return false;
  1879     if (options->ExpandQueries())
  1880       return true;
  1883   if (mResult && mResult->mRootNode == this)
  1884     return true;
  1886   return false;
  1890 /**
  1891  * Some query with a particular result type can contain other queries.  They
  1892  * must be always expandable
  1893  */
  1894 bool
  1895 nsNavHistoryQueryResultNode::IsContainersQuery()
  1897   uint16_t resultType = Options()->ResultType();
  1898   return resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
  1899          resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
  1900          resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY ||
  1901          resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY;
  1905 /**
  1906  * Here we do not want to call ContainerResultNode::OnRemoving since our own
  1907  * ClearChildren will do the same thing and more (unregister the observers).
  1908  * The base ResultNode::OnRemoving will clear some regular node stats, so it
  1909  * is OK.
  1910  */
  1911 void
  1912 nsNavHistoryQueryResultNode::OnRemoving()
  1914   nsNavHistoryResultNode::OnRemoving();
  1915   ClearChildren(true);
  1916   mResult = nullptr;
  1920 /**
  1921  * Marks the container as open, rebuilding results if they are invalid.  We
  1922  * may still have valid results if the container was previously open and
  1923  * nothing happened since closing it.
  1925  * We do not handle CloseContainer specially.  The default one just marks the
  1926  * container as closed, but doesn't actually mark the results as invalid.
  1927  * The results will be invalidated by the next history or bookmark
  1928  * notification that comes in.  This means if you open and close the item
  1929  * without anything happening in between, it will be fast (this actually
  1930  * happens when results are used as menus).
  1931  */
  1932 nsresult
  1933 nsNavHistoryQueryResultNode::OpenContainer()
  1935   NS_ASSERTION(!mExpanded, "Container must be closed to open it");
  1936   mExpanded = true;
  1938   nsresult rv;
  1940   if (!CanExpand())
  1941     return NS_OK;
  1942   if (!mContentsValid) {
  1943     rv = FillChildren();
  1944     NS_ENSURE_SUCCESS(rv, rv);
  1947   rv = NotifyOnStateChange(STATE_CLOSED);
  1948   NS_ENSURE_SUCCESS(rv, rv);
  1950   return NS_OK;
  1954 /**
  1955  * When we have valid results we can always give an exact answer.  When we
  1956  * don't we just assume we'll have results, since actually doing the query
  1957  * might be hard.  This is used to draw twisties on the tree, so precise results
  1958  * don't matter.
  1959  */
  1960 NS_IMETHODIMP
  1961 nsNavHistoryQueryResultNode::GetHasChildren(bool* aHasChildren)
  1963   *aHasChildren = false;
  1965   if (!CanExpand()) {
  1966     return NS_OK;
  1969   uint16_t resultType = mOptions->ResultType();
  1971   // Tags are always populated, otherwise they are removed.
  1972   if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) {
  1973     *aHasChildren = true;
  1974     return NS_OK;
  1977   // For tag containers query we must check if we have any tag
  1978   if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) {
  1979     nsCOMPtr<nsITaggingService> tagging =
  1980       do_GetService(NS_TAGGINGSERVICE_CONTRACTID);
  1981     if (tagging) {
  1982       bool hasTags;
  1983       *aHasChildren = NS_SUCCEEDED(tagging->GetHasTags(&hasTags)) && hasTags;
  1985     return NS_OK;
  1988   // For history containers query we must check if we have any history
  1989   if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
  1990       resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
  1991       resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY) {
  1992     nsNavHistory* history = nsNavHistory::GetHistoryService();
  1993     NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  1994     return history->GetHasHistoryEntries(aHasChildren);
  1997   //XXX: For other containers queries we must:
  1998   // 1. If it's open, just check mChildren for containers
  1999   // 2. Else null the view (keep it in a var), open container, check mChildren
  2000   //    for containers, close container, reset the view
  2002   if (mContentsValid) {
  2003     *aHasChildren = (mChildren.Count() > 0);
  2004     return NS_OK;
  2006   *aHasChildren = true;
  2007   return NS_OK;
  2011 /**
  2012  * This doesn't just return mURI because in the case of queries that may
  2013  * be lazily constructed from the query objects.
  2014  */
  2015 NS_IMETHODIMP
  2016 nsNavHistoryQueryResultNode::GetUri(nsACString& aURI)
  2018   nsresult rv = VerifyQueriesSerialized();
  2019   NS_ENSURE_SUCCESS(rv, rv);
  2020   aURI = mURI;
  2021   return NS_OK;
  2025 NS_IMETHODIMP
  2026 nsNavHistoryQueryResultNode::GetFolderItemId(int64_t* aItemId)
  2028   *aItemId = mItemId;
  2029   return NS_OK;
  2033 NS_IMETHODIMP
  2034 nsNavHistoryQueryResultNode::GetQueries(uint32_t* queryCount,
  2035                                         nsINavHistoryQuery*** queries)
  2037   nsresult rv = VerifyQueriesParsed();
  2038   NS_ENSURE_SUCCESS(rv, rv);
  2039   NS_ASSERTION(mQueries.Count() > 0, "Must have >= 1 query");
  2041   *queries = static_cast<nsINavHistoryQuery**>
  2042                         (nsMemory::Alloc(mQueries.Count() * sizeof(nsINavHistoryQuery*)));
  2043   NS_ENSURE_TRUE(*queries, NS_ERROR_OUT_OF_MEMORY);
  2045   for (int32_t i = 0; i < mQueries.Count(); ++i)
  2046     NS_ADDREF((*queries)[i] = mQueries[i]);
  2047   *queryCount = mQueries.Count();
  2048   return NS_OK;
  2052 NS_IMETHODIMP
  2053 nsNavHistoryQueryResultNode::GetQueryOptions(
  2054                                       nsINavHistoryQueryOptions** aQueryOptions)
  2056   *aQueryOptions = Options();
  2057   NS_ADDREF(*aQueryOptions);
  2058   return NS_OK;
  2061 /**
  2062  * Safe options getter, ensures queries are parsed first.
  2063  */
  2064 nsNavHistoryQueryOptions*
  2065 nsNavHistoryQueryResultNode::Options()
  2067   nsresult rv = VerifyQueriesParsed();
  2068   if (NS_FAILED(rv))
  2069     return nullptr;
  2070   NS_ASSERTION(mOptions, "Options invalid, cannot generate from URI");
  2071   return mOptions;
  2075 nsresult
  2076 nsNavHistoryQueryResultNode::VerifyQueriesParsed()
  2078   if (mQueries.Count() > 0) {
  2079     NS_ASSERTION(mOptions, "If a result has queries, it also needs options");
  2080     return NS_OK;
  2082   NS_ASSERTION(!mURI.IsEmpty(),
  2083                "Query nodes must have either a URI or query/options");
  2085   nsNavHistory* history = nsNavHistory::GetHistoryService();
  2086   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  2088   nsresult rv = history->QueryStringToQueryArray(mURI, &mQueries,
  2089                                                  getter_AddRefs(mOptions));
  2090   NS_ENSURE_SUCCESS(rv, rv);
  2092   mLiveUpdate = history->GetUpdateRequirements(mQueries, mOptions,
  2093                                                &mHasSearchTerms);
  2094   return NS_OK;
  2098 nsresult
  2099 nsNavHistoryQueryResultNode::VerifyQueriesSerialized()
  2101   if (!mURI.IsEmpty()) {
  2102     return NS_OK;
  2104   NS_ASSERTION(mQueries.Count() > 0 && mOptions,
  2105                "Query nodes must have either a URI or query/options");
  2107   nsTArray<nsINavHistoryQuery*> flatQueries;
  2108   flatQueries.SetCapacity(mQueries.Count());
  2109   for (int32_t i = 0; i < mQueries.Count(); ++i)
  2110     flatQueries.AppendElement(static_cast<nsINavHistoryQuery*>
  2111                                          (mQueries.ObjectAt(i)));
  2113   nsNavHistory* history = nsNavHistory::GetHistoryService();
  2114   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  2116   nsresult rv = history->QueriesToQueryString(flatQueries.Elements(),
  2117                                               flatQueries.Length(),
  2118                                               mOptions, mURI);
  2119   NS_ENSURE_SUCCESS(rv, rv);
  2120   NS_ENSURE_STATE(!mURI.IsEmpty());
  2121   return NS_OK;
  2125 nsresult
  2126 nsNavHistoryQueryResultNode::FillChildren()
  2128   NS_ASSERTION(!mContentsValid,
  2129                "Don't call FillChildren when contents are valid");
  2130   NS_ASSERTION(mChildren.Count() == 0,
  2131                "We are trying to fill children when there already are some");
  2133   nsNavHistory* history = nsNavHistory::GetHistoryService();
  2134   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  2136   // get the results from the history service
  2137   nsresult rv = VerifyQueriesParsed();
  2138   NS_ENSURE_SUCCESS(rv, rv);
  2139   rv = history->GetQueryResults(this, mQueries, mOptions, &mChildren);
  2140   NS_ENSURE_SUCCESS(rv, rv);
  2142   // it is important to call FillStats to fill in the parents on all
  2143   // nodes and the result node pointers on the containers
  2144   FillStats();
  2146   uint16_t sortType = GetSortType();
  2148   if (mResult && mResult->mNeedsToApplySortingMode) {
  2149     // We should repopulate container and then apply sortingMode.  To avoid
  2150     // sorting 2 times we simply do that here.
  2151     mResult->SetSortingMode(mResult->mSortingMode);
  2153   else if (mOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY ||
  2154            sortType != nsINavHistoryQueryOptions::SORT_BY_NONE) {
  2155     // The default SORT_BY_NONE sorts by the bookmark index (position), 
  2156     // which we do not have for history queries.
  2157     // Once we've computed all tree stats, we can sort, because containers will
  2158     // then have proper visit counts and dates.
  2159     SortComparator comparator = GetSortingComparator(GetSortType());
  2160     if (comparator) {
  2161       nsAutoCString sortingAnnotation;
  2162       GetSortingAnnotation(sortingAnnotation);
  2163       // Usually containers queries results comes already sorted from the
  2164       // database, but some locales could have special rules to sort by title.
  2165       // RecursiveSort won't apply these rules to containers in containers
  2166       // queries because when setting sortingMode on the result we want to sort
  2167       // contained items (bug 473157).
  2168       // Base container RecursiveSort will sort both our children and all
  2169       // descendants, and is used in this case because we have to do manual
  2170       // title sorting.
  2171       // Query RecursiveSort will instead only sort descendants if we are a
  2172       // constinaersQuery, e.g. a grouped query that will return other queries.
  2173       // For other type of queries it will act as the base one.
  2174       if (IsContainersQuery() &&
  2175           sortType == mOptions->SortingMode() &&
  2176           (sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING ||
  2177            sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING))
  2178         nsNavHistoryContainerResultNode::RecursiveSort(sortingAnnotation.get(), comparator);
  2179       else
  2180         RecursiveSort(sortingAnnotation.get(), comparator);
  2184   // if we are limiting our results remove items from the end of the
  2185   // mChildren array after sorting. This is done for root node only.
  2186   // note, if count < max results, we won't do anything.
  2187   if (!mParent && mOptions->MaxResults()) {
  2188     while ((uint32_t)mChildren.Count() > mOptions->MaxResults())
  2189       mChildren.RemoveObjectAt(mChildren.Count() - 1);
  2192   nsNavHistoryResult* result = GetResult();
  2193   NS_ENSURE_STATE(result);
  2195   if (mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY ||
  2196       mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_UNIFIED) {
  2197     // Date containers that contain site containers have no reason to observe
  2198     // history, if the inside site container is expanded it will update,
  2199     // otherwise we are going to refresh the parent query.
  2200     if (!mParent || mParent->mOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY) {
  2201       // register with the result for history updates
  2202       result->AddHistoryObserver(this);
  2206   if (mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS ||
  2207       mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_UNIFIED ||
  2208       mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS ||
  2209       mHasSearchTerms) {
  2210     // register with the result for bookmark updates
  2211     result->AddAllBookmarksObserver(this);
  2214   mContentsValid = true;
  2215   return NS_OK;
  2219 /**
  2220  * Call with unregister = false when we are going to update the children (for
  2221  * example, when the container is open).  This will clear the list and notify
  2222  * all the children that they are going away.
  2224  * When the results are becoming invalid and we are not going to refresh them,
  2225  * set unregister = true, which will unregister the listener from the
  2226  * result if any.  We use unregister = false when we are refreshing the list
  2227  * immediately so want to stay a notifier.
  2228  */
  2229 void
  2230 nsNavHistoryQueryResultNode::ClearChildren(bool aUnregister)
  2232   for (int32_t i = 0; i < mChildren.Count(); ++i)
  2233     mChildren[i]->OnRemoving();
  2234   mChildren.Clear();
  2236   if (aUnregister && mContentsValid) {
  2237     nsNavHistoryResult* result = GetResult();
  2238     if (result) {
  2239       result->RemoveHistoryObserver(this);
  2240       result->RemoveAllBookmarksObserver(this);
  2243   mContentsValid = false;
  2247 /**
  2248  * This is called to update the result when something has changed that we
  2249  * can not incrementally update.
  2250  */
  2251 nsresult
  2252 nsNavHistoryQueryResultNode::Refresh()
  2254   nsNavHistoryResult* result = GetResult();
  2255   NS_ENSURE_STATE(result);
  2256   if (result->mBatchInProgress) {
  2257     result->requestRefresh(this);
  2258     return NS_OK;
  2261   // This is not a root node but it does not have a parent - this means that 
  2262   // the node has already been cleared and it is now called, because it was 
  2263   // left in a local copy of the observers array.
  2264   if (mIndentLevel > -1 && !mParent)
  2265     return NS_OK;
  2267   // Do not refresh if we are not expanded or if we are child of a query
  2268   // containing other queries.  In this case calling Refresh for each child
  2269   // query could cause a major slowdown.  We should not refresh nested
  2270   // queries, since we will already refresh the parent one.
  2271   if (!mExpanded ||
  2272       (mParent && mParent->IsQuery() &&
  2273        mParent->GetAsQuery()->IsContainersQuery())) {
  2274     // Don't update, just invalidate and unhook
  2275     ClearChildren(true);
  2276     return NS_OK; // no updates in tree state
  2279   if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS)
  2280     ClearChildren(true);
  2281   else
  2282     ClearChildren(false);
  2284   // Ignore errors from FillChildren, since we will still want to refresh
  2285   // the tree (there just might not be anything in it on error).
  2286   (void)FillChildren();
  2288   NOTIFY_RESULT_OBSERVERS(result, InvalidateContainer(TO_CONTAINER(this)));
  2289   return NS_OK;
  2293 /**
  2294  * Here, we override GetSortType to return the current sorting for this
  2295  * query.  GetSortType is used when dynamically inserting query results so we
  2296  * can see which comparator we should use to find the proper insertion point
  2297  * (it shouldn't be called from folder containers which maintain their own
  2298  * sorting).
  2300  * Normally, the container just forwards it up the chain.  This is what we want
  2301  * for host groups, for example.  For queries, we often want to use the query's
  2302  * sorting mode.
  2304  * However, we only use this query node's sorting when it is not the root.
  2305  * When it is the root, we use the result's sorting mode.  This is because
  2306  * there are two cases:
  2307  *   - You are looking at a bookmark hierarchy that contains an embedded
  2308  *     result.  We should always use the query's sort ordering since the result
  2309  *     node's headers have nothing to do with us (and are disabled).
  2310  *   - You are looking at a query in the tree.  In this case, we want the
  2311  *     result sorting to override ours (it should be initialized to the same
  2312  *     sorting mode).
  2313  */
  2314 uint16_t
  2315 nsNavHistoryQueryResultNode::GetSortType()
  2317   if (mParent)
  2318     return mOptions->SortingMode();
  2319   if (mResult)
  2320     return mResult->mSortingMode;
  2322   // This is a detached container, just use natural order.
  2323   return nsINavHistoryQueryOptions::SORT_BY_NONE;
  2327 void
  2328 nsNavHistoryQueryResultNode::GetSortingAnnotation(nsACString& aAnnotation) {
  2329   if (mParent) {
  2330     // use our sorting, we are not the root
  2331     mOptions->GetSortingAnnotation(aAnnotation);
  2333   else if (mResult) {
  2334     aAnnotation.Assign(mResult->mSortingAnnotation);
  2338 void
  2339 nsNavHistoryQueryResultNode::RecursiveSort(
  2340     const char* aData, SortComparator aComparator)
  2342   void* data = const_cast<void*>(static_cast<const void*>(aData));
  2344   if (!IsContainersQuery())
  2345     mChildren.Sort(aComparator, data);
  2347   for (int32_t i = 0; i < mChildren.Count(); ++i) {
  2348     if (mChildren[i]->IsContainer())
  2349       mChildren[i]->GetAsContainer()->RecursiveSort(aData, aComparator);
  2354 NS_IMETHODIMP
  2355 nsNavHistoryQueryResultNode::OnBeginUpdateBatch()
  2357   return NS_OK;
  2361 NS_IMETHODIMP
  2362 nsNavHistoryQueryResultNode::OnEndUpdateBatch()
  2364   // If the query has no children it's possible it's not yet listening to
  2365   // bookmarks changes, in such a case it's safer to force a refresh to gather
  2366   // eventual new nodes matching query options.
  2367   if (mChildren.Count() == 0) {
  2368     nsresult rv = Refresh();
  2369     NS_ENSURE_SUCCESS(rv, rv);
  2372   mBatchChanges = 0;
  2373   return NS_OK;
  2376 static nsresult setHistoryDetailsCallback(nsNavHistoryResultNode* aNode,
  2377                                           const void* aClosure,
  2378                                           const nsNavHistoryResult* aResult)
  2380   const nsNavHistoryResultNode* updatedNode =
  2381     static_cast<const nsNavHistoryResultNode*>(aClosure);
  2383   aNode->mAccessCount = updatedNode->mAccessCount;
  2384   aNode->mTime = updatedNode->mTime;
  2385   aNode->mFrecency = updatedNode->mFrecency;
  2386   aNode->mHidden = updatedNode->mHidden;
  2388   return NS_OK;
  2391 /**
  2392  * Here we need to update all copies of the URI we have with the new visit
  2393  * count, and potentially add a new entry in our query.  This is the most
  2394  * common update operation and it is important that it be as efficient as
  2395  * possible.
  2396  */
  2397 NS_IMETHODIMP
  2398 nsNavHistoryQueryResultNode::OnVisit(nsIURI* aURI, int64_t aVisitId,
  2399                                      PRTime aTime, int64_t aSessionId,
  2400                                      int64_t aReferringId,
  2401                                      uint32_t aTransitionType,
  2402                                      const nsACString& aGUID,
  2403                                      bool aHidden,
  2404                                      uint32_t* aAdded)
  2406   if (aHidden && !mOptions->IncludeHidden())
  2407     return NS_OK;
  2409   nsNavHistoryResult* result = GetResult();
  2410   NS_ENSURE_STATE(result);
  2411   if (result->mBatchInProgress &&
  2412       ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
  2413     nsresult rv = Refresh();
  2414     NS_ENSURE_SUCCESS(rv, rv);
  2415     return NS_OK;
  2418   nsNavHistory* history = nsNavHistory::GetHistoryService();
  2419   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  2421   switch(mLiveUpdate) {
  2422     case QUERYUPDATE_HOST: {
  2423       // For these simple yet common cases we can check the host ourselves
  2424       // before doing the overhead of creating a new result node.
  2425       MOZ_ASSERT(mQueries.Count() == 1,
  2426                  "Host updated queries can have only one object");
  2427       nsRefPtr<nsNavHistoryQuery> query = do_QueryObject(mQueries[0]);
  2429       bool hasDomain;
  2430       query->GetHasDomain(&hasDomain);
  2431       if (!hasDomain)
  2432         return NS_OK;
  2434       nsAutoCString host;
  2435       if (NS_FAILED(aURI->GetAsciiHost(host)))
  2436         return NS_OK;
  2438       if (!query->Domain().Equals(host))
  2439         return NS_OK;
  2441       // Fall through to check the time, if the time is not present it will
  2442       // still match.
  2445     case QUERYUPDATE_TIME: {
  2446       // For these simple yet common cases we can check the time ourselves
  2447       // before doing the overhead of creating a new result node.
  2448       MOZ_ASSERT(mQueries.Count() == 1,
  2449                  "Time updated queries can have only one object");
  2450       nsRefPtr<nsNavHistoryQuery> query = do_QueryObject(mQueries[0]);
  2452       bool hasIt;
  2453       query->GetHasBeginTime(&hasIt);
  2454       if (hasIt) {
  2455         PRTime beginTime = history->NormalizeTime(query->BeginTimeReference(),
  2456                                                   query->BeginTime());
  2457         if (aTime < beginTime)
  2458           return NS_OK; // before our time range
  2460       query->GetHasEndTime(&hasIt);
  2461       if (hasIt) {
  2462         PRTime endTime = history->NormalizeTime(query->EndTimeReference(),
  2463                                                 query->EndTime());
  2464         if (aTime > endTime)
  2465           return NS_OK; // after our time range
  2467       // Now we know that our visit satisfies the time range, fallback to the
  2468       // QUERYUPDATE_SIMPLE case.
  2471     case QUERYUPDATE_SIMPLE: {
  2472       // If all of the queries are filtered by some transitions, skip the
  2473       // update if aTransitionType doesn't match any of them.
  2474       if (mTransitions.Length() > 0 && !mTransitions.Contains(aTransitionType))
  2475         return NS_OK;
  2477       // The history service can tell us whether the new item should appear
  2478       // in the result.  We first have to construct a node for it to check.
  2479       nsRefPtr<nsNavHistoryResultNode> addition;
  2480       nsresult rv = history->VisitIdToResultNode(aVisitId, mOptions,
  2481                                                  getter_AddRefs(addition));
  2482       NS_ENSURE_SUCCESS(rv, rv);
  2483       NS_ENSURE_STATE(addition);
  2484       addition->mTransitionType = aTransitionType;
  2485       if (!history->EvaluateQueryForNode(mQueries, mOptions, addition))
  2486         return NS_OK; // don't need to include in our query
  2488       if (mOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_VISIT) {
  2489         // If this is a visit type query, just insert the new visit.  We never
  2490         // update visits, only add or remove them.
  2491         rv = InsertSortedChild(addition);
  2492         NS_ENSURE_SUCCESS(rv, rv);
  2493       } else {
  2494         uint16_t sortType = GetSortType();
  2495         bool updateSorting =
  2496           sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
  2497           sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING ||
  2498           sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
  2499           sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING ||
  2500           sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING ||
  2501           sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING;
  2503         if (!UpdateURIs(false, true, updateSorting, addition->mURI,
  2504                         setHistoryDetailsCallback,
  2505                         const_cast<void*>(static_cast<void*>(addition.get())))) {
  2506           // Couldn't find a node to update.
  2507           rv = InsertSortedChild(addition);
  2508           NS_ENSURE_SUCCESS(rv, rv);
  2512       if (aAdded)
  2513         ++(*aAdded);
  2515       break;
  2518     case QUERYUPDATE_COMPLEX:
  2519     case QUERYUPDATE_COMPLEX_WITH_BOOKMARKS:
  2520       // need to requery in complex cases
  2521       return Refresh();
  2523     default:
  2524       MOZ_ASSERT(false, "Invalid value for mLiveUpdate");
  2525       return Refresh();
  2528   return NS_OK;
  2532 /**
  2533  * Find every node that matches this URI and rename it.  We try to do
  2534  * incremental updates here, even when we are closed, because changing titles
  2535  * is easier than requerying if we are invalid.
  2537  * This actually gets called a lot.  Typically, we will get an AddURI message
  2538  * when the user visits the page, and then the title will be set asynchronously
  2539  * when the title element of the page is parsed.
  2540  */
  2541 NS_IMETHODIMP
  2542 nsNavHistoryQueryResultNode::OnTitleChanged(nsIURI* aURI,
  2543                                             const nsAString& aPageTitle,
  2544                                             const nsACString& aGUID)
  2546   if (!mExpanded) {
  2547     // When we are not expanded, we don't update, just invalidate and unhook.
  2548     // It would still be pretty easy to traverse the results and update the
  2549     // titles, but when a title changes, its unlikely that it will be the only
  2550     // thing.  Therefore, we just give up.
  2551     ClearChildren(true);
  2552     return NS_OK; // no updates in tree state
  2555   nsNavHistoryResult* result = GetResult();
  2556   NS_ENSURE_STATE(result);
  2557   if (result->mBatchInProgress &&
  2558       ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
  2559     nsresult rv = Refresh();
  2560     NS_ENSURE_SUCCESS(rv, rv);
  2561     return NS_OK;
  2564   // compute what the new title should be
  2565   NS_ConvertUTF16toUTF8 newTitle(aPageTitle);
  2567   bool onlyOneEntry =
  2568     mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_URI ||
  2569     mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS;
  2571   // See if our queries have any search term matching.
  2572   if (mHasSearchTerms) {
  2573     // Find all matching URI nodes.
  2574     nsCOMArray<nsNavHistoryResultNode> matches;
  2575     nsAutoCString spec;
  2576     nsresult rv = aURI->GetSpec(spec);
  2577     NS_ENSURE_SUCCESS(rv, rv);
  2578     RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
  2579     if (matches.Count() == 0) {
  2580       // This could be a new node matching the query, thus we could need
  2581       // to add it to the result.
  2582       nsRefPtr<nsNavHistoryResultNode> node;
  2583       nsNavHistory* history = nsNavHistory::GetHistoryService();
  2584       NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  2585       rv = history->URIToResultNode(aURI, mOptions, getter_AddRefs(node));
  2586       NS_ENSURE_SUCCESS(rv, rv);
  2587       if (history->EvaluateQueryForNode(mQueries, mOptions, node)) {
  2588         rv = InsertSortedChild(node, true);
  2589         NS_ENSURE_SUCCESS(rv, rv);
  2592     for (int32_t i = 0; i < matches.Count(); ++i) {
  2593       // For each matched node we check if it passes the query filter, if not
  2594       // we remove the node from the result, otherwise we'll update the title
  2595       // later.
  2596       nsNavHistoryResultNode* node = matches[i];
  2597       // We must check the node with the new title.
  2598       node->mTitle = newTitle;
  2600       nsNavHistory* history = nsNavHistory::GetHistoryService();
  2601       NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  2602       if (!history->EvaluateQueryForNode(mQueries, mOptions, node)) {
  2603         nsNavHistoryContainerResultNode* parent = node->mParent;
  2604         // URI nodes should always have parents
  2605         NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
  2606         int32_t childIndex = parent->FindChild(node);
  2607         NS_ASSERTION(childIndex >= 0, "Child not found in parent");
  2608         parent->RemoveChildAt(childIndex);
  2613   return ChangeTitles(aURI, newTitle, true, onlyOneEntry);
  2617 NS_IMETHODIMP
  2618 nsNavHistoryQueryResultNode::OnFrecencyChanged(nsIURI* aURI,
  2619                                                int32_t aNewFrecency,
  2620                                                const nsACString& aGUID,
  2621                                                bool aHidden,
  2622                                                PRTime aLastVisitDate)
  2624   return NS_OK;
  2628 NS_IMETHODIMP
  2629 nsNavHistoryQueryResultNode::OnManyFrecenciesChanged()
  2631   return NS_OK;
  2635 /**
  2636  * Here, we can always live update by just deleting all occurrences of
  2637  * the given URI.
  2638  */
  2639 NS_IMETHODIMP
  2640 nsNavHistoryQueryResultNode::OnDeleteURI(nsIURI* aURI,
  2641                                          const nsACString& aGUID,
  2642                                          uint16_t aReason)
  2644   nsNavHistoryResult* result = GetResult();
  2645   NS_ENSURE_STATE(result);
  2646   if (result->mBatchInProgress &&
  2647       ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
  2648     nsresult rv = Refresh();
  2649     NS_ENSURE_SUCCESS(rv, rv);
  2650     return NS_OK;
  2653   if (IsContainersQuery()) {
  2654     // Incremental updates of query returning queries are pretty much
  2655     // complicated.  In this case it's possible one of the child queries has
  2656     // no more children and it should be removed.  Unfortunately there is no
  2657     // way to know that without executing the child query and counting results.
  2658     nsresult rv = Refresh();
  2659     NS_ENSURE_SUCCESS(rv, rv);
  2660     return NS_OK;
  2663   bool onlyOneEntry = (mOptions->ResultType() ==
  2664                          nsINavHistoryQueryOptions::RESULTS_AS_URI ||
  2665                          mOptions->ResultType() ==
  2666                          nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS);
  2667   nsAutoCString spec;
  2668   nsresult rv = aURI->GetSpec(spec);
  2669   NS_ENSURE_SUCCESS(rv, rv);
  2671   nsCOMArray<nsNavHistoryResultNode> matches;
  2672   RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
  2673   for (int32_t i = 0; i < matches.Count(); ++i) {
  2674     nsNavHistoryResultNode* node = matches[i];
  2675     nsNavHistoryContainerResultNode* parent = node->mParent;
  2676     // URI nodes should always have parents
  2677     NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
  2679     int32_t childIndex = parent->FindChild(node);
  2680     NS_ASSERTION(childIndex >= 0, "Child not found in parent");
  2681     parent->RemoveChildAt(childIndex);
  2682     if (parent->mChildren.Count() == 0 && parent->IsQuery() &&
  2683         parent->mIndentLevel > -1) {
  2684       // When query subcontainers (like hosts) get empty we should remove them
  2685       // as well.  If the parent is not the root node, append it to our list
  2686       // and it will get evaluated later in the loop.
  2687       matches.AppendObject(parent);
  2690   return NS_OK;
  2694 NS_IMETHODIMP
  2695 nsNavHistoryQueryResultNode::OnClearHistory()
  2697   nsresult rv = Refresh();
  2698   NS_ENSURE_SUCCESS(rv, rv);
  2699   return NS_OK;
  2703 static nsresult setFaviconCallback(nsNavHistoryResultNode* aNode,
  2704                                    const void* aClosure,
  2705                                    const nsNavHistoryResult* aResult)
  2707   const nsCString* newFavicon = static_cast<const nsCString*>(aClosure);
  2708   aNode->mFaviconURI = *newFavicon;
  2710   if (aResult && (!aNode->mParent || aNode->mParent->AreChildrenVisible()))
  2711     NOTIFY_RESULT_OBSERVERS(aResult, NodeIconChanged(aNode));
  2713   return NS_OK;
  2717 NS_IMETHODIMP
  2718 nsNavHistoryQueryResultNode::OnPageChanged(nsIURI* aURI,
  2719                                            uint32_t aChangedAttribute,
  2720                                            const nsAString& aNewValue,
  2721                                            const nsACString& aGUID)
  2723   nsAutoCString spec;
  2724   nsresult rv = aURI->GetSpec(spec);
  2725   NS_ENSURE_SUCCESS(rv, rv);
  2727   switch (aChangedAttribute) {
  2728     case nsINavHistoryObserver::ATTRIBUTE_FAVICON: {
  2729       NS_ConvertUTF16toUTF8 newFavicon(aNewValue);
  2730       bool onlyOneEntry = (mOptions->ResultType() ==
  2731                              nsINavHistoryQueryOptions::RESULTS_AS_URI ||
  2732                              mOptions->ResultType() ==
  2733                              nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS);
  2734       UpdateURIs(true, onlyOneEntry, false, spec, setFaviconCallback,
  2735                  &newFavicon);
  2736       break;
  2738     default:
  2739       NS_WARNING("Unknown page changed notification");
  2741   return NS_OK;
  2745 NS_IMETHODIMP
  2746 nsNavHistoryQueryResultNode::OnDeleteVisits(nsIURI* aURI,
  2747                                             PRTime aVisitTime,
  2748                                             const nsACString& aGUID,
  2749                                             uint16_t aReason,
  2750                                             uint32_t aTransitionType)
  2752   NS_PRECONDITION(mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY,
  2753                   "Bookmarks queries should not get a OnDeleteVisits notification");
  2754   if (aVisitTime == 0) {
  2755     // All visits for this uri have been removed, but the uri won't be removed
  2756     // from the databse, most likely because it's a bookmark.  For a history
  2757     // query this is equivalent to a onDeleteURI notification.
  2758     nsresult rv = OnDeleteURI(aURI, aGUID, aReason);
  2759     NS_ENSURE_SUCCESS(rv, rv);
  2761   if (aTransitionType > 0) {
  2762     // All visits for aTransitionType have been removed, if the query is
  2763     // filtering on such transition type, this is equivalent to an onDeleteURI
  2764     // notification.
  2765     if (mTransitions.Length() > 0 && mTransitions.Contains(aTransitionType)) {
  2766       nsresult rv = OnDeleteURI(aURI, aGUID, aReason);
  2767       NS_ENSURE_SUCCESS(rv, rv);
  2771   return NS_OK;
  2774 nsresult
  2775 nsNavHistoryQueryResultNode::NotifyIfTagsChanged(nsIURI* aURI)
  2777   nsNavHistoryResult* result = GetResult();
  2778   NS_ENSURE_STATE(result);
  2779   nsAutoCString spec;
  2780   nsresult rv = aURI->GetSpec(spec);
  2781   NS_ENSURE_SUCCESS(rv, rv);
  2782   bool onlyOneEntry = (mOptions->ResultType() ==
  2783                          nsINavHistoryQueryOptions::RESULTS_AS_URI ||
  2784                          mOptions->ResultType() ==
  2785                          nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS
  2786                          );
  2788   // Find matching URI nodes.
  2789   nsRefPtr<nsNavHistoryResultNode> node;
  2790   nsNavHistory* history = nsNavHistory::GetHistoryService();
  2792   nsCOMArray<nsNavHistoryResultNode> matches;
  2793   RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
  2795   if (matches.Count() == 0 && mHasSearchTerms && !mRemovingURI) {
  2796     // A new tag has been added, it's possible it matches our query.
  2797     NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  2798     rv = history->URIToResultNode(aURI, mOptions, getter_AddRefs(node));
  2799     NS_ENSURE_SUCCESS(rv, rv);
  2800     if (history->EvaluateQueryForNode(mQueries, mOptions, node)) {
  2801       rv = InsertSortedChild(node, true);
  2802       NS_ENSURE_SUCCESS(rv, rv);
  2806   for (int32_t i = 0; i < matches.Count(); ++i) {
  2807     nsNavHistoryResultNode* node = matches[i];
  2808     // Force a tags update before checking the node.
  2809     node->mTags.SetIsVoid(true);
  2810     nsAutoString tags;
  2811     rv = node->GetTags(tags);
  2812     NS_ENSURE_SUCCESS(rv, rv);
  2813     // It's possible now this node does not respect anymore the conditions.
  2814     // In such a case it should be removed.
  2815     if (mHasSearchTerms &&
  2816         !history->EvaluateQueryForNode(mQueries, mOptions, node)) {
  2817       nsNavHistoryContainerResultNode* parent = node->mParent;
  2818       // URI nodes should always have parents
  2819       NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
  2820       int32_t childIndex = parent->FindChild(node);
  2821       NS_ASSERTION(childIndex >= 0, "Child not found in parent");
  2822       parent->RemoveChildAt(childIndex);
  2824     else {
  2825       NOTIFY_RESULT_OBSERVERS(result, NodeTagsChanged(node));
  2829   return NS_OK;
  2832 /**
  2833  * These are the bookmark observer functions for query nodes.  They listen
  2834  * for bookmark events and refresh the results if we have any dependence on
  2835  * the bookmark system.
  2836  */
  2837 NS_IMETHODIMP
  2838 nsNavHistoryQueryResultNode::OnItemAdded(int64_t aItemId,
  2839                                          int64_t aParentId,
  2840                                          int32_t aIndex,
  2841                                          uint16_t aItemType,
  2842                                          nsIURI* aURI,
  2843                                          const nsACString& aTitle,
  2844                                          PRTime aDateAdded,
  2845                                          const nsACString& aGUID,
  2846                                          const nsACString& aParentGUID)
  2848   if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
  2849       mLiveUpdate != QUERYUPDATE_SIMPLE &&  mLiveUpdate != QUERYUPDATE_TIME) {
  2850     nsresult rv = Refresh();
  2851     NS_ENSURE_SUCCESS(rv, rv);
  2853   return NS_OK;
  2857 NS_IMETHODIMP
  2858 nsNavHistoryQueryResultNode::OnItemRemoved(int64_t aItemId,
  2859                                            int64_t aParentId,
  2860                                            int32_t aIndex,
  2861                                            uint16_t aItemType,
  2862                                            nsIURI* aURI,
  2863                                            const nsACString& aGUID,
  2864                                            const nsACString& aParentGUID)
  2866   mRemovingURI = aURI;
  2867   if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
  2868       mLiveUpdate != QUERYUPDATE_SIMPLE && mLiveUpdate != QUERYUPDATE_TIME) {
  2869     nsresult rv = Refresh();
  2870     NS_ENSURE_SUCCESS(rv, rv);
  2872   return NS_OK;
  2876 NS_IMETHODIMP
  2877 nsNavHistoryQueryResultNode::OnItemChanged(int64_t aItemId,
  2878                                            const nsACString& aProperty,
  2879                                            bool aIsAnnotationProperty,
  2880                                            const nsACString& aNewValue,
  2881                                            PRTime aLastModified,
  2882                                            uint16_t aItemType,
  2883                                            int64_t aParentId,
  2884                                            const nsACString& aGUID,
  2885                                            const nsACString& aParentGUID)
  2887   // History observers should not get OnItemChanged
  2888   // but should get the corresponding history notifications instead.
  2889   // For bookmark queries, "all bookmark" observers should get OnItemChanged.
  2890   // For example, when a title of a bookmark changes, we want that to refresh.
  2892   if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS) {
  2893     switch (aItemType) {
  2894       case nsINavBookmarksService::TYPE_SEPARATOR:
  2895         // No separators in queries.
  2896         return NS_OK;
  2897       case nsINavBookmarksService::TYPE_FOLDER:
  2898         // Queries never result as "folders", but the tags-query results as
  2899         // special "tag" containers, which should follow their corresponding
  2900         // folders titles.
  2901         if (mOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY)
  2902           return NS_OK;
  2903       default:
  2904         (void)Refresh();
  2907   else {
  2908     // Some node could observe both bookmarks and history.  But a node observing
  2909     // only history should never get a bookmark notification.
  2910     NS_WARN_IF_FALSE(mResult && (mResult->mIsAllBookmarksObserver || mResult->mIsBookmarkFolderObserver),
  2911                      "history observers should not get OnItemChanged, but should get the corresponding history notifications instead");
  2913     // Tags in history queries are a special case since tags are per uri and
  2914     // we filter tags based on searchterms.
  2915     if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
  2916         aProperty.EqualsLiteral("tags")) {
  2917       nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
  2918       NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
  2919       nsCOMPtr<nsIURI> uri;
  2920       nsresult rv = bookmarks->GetBookmarkURI(aItemId, getter_AddRefs(uri));
  2921       NS_ENSURE_SUCCESS(rv, rv);
  2922       rv = NotifyIfTagsChanged(uri);
  2923       NS_ENSURE_SUCCESS(rv, rv);
  2927   return nsNavHistoryResultNode::OnItemChanged(aItemId, aProperty,
  2928                                                aIsAnnotationProperty,
  2929                                                aNewValue, aLastModified,
  2930                                                aItemType, aParentId, aGUID,
  2931                                                aParentGUID);
  2934 NS_IMETHODIMP
  2935 nsNavHistoryQueryResultNode::OnItemVisited(int64_t aItemId,
  2936                                            int64_t aVisitId,
  2937                                            PRTime aTime,
  2938                                            uint32_t aTransitionType,
  2939                                            nsIURI* aURI,
  2940                                            int64_t aParentId,
  2941                                            const nsACString& aGUID,
  2942                                            const nsACString& aParentGUID)
  2944   // for bookmark queries, "all bookmark" observer should get OnItemVisited
  2945   // but it is ignored.
  2946   if (mLiveUpdate != QUERYUPDATE_COMPLEX_WITH_BOOKMARKS)
  2947     NS_WARN_IF_FALSE(mResult && (mResult->mIsAllBookmarksObserver || mResult->mIsBookmarkFolderObserver),
  2948                      "history observers should not get OnItemVisited, but should get OnVisit instead");
  2949   return NS_OK;
  2952 NS_IMETHODIMP
  2953 nsNavHistoryQueryResultNode::OnItemMoved(int64_t aFolder,
  2954                                          int64_t aOldParent,
  2955                                          int32_t aOldIndex,
  2956                                          int64_t aNewParent,
  2957                                          int32_t aNewIndex,
  2958                                          uint16_t aItemType,
  2959                                          const nsACString& aGUID,
  2960                                          const nsACString& aOldParentGUID,
  2961                                          const nsACString& aNewParentGUID)
  2963   // 1. The query cannot be affected by the item's position
  2964   // 2. For the time being, we cannot optimize this not to update
  2965   //    queries which are not restricted to some folders, due to way
  2966   //    sub-queries are updated (see Refresh)
  2967   if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS &&
  2968       aItemType != nsINavBookmarksService::TYPE_SEPARATOR &&
  2969       aOldParent != aNewParent) {
  2970     return Refresh();
  2972   return NS_OK;
  2975 /**
  2976  * HOW DYNAMIC FOLDER UPDATING WORKS
  2978  * When you create a result, it will automatically keep itself in sync with
  2979  * stuff that happens in the system.  For folder nodes, this means changes to
  2980  * bookmarks.
  2982  * A folder will fill its children "when necessary." This means it is being
  2983  * opened or whether we need to see if it is empty for twisty drawing.  It will
  2984  * then register its ID with the main result object that owns it.  This result
  2985  * object will listen for all bookmark notifications and pass those
  2986  * notifications to folder nodes that have registered for that specific folder
  2987  * ID.
  2989  * When a bookmark folder is closed, it will not clear its children.  Instead,
  2990  * it will keep them and also stay registered as a listener.  This means that
  2991  * you can more quickly re-open the same folder without doing any work.  This
  2992  * happens a lot for menus, and bookmarks don't change very often.
  2994  * When a message comes in and the folder is open, we will do the correct
  2995  * operations to keep ourselves in sync with the bookmark service.  If the
  2996  * folder is closed, we just clear our list to mark it as invalid and
  2997  * unregister as a listener.  This means we do not have to keep maintaining
  2998  * an up-to-date list for the entire bookmark menu structure in every place
  2999  * it is used.
  3000  */
  3001 NS_IMPL_ISUPPORTS_INHERITED(nsNavHistoryFolderResultNode,
  3002                             nsNavHistoryContainerResultNode,
  3003                             nsINavHistoryQueryResultNode)
  3005 nsNavHistoryFolderResultNode::nsNavHistoryFolderResultNode(
  3006     const nsACString& aTitle, nsNavHistoryQueryOptions* aOptions,
  3007     int64_t aFolderId) :
  3008   nsNavHistoryContainerResultNode(EmptyCString(), aTitle, EmptyCString(),
  3009                                   nsNavHistoryResultNode::RESULT_TYPE_FOLDER,
  3010                                   false, aOptions),
  3011   mContentsValid(false),
  3012   mQueryItemId(-1),
  3013   mIsRegisteredFolderObserver(false)
  3015   mItemId = aFolderId;
  3018 nsNavHistoryFolderResultNode::~nsNavHistoryFolderResultNode()
  3020   if (mIsRegisteredFolderObserver && mResult)
  3021     mResult->RemoveBookmarkFolderObserver(this, mItemId);
  3025 /**
  3026  * Here we do not want to call ContainerResultNode::OnRemoving since our own
  3027  * ClearChildren will do the same thing and more (unregister the observers).
  3028  * The base ResultNode::OnRemoving will clear some regular node stats, so it is
  3029  * OK.
  3030  */
  3031 void
  3032 nsNavHistoryFolderResultNode::OnRemoving()
  3034   nsNavHistoryResultNode::OnRemoving();
  3035   ClearChildren(true);
  3036   mResult = nullptr;
  3040 nsresult
  3041 nsNavHistoryFolderResultNode::OpenContainer()
  3043   NS_ASSERTION(!mExpanded, "Container must be expanded to close it");
  3044   nsresult rv;
  3046   if (!mContentsValid) {
  3047     rv = FillChildren();
  3048     NS_ENSURE_SUCCESS(rv, rv);
  3050   mExpanded = true;
  3052   rv = NotifyOnStateChange(STATE_CLOSED);
  3053   NS_ENSURE_SUCCESS(rv, rv);
  3055   return NS_OK;
  3059 /**
  3060  * The async version of OpenContainer.
  3061  */
  3062 nsresult
  3063 nsNavHistoryFolderResultNode::OpenContainerAsync()
  3065   NS_ASSERTION(!mExpanded, "Container already expanded when opening it");
  3067   // If the children are valid, open the container synchronously.  This will be
  3068   // the case when the container has already been opened and any other time
  3069   // FillChildren or FillChildrenAsync has previously been called.
  3070   if (mContentsValid)
  3071     return OpenContainer();
  3073   nsresult rv = FillChildrenAsync();
  3074   NS_ENSURE_SUCCESS(rv, rv);
  3076   rv = NotifyOnStateChange(STATE_CLOSED);
  3077   NS_ENSURE_SUCCESS(rv, rv);
  3079   return NS_OK;
  3083 /**
  3084  * @see nsNavHistoryQueryResultNode::HasChildren.  The semantics here are a
  3085  * little different.  Querying the contents of a bookmark folder is relatively
  3086  * fast and it is common to have empty folders.  Therefore, we always want to
  3087  * return the correct result so that twisties are drawn properly.
  3088  */
  3089 NS_IMETHODIMP
  3090 nsNavHistoryFolderResultNode::GetHasChildren(bool* aHasChildren)
  3092   if (!mContentsValid) {
  3093     nsresult rv = FillChildren();
  3094     NS_ENSURE_SUCCESS(rv, rv);
  3096   *aHasChildren = (mChildren.Count() > 0);
  3097   return NS_OK;
  3100 /**
  3101  * @return the id of the item from which the folder node was generated, it
  3102  * could be either a concrete folder-itemId or the id used in a
  3103  * simple-folder-query-bookmark (place:folder=X).
  3104  */
  3105 NS_IMETHODIMP
  3106 nsNavHistoryFolderResultNode::GetItemId(int64_t* aItemId)
  3108   *aItemId = mQueryItemId == -1 ? mItemId : mQueryItemId;
  3109   return NS_OK;
  3112 /**
  3113  * Here, we override the getter and ignore the value stored in our object.
  3114  * The bookmarks service can tell us whether this folder should be read-only
  3115  * or not.
  3117  * It would be nice to put this code in the folder constructor, but the
  3118  * database was complaining.  I believe it is because most folders are created
  3119  * while enumerating the bookmarks table and having a statement open, and doing
  3120  * another statement might make it unhappy in some cases.
  3121  */
  3122 NS_IMETHODIMP
  3123 nsNavHistoryFolderResultNode::GetChildrenReadOnly(bool *aChildrenReadOnly)
  3125   nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
  3126   NS_ENSURE_TRUE(bookmarks, NS_ERROR_UNEXPECTED);
  3127   return bookmarks->GetFolderReadonly(mItemId, aChildrenReadOnly);
  3131 NS_IMETHODIMP
  3132 nsNavHistoryFolderResultNode::GetFolderItemId(int64_t* aItemId)
  3134   *aItemId = mItemId;
  3135   return NS_OK;
  3138 /**
  3139  * Lazily computes the URI for this specific folder query with the current
  3140  * options.
  3141  */
  3142 NS_IMETHODIMP
  3143 nsNavHistoryFolderResultNode::GetUri(nsACString& aURI)
  3145   if (!mURI.IsEmpty()) {
  3146     aURI = mURI;
  3147     return NS_OK;
  3150   uint32_t queryCount;
  3151   nsINavHistoryQuery** queries;
  3152   nsresult rv = GetQueries(&queryCount, &queries);
  3153   NS_ENSURE_SUCCESS(rv, rv);
  3155   nsNavHistory* history = nsNavHistory::GetHistoryService();
  3156   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  3158   rv = history->QueriesToQueryString(queries, queryCount, mOptions, aURI);
  3159   for (uint32_t queryIndex = 0; queryIndex < queryCount; ++queryIndex) {
  3160     NS_RELEASE(queries[queryIndex]);
  3162   nsMemory::Free(queries);
  3163   return rv;
  3167 /**
  3168  * @return the queries that give you this bookmarks folder
  3169  */
  3170 NS_IMETHODIMP
  3171 nsNavHistoryFolderResultNode::GetQueries(uint32_t* queryCount,
  3172                                          nsINavHistoryQuery*** queries)
  3174   // get the query object
  3175   nsCOMPtr<nsINavHistoryQuery> query;
  3176   nsNavHistory* history = nsNavHistory::GetHistoryService();
  3177   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  3178   nsresult rv = history->GetNewQuery(getter_AddRefs(query));
  3179   NS_ENSURE_SUCCESS(rv, rv);
  3181   // query just has the folder ID set and nothing else
  3182   rv = query->SetFolders(&mItemId, 1);
  3183   NS_ENSURE_SUCCESS(rv, rv);
  3185   // make array of our 1 query
  3186   *queries = static_cast<nsINavHistoryQuery**>
  3187                         (nsMemory::Alloc(sizeof(nsINavHistoryQuery*)));
  3188   if (!*queries)
  3189     return NS_ERROR_OUT_OF_MEMORY;
  3190   NS_ADDREF((*queries)[0] = query);
  3191   *queryCount = 1;
  3192   return NS_OK;
  3196 /**
  3197  * Options for the query that gives you this bookmarks folder.  This is just
  3198  * the options for the folder with the current folder ID set.
  3199  */
  3200 NS_IMETHODIMP
  3201 nsNavHistoryFolderResultNode::GetQueryOptions(
  3202                                       nsINavHistoryQueryOptions** aQueryOptions)
  3204   NS_ASSERTION(mOptions, "Options invalid");
  3206   *aQueryOptions = mOptions;
  3207   NS_ADDREF(*aQueryOptions);
  3208   return NS_OK;
  3212 nsresult
  3213 nsNavHistoryFolderResultNode::FillChildren()
  3215   NS_ASSERTION(!mContentsValid,
  3216                "Don't call FillChildren when contents are valid");
  3217   NS_ASSERTION(mChildren.Count() == 0,
  3218                "We are trying to fill children when there already are some");
  3220   nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
  3221   NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
  3223   // Actually get the folder children from the bookmark service.
  3224   nsresult rv = bookmarks->QueryFolderChildren(mItemId, mOptions, &mChildren);
  3225   NS_ENSURE_SUCCESS(rv, rv);
  3227   // PERFORMANCE: it may be better to also fill any child folders at this point
  3228   // so that we can draw tree twisties without doing a separate query later.
  3229   // If we don't end up drawing twisties a lot, it doesn't matter. If we do
  3230   // this, we should wrap everything in a transaction here on the bookmark
  3231   // service's connection.
  3233   return OnChildrenFilled();
  3237 /**
  3238  * Performs some tasks after all the children of the container have been added.
  3239  * The container's contents are not valid until this method has been called.
  3240  */
  3241 nsresult
  3242 nsNavHistoryFolderResultNode::OnChildrenFilled()
  3244   // It is important to call FillStats to fill in the parents on all
  3245   // nodes and the result node pointers on the containers.
  3246   FillStats();
  3248   if (mResult && mResult->mNeedsToApplySortingMode) {
  3249     // We should repopulate container and then apply sortingMode.  To avoid
  3250     // sorting 2 times we simply do that here.
  3251     mResult->SetSortingMode(mResult->mSortingMode);
  3253   else {
  3254     // Once we've computed all tree stats, we can sort, because containers will
  3255     // then have proper visit counts and dates.
  3256     SortComparator comparator = GetSortingComparator(GetSortType());
  3257     if (comparator) {
  3258       nsAutoCString sortingAnnotation;
  3259       GetSortingAnnotation(sortingAnnotation);
  3260       RecursiveSort(sortingAnnotation.get(), comparator);
  3264   // If we are limiting our results remove items from the end of the
  3265   // mChildren array after sorting.  This is done for root node only.
  3266   // Note, if count < max results, we won't do anything.
  3267   if (!mParent && mOptions->MaxResults()) {
  3268     while ((uint32_t)mChildren.Count() > mOptions->MaxResults())
  3269       mChildren.RemoveObjectAt(mChildren.Count() - 1);
  3272   // Register with the result for updates.
  3273   EnsureRegisteredAsFolderObserver();
  3275   mContentsValid = true;
  3276   return NS_OK;
  3280 /**
  3281  * Registers the node with its result as a folder observer if it is not already
  3282  * registered.
  3283  */
  3284 void
  3285 nsNavHistoryFolderResultNode::EnsureRegisteredAsFolderObserver()
  3287   if (!mIsRegisteredFolderObserver && mResult) {
  3288     mResult->AddBookmarkFolderObserver(this, mItemId);
  3289     mIsRegisteredFolderObserver = true;
  3294 /**
  3295  * The async version of FillChildren.  This begins asynchronous execution by
  3296  * calling nsNavBookmarks::QueryFolderChildrenAsync.  During execution, this
  3297  * node's async Storage callbacks, HandleResult and HandleCompletion, will be
  3298  * called.
  3299  */
  3300 nsresult
  3301 nsNavHistoryFolderResultNode::FillChildrenAsync()
  3303   NS_ASSERTION(!mContentsValid, "FillChildrenAsync when contents are valid");
  3304   NS_ASSERTION(mChildren.Count() == 0, "FillChildrenAsync when children exist");
  3306   // ProcessFolderNodeChild, called in HandleResult, increments this for every
  3307   // result row it processes.  Initialize it here as we begin async execution.
  3308   mAsyncBookmarkIndex = -1;
  3310   nsNavBookmarks* bmSvc = nsNavBookmarks::GetBookmarksService();
  3311   NS_ENSURE_TRUE(bmSvc, NS_ERROR_OUT_OF_MEMORY);
  3312   nsresult rv =
  3313     bmSvc->QueryFolderChildrenAsync(this, mItemId,
  3314                                     getter_AddRefs(mAsyncPendingStmt));
  3315   NS_ENSURE_SUCCESS(rv, rv);
  3317   // Register with the result for updates.  All updates during async execution
  3318   // will cause it to be restarted.
  3319   EnsureRegisteredAsFolderObserver();
  3321   return NS_OK;
  3325 /**
  3326  * A mozIStorageStatementCallback method.  Called during the async execution
  3327  * begun by FillChildrenAsync.
  3329  * @param aResultSet
  3330  *        The result set containing the data from the database.
  3331  */
  3332 NS_IMETHODIMP
  3333 nsNavHistoryFolderResultNode::HandleResult(mozIStorageResultSet* aResultSet)
  3335   NS_ENSURE_ARG_POINTER(aResultSet);
  3337   nsNavBookmarks* bmSvc = nsNavBookmarks::GetBookmarksService();
  3338   if (!bmSvc) {
  3339     CancelAsyncOpen(false);
  3340     return NS_ERROR_OUT_OF_MEMORY;
  3343   // Consume all the currently available rows of the result set.
  3344   nsCOMPtr<mozIStorageRow> row;
  3345   while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
  3346     nsresult rv = bmSvc->ProcessFolderNodeRow(row, mOptions, &mChildren,
  3347                                               mAsyncBookmarkIndex);
  3348     if (NS_FAILED(rv)) {
  3349       CancelAsyncOpen(false);
  3350       return rv;
  3354   return NS_OK;
  3358 /**
  3359  * A mozIStorageStatementCallback method.  Called during the async execution
  3360  * begun by FillChildrenAsync.
  3362  * @param aReason
  3363  *        Indicates the final state of execution.
  3364  */
  3365 NS_IMETHODIMP
  3366 nsNavHistoryFolderResultNode::HandleCompletion(uint16_t aReason)
  3368   if (aReason == mozIStorageStatementCallback::REASON_FINISHED &&
  3369       mAsyncCanceledState == NOT_CANCELED) {
  3370     // Async execution successfully completed.  The container is ready to open.
  3372     nsresult rv = OnChildrenFilled();
  3373     NS_ENSURE_SUCCESS(rv, rv);
  3375     mExpanded = true;
  3376     mAsyncPendingStmt = nullptr;
  3378     // Notify observers only after mExpanded and mAsyncPendingStmt are set.
  3379     rv = NotifyOnStateChange(STATE_LOADING);
  3380     NS_ENSURE_SUCCESS(rv, rv);
  3383   else if (mAsyncCanceledState == CANCELED_RESTART_NEEDED) {
  3384     // Async execution was canceled and needs to be restarted.
  3385     mAsyncCanceledState = NOT_CANCELED;
  3386     ClearChildren(false);
  3387     FillChildrenAsync();
  3390   else {
  3391     // Async execution failed or was canceled without restart.  Remove all
  3392     // children and close the container, notifying observers.
  3393     mAsyncCanceledState = NOT_CANCELED;
  3394     ClearChildren(true);
  3395     CloseContainer();
  3398   return NS_OK;
  3402 void
  3403 nsNavHistoryFolderResultNode::ClearChildren(bool unregister)
  3405   for (int32_t i = 0; i < mChildren.Count(); ++i)
  3406     mChildren[i]->OnRemoving();
  3407   mChildren.Clear();
  3409   bool needsUnregister = unregister && (mContentsValid || mAsyncPendingStmt);
  3410   if (needsUnregister && mResult && mIsRegisteredFolderObserver) {
  3411     mResult->RemoveBookmarkFolderObserver(this, mItemId);
  3412     mIsRegisteredFolderObserver = false;
  3414   mContentsValid = false;
  3418 /**
  3419  * This is called to update the result when something has changed that we
  3420  * can not incrementally update.
  3421  */
  3422 nsresult
  3423 nsNavHistoryFolderResultNode::Refresh()
  3425   nsNavHistoryResult* result = GetResult();
  3426   NS_ENSURE_STATE(result);
  3427   if (result->mBatchInProgress) {
  3428     result->requestRefresh(this);
  3429     return NS_OK;
  3432   ClearChildren(true);
  3434   if (!mExpanded) {
  3435     // When we are not expanded, we don't update, just invalidate and unhook.
  3436     return NS_OK;
  3439   // Ignore errors from FillChildren, since we will still want to refresh
  3440   // the tree (there just might not be anything in it on error).  ClearChildren
  3441   // has unregistered us as an observer since FillChildren will try to
  3442   // re-register us.
  3443   (void)FillChildren();
  3445   NOTIFY_RESULT_OBSERVERS(result, InvalidateContainer(TO_CONTAINER(this)));
  3446   return NS_OK;
  3450 /**
  3451  * Implements the logic described above the constructor.  This sees if we
  3452  * should do an incremental update and returns true if so.  If not, it
  3453  * invalidates our children, unregisters us an observer, and returns false.
  3454  */
  3455 bool
  3456 nsNavHistoryFolderResultNode::StartIncrementalUpdate()
  3458   // if any items are excluded, we can not do incremental updates since the
  3459   // indices from the bookmark service will not be valid
  3461   if (!mOptions->ExcludeItems() &&
  3462       !mOptions->ExcludeQueries() &&
  3463       !mOptions->ExcludeReadOnlyFolders()) {
  3464     // easy case: we are visible, always do incremental update
  3465     if (mExpanded || AreChildrenVisible())
  3466       return true;
  3468     nsNavHistoryResult* result = GetResult();
  3469     NS_ENSURE_TRUE(result, false);
  3471     // When any observers are attached also do incremental updates if our
  3472     // parent is visible, so that twisties are drawn correctly.
  3473     if (mParent)
  3474       return result->mObservers.Length() > 0;
  3477   // otherwise, we don't do incremental updates, invalidate and unregister
  3478   (void)Refresh();
  3479   return false;
  3483 /**
  3484  * This function adds aDelta to all bookmark indices between the two endpoints,
  3485  * inclusive.  It is used when items are added or removed from the bookmark
  3486  * folder.
  3487  */
  3488 void
  3489 nsNavHistoryFolderResultNode::ReindexRange(int32_t aStartIndex,
  3490                                            int32_t aEndIndex,
  3491                                            int32_t aDelta)
  3493   for (int32_t i = 0; i < mChildren.Count(); ++i) {
  3494     nsNavHistoryResultNode* node = mChildren[i];
  3495     if (node->mBookmarkIndex >= aStartIndex &&
  3496         node->mBookmarkIndex <= aEndIndex)
  3497       node->mBookmarkIndex += aDelta;
  3502 /**
  3503  * Searches this folder for a node with the given id.
  3505  * @return the node if found, null otherwise.
  3506  * @note Does not addref the node!
  3507  */
  3508 nsNavHistoryResultNode*
  3509 nsNavHistoryFolderResultNode::FindChildById(int64_t aItemId,
  3510     uint32_t* aNodeIndex)
  3512   for (int32_t i = 0; i < mChildren.Count(); ++i) {
  3513     if (mChildren[i]->mItemId == aItemId ||
  3514         (mChildren[i]->IsFolder() &&
  3515          mChildren[i]->GetAsFolder()->mQueryItemId == aItemId)) {
  3516       *aNodeIndex = i;
  3517       return mChildren[i];
  3520   return nullptr;
  3524 // Used by nsNavHistoryFolderResultNode's nsINavBookmarkObserver methods below.
  3525 // If the container is notified of a bookmark event while asynchronous execution
  3526 // is pending, this restarts it and returns.
  3527 #define RESTART_AND_RETURN_IF_ASYNC_PENDING() \
  3528   if (mAsyncPendingStmt) { \
  3529     CancelAsyncOpen(true); \
  3530     return NS_OK; \
  3534 NS_IMETHODIMP
  3535 nsNavHistoryFolderResultNode::OnBeginUpdateBatch()
  3537   return NS_OK;
  3541 NS_IMETHODIMP
  3542 nsNavHistoryFolderResultNode::OnEndUpdateBatch()
  3544   return NS_OK;
  3548 NS_IMETHODIMP
  3549 nsNavHistoryFolderResultNode::OnItemAdded(int64_t aItemId,
  3550                                           int64_t aParentFolder,
  3551                                           int32_t aIndex,
  3552                                           uint16_t aItemType,
  3553                                           nsIURI* aURI,
  3554                                           const nsACString& aTitle,
  3555                                           PRTime aDateAdded,
  3556                                           const nsACString& aGUID,
  3557                                           const nsACString& aParentGUID)
  3559   NS_ASSERTION(aParentFolder == mItemId, "Got wrong bookmark update");
  3561   bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
  3562                         (mParent && mParent->mOptions->ExcludeItems()) ||
  3563                         mOptions->ExcludeItems();
  3565   // here, try to do something reasonable if the bookmark service gives us
  3566   // a bogus index.
  3567   if (aIndex < 0) {
  3568     NS_NOTREACHED("Invalid index for item adding: <0");
  3569     aIndex = 0;
  3571   else if (aIndex > mChildren.Count()) {
  3572     if (!excludeItems) {
  3573       // Something wrong happened while updating indexes.
  3574       NS_NOTREACHED("Invalid index for item adding: greater than count");
  3576     aIndex = mChildren.Count();
  3579   RESTART_AND_RETURN_IF_ASYNC_PENDING();
  3581   nsresult rv;
  3583   // Check for query URIs, which are bookmarks, but treated as containers
  3584   // in results and views.
  3585   bool isQuery = false;
  3586   if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
  3587     NS_ASSERTION(aURI, "Got a null URI when we are a bookmark?!");
  3588     nsAutoCString itemURISpec;
  3589     rv = aURI->GetSpec(itemURISpec);
  3590     NS_ENSURE_SUCCESS(rv, rv);
  3591     isQuery = IsQueryURI(itemURISpec);
  3594   if (aItemType != nsINavBookmarksService::TYPE_FOLDER &&
  3595       !isQuery && excludeItems) {
  3596     // don't update items when we aren't displaying them, but we still need
  3597     // to adjust bookmark indices to account for the insertion
  3598     ReindexRange(aIndex, INT32_MAX, 1);
  3599     return NS_OK;
  3602   if (!StartIncrementalUpdate())
  3603     return NS_OK; // folder was completely refreshed for us
  3605   // adjust indices to account for insertion
  3606   ReindexRange(aIndex, INT32_MAX, 1);
  3608   nsRefPtr<nsNavHistoryResultNode> node;
  3609   if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
  3610     nsNavHistory* history = nsNavHistory::GetHistoryService();
  3611     NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  3612     rv = history->BookmarkIdToResultNode(aItemId, mOptions, getter_AddRefs(node));
  3613     NS_ENSURE_SUCCESS(rv, rv);
  3615   else if (aItemType == nsINavBookmarksService::TYPE_FOLDER) {
  3616     nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
  3617     NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
  3618     rv = bookmarks->ResultNodeForContainer(aItemId, mOptions, getter_AddRefs(node));
  3619     NS_ENSURE_SUCCESS(rv, rv);
  3621   else if (aItemType == nsINavBookmarksService::TYPE_SEPARATOR) {
  3622     node = new nsNavHistorySeparatorResultNode();
  3623     NS_ENSURE_TRUE(node, NS_ERROR_OUT_OF_MEMORY);
  3624     node->mItemId = aItemId;
  3627   node->mBookmarkIndex = aIndex;
  3629   if (aItemType == nsINavBookmarksService::TYPE_SEPARATOR ||
  3630       GetSortType() == nsINavHistoryQueryOptions::SORT_BY_NONE) {
  3631     // insert at natural bookmarks position
  3632     return InsertChildAt(node, aIndex);
  3635   // insert at sorted position
  3636   return InsertSortedChild(node, false);
  3640 NS_IMETHODIMP
  3641 nsNavHistoryFolderResultNode::OnItemRemoved(int64_t aItemId,
  3642                                             int64_t aParentFolder,
  3643                                             int32_t aIndex,
  3644                                             uint16_t aItemType,
  3645                                             nsIURI* aURI,
  3646                                             const nsACString& aGUID,
  3647                                             const nsACString& aParentGUID)
  3649   // We only care about notifications when a child changes.  When the deleted
  3650   // item is us, our parent should also be registered and will remove us from
  3651   // its list.
  3652   if (mItemId == aItemId)
  3653     return NS_OK;
  3655   NS_ASSERTION(aParentFolder == mItemId, "Got wrong bookmark update");
  3657   RESTART_AND_RETURN_IF_ASYNC_PENDING();
  3659   bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
  3660                         (mParent && mParent->mOptions->ExcludeItems()) ||
  3661                         mOptions->ExcludeItems();
  3663   // don't trust the index from the bookmark service, find it ourselves.  The
  3664   // sorting could be different, or the bookmark services indices and ours might
  3665   // be out of sync somehow.
  3666   uint32_t index;
  3667   nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
  3668   if (!node) {
  3669     if (excludeItems)
  3670       return NS_OK;
  3672     NS_NOTREACHED("Removing item we don't have");
  3673     return NS_ERROR_FAILURE;
  3676   if ((node->IsURI() || node->IsSeparator()) && excludeItems) {
  3677     // don't update items when we aren't displaying them, but we do need to
  3678     // adjust everybody's bookmark indices to account for the removal
  3679     ReindexRange(aIndex, INT32_MAX, -1);
  3680     return NS_OK;
  3683   if (!StartIncrementalUpdate())
  3684     return NS_OK; // we are completely refreshed
  3686   // shift all following indices down
  3687   ReindexRange(aIndex + 1, INT32_MAX, -1);
  3689   return RemoveChildAt(index);
  3693 NS_IMETHODIMP
  3694 nsNavHistoryResultNode::OnItemChanged(int64_t aItemId,
  3695                                       const nsACString& aProperty,
  3696                                       bool aIsAnnotationProperty,
  3697                                       const nsACString& aNewValue,
  3698                                       PRTime aLastModified,
  3699                                       uint16_t aItemType,
  3700                                       int64_t aParentId,
  3701                                       const nsACString& aGUID,
  3702                                       const nsACString& aParentGUID)
  3704   if (aItemId != mItemId)
  3705     return NS_OK;
  3707   mLastModified = aLastModified;
  3709   nsNavHistoryResult* result = GetResult();
  3710   NS_ENSURE_STATE(result);
  3712   bool shouldNotify = !mParent || mParent->AreChildrenVisible();
  3714   if (aIsAnnotationProperty) {
  3715     if (shouldNotify)
  3716       NOTIFY_RESULT_OBSERVERS(result, NodeAnnotationChanged(this, aProperty));
  3718   else if (aProperty.EqualsLiteral("title")) {
  3719     // XXX: what should we do if the new title is void?
  3720     mTitle = aNewValue;
  3721     if (shouldNotify)
  3722       NOTIFY_RESULT_OBSERVERS(result, NodeTitleChanged(this, mTitle));
  3724   else if (aProperty.EqualsLiteral("uri")) {
  3725     // clear the tags string as well
  3726     mTags.SetIsVoid(true);
  3727     mURI = aNewValue;
  3728     if (shouldNotify)
  3729       NOTIFY_RESULT_OBSERVERS(result, NodeURIChanged(this, mURI));
  3731   else if (aProperty.EqualsLiteral("favicon")) {
  3732     mFaviconURI = aNewValue;
  3733     if (shouldNotify)
  3734       NOTIFY_RESULT_OBSERVERS(result, NodeIconChanged(this));
  3736   else if (aProperty.EqualsLiteral("cleartime")) {
  3737     mTime = 0;
  3738     if (shouldNotify) {
  3739       NOTIFY_RESULT_OBSERVERS(result,
  3740                               NodeHistoryDetailsChanged(this, 0, mAccessCount));
  3743   else if (aProperty.EqualsLiteral("tags")) {
  3744     mTags.SetIsVoid(true);
  3745     if (shouldNotify)
  3746       NOTIFY_RESULT_OBSERVERS(result, NodeTagsChanged(this));
  3748   else if (aProperty.EqualsLiteral("dateAdded")) {
  3749     // aNewValue has the date as a string, but we can use aLastModified,
  3750     // because it's set to the same value when dateAdded is changed.
  3751     mDateAdded = aLastModified;
  3752     if (shouldNotify)
  3753       NOTIFY_RESULT_OBSERVERS(result, NodeDateAddedChanged(this, mDateAdded));
  3755   else if (aProperty.EqualsLiteral("lastModified")) {
  3756     if (shouldNotify) {
  3757       NOTIFY_RESULT_OBSERVERS(result,
  3758                               NodeLastModifiedChanged(this, aLastModified));
  3761   else if (aProperty.EqualsLiteral("keyword")) {
  3762     if (shouldNotify)
  3763       NOTIFY_RESULT_OBSERVERS(result, NodeKeywordChanged(this, aNewValue));
  3765   else
  3766     NS_NOTREACHED("Unknown bookmark property changing.");
  3768   if (!mParent)
  3769     return NS_OK;
  3771   // DO NOT OPTIMIZE THIS TO CHECK aProperty
  3772   // The sorting methods fall back to each other so we need to re-sort the
  3773   // result even if it's not set to sort by the given property.
  3774   int32_t ourIndex = mParent->FindChild(this);
  3775   NS_ASSERTION(ourIndex >= 0, "Could not find self in parent");
  3776   if (ourIndex >= 0)
  3777     mParent->EnsureItemPosition(ourIndex);
  3779   return NS_OK;
  3783 NS_IMETHODIMP
  3784 nsNavHistoryFolderResultNode::OnItemChanged(int64_t aItemId,
  3785                                             const nsACString& aProperty,
  3786                                             bool aIsAnnotationProperty,
  3787                                             const nsACString& aNewValue,
  3788                                             PRTime aLastModified,
  3789                                             uint16_t aItemType,
  3790                                             int64_t aParentId,
  3791                                             const nsACString& aGUID,
  3792                                             const nsACString&aParentGUID)
  3794   // The query-item's title is used for simple-query nodes
  3795   if (mQueryItemId != -1) {
  3796     bool isTitleChange = aProperty.EqualsLiteral("title");
  3797     if ((mQueryItemId == aItemId && !isTitleChange) ||
  3798         (mQueryItemId != aItemId && isTitleChange)) {
  3799       return NS_OK;
  3803   RESTART_AND_RETURN_IF_ASYNC_PENDING();
  3805   return nsNavHistoryResultNode::OnItemChanged(aItemId, aProperty,
  3806                                                aIsAnnotationProperty,
  3807                                                aNewValue, aLastModified,
  3808                                                aItemType, aParentId, aGUID,
  3809                                                aParentGUID);
  3812 /**
  3813  * Updates visit count and last visit time and refreshes.
  3814  */
  3815 NS_IMETHODIMP
  3816 nsNavHistoryFolderResultNode::OnItemVisited(int64_t aItemId,
  3817                                             int64_t aVisitId,
  3818                                             PRTime aTime,
  3819                                             uint32_t aTransitionType,
  3820                                             nsIURI* aURI,
  3821                                             int64_t aParentId,
  3822                                             const nsACString& aGUID,
  3823                                             const nsACString& aParentGUID)
  3825   bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
  3826                         (mParent && mParent->mOptions->ExcludeItems()) ||
  3827                         mOptions->ExcludeItems();
  3828   if (excludeItems)
  3829     return NS_OK; // don't update items when we aren't displaying them
  3831   RESTART_AND_RETURN_IF_ASYNC_PENDING();
  3833   if (!StartIncrementalUpdate())
  3834     return NS_OK;
  3836   uint32_t nodeIndex;
  3837   nsNavHistoryResultNode* node = FindChildById(aItemId, &nodeIndex);
  3838   if (!node)
  3839     return NS_ERROR_FAILURE;
  3841   // Update node.
  3842   node->mTime = aTime;
  3843   ++node->mAccessCount;
  3845   // Update us.
  3846   int32_t oldAccessCount = mAccessCount;
  3847   ++mAccessCount;
  3848   if (aTime > mTime)
  3849     mTime = aTime;
  3850   nsresult rv = ReverseUpdateStats(mAccessCount - oldAccessCount);
  3851   NS_ENSURE_SUCCESS(rv, rv);
  3853   // Update frecency for proper frecency ordering.
  3854   // TODO (bug 832617): we may avoid one query here, by providing the new
  3855   // frecency value in the notification.
  3856   nsNavHistory* history = nsNavHistory::GetHistoryService();
  3857   NS_ENSURE_TRUE(history, NS_OK);
  3858   nsRefPtr<nsNavHistoryResultNode> visitNode;
  3859   rv = history->VisitIdToResultNode(aVisitId, mOptions,
  3860                                     getter_AddRefs(visitNode));
  3861   NS_ENSURE_SUCCESS(rv, rv);
  3862   NS_ENSURE_STATE(visitNode);
  3863   node->mFrecency = visitNode->mFrecency;
  3865   if (AreChildrenVisible()) {
  3866     // Sorting has not changed, just redraw the row if it's visible.
  3867     nsNavHistoryResult* result = GetResult();
  3868     NOTIFY_RESULT_OBSERVERS(result,
  3869                             NodeHistoryDetailsChanged(node, mTime, mAccessCount));
  3872   // Update sorting if necessary.
  3873   uint32_t sortType = GetSortType();
  3874   if (sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
  3875       sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING ||
  3876       sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
  3877       sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING ||
  3878       sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING ||
  3879       sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING) {
  3880     int32_t childIndex = FindChild(node);
  3881     NS_ASSERTION(childIndex >= 0, "Could not find child we just got a reference to");
  3882     if (childIndex >= 0) {
  3883       EnsureItemPosition(childIndex);
  3887   return NS_OK;
  3891 NS_IMETHODIMP
  3892 nsNavHistoryFolderResultNode::OnItemMoved(int64_t aItemId,
  3893                                           int64_t aOldParent,
  3894                                           int32_t aOldIndex,
  3895                                           int64_t aNewParent,
  3896                                           int32_t aNewIndex,
  3897                                           uint16_t aItemType,
  3898                                           const nsACString& aGUID,
  3899                                           const nsACString& aOldParentGUID,
  3900                                           const nsACString& aNewParentGUID)
  3902   NS_ASSERTION(aOldParent == mItemId || aNewParent == mItemId,
  3903                "Got a bookmark message that doesn't belong to us");
  3905   RESTART_AND_RETURN_IF_ASYNC_PENDING();
  3907   if (!StartIncrementalUpdate())
  3908     return NS_OK; // entire container was refreshed for us
  3910   if (aOldParent == aNewParent) {
  3911     // getting moved within the same folder, we don't want to do a remove and
  3912     // an add because that will lose your tree state.
  3914     // adjust bookmark indices
  3915     ReindexRange(aOldIndex + 1, INT32_MAX, -1);
  3916     ReindexRange(aNewIndex, INT32_MAX, 1);
  3918     uint32_t index;
  3919     nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
  3920     if (!node) {
  3921       NS_NOTREACHED("Can't find folder that is moving!");
  3922       return NS_ERROR_FAILURE;
  3924     NS_ASSERTION(index < uint32_t(mChildren.Count()), "Invalid index!");
  3925     node->mBookmarkIndex = aNewIndex;
  3927     // adjust position
  3928     EnsureItemPosition(index);
  3929     return NS_OK;
  3930   } else {
  3931     // moving between two different folders, just do a remove and an add
  3932     nsCOMPtr<nsIURI> itemURI;
  3933     nsAutoCString itemTitle;
  3934     if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
  3935       nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
  3936       NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
  3937       nsresult rv = bookmarks->GetBookmarkURI(aItemId, getter_AddRefs(itemURI));
  3938       NS_ENSURE_SUCCESS(rv, rv);
  3939       rv = bookmarks->GetItemTitle(aItemId, itemTitle);
  3940       NS_ENSURE_SUCCESS(rv, rv);
  3942     if (aOldParent == mItemId) {
  3943       OnItemRemoved(aItemId, aOldParent, aOldIndex, aItemType, itemURI,
  3944                     aGUID, aOldParentGUID);
  3946     if (aNewParent == mItemId) {
  3947       OnItemAdded(aItemId, aNewParent, aNewIndex, aItemType, itemURI, itemTitle,
  3948                   PR_Now(), // This is a dummy dateAdded, not the real value.
  3949                   aGUID, aNewParentGUID);
  3952   return NS_OK;
  3956 /**
  3957  * Separator nodes do not hold any data.
  3958  */
  3959 nsNavHistorySeparatorResultNode::nsNavHistorySeparatorResultNode()
  3960   : nsNavHistoryResultNode(EmptyCString(), EmptyCString(),
  3961                            0, 0, EmptyCString())
  3966 static PLDHashOperator
  3967 RemoveBookmarkFolderObserversCallback(nsTrimInt64HashKey::KeyType aKey,
  3968                                       nsNavHistoryResult::FolderObserverList*& aData,
  3969                                       void* userArg)
  3971   delete aData;
  3972   return PL_DHASH_REMOVE;
  3975 NS_IMPL_CYCLE_COLLECTION_CLASS(nsNavHistoryResult)
  3977 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsNavHistoryResult)
  3978   tmp->StopObserving();
  3979   NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootNode)
  3980   NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers)
  3981   tmp->mBookmarkFolderObservers.Enumerate(&RemoveBookmarkFolderObserversCallback, nullptr);
  3982   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAllBookmarksObservers)
  3983   NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistoryObservers)
  3984 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
  3986 static PLDHashOperator
  3987 TraverseBookmarkFolderObservers(nsTrimInt64HashKey::KeyType aKey,
  3988                                 nsNavHistoryResult::FolderObserverList* &aData,
  3989                                 void *aClosure)
  3991   nsCycleCollectionTraversalCallback* cb =
  3992     static_cast<nsCycleCollectionTraversalCallback*>(aClosure);
  3993   for (uint32_t i = 0; i < aData->Length(); ++i) {
  3994     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb,
  3995                                        "mBookmarkFolderObservers value[i]");
  3996     nsNavHistoryResultNode* node = aData->ElementAt(i);
  3997     cb->NoteXPCOMChild(node);
  3999   return PL_DHASH_NEXT;
  4002 static void
  4003 traverseResultObservers(nsMaybeWeakPtrArray<nsINavHistoryResultObserver> aObservers,
  4004                         void *aClosure)
  4006   nsCycleCollectionTraversalCallback* cb =
  4007     static_cast<nsCycleCollectionTraversalCallback*>(aClosure);
  4008   for (uint32_t i = 0; i < aObservers.Length(); ++i) {
  4009     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mResultObservers value[i]");
  4010     const nsCOMPtr<nsINavHistoryResultObserver> &obs = aObservers.ElementAt(i);
  4011     cb->NoteXPCOMChild(obs);
  4015 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNavHistoryResult)
  4016   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootNode)
  4017   traverseResultObservers(tmp->mObservers, &cb);
  4018   tmp->mBookmarkFolderObservers.Enumerate(&TraverseBookmarkFolderObservers, &cb);
  4019   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAllBookmarksObservers)
  4020   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHistoryObservers)
  4021 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
  4023 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNavHistoryResult)
  4024 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNavHistoryResult)
  4026 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNavHistoryResult)
  4027   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryResult)
  4028   NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryResult)
  4029   NS_INTERFACE_MAP_ENTRY(nsINavHistoryResult)
  4030   NS_INTERFACE_MAP_ENTRY(nsINavBookmarkObserver)
  4031   NS_INTERFACE_MAP_ENTRY(nsINavHistoryObserver)
  4032   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  4033 NS_INTERFACE_MAP_END
  4035 nsNavHistoryResult::nsNavHistoryResult(nsNavHistoryContainerResultNode* aRoot)
  4036   : mRootNode(aRoot)
  4037   , mNeedsToApplySortingMode(false)
  4038   , mIsHistoryObserver(false)
  4039   , mIsBookmarkFolderObserver(false)
  4040   , mIsAllBookmarksObserver(false)
  4041   , mBookmarkFolderObservers(128)
  4042   , mBatchInProgress(false)
  4043   , mSuppressNotifications(false)
  4045   mRootNode->mResult = this;
  4048 nsNavHistoryResult::~nsNavHistoryResult()
  4050   // delete all bookmark folder observer arrays which are allocated on the heap
  4051   mBookmarkFolderObservers.Enumerate(&RemoveBookmarkFolderObserversCallback, nullptr);
  4054 void
  4055 nsNavHistoryResult::StopObserving()
  4057   if (mIsBookmarkFolderObserver || mIsAllBookmarksObserver) {
  4058     nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
  4059     if (bookmarks) {
  4060       bookmarks->RemoveObserver(this);
  4061       mIsBookmarkFolderObserver = false;
  4062       mIsAllBookmarksObserver = false;
  4065   if (mIsHistoryObserver) {
  4066     nsNavHistory* history = nsNavHistory::GetHistoryService();
  4067     if (history) {
  4068       history->RemoveObserver(this);
  4069       mIsHistoryObserver = false;
  4074 /**
  4075  * @note you must call AddRef before this, since we may do things like
  4076  * register ourselves.
  4077  */
  4078 nsresult
  4079 nsNavHistoryResult::Init(nsINavHistoryQuery** aQueries,
  4080                          uint32_t aQueryCount,
  4081                          nsNavHistoryQueryOptions *aOptions)
  4083   nsresult rv;
  4084   NS_ASSERTION(aOptions, "Must have valid options");
  4085   NS_ASSERTION(aQueries && aQueryCount > 0, "Must have >1 query in result");
  4087   // Fill saved source queries with copies of the original (the caller might
  4088   // change their original objects, and we always want to reflect the source
  4089   // parameters).
  4090   for (uint32_t i = 0; i < aQueryCount; ++i) {
  4091     nsCOMPtr<nsINavHistoryQuery> queryClone;
  4092     rv = aQueries[i]->Clone(getter_AddRefs(queryClone));
  4093     NS_ENSURE_SUCCESS(rv, rv);
  4094     if (!mQueries.AppendObject(queryClone))
  4095       return NS_ERROR_OUT_OF_MEMORY;
  4097   rv = aOptions->Clone(getter_AddRefs(mOptions));
  4098   NS_ENSURE_SUCCESS(rv, rv);
  4099   mSortingMode = aOptions->SortingMode();
  4100   rv = aOptions->GetSortingAnnotation(mSortingAnnotation);
  4101   NS_ENSURE_SUCCESS(rv, rv);
  4103   NS_ASSERTION(mRootNode->mIndentLevel == -1,
  4104                "Root node's indent level initialized wrong");
  4105   mRootNode->FillStats();
  4107   return NS_OK;
  4111 /**
  4112  * Constructs a new history result object.
  4113  */
  4114 nsresult // static
  4115 nsNavHistoryResult::NewHistoryResult(nsINavHistoryQuery** aQueries,
  4116                                      uint32_t aQueryCount,
  4117                                      nsNavHistoryQueryOptions* aOptions,
  4118                                      nsNavHistoryContainerResultNode* aRoot,
  4119                                      bool aBatchInProgress,
  4120                                      nsNavHistoryResult** result)
  4122   *result = new nsNavHistoryResult(aRoot);
  4123   if (!*result)
  4124     return NS_ERROR_OUT_OF_MEMORY;
  4125   NS_ADDREF(*result); // must happen before Init
  4126   // Correctly set mBatchInProgress for the result based on the root node value.
  4127   (*result)->mBatchInProgress = aBatchInProgress;
  4128   nsresult rv = (*result)->Init(aQueries, aQueryCount, aOptions);
  4129   if (NS_FAILED(rv)) {
  4130     NS_RELEASE(*result);
  4131     *result = nullptr;
  4132     return rv;
  4135   return NS_OK;
  4139 void
  4140 nsNavHistoryResult::AddHistoryObserver(nsNavHistoryQueryResultNode* aNode)
  4142   if (!mIsHistoryObserver) {
  4143       nsNavHistory* history = nsNavHistory::GetHistoryService();
  4144       NS_ASSERTION(history, "Can't create history service");
  4145       history->AddObserver(this, true);
  4146       mIsHistoryObserver = true;
  4148   // Don't add duplicate observers.  In some case we don't unregister when
  4149   // children are cleared (see ClearChildren) and the next FillChildren call
  4150   // will try to add the observer again.
  4151   if (mHistoryObservers.IndexOf(aNode) == mHistoryObservers.NoIndex) {
  4152     mHistoryObservers.AppendElement(aNode);
  4157 void
  4158 nsNavHistoryResult::AddAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode)
  4160   if (!mIsAllBookmarksObserver && !mIsBookmarkFolderObserver) {
  4161     nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
  4162     if (!bookmarks) {
  4163       NS_NOTREACHED("Can't create bookmark service");
  4164       return;
  4166     bookmarks->AddObserver(this, true);
  4167     mIsAllBookmarksObserver = true;
  4169   // Don't add duplicate observers.  In some case we don't unregister when
  4170   // children are cleared (see ClearChildren) and the next FillChildren call
  4171   // will try to add the observer again.
  4172   if (mAllBookmarksObservers.IndexOf(aNode) == mAllBookmarksObservers.NoIndex) {
  4173     mAllBookmarksObservers.AppendElement(aNode);
  4178 void
  4179 nsNavHistoryResult::AddBookmarkFolderObserver(nsNavHistoryFolderResultNode* aNode,
  4180                                               int64_t aFolder)
  4182   if (!mIsBookmarkFolderObserver && !mIsAllBookmarksObserver) {
  4183     nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
  4184     if (!bookmarks) {
  4185       NS_NOTREACHED("Can't create bookmark service");
  4186       return;
  4188     bookmarks->AddObserver(this, true);
  4189     mIsBookmarkFolderObserver = true;
  4191   // Don't add duplicate observers.  In some case we don't unregister when
  4192   // children are cleared (see ClearChildren) and the next FillChildren call
  4193   // will try to add the observer again.
  4194   FolderObserverList* list = BookmarkFolderObserversForId(aFolder, true);
  4195   if (list->IndexOf(aNode) == list->NoIndex) {
  4196     list->AppendElement(aNode);
  4201 void
  4202 nsNavHistoryResult::RemoveHistoryObserver(nsNavHistoryQueryResultNode* aNode)
  4204   mHistoryObservers.RemoveElement(aNode);
  4208 void
  4209 nsNavHistoryResult::RemoveAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode)
  4211   mAllBookmarksObservers.RemoveElement(aNode);
  4215 void
  4216 nsNavHistoryResult::RemoveBookmarkFolderObserver(nsNavHistoryFolderResultNode* aNode,
  4217                                                  int64_t aFolder)
  4219   FolderObserverList* list = BookmarkFolderObserversForId(aFolder, false);
  4220   if (!list)
  4221     return; // we don't even have an entry for that folder
  4222   list->RemoveElement(aNode);
  4226 nsNavHistoryResult::FolderObserverList*
  4227 nsNavHistoryResult::BookmarkFolderObserversForId(int64_t aFolderId, bool aCreate)
  4229   FolderObserverList* list;
  4230   if (mBookmarkFolderObservers.Get(aFolderId, &list))
  4231     return list;
  4232   if (!aCreate)
  4233     return nullptr;
  4235   // need to create a new list
  4236   list = new FolderObserverList;
  4237   mBookmarkFolderObservers.Put(aFolderId, list);
  4238   return list;
  4242 NS_IMETHODIMP
  4243 nsNavHistoryResult::GetSortingMode(uint16_t* aSortingMode)
  4245   *aSortingMode = mSortingMode;
  4246   return NS_OK;
  4250 NS_IMETHODIMP
  4251 nsNavHistoryResult::SetSortingMode(uint16_t aSortingMode)
  4253   NS_ENSURE_STATE(mRootNode);
  4255   if (aSortingMode > nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING)
  4256     return NS_ERROR_INVALID_ARG;
  4258   // Keep everything in sync.
  4259   NS_ASSERTION(mOptions, "Options should always be present for a root query");
  4261   mSortingMode = aSortingMode;
  4263   if (!mRootNode->mExpanded) {
  4264     // Need to do this later when node will be expanded.
  4265     mNeedsToApplySortingMode = true;
  4266     return NS_OK;
  4269   // Actually do sorting.
  4270   nsNavHistoryContainerResultNode::SortComparator comparator =
  4271       nsNavHistoryContainerResultNode::GetSortingComparator(aSortingMode);
  4272   if (comparator) {
  4273     nsNavHistory* history = nsNavHistory::GetHistoryService();
  4274     NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  4275     mRootNode->RecursiveSort(mSortingAnnotation.get(), comparator);
  4278   NOTIFY_RESULT_OBSERVERS(this, SortingChanged(aSortingMode));
  4279   NOTIFY_RESULT_OBSERVERS(this, InvalidateContainer(mRootNode));
  4280   return NS_OK;
  4284 NS_IMETHODIMP
  4285 nsNavHistoryResult::GetSortingAnnotation(nsACString& _result) {
  4286   _result.Assign(mSortingAnnotation);
  4287   return NS_OK;
  4291 NS_IMETHODIMP
  4292 nsNavHistoryResult::SetSortingAnnotation(const nsACString& aSortingAnnotation) {
  4293   mSortingAnnotation.Assign(aSortingAnnotation);
  4294   return NS_OK;
  4298 NS_IMETHODIMP
  4299 nsNavHistoryResult::AddObserver(nsINavHistoryResultObserver* aObserver,
  4300                                 bool aOwnsWeak)
  4302   NS_ENSURE_ARG(aObserver);
  4303   nsresult rv = mObservers.AppendWeakElement(aObserver, aOwnsWeak);
  4304   NS_ENSURE_SUCCESS(rv, rv);
  4306   rv = aObserver->SetResult(this);
  4307   NS_ENSURE_SUCCESS(rv, rv);
  4309   // If we are batching, notify a fake batch start to the observers.
  4310   // Not doing so would then notify a not coupled batch end.
  4311   if (mBatchInProgress) {
  4312     NOTIFY_RESULT_OBSERVERS(this, Batching(true));
  4315   return NS_OK;
  4319 NS_IMETHODIMP
  4320 nsNavHistoryResult::RemoveObserver(nsINavHistoryResultObserver* aObserver)
  4322   NS_ENSURE_ARG(aObserver);
  4323   return mObservers.RemoveWeakElement(aObserver);
  4327 NS_IMETHODIMP
  4328 nsNavHistoryResult::GetSuppressNotifications(bool* _retval)
  4330   *_retval = mSuppressNotifications;
  4331   return NS_OK;
  4335 NS_IMETHODIMP
  4336 nsNavHistoryResult::SetSuppressNotifications(bool aSuppressNotifications)
  4338   mSuppressNotifications = aSuppressNotifications;
  4339   return NS_OK;
  4343 NS_IMETHODIMP
  4344 nsNavHistoryResult::GetRoot(nsINavHistoryContainerResultNode** aRoot)
  4346   if (!mRootNode) {
  4347     NS_NOTREACHED("Root is null");
  4348     *aRoot = nullptr;
  4349     return NS_ERROR_FAILURE;
  4351   return mRootNode->QueryInterface(NS_GET_IID(nsINavHistoryContainerResultNode),
  4352                                    reinterpret_cast<void**>(aRoot));
  4356 void
  4357 nsNavHistoryResult::requestRefresh(nsNavHistoryContainerResultNode* aContainer)
  4359   // Don't add twice the same container.
  4360   if (mRefreshParticipants.IndexOf(aContainer) == mRefreshParticipants.NoIndex)
  4361     mRefreshParticipants.AppendElement(aContainer);
  4364 // nsINavBookmarkObserver implementation
  4366 // Here, it is important that we create a COPY of the observer array. Some
  4367 // observers will requery themselves, which may cause the observer array to
  4368 // be modified or added to.
  4369 #define ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(_folderId, _functionCall) \
  4370   PR_BEGIN_MACRO \
  4371     FolderObserverList* _fol = BookmarkFolderObserversForId(_folderId, false); \
  4372     if (_fol) { \
  4373       FolderObserverList _listCopy(*_fol); \
  4374       for (uint32_t _fol_i = 0; _fol_i < _listCopy.Length(); ++_fol_i) { \
  4375         if (_listCopy[_fol_i]) \
  4376           _listCopy[_fol_i]->_functionCall; \
  4377       } \
  4378     } \
  4379   PR_END_MACRO
  4380 #define ENUMERATE_LIST_OBSERVERS(_listType, _functionCall, _observersList, _conditionCall) \
  4381   PR_BEGIN_MACRO \
  4382     _listType _listCopy(_observersList); \
  4383     for (uint32_t _obs_i = 0; _obs_i < _listCopy.Length(); ++_obs_i) { \
  4384       if (_listCopy[_obs_i] && _listCopy[_obs_i]->_conditionCall) \
  4385         _listCopy[_obs_i]->_functionCall; \
  4386     } \
  4387   PR_END_MACRO
  4388 #define ENUMERATE_QUERY_OBSERVERS(_functionCall, _observersList, _conditionCall) \
  4389   ENUMERATE_LIST_OBSERVERS(QueryObserverList, _functionCall, _observersList, _conditionCall)
  4390 #define ENUMERATE_ALL_BOOKMARKS_OBSERVERS(_functionCall) \
  4391   ENUMERATE_QUERY_OBSERVERS(_functionCall, mAllBookmarksObservers, IsQuery())
  4392 #define ENUMERATE_HISTORY_OBSERVERS(_functionCall) \
  4393   ENUMERATE_QUERY_OBSERVERS(_functionCall, mHistoryObservers, IsQuery())
  4395 #define NOTIFY_REFRESH_PARTICIPANTS() \
  4396   PR_BEGIN_MACRO \
  4397   ENUMERATE_LIST_OBSERVERS(ContainerObserverList, Refresh(), mRefreshParticipants, IsContainer()); \
  4398   mRefreshParticipants.Clear(); \
  4399   PR_END_MACRO
  4401 NS_IMETHODIMP
  4402 nsNavHistoryResult::OnBeginUpdateBatch()
  4404   // Since we could be observing both history and bookmarks, it's possible both
  4405   // notify the batch.  We can safely ignore nested calls.
  4406   if (!mBatchInProgress) {
  4407     mBatchInProgress = true;
  4408     ENUMERATE_HISTORY_OBSERVERS(OnBeginUpdateBatch());
  4409     ENUMERATE_ALL_BOOKMARKS_OBSERVERS(OnBeginUpdateBatch());
  4411     NOTIFY_RESULT_OBSERVERS(this, Batching(true));
  4414   return NS_OK;
  4418 NS_IMETHODIMP
  4419 nsNavHistoryResult::OnEndUpdateBatch()
  4421   // Since we could be observing both history and bookmarks, it's possible both
  4422   // notify the batch.  We can safely ignore nested calls.
  4423   // Notice it's possible we are notified OnEndUpdateBatch more times than
  4424   // onBeginUpdateBatch, since the result could be created in the middle of
  4425   // nested batches.
  4426   if (mBatchInProgress) {
  4427     ENUMERATE_HISTORY_OBSERVERS(OnEndUpdateBatch());
  4428     ENUMERATE_ALL_BOOKMARKS_OBSERVERS(OnEndUpdateBatch());
  4430     // Setting mBatchInProgress before notifying the end of the batch to
  4431     // observers would make evantual calls to Refresh() directly handled rather
  4432     // than enqueued.  Thus set it just before handling refreshes.
  4433     mBatchInProgress = false;
  4434     NOTIFY_REFRESH_PARTICIPANTS();
  4435     NOTIFY_RESULT_OBSERVERS(this, Batching(false));
  4438   return NS_OK;
  4442 NS_IMETHODIMP
  4443 nsNavHistoryResult::OnItemAdded(int64_t aItemId,
  4444                                 int64_t aParentId,
  4445                                 int32_t aIndex,
  4446                                 uint16_t aItemType,
  4447                                 nsIURI* aURI,
  4448                                 const nsACString& aTitle,
  4449                                 PRTime aDateAdded,
  4450                                 const nsACString& aGUID,
  4451                                 const nsACString& aParentGUID)
  4453   ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
  4454     OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
  4455                 aGUID, aParentGUID)
  4456   );
  4457   ENUMERATE_HISTORY_OBSERVERS(
  4458     OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
  4459                 aGUID, aParentGUID)
  4460   );
  4461   ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
  4462     OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
  4463                 aGUID, aParentGUID)
  4464   );
  4465   return NS_OK;
  4469 NS_IMETHODIMP
  4470 nsNavHistoryResult::OnItemRemoved(int64_t aItemId,
  4471                                   int64_t aParentId,
  4472                                   int32_t aIndex,
  4473                                   uint16_t aItemType,
  4474                                   nsIURI* aURI,
  4475                                   const nsACString& aGUID,
  4476                                   const nsACString& aParentGUID)
  4478   ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
  4479       OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
  4480                     aParentGUID));
  4481   ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
  4482       OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
  4483                     aParentGUID));
  4484   ENUMERATE_HISTORY_OBSERVERS(
  4485       OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
  4486                     aParentGUID));
  4487   return NS_OK;
  4491 NS_IMETHODIMP
  4492 nsNavHistoryResult::OnItemChanged(int64_t aItemId,
  4493                                   const nsACString &aProperty,
  4494                                   bool aIsAnnotationProperty,
  4495                                   const nsACString &aNewValue,
  4496                                   PRTime aLastModified,
  4497                                   uint16_t aItemType,
  4498                                   int64_t aParentId,
  4499                                   const nsACString& aGUID,
  4500                                   const nsACString& aParentGUID)
  4502   ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
  4503     OnItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue,
  4504                   aLastModified, aItemType, aParentId, aGUID, aParentGUID));
  4506   // Note: folder-nodes set their own bookmark observer only once they're
  4507   // opened, meaning we cannot optimize this code path for changes done to
  4508   // folder-nodes.
  4510   FolderObserverList* list = BookmarkFolderObserversForId(aParentId, false);
  4511   if (!list)
  4512     return NS_OK;
  4514   for (uint32_t i = 0; i < list->Length(); ++i) {
  4515     nsRefPtr<nsNavHistoryFolderResultNode> folder = list->ElementAt(i);
  4516     if (folder) {
  4517       uint32_t nodeIndex;
  4518       nsRefPtr<nsNavHistoryResultNode> node =
  4519         folder->FindChildById(aItemId, &nodeIndex);
  4520       // if ExcludeItems is true we don't update non visible items
  4521       bool excludeItems = (mRootNode->mOptions->ExcludeItems()) ||
  4522                              folder->mOptions->ExcludeItems();
  4523       if (node &&
  4524           (!excludeItems || !(node->IsURI() || node->IsSeparator())) &&
  4525           folder->StartIncrementalUpdate()) {
  4526         node->OnItemChanged(aItemId, aProperty, aIsAnnotationProperty,
  4527                             aNewValue, aLastModified, aItemType, aParentId,
  4528                             aGUID, aParentGUID);
  4533   // Note: we do NOT call history observers in this case.  This notification is
  4534   // the same as other history notification, except that here we know the item
  4535   // is a bookmark.  History observers will handle the history notification
  4536   // instead.
  4537   return NS_OK;
  4541 NS_IMETHODIMP
  4542 nsNavHistoryResult::OnItemVisited(int64_t aItemId,
  4543                                   int64_t aVisitId,
  4544                                   PRTime aVisitTime,
  4545                                   uint32_t aTransitionType,
  4546                                   nsIURI* aURI,
  4547                                   int64_t aParentId,
  4548                                   const nsACString& aGUID,
  4549                                   const nsACString& aParentGUID)
  4551   ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
  4552       OnItemVisited(aItemId, aVisitId, aVisitTime, aTransitionType, aURI,
  4553                     aParentId, aGUID, aParentGUID));
  4554   ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
  4555       OnItemVisited(aItemId, aVisitId, aVisitTime, aTransitionType, aURI,
  4556                     aParentId, aGUID, aParentGUID));
  4557   // Note: we do NOT call history observers in this case.  This notification is
  4558   // the same as OnVisit, except that here we know the item is a bookmark.
  4559   // History observers will handle the history notification instead.
  4560   return NS_OK;
  4564 /**
  4565  * Need to notify both the source and the destination folders (if they are
  4566  * different).
  4567  */
  4568 NS_IMETHODIMP
  4569 nsNavHistoryResult::OnItemMoved(int64_t aItemId,
  4570                                 int64_t aOldParent,
  4571                                 int32_t aOldIndex,
  4572                                 int64_t aNewParent,
  4573                                 int32_t aNewIndex,
  4574                                 uint16_t aItemType,
  4575                                 const nsACString& aGUID,
  4576                                 const nsACString& aOldParentGUID,
  4577                                 const nsACString& aNewParentGUID)
  4579   ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aOldParent,
  4580       OnItemMoved(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex,
  4581                   aItemType, aGUID, aOldParentGUID, aNewParentGUID));
  4582   if (aNewParent != aOldParent) {
  4583     ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aNewParent,
  4584         OnItemMoved(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex,
  4585                     aItemType, aGUID, aOldParentGUID, aNewParentGUID));
  4587   ENUMERATE_ALL_BOOKMARKS_OBSERVERS(OnItemMoved(aItemId, aOldParent, aOldIndex,
  4588                                                 aNewParent, aNewIndex,
  4589                                                 aItemType, aGUID,
  4590                                                 aOldParentGUID,
  4591                                                 aNewParentGUID));
  4592   ENUMERATE_HISTORY_OBSERVERS(OnItemMoved(aItemId, aOldParent, aOldIndex,
  4593                                           aNewParent, aNewIndex, aItemType,
  4594                                           aGUID, aOldParentGUID,
  4595                                           aNewParentGUID));
  4596   return NS_OK;
  4600 NS_IMETHODIMP
  4601 nsNavHistoryResult::OnVisit(nsIURI* aURI, int64_t aVisitId, PRTime aTime,
  4602                             int64_t aSessionId, int64_t aReferringId,
  4603                             uint32_t aTransitionType, const nsACString& aGUID,
  4604                             bool aHidden)
  4606   uint32_t added = 0;
  4608   ENUMERATE_HISTORY_OBSERVERS(OnVisit(aURI, aVisitId, aTime, aSessionId,
  4609                                       aReferringId, aTransitionType, aGUID,
  4610                                       aHidden, &added));
  4612   if (!mRootNode->mExpanded)
  4613     return NS_OK;
  4615   // If this visit is accepted by an overlapped container, and not all
  4616   // overlapped containers are visible, we should still call Refresh if the
  4617   // visit falls into any of them.
  4618   bool todayIsMissing = false;
  4619   uint32_t resultType = mRootNode->mOptions->ResultType();
  4620   if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
  4621       resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY) {
  4622     uint32_t childCount;
  4623     nsresult rv = mRootNode->GetChildCount(&childCount);
  4624     NS_ENSURE_SUCCESS(rv, rv);
  4625     if (childCount) {
  4626       nsCOMPtr<nsINavHistoryResultNode> firstChild;
  4627       rv = mRootNode->GetChild(0, getter_AddRefs(firstChild));
  4628       NS_ENSURE_SUCCESS(rv, rv);
  4629       nsAutoCString title;
  4630       rv = firstChild->GetTitle(title);
  4631       NS_ENSURE_SUCCESS(rv, rv);
  4632       nsNavHistory* history = nsNavHistory::GetHistoryService();
  4633       NS_ENSURE_TRUE(history, NS_OK);
  4634       nsAutoCString todayLabel;
  4635       history->GetStringFromName(
  4636         MOZ_UTF16("finduri-AgeInDays-is-0"), todayLabel);
  4637       todayIsMissing = !todayLabel.Equals(title);
  4641   if (!added || todayIsMissing) {
  4642     // None of registered query observers has accepted our URI.  This means,
  4643     // that a matching query either was not expanded or it does not exist.
  4644     uint32_t resultType = mRootNode->mOptions->ResultType();
  4645     if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
  4646         resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY) {
  4647       // If the visit falls into the Today bucket and the bucket exists, it was
  4648       // just not expanded, thus there's no reason to update.
  4649       int64_t beginOfToday =
  4650         nsNavHistory::NormalizeTime(nsINavHistoryQuery::TIME_RELATIVE_TODAY, 0);
  4651       if (todayIsMissing || aTime < beginOfToday) {
  4652         (void)mRootNode->GetAsQuery()->Refresh();
  4654       return NS_OK;
  4657     if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY) {
  4658       (void)mRootNode->GetAsQuery()->Refresh();
  4659       return NS_OK;
  4662     // We are result of a folder node, then we should run through history
  4663     // observers that are containers queries and refresh them.
  4664     // We use a copy of the observers array since requerying could potentially
  4665     // cause changes to the array.
  4666     ENUMERATE_QUERY_OBSERVERS(Refresh(), mHistoryObservers, IsContainersQuery());
  4669   return NS_OK;
  4673 NS_IMETHODIMP
  4674 nsNavHistoryResult::OnTitleChanged(nsIURI* aURI,
  4675                                    const nsAString& aPageTitle,
  4676                                    const nsACString& aGUID)
  4678   ENUMERATE_HISTORY_OBSERVERS(OnTitleChanged(aURI, aPageTitle, aGUID));
  4679   return NS_OK;
  4683 NS_IMETHODIMP
  4684 nsNavHistoryResult::OnFrecencyChanged(nsIURI* aURI,
  4685                                       int32_t aNewFrecency,
  4686                                       const nsACString& aGUID,
  4687                                       bool aHidden,
  4688                                       PRTime aLastVisitDate)
  4690   return NS_OK;
  4694 NS_IMETHODIMP
  4695 nsNavHistoryResult::OnManyFrecenciesChanged()
  4697   return NS_OK;
  4701 NS_IMETHODIMP
  4702 nsNavHistoryResult::OnDeleteURI(nsIURI *aURI,
  4703                                 const nsACString& aGUID,
  4704                                 uint16_t aReason)
  4706   ENUMERATE_HISTORY_OBSERVERS(OnDeleteURI(aURI, aGUID, aReason));
  4707   return NS_OK;
  4711 NS_IMETHODIMP
  4712 nsNavHistoryResult::OnClearHistory()
  4714   ENUMERATE_HISTORY_OBSERVERS(OnClearHistory());
  4715   return NS_OK;
  4719 NS_IMETHODIMP
  4720 nsNavHistoryResult::OnPageChanged(nsIURI* aURI,
  4721                                   uint32_t aChangedAttribute,
  4722                                   const nsAString& aValue,
  4723                                   const nsACString& aGUID)
  4725   ENUMERATE_HISTORY_OBSERVERS(OnPageChanged(aURI, aChangedAttribute, aValue, aGUID));
  4726   return NS_OK;
  4730 /**
  4731  * Don't do anything when visits expire.
  4732  */
  4733 NS_IMETHODIMP
  4734 nsNavHistoryResult::OnDeleteVisits(nsIURI* aURI,
  4735                                    PRTime aVisitTime,
  4736                                    const nsACString& aGUID,
  4737                                    uint16_t aReason,
  4738                                    uint32_t aTransitionType)
  4740   ENUMERATE_HISTORY_OBSERVERS(OnDeleteVisits(aURI, aVisitTime, aGUID, aReason,
  4741                                              aTransitionType));
  4742   return NS_OK;

mercurial